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.
 - 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.
 - 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.
   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:
 Returns:
 - result: The generated UUID.
 - 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.")
 	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 {
 	if realized_node, ok := node.?; ok {
 		mutable_node := realized_node
 		mutable_node := realized_node
@@ -79,12 +81,13 @@ Inputs:
   If unspecified, it will be replaced with random bits.
   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.
 - 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.
   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:
 Returns:
 - result: The generated UUID.
 - 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)
 	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
 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.
 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:
 Returns:
 - result: The generated UUID.
 - 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)
 	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
 	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
 This UUID will be pseudorandom, save for 6 pre-determined version and variant
 bits, a 48 bit timestamp, and 12 bits of counter state.
 bits, a 48 bit timestamp, and 12 bits of counter state.
@@ -178,14 +184,15 @@ Example:
 
 
 Inputs:
 Inputs:
 - counter: A 12-bit value, incremented each time a UUID is generated in a batch.
 - 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:
 Returns:
 - result: The generated UUID.
 - 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(.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).")
 	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)unix_time_in_milliseconds << VERSION_7_TIME_SHIFT
 	temporary |= cast(u128be)counter << VERSION_7_COUNTER_SHIFT
 	temporary |= cast(u128be)counter << VERSION_7_COUNTER_SHIFT
@@ -203,3 +210,8 @@ generate_v7_counter :: proc(counter: u16) -> (result: Identifier) {
 
 
 	return
 	return
 }
 }
+
+generate_v7 :: proc {
+	generate_v7_basic,
+	generate_v7_with_counter,
+}

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

@@ -1,6 +1,7 @@
 package uuid
 package uuid
 
 
 import "base:runtime"
 import "base:runtime"
+import "core:time"
 
 
 /*
 /*
 Convert a string to a UUID.
 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:
 Inputs:
 - id: The identifier.
 - id: The identifier.
@@ -135,7 +136,7 @@ Inputs:
 Returns:
 Returns:
 - timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
 - 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: [8]u8
 
 
 	timestamp_octets[0] = id[0]
 	timestamp_octets[0] = id[0]
@@ -151,8 +152,23 @@ time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
 	return cast(u64)transmute(u64le)timestamp_octets
 	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:
 Inputs:
 - id: The identifier.
 - id: The identifier.
@@ -160,7 +176,7 @@ Inputs:
 Returns:
 Returns:
 - timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
 - 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
 	temporary := transmute(u128be)id
 
 
 	timestamp |= cast(u64)(temporary & 0xFFFFFFFF_FFFF0000_00000000_00000000 >> 68)
 	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:
 Inputs:
 - id: The identifier.
 - id: The identifier.
@@ -178,11 +208,24 @@ Inputs:
 Returns:
 Returns:
 - timestamp: The timestamp, in milliseconds since the UNIX epoch.
 - 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
 	time_bits := transmute(u128be)id & VERSION_7_TIME_MASK
 	return cast(u64)(time_bits >> VERSION_7_TIME_SHIFT)
 	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.
 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) {
 test_v1 :: proc(t: ^testing.T) {
 	context.random_generator = crypto.random_generator()
 	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
 	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)
 	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)
 test_v6 :: proc(t: ^testing.T) {
 test_v6 :: proc(t: ^testing.T) {
 	context.random_generator = crypto.random_generator()
 	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
 	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)
 	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)
 test_v7 :: proc(t: ^testing.T) {
 test_v7 :: proc(t: ^testing.T) {
 	context.random_generator = crypto.random_generator()
 	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)
 	log.debugf("D: %02x", v7_with_counter)
 	testing.expect_value(t, uuid.counter_v7(v7_with_counter), 0x555)
 	testing.expect_value(t, uuid.counter_v7(v7_with_counter), 0x555)
+
 }
 }
 
 
 @(test)
 @(test)