diff --git a/src/index.ts b/src/index.ts index d3cbec712..197bc443b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -41,6 +41,7 @@ export { Protocols } from './utils/protocols.js'; export { ProtocolsConfigure, ProtocolsConfigureOptions } from './interfaces/protocols-configure.js'; export { ProtocolsQuery, ProtocolsQueryOptions } from './interfaces/protocols-query.js'; export { Records } from './utils/records.js'; +export { createTimestamp } from './utils/time.js'; export { RecordsDelete, RecordsDeleteOptions } from './interfaces/records-delete.js'; export { RecordsRead, RecordsReadOptions } from './interfaces/records-read.js'; export { Secp256k1 } from './utils/secp256k1.js'; diff --git a/src/utils/time.ts b/src/utils/time.ts index fab05d85a..070fb99b1 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -18,6 +18,28 @@ export function getCurrentTimeInHighPrecision(): string { return Temporal.Now.instant().toString({ smallestUnit: 'microseconds' }); } +/** + * Creates a UTC ISO-8601 timestamp in microsecond precision accepted by DWN. + * @param options - Options for creating the timestamp. + * @returns string + */ +export function createTimestamp( + options: {year?: number, month?: number, day?: number, hour?: number, minute?: number, second?: number, millisecond?: number, microsecond?: number} +): string { + const { year, month, day, hour, minute, second, millisecond, microsecond } = options; + return Temporal.ZonedDateTime.from({ + timeZone: 'UTC', + year, + month, + day, + hour, + minute, + second, + millisecond, + microsecond + }).toInstant().toString({ smallestUnit: 'microseconds' }); +} + /** * We must sleep for at least 2ms to avoid timestamp collisions during testing. * https://github.com/TBD54566975/dwn-sdk-js/issues/481 @@ -35,6 +57,6 @@ export function validateTimestamp(timestamp: string): void { try { Temporal.Instant.from(timestamp); } catch { - throw new DwnError(DwnErrorCode.TimestampInvalid,`Invalid timestamp: ${timestamp}`); + throw new DwnError(DwnErrorCode.TimestampInvalid, `Invalid timestamp: ${timestamp}`); } } diff --git a/tests/utils/test-data-generator.ts b/tests/utils/test-data-generator.ts index 41186f4ab..59ae70bb3 100644 --- a/tests/utils/test-data-generator.ts +++ b/tests/utils/test-data-generator.ts @@ -37,6 +37,7 @@ import type { PrivateJwk, PublicJwk } from '../../src/types/jose-types.js'; import * as cbor from '@ipld/dag-cbor'; import { CID } from 'multiformats/cid'; +import { createTimestamp } from '../../src/index.js'; import { DataStream } from '../../src/utils/data-stream.js'; import { getCurrentTimeInHighPrecision } from '../../src/utils/time.js'; import { PermissionsGrant } from '../../src/interfaces/permissions-grant.js'; @@ -832,15 +833,14 @@ export class TestDataGenerator { * @returns random UTC ISO-8601 timestamp */ public static randomTimestamp(): string { - return Temporal.ZonedDateTime.from({ - timeZone : 'UTC', - year : this.randomInt(2000, 2022), - month : this.randomInt(1, 12), - day : this.randomInt(1, 28), - hour : this.randomInt(0, 23), - minute : this.randomInt(0, 59), - second : this.randomInt(0, 59), - }).toInstant().toString({ smallestUnit: 'microseconds' }); + return createTimestamp({ + year : this.randomInt(2000, 2022), + month : this.randomInt(1, 12), + day : this.randomInt(1, 28), + hour : this.randomInt(0, 23), + minute : this.randomInt(0, 59), + second : this.randomInt(0, 59), + }); } /** diff --git a/tests/utils/time.spec.ts b/tests/utils/time.spec.ts index b92d2170b..3414a8d5b 100644 --- a/tests/utils/time.spec.ts +++ b/tests/utils/time.spec.ts @@ -1,7 +1,7 @@ import { DwnErrorCode } from '../../src/core/dwn-error.js'; import { expect } from 'chai'; import { TestDataGenerator } from '../utils/test-data-generator.js'; -import { validateTimestamp } from '../../src/utils/time.js'; +import { createTimestamp, validateTimestamp } from '../../src/utils/time.js'; describe('time', () => { @@ -26,4 +26,35 @@ describe('time', () => { }); }); }); + + describe('createTimestamp', () => { + it('should create a valid timestamp', () => { + const timestamp = createTimestamp({ + year : 2022, + month : 4, + day : 29, + hour : 10, + minute : 30, + second : 0, + millisecond : 123, + microsecond : 456 + }); + expect(timestamp).to.equal('2022-04-29T10:30:00.123456Z'); + }); + + for (let i = 0; i < 5; i++) { + const year = TestDataGenerator.randomInt(1900, 2500); + const month = TestDataGenerator.randomInt(1, 12); + const day = TestDataGenerator.randomInt(1, 28); + const hour = TestDataGenerator.randomInt(0, 23); + const minute = TestDataGenerator.randomInt(0, 59); + const second = TestDataGenerator.randomInt(0, 59); + const millisecond = TestDataGenerator.randomInt(0, 999); + const microsecond = TestDataGenerator.randomInt(0, 999); + it(`should create a valid timestamp for random values ${i}`, () => { + const timestamp = createTimestamp({ year, month, day, hour, minute, second, millisecond, microsecond }); + expect(()=> validateTimestamp(timestamp)).to.not.throw(); + }); + } + }); }); \ No newline at end of file