From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Kieran Wallbanks <kieran.wallbanks@gmail.com>
Date: Fri, 2 Apr 2021 17:28:58 +0100
Subject: [PATCH] Add Tick TemporalUnit


diff --git a/src/main/java/io/papermc/paper/util/Tick.java b/src/main/java/io/papermc/paper/util/Tick.java
new file mode 100644
index 0000000000000000000000000000000000000000..10430f02e1d1e654383154c04473f07469bc7fee
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/Tick.java
@@ -0,0 +1,95 @@
+package io.papermc.paper.util;
+
+import net.kyori.adventure.util.Ticks;
+import org.jetbrains.annotations.NotNull;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalUnit;
+import java.util.Objects;
+
+/**
+ * A TemporalUnit that represents the target length of one server tick. This is defined
+ * as 50 milliseconds. Note that this class is not for measuring the length that a tick
+ * took, rather it is used for simple conversion between times and ticks.
+ * @see #tick()
+ */
+public final class Tick implements TemporalUnit {
+    private static final Tick INSTANCE = new Tick(Ticks.SINGLE_TICK_DURATION_MS);
+
+    private final long milliseconds;
+
+    /**
+     * Gets the instance of the tick temporal unit.
+     * @return the tick instance
+     */
+    public static @NotNull Tick tick() {
+        return INSTANCE;
+    }
+
+    /**
+     * Creates a new tick.
+     * @param length the length of the tick in milliseconds
+     * @see #tick()
+     */
+    private Tick(long length) {
+        this.milliseconds = length;
+    }
+
+    /**
+     * Creates a duration from an amount of ticks. This is shorthand for
+     * {@link Duration#of(long, TemporalUnit)} called with the amount of ticks and
+     * {@link #tick()}.
+     * @param ticks the amount of ticks
+     * @return the duration
+     */
+    public static @NotNull Duration of(long ticks) {
+        return Duration.of(ticks, INSTANCE);
+    }
+
+    /**
+     * Gets the number of whole ticks that occur in the provided duration. Note that this
+     * method returns an {@code int} as this is the unit that Minecraft stores ticks in.
+     * @param duration the duration
+     * @return the number of whole ticks in this duration
+     * @throws ArithmeticException if the duration is zero or an overflow occurs
+     */
+    public int fromDuration(@NotNull Duration duration) {
+        Objects.requireNonNull(duration, "duration cannot be null");
+        return Math.toIntExact(Math.floorDiv(duration.toMillis(), this.milliseconds));
+    }
+
+    @Override
+    public @NotNull Duration getDuration() {
+        return Duration.ofMillis(this.milliseconds);
+    }
+
+    // Note: This is a workaround in order to allow calculations with this duration.
+    // See: Duration#add
+    @Override
+    public boolean isDurationEstimated() {
+        return false;
+    }
+
+    @Override
+    public boolean isDateBased() {
+        return false;
+    }
+
+    @Override
+    public boolean isTimeBased() {
+        return true;
+    }
+
+    @SuppressWarnings("unchecked") // following ChronoUnit#addTo
+    @Override
+    public <R extends Temporal> @NotNull R addTo(@NotNull R temporal, long amount) {
+        return (R) temporal.plus(getDuration().multipliedBy(amount));
+    }
+
+    @Override
+    public long between(@NotNull Temporal start, @NotNull Temporal end) {
+        return start.until(end, ChronoUnit.MILLIS) / this.milliseconds;
+    }
+}
diff --git a/src/test/java/io/papermc/paper/util/TickTest.java b/src/test/java/io/papermc/paper/util/TickTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f147d909f2fc710c1d12bac3c7b66c2883139026
--- /dev/null
+++ b/src/test/java/io/papermc/paper/util/TickTest.java
@@ -0,0 +1,43 @@
+package io.papermc.paper.util;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TickTest {
+
+    @Test
+    public void testTickLength() {
+        assertEquals(50, Duration.of(1, Tick.tick()).toMillis());
+        assertEquals(100, Duration.of(2, Tick.tick()).toMillis());
+    }
+
+    @Test
+    public void testTickFromDuration() {
+        assertEquals(0, Tick.tick().fromDuration(Duration.ofMillis(0)));
+        assertEquals(0, Tick.tick().fromDuration(Duration.ofMillis(10)));
+        assertEquals(1, Tick.tick().fromDuration(Duration.ofMillis(60)));
+        assertEquals(2, Tick.tick().fromDuration(Duration.ofMillis(100)));
+    }
+
+    @Test
+    public void testAddTickToInstant() {
+        Instant now = Instant.now();
+        assertEquals(now, now.plus(0, Tick.tick()));
+        assertEquals(now.plus(50, ChronoUnit.MILLIS), now.plus(1, Tick.tick()));
+        assertEquals(now.plus(100, ChronoUnit.MILLIS), now.plus(2, Tick.tick()));
+        assertEquals(now.plus(150, ChronoUnit.MILLIS), now.plus(3, Tick.tick()));
+    }
+
+    @Test
+    public void testTicksBetweenInstants() {
+        Instant now = Instant.now();
+        assertEquals(0, now.until(now.plus(20, ChronoUnit.MILLIS), Tick.tick()));
+        assertEquals(1, now.until(now.plus(50, ChronoUnit.MILLIS), Tick.tick()));
+        assertEquals(1, now.until(now.plus(60, ChronoUnit.MILLIS), Tick.tick()));
+        assertEquals(2, now.until(now.plus(100, ChronoUnit.MILLIS), Tick.tick()));
+    }
+}
