diff --git a/uuid/v7.ts b/uuid/v7.ts index aad71429696c..2eb01a308995 100644 --- a/uuid/v7.ts +++ b/uuid/v7.ts @@ -55,7 +55,8 @@ export function validate(id: string): boolean { * uses none of the optional monotonicity methods of * {@link https://www.rfc-editor.org/rfc/rfc9562.html#section-6.2 | RFC 9562 section 6.2}. * - * @throws {RangeError} If the timestamp is not a non-negative integer. + * @throws {RangeError} If the timestamp is not an integer between 0 and + * 2**48 - 1, the maximum value representable in the UUIDv7 timestamp field. * * @param timestamp Unix Epoch timestamp in milliseconds. * @@ -71,14 +72,16 @@ export function validate(id: string): boolean { * ``` */ export function generate(timestamp: number = Date.now()): string { - const bytes = new Uint8Array(16); - const view = new DataView(bytes.buffer); - // Unix timestamp in milliseconds (truncated to 48 bits) - if (!Number.isInteger(timestamp) || timestamp < 0) { + if ( + !Number.isInteger(timestamp) || timestamp < 0 || timestamp > 2 ** 48 - 1 + ) { throw new RangeError( - `Cannot generate UUID as timestamp must be a non-negative integer: timestamp ${timestamp}`, + `Cannot generate UUID as timestamp must be an integer between 0 and 2**48 - 1: timestamp ${timestamp}`, ); } + const bytes = new Uint8Array(16); + const view = new DataView(bytes.buffer); + // Unix timestamp in milliseconds occupies the first 48 bits view.setBigUint64(0, BigInt(timestamp) << 16n); crypto.getRandomValues(bytes.subarray(6)); // Version (4 bits) Occupies bits 48 through 51 of octet 6. diff --git a/uuid/v7_test.ts b/uuid/v7_test.ts index a6b9ea5c9767..e101b98aebc5 100644 --- a/uuid/v7_test.ts +++ b/uuid/v7_test.ts @@ -52,18 +52,30 @@ Deno.test("generate() throws on invalid timestamp", () => { assertThrows( () => generate(-1), RangeError, - "Cannot generate UUID as timestamp must be a non-negative integer: timestamp -1", + "Cannot generate UUID as timestamp must be an integer between 0 and 2**48 - 1: timestamp -1", ); assertThrows( () => generate(NaN), RangeError, - "Cannot generate UUID as timestamp must be a non-negative integer: timestamp NaN", + "Cannot generate UUID as timestamp must be an integer between 0 and 2**48 - 1: timestamp NaN", ); assertThrows( () => generate(2.3), RangeError, - "Cannot generate UUID as timestamp must be a non-negative integer: timestamp 2.3", + "Cannot generate UUID as timestamp must be an integer between 0 and 2**48 - 1: timestamp 2.3", ); + assertThrows( + () => generate(2 ** 48), + RangeError, + "Cannot generate UUID as timestamp must be an integer between 0 and 2**48 - 1: timestamp 281474976710656", + ); +}); + +Deno.test("generate() supports the maximum 48-bit timestamp", () => { + const timestamp = 2 ** 48 - 1; + const u = generate(timestamp); + assert(validate(u), `${u} is not a valid uuid v7`); + assertEquals(extractTimestamp(u), timestamp); }); Deno.test("validate() checks if a string is a valid v7 UUID", () => {