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.
This commit is contained in:
Feoramund
2024-06-22 14:29:16 -04:00
parent ea771d0cb7
commit 9b265b2309
3 changed files with 134 additions and 80 deletions
+31 -19
View File
@@ -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,
}
+53 -10
View File
@@ -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,6 +152,39 @@ time_v1 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
return cast(u64)transmute(u64le)timestamp_octets
}
/*
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.
Returns:
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
*/
raw_time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
temporary := transmute(u128be)id
timestamp |= cast(u64)(temporary & 0xFFFFFFFF_FFFF0000_00000000_00000000 >> 68)
timestamp |= cast(u64)(temporary & 0x00000000_00000FFF_00000000_00000000 >> 64)
return timestamp
}
/*
Get the timestamp of a version 6 UUID.
@@ -160,13 +194,23 @@ Inputs:
Returns:
- timestamp: The timestamp, in 100-nanosecond intervals since 1582-10-15.
*/
time_v6 :: proc "contextless" (id: Identifier) -> (timestamp: u64) {
temporary := transmute(u128be)id
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)
}
timestamp |= cast(u64)(temporary & 0xFFFFFFFF_FFFF0000_00000000_00000000 >> 68)
timestamp |= cast(u64)(temporary & 0x00000000_00000FFF_00000000_00000000 >> 64)
/*
Get the raw timestamp of a version 7 UUID.
return timestamp
Inputs:
- id: The identifier.
Returns:
- timestamp: The timestamp, in milliseconds since the UNIX epoch.
*/
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)
}
/*
@@ -178,9 +222,8 @@ Inputs:
Returns:
- timestamp: The timestamp, in milliseconds since the UNIX epoch.
*/
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)
time_v7 :: proc "contextless" (id: Identifier) -> (timestamp: time.Time) {
return time.time_add({}, cast(time.Duration)(raw_time_v7(id) * 1e6))
}
/*
+50 -51
View File
@@ -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 := uuid.time_v1(v1_a)
time_b := uuid.time_v1(v1_b)
time_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) }
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)
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)
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)
time_bits_a := uuid.time_v7(v7_a)
time_bits_b := uuid.time_v7(v7_b)
time_bits_c := uuid.time_v7(v7_c)
v7_a := uuid.generate_v7(point_a)
v7_b := uuid.generate_v7(point_b)
v7_c := uuid.generate_v7(point_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)
time_a := uuid.time_v7(v7_a)
time_b := uuid.time_v7(v7_b)
time_c := uuid.time_v7(v7_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.")
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)
v7_with_counter := uuid.generate_v7_counter(0x555)
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(0x555)
log.debugf("D: %02x", v7_with_counter)
testing.expect_value(t, uuid.counter_v7(v7_with_counter), 0x555)
}
@(test)