mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-18 16:51:31 -07:00
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:
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user