diff --git a/src/modules/string/index.ts b/src/modules/string/index.ts index cfee0dc13fe..bd8283e9dc5 100644 --- a/src/modules/string/index.ts +++ b/src/modules/string/index.ts @@ -704,6 +704,59 @@ export class StringModule extends SimpleModuleBase { .replaceAll('y', () => this.faker.number.hex({ min: 0x8, max: 0xb })); } + /** + * Returns a ULID ([Universally Unique Lexicographically Sortable Identifier](https://github.com/ulid/spec)). + * + * @param options The optional options object. + * @param options.refDate The date to use as reference point for the newly generated ULID encoded timestamp. Defaults to `faker.defaultRefDate()`. + * + * @example + * faker.string.ulid() // '01ARZ3NDEKTSV4RRFFQ69G5FAV' + * + * @since 8.2.0 + */ + ulid( + options: { + /** + * The date to use as reference point for the newly generated ULID encoded timestamp. + * + * @default faker.defaultRefDate() + */ + refDate?: string | Date | number; + } = {} + ): string { + const encodingCharacters = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; // Crockford's Base32 - Excludes I, L, O, and U which may be confused with numbers + const encodingLength = encodingCharacters.length; + const encodeTime = (now: number) => { + let mod; + let len = 10; + let str = ''; + for (; len > 0; len--) { + mod = now % encodingLength; + str = encodingCharacters.charAt(mod) + str; + now = (now - mod) / encodingLength; + } + + return str; + }; + + const { refDate } = options; + let date = refDate; + + if (date == null) { + date = this.faker.defaultRefDate(); + } + + date = new Date(date); + if (Number.isNaN(date.valueOf())) { + date = this.faker.defaultRefDate(); + } + + return ( + encodeTime(date.getTime()) + this.fromCharacters(encodingCharacters, 16) + ); + } + /** * Generates a [Nano ID](https://github.com/ai/nanoid). * diff --git a/test/modules/__snapshots__/string.spec.ts.snap b/test/modules/__snapshots__/string.spec.ts.snap index 31e4c28dd41..eeb6e8754dc 100644 --- a/test/modules/__snapshots__/string.spec.ts.snap +++ b/test/modules/__snapshots__/string.spec.ts.snap @@ -164,6 +164,22 @@ exports[`string > 42 > symbol > with length parameter 5`] = `">%*,/"`; exports[`string > 42 > symbol > with length range 1`] = `"}\\>%%"\`>[!~_'&"`; +exports[`string > 42 > ulid > noArgs 1`] = `"01HSCFQK6RBYQK441VKP0ZT655"`; + +exports[`string > 42 > ulid > noArgs 2`] = `"01HSCFQK6R9GD9K49BES6GJ1K5"`; + +exports[`string > 42 > ulid > noArgs 3`] = `"01HSCFQK6S2YYS93NE3F1X8N9G"`; + +exports[`string > 42 > ulid > noArgs 4`] = `"01HSCFQK6SH5ZRYWKX261AC8TB"`; + +exports[`string > 42 > ulid > noArgs 5`] = `"01HSCFQK6S8H4S2ZR60TPQR2B3"`; + +exports[`string > 42 > ulid > with only Date refDate 1`] = `"01EZ2S259ZBYQK441VKP0ZT655"`; + +exports[`string > 42 > ulid > with only number refDate 1`] = `"01EZ2S259ZBYQK441VKP0ZT655"`; + +exports[`string > 42 > ulid > with only string refDate 1`] = `"01EZ2S259ZBYQK441VKP0ZT655"`; + exports[`string > 42 > uuid 1`] = `"5fb9220d-9b0f-4d32-a248-6492457c3890"`; exports[`string > 42 > uuid 2`] = `"21ffc41a-7170-4e4a-9488-2fcfe9e13056"`; @@ -338,6 +354,22 @@ exports[`string > 1211 > symbol > with length parameter 5`] = `"~]-|<"`; exports[`string > 1211 > symbol > with length range 1`] = `"{(~@@],[_]?_.\`\`'=',~"`; +exports[`string > 1211 > ulid > noArgs 1`] = `"01HSCFQK7SXW7ZNNRBPTRMTDVV"`; + +exports[`string > 1211 > ulid > noArgs 2`] = `"01HSCFQK7S6J6BZRCXHZ79PGBV"`; + +exports[`string > 1211 > ulid > noArgs 3`] = `"01HSCFQK7SC895HP4PF7D6VJF1"`; + +exports[`string > 1211 > ulid > noArgs 4`] = `"01HSCFQK7SP5P8RGE6KV3VECFJ"`; + +exports[`string > 1211 > ulid > noArgs 5`] = `"01HSCFQK7SSHXZTV7PTAW0HRE2"`; + +exports[`string > 1211 > ulid > with only Date refDate 1`] = `"01EZ2S259ZXW7ZNNRBPTRMTDVV"`; + +exports[`string > 1211 > ulid > with only number refDate 1`] = `"01EZ2S259ZXW7ZNNRBPTRMTDVV"`; + +exports[`string > 1211 > ulid > with only string refDate 1`] = `"01EZ2S259ZXW7ZNNRBPTRMTDVV"`; + exports[`string > 1211 > uuid 1`] = `"ee3faac5-bdca-4d6d-9d39-35fc6e8f34b8"`; exports[`string > 1211 > uuid 2`] = `"d64428b2-b736-43d9-970b-2b4c8739d1d7"`; @@ -512,6 +544,22 @@ exports[`string > 1337 > symbol > with length parameter 5`] = `"]'*@:"`; exports[`string > 1337 > symbol > with length range 1`] = `"&)/+;)~\\$-?%"`; +exports[`string > 1337 > ulid > noArgs 1`] = `"01HSCFQK89858EAG8ZQ3CM4ZES"`; + +exports[`string > 1337 > ulid > noArgs 2`] = `"01HSCFQK89SBDJR69NF5D6HT5Y"`; + +exports[`string > 1337 > ulid > noArgs 3`] = `"01HSCFQK89DGG0QZ54BP0B1SBP"`; + +exports[`string > 1337 > ulid > noArgs 4`] = `"01HSCFQK89FZTKJZ802YZFAQ0R"`; + +exports[`string > 1337 > ulid > noArgs 5`] = `"01HSCFQK89N6NX5XAP8G5EECSF"`; + +exports[`string > 1337 > ulid > with only Date refDate 1`] = `"01EZ2S259Z858EAG8ZQ3CM4ZES"`; + +exports[`string > 1337 > ulid > with only number refDate 1`] = `"01EZ2S259Z858EAG8ZQ3CM4ZES"`; + +exports[`string > 1337 > ulid > with only string refDate 1`] = `"01EZ2S259Z858EAG8ZQ3CM4ZES"`; + exports[`string > 1337 > uuid 1`] = `"4247584f-b16a-42f7-8cc5-69c34a72638d"`; exports[`string > 1337 > uuid 2`] = `"f6880bf2-25b0-450c-a5b7-fd99f401ff75"`; diff --git a/test/modules/string.spec.ts b/test/modules/string.spec.ts index 22fa8aeba88..ccb6bde0640 100644 --- a/test/modules/string.spec.ts +++ b/test/modules/string.spec.ts @@ -113,6 +113,17 @@ describe('string', () => { t.itRepeated('uuid', 5); + t.describe('ulid', (t) => { + const ulidRefDate = '2021-02-21T17:09:15.711Z'; + + t.itRepeated('noArgs', 5) + .it('with only string refDate', { refDate: ulidRefDate }) + .it('with only Date refDate', { refDate: new Date(ulidRefDate) }) + .it('with only number refDate', { + refDate: new Date(ulidRefDate).getTime(), + }); + }); + t.describe('nanoid', (t) => { t.itRepeated('noArgs', 5) .it('with length parameter', 30) @@ -750,6 +761,14 @@ describe('string', () => { }); }); + describe(`ulid`, () => { + it('generates a valid ULID', () => { + const ulid = faker.string.ulid(); + const regex = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/; + expect(ulid).toMatch(regex); + }); + }); + describe(`nanoid`, () => { it('generates a valid Nano ID', () => { const id = faker.string.nanoid();