Skip to content

Commit

Permalink
Snowflake: tests, checks in constructor that takes instant, endOfTime…
Browse files Browse the repository at this point in the history
… constant, more documentation
  • Loading branch information
lukellmann committed Aug 25, 2021
1 parent 9c6ba3d commit 3f9ad82
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 3 deletions.
35 changes: 32 additions & 3 deletions common/src/main/kotlin/entity/Snowflake.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,31 @@ class Snowflake(val value: Long) : Comparable<Snowflake> {

/**
* Creates a Snowflake from a given [instant].
*
* If the given [instant] is too far in the past / future, this constructor will create
* an instance with a [timeStamp] equal to [Snowflake.min] / [Snowflake.max].
*/
constructor(instant: Instant) : this((instant.toEpochMilliseconds() - discordEpochLong) shl 22)
constructor(instant: Instant) : this(
instant.toEpochMilliseconds()
.coerceAtLeast(discordEpochLong) // time before is unknown to Snowflakes
.minus(discordEpochLong)
.coerceAtMost(maxMillisecondsSinceDiscordEpoch) // time after is unknown to Snowflakes
.shl(22)
)

/**
* A [String] representation of this Snowflake's [value].
*/
val asString get() = value.toString()

/**
* The point in time this Snowflake represents.
*/
val timeStamp: Instant get() = Instant.fromEpochMilliseconds(discordEpochLong + (value ushr 22))

/**
* A [TimeMark] for the point in time this Snowflake represents.
*/
val timeMark: TimeMark get() = SnowflakeMark(timeStamp)

override fun compareTo(other: Snowflake): Int = value.ushr(22).compareTo(other.value.ushr(22))
Expand All @@ -53,14 +71,25 @@ class Snowflake(val value: Long) : Comparable<Snowflake> {
}

companion object {
private const val discordEpochLong = 1420070400000L
private const val discordEpochLong = 1420070400000L // 42 one bits
private const val maxMillisecondsSinceDiscordEpoch = 0b111111111111111111111111111111111111111111L

/**
* The point in time that marks the Discord Epoch (the first second of 2015).
*/
val discordEpochStart: Instant = Instant.fromEpochMilliseconds(discordEpochLong)

/**
* The last point in time a Snowflake can represent.
*/
val endOfTime: Instant =
Instant.fromEpochMilliseconds(discordEpochLong + maxMillisecondsSinceDiscordEpoch)

/**
* The maximum value a Snowflake can hold.
* Useful when requesting paginated entities.
*/
val max: Snowflake = Snowflake(Long.MAX_VALUE)
val max: Snowflake = Snowflake(-1)

/**
* The minimum value a Snowflake can hold.
Expand Down
57 changes: 57 additions & 0 deletions common/src/test/kotlin/entity/SnowflakeTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package entity

import dev.kord.common.entity.Snowflake
import kotlinx.datetime.Clock
import kotlinx.datetime.DateTimeUnit.Companion.MILLISECOND
import kotlinx.datetime.Instant
import kotlinx.datetime.minus
import kotlinx.datetime.plus
import kotlin.test.*

class SnowflakeTest {

@Test
fun `Snowflake with value 0 has timeStamp equal to the timeStamp of discordEpochStart`() {
val snowflake = Snowflake(0)
assertEquals(Snowflake.discordEpochStart, snowflake.timeStamp)
}

@Test
fun `Snowflake with value -1 has timeStamp equal to the timeStamp of endOfTime`() {
val snowflake = Snowflake(-1)
assertEquals(Snowflake.endOfTime, snowflake.timeStamp)
}

@Test
fun `Snowflake created from instant far in the past has timeStamp equal the timeStamp of Snowflake min`() {
val snowflake = Snowflake(Instant.DISTANT_PAST)
assertEquals(Snowflake.min.timeStamp, snowflake.timeStamp)
}

@Test
fun `Snowflake created from instant far in the future has timeStamp equal the timeStamp of Snowflake max`() {
val snowflake = Snowflake(Instant.DISTANT_FUTURE)
assertEquals(Snowflake.max.timeStamp, snowflake.timeStamp)
}

@Test
fun `Snowflake's timeStamp calculates an Instant close to the Instant the Snowflake was created from`() {
val instant = Clock.System.now()
val snowflake = Snowflake(instant)

// snowflake timestamps have a millisecond accuracy -> allow +/-1 millisecond from original instant
val validTimeRange = instant.minus(1, MILLISECOND)..instant.plus(1, MILLISECOND)

assertContains(validTimeRange, snowflake.timeStamp)
}

@Test
fun `min Snowflake's timeMark has passed`() {
assertTrue(Snowflake.min.timeMark.hasPassedNow())
}

@Test
fun `max Snowflake's timeMark has not passed`() {
assertFalse(Snowflake.max.timeMark.hasPassedNow())
}
}

0 comments on commit 3f9ad82

Please sign in to comment.