Browse Source

Improve time-related API in `uuid` package

- Let timestamps be specified by the user.
- Change `time_v*` to `raw_time_v*` and implement an API that returns
  timestamps from the `time` package.
Feoramund 1 year ago
parent
commit
9b265b2309

+ 31 - 19
core/encoding/uuid/generation.odin

@@ -11,25 +11,27 @@ Inputs:
 - clock_seq: The clock sequence, a number which must be initialized to a random number once in the lifetime of a system.
 - node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system.
   If one is not provided or available, 48 bits of random state will take its place.
+- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
 
 Returns:
 - result: The generated UUID.
 */
-generate_v1 :: proc(clock_seq: u16, node: Maybe([6]u8) = nil) -> (result: Identifier) {
+generate_v1 :: proc(clock_seq: u16, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
 	assert(clock_seq <= 0x3FFF, "The clock sequence can only hold 14 bits of data; no number greater than 16,383.")
-	unix_time_in_hns_intervals := time.to_unix_nanoseconds(time.now()) / 100
-	timestamp := cast(u64le)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
-	timestamp_octets := transmute([8]u8)timestamp
+	unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100
 
-	result[0] = timestamp_octets[0]
-	result[1] = timestamp_octets[1]
-	result[2] = timestamp_octets[2]
-	result[3] = timestamp_octets[3]
-	result[4] = timestamp_octets[4]
-	result[5] = timestamp_octets[5]
+	uuid_timestamp := cast(u64le)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
+	uuid_timestamp_octets := transmute([8]u8)uuid_timestamp
 
-	result[6] = timestamp_octets[6] >> 4
-	result[7] = timestamp_octets[6] << 4 | timestamp_octets[7]
+	result[0] = uuid_timestamp_octets[0]
+	result[1] = uuid_timestamp_octets[1]
+	result[2] = uuid_timestamp_octets[2]
+	result[3] = uuid_timestamp_octets[3]
+	result[4] = uuid_timestamp_octets[4]
+	result[5] = uuid_timestamp_octets[5]
+
+	result[6] = uuid_timestamp_octets[6] >> 4
+	result[7] = uuid_timestamp_octets[6] << 4 | uuid_timestamp_octets[7]
 
 	if realized_node, ok := node.?; ok {
 		mutable_node := realized_node
@@ -79,12 +81,13 @@ Inputs:
   If unspecified, it will be replaced with random bits.
 - node: An optional 48-bit spatially unique identifier, specified to be the IEEE 802 address of the system.
   If one is not provided or available, 48 bits of random state will take its place.
+- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
 
 Returns:
 - result: The generated UUID.
 */
-generate_v6 :: proc(clock_seq: Maybe(u16) = nil, node: Maybe([6]u8) = nil) -> (result: Identifier) {
-	unix_time_in_hns_intervals := time.to_unix_nanoseconds(time.now()) / 100
+generate_v6 :: proc(clock_seq: Maybe(u16) = nil, node: Maybe([6]u8) = nil, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
+	unix_time_in_hns_intervals := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 100
 
 	timestamp := cast(u128be)(HNS_INTERVALS_BETWEEN_GREG_AND_UNIX + unix_time_in_hns_intervals)
 
@@ -128,12 +131,15 @@ bits and a 48 bit timestamp.
 It is designed with time-based sorting in mind, such as for database usage, as
 the highest bits are allocated from the timestamp of when it is created.
 
+Inputs:
+- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
+
 Returns:
 - result: The generated UUID.
 */
-generate_v7 :: proc() -> (result: Identifier) {
+generate_v7_basic :: proc(timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
 	assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
-	unix_time_in_milliseconds := time.to_unix_nanoseconds(time.now()) / 1e6
+	unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6
 
 	temporary := cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT
 
@@ -152,7 +158,7 @@ generate_v7 :: proc() -> (result: Identifier) {
 }
 
 /*
-Generate a version 7 UUID with an incremented counter.
+Generate a version 7 UUID that has an incremented counter.
 
 This UUID will be pseudorandom, save for 6 pre-determined version and variant
 bits, a 48 bit timestamp, and 12 bits of counter state.
@@ -178,14 +184,15 @@ Example:
 
 Inputs:
 - counter: A 12-bit value, incremented each time a UUID is generated in a batch.
+- timestamp: A timestamp from the `core:time` package, or `nil` to use the current time.
 
 Returns:
 - result: The generated UUID.
 */
-generate_v7_counter :: proc(counter: u16) -> (result: Identifier) {
+generate_v7_with_counter :: proc(counter: u16, timestamp: Maybe(time.Time) = nil) -> (result: Identifier) {
 	assert(.Cryptographic in runtime.random_generator_query_info(context.random_generator), NO_CSPRNG_ERROR)
 	assert(counter <= 0x0fff, "This implementation of the version 7 UUID does not support counters in excess of 12 bits (4,095).")
-	unix_time_in_milliseconds := time.to_unix_nanoseconds(time.now()) / 1e6
+	unix_time_in_milliseconds := time.to_unix_nanoseconds(timestamp.? or_else time.now()) / 1e6
 
 	temporary := cast(u128be)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT
 	temporary |= cast(u128be)counter << VERSION_7_COUNTER_SHIFT
@@ -203,3 +210,8 @@ generate_v7_counter :: proc(counter: u16) -> (result: Identifier) {
 
 	return
 }
+
+generate_v7 :: proc {
+	generate_v7_basic,
+	generate_v7_with_counter,
+}

+ 49 - 6
core/encoding/uuid/reading.odin

@@ -1,6 +1,7 @@
 package uuid
 
 import "base:runtime"
+import "core:time"
 
 /*
 Convert a string to a UUID.
@@ -127,7 +128,7 @@ node :: proc "contextless" (id: Identifier) -> (node: [6]u8) {
 }
 
 /*
-Get the timestamp of a version 1 UUID.
+Get the raw timestamp of a version 1 UUID.
 
 Inputs:
 - id: The identifier.
@@ -135,7 +136,7 @@ Inputs:
 Returns:
 - timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
 */
-time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
+raw_time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
 	timestamp_octets: [8]u8
 
 	timestamp_octets[0] = id[0]
@@ -151,8 +152,23 @@ time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
 	return cast(u64)transmute(u64le)timestamp_octets
 }
 
+
 /*
-Get the timestamp of a version 6 UUID.
+Get the timestamp of a version 1 UUID.
+
+Inputs:
+- id: The identifier.
+
+Returns:
+- timestamp: The timestamp of the UUID.
+*/
+time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
+	delta := cast(time.Duration)(raw_time_v1(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100
+	return time.time_add({}, delta)
+}
+
+/*
+Get the raw timestamp of a version 6 UUID.
 
 Inputs:
 - id: The identifier.
@@ -160,7 +176,7 @@ Inputs:
 Returns:
 - timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
 */
-time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
+raw_time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
 	temporary := transmute(u128be)id
 
 	timestamp |= cast(u64)(temporary & 0xFFFFFFFF_FFFF0000_00000000_00000000 >> 68)
@@ -170,7 +186,21 @@ time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
 }
 
 /*
-Get the timestamp of a version 7 UUID.
+Get the timestamp of a version 6 UUID.
+
+Inputs:
+- id: The identifier.
+
+Returns:
+- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
+*/
+time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
+	delta := cast(time.Duration)(raw_time_v6(id) - HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100
+	return time.time_add({}, delta)
+}
+
+/*
+Get the raw timestamp of a version 7 UUID.
 
 Inputs:
 - id: The identifier.
@@ -178,11 +208,24 @@ Inputs:
 Returns:
 - timestamp: The timestamp, in milliseconds since the UNIX epoch.
 */
-time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
+raw_time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
 	time_bits := transmute(u128be)id & VERSION_7_TIME_MASK
 	return cast(u64)(time_bits >> VERSION_7_TIME_SHIFT)
 }
 
+/*
+Get the timestamp of a version 7 UUID.
+
+Inputs:
+- id: The identifier.
+
+Returns:
+- timestamp: The timestamp, in milliseconds since the UNIX epoch.
+*/
+time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
+	return time.time_add({}, cast(time.Duration)(raw_time_v7(id) * 1e6))
+}
+
 /*
 Get the 12-bit counter value of a version 7 UUID.
 

+ 50 - 51
tests/core/encoding/uuid/test_core_uuid.odin

@@ -67,87 +67,86 @@ test_legacy_namespaced_uuids :: proc(t: ^testing.T) {
 test_v1 :: proc(t: ^testing.T) {
 	context.random_generator = crypto.random_generator()
 
+	point_a := time.time_add({}, 1 * time.Second)
+	point_b := time.time_add({}, 3 * time.Second)
+	point_c := time.time_add({}, 5 * time.Second)
+
 	CLOCK :: 0x3A1A
-	v1_a := uuid.generate_v1(CLOCK)
-	time.sleep(10 * time.Millisecond)
-	v1_b := uuid.generate_v1(CLOCK)
-	time.sleep(10 * time.Millisecond)
-	v1_c := uuid.generate_v1(CLOCK)
+	v1_a := uuid.generate_v1(CLOCK, nil, point_a)
+	v1_b := uuid.generate_v1(CLOCK, nil, point_b)
+	v1_c := uuid.generate_v1(CLOCK, nil, point_c)
 
 	testing.expect_value(t, uuid.clock_seq(v1_a), CLOCK)
 
-	time_bits_a := uuid.time_v1(v1_a)
-	time_bits_b := uuid.time_v1(v1_b)
-	time_bits_c := uuid.time_v1(v1_c)
-
-	time_a := time.Time { _nsec = cast(i64)((time_bits_a - uuid.HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) }
-	time_b := time.Time { _nsec = cast(i64)((time_bits_b - uuid.HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) }
-	time_c := time.Time { _nsec = cast(i64)((time_bits_c - uuid.HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) }
+	time_a := uuid.time_v1(v1_a)
+	time_b := uuid.time_v1(v1_b)
+	time_c := uuid.time_v1(v1_c)
 
-	log.debugf("A: %02x, %i, %v", v1_a, time_bits_a, time_a)
-	log.debugf("B: %02x, %i, %v", v1_b, time_bits_b, time_b)
-	log.debugf("C: %02x, %i, %v", v1_c, time_bits_c, time_c)
+	log.debugf("A: %02x, %v", v1_a, time_a)
+	log.debugf("B: %02x, %v", v1_b, time_b)
+	log.debugf("C: %02x, %v", v1_c, time_c)
 
-	testing.expect(t, time_bits_b > time_bits_a, "The time bits on the later-generated v1 UUID are lesser than the earlier UUID.")
-	testing.expect(t, time_bits_c > time_bits_b, "The time bits on the later-generated v1 UUID are lesser than the earlier UUID.")
-	testing.expect(t, time_bits_c > time_bits_a, "The time bits on the later-generated v1 UUID are lesser than the earlier UUID.")
+	testing.expect(t, time.diff(time_a, time_b) > 0, "The time on the later-generated v1 UUID is earlier than its successor.")
+	testing.expect(t, time.diff(time_b, time_c) > 0, "The time on the later-generated v1 UUID is earlier than its successor.")
+	testing.expect(t, time.diff(time_a, time_c) > 0, "The time on the later-generated v1 UUID is earlier than its successor.")
 }
 
 @(test)
 test_v6 :: proc(t: ^testing.T) {
 	context.random_generator = crypto.random_generator()
 
+	point_a := time.time_add({}, 1 * time.Second)
+	point_b := time.time_add({}, 3 * time.Second)
+	point_c := time.time_add({}, 5 * time.Second)
+
 	CLOCK :: 0x3A1A
-	v6_a := uuid.generate_v6(CLOCK)
-	time.sleep(10 * time.Millisecond)
-	v6_b := uuid.generate_v6(CLOCK)
-	time.sleep(10 * time.Millisecond)
-	v6_c := uuid.generate_v6(CLOCK)
+	v6_a := uuid.generate_v6(CLOCK, nil, point_a)
+	v6_b := uuid.generate_v6(CLOCK, nil, point_b)
+	v6_c := uuid.generate_v6(CLOCK, nil, point_c)
 
 	testing.expect_value(t, uuid.clock_seq(v6_a), CLOCK)
 
-	time_bits_a := uuid.time_v6(v6_a)
-	time_bits_b := uuid.time_v6(v6_b)
-	time_bits_c := uuid.time_v6(v6_c)
+	time_a := uuid.time_v6(v6_a)
+	time_b := uuid.time_v6(v6_b)
+	time_c := uuid.time_v6(v6_c)
 
-	time_a := time.Time { _nsec = cast(i64)((time_bits_a - uuid.HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) }
-	time_b := time.Time { _nsec = cast(i64)((time_bits_b - uuid.HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) }
-	time_c := time.Time { _nsec = cast(i64)((time_bits_c - uuid.HNS_INTERVALS_BETWEEN_GREG_AND_UNIX) * 100) }
+	log.debugf("A: %02x, %v", v6_a, time_a)
+	log.debugf("B: %02x, %v", v6_b, time_b)
+	log.debugf("C: %02x, %v", v6_c, time_c)
 
-	log.debugf("A: %02x, %i, %v", v6_a, time_bits_a, time_a)
-	log.debugf("B: %02x, %i, %v", v6_b, time_bits_b, time_b)
-	log.debugf("C: %02x, %i, %v", v6_c, time_bits_c, time_c)
-
-	testing.expect(t, time_bits_b > time_bits_a, "The time bits on the later-generated v6 UUID are lesser than the earlier UUID.")
-	testing.expect(t, time_bits_c > time_bits_b, "The time bits on the later-generated v6 UUID are lesser than the earlier UUID.")
-	testing.expect(t, time_bits_c > time_bits_a, "The time bits on the later-generated v6 UUID are lesser than the earlier UUID.")
+	testing.expect(t, time.diff(time_a, time_b) > 0, "The time on the later-generated v6 UUID is earlier than its successor.")
+	testing.expect(t, time.diff(time_b, time_c) > 0, "The time on the later-generated v6 UUID is earlier than its successor.")
+	testing.expect(t, time.diff(time_a, time_c) > 0, "The time on the later-generated v6 UUID is earlier than its successor.")
 }
 
 @(test)
 test_v7 :: proc(t: ^testing.T) {
 	context.random_generator = crypto.random_generator()
 
-	v7_a := uuid.generate_v7()
-	time.sleep(10 * time.Millisecond)
-	v7_b := uuid.generate_v7()
-	time.sleep(10 * time.Millisecond)
-	v7_c := uuid.generate_v7()
+	point_a := time.time_add({}, 1 * time.Second)
+	point_b := time.time_add({}, 3 * time.Second)
+	point_c := time.time_add({}, 5 * time.Second)
+
+	v7_a := uuid.generate_v7(point_a)
+	v7_b := uuid.generate_v7(point_b)
+	v7_c := uuid.generate_v7(point_c)
 
-	time_bits_a := uuid.time_v7(v7_a)
-	time_bits_b := uuid.time_v7(v7_b)
-	time_bits_c := uuid.time_v7(v7_c)
+	time_a := uuid.time_v7(v7_a)
+	time_b := uuid.time_v7(v7_b)
+	time_c := uuid.time_v7(v7_c)
 
-	log.debugf("A: %02x, %i", v7_a, time_bits_a)
-	log.debugf("B: %02x, %i", v7_b, time_bits_b)
-	log.debugf("C: %02x, %i", v7_c, time_bits_c)
+	log.debugf("A: %02x, %v", v7_a, time_a)
+	log.debugf("B: %02x, %v", v7_b, time_b)
+	log.debugf("C: %02x, %v", v7_c, time_c)
 
-	testing.expect(t, time_bits_b > time_bits_a, "The time bits on the later-generated v7 UUID are lesser than the earlier UUID.")
-	testing.expect(t, time_bits_c > time_bits_b, "The time bits on the later-generated v7 UUID are lesser than the earlier UUID.")
-	testing.expect(t, time_bits_c > time_bits_a, "The time bits on the later-generated v7 UUID are lesser than the earlier UUID.")
+	testing.expect(t, time.diff(time_a, time_b) > 0, "The time on the later-generated v7 UUID is earlier than its successor.")
+	testing.expect(t, time.diff(time_b, time_c) > 0, "The time on the later-generated v7 UUID is earlier than its successor.")
+	testing.expect(t, time.diff(time_a, time_c) > 0, "The time on the later-generated v7 UUID is earlier than its successor.")
 
-	v7_with_counter := uuid.generate_v7_counter(0x555)
+	v7_with_counter := uuid.generate_v7(0x555)
 	log.debugf("D: %02x", v7_with_counter)
 	testing.expect_value(t, uuid.counter_v7(v7_with_counter), 0x555)
+
 }
 
 @(test)