Skip to content

Commit ed94bc7

Browse files
committed
Support literal square brackets in format strings
Allow escaping square brackets with backslashes to output them literally in formatted dates and parse them from input strings.
1 parent e2066ae commit ed94bc7

File tree

3 files changed

+23
-1
lines changed

3 files changed

+23
-1
lines changed

src/compile.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,24 @@
22

33
export type CompiledObject = string[];
44

5+
// Escape sequences for literal brackets
6+
const LBRACKET = /\\\[/g;
7+
const RBRACKET = /\\\]/g;
8+
9+
// Private Use Area characters to temporarily replace literal brackets
10+
const PUA_LBRACKET = /\uE000/g;
11+
const PUA_RBRACKET = /\uE001/g;
12+
13+
// Regular expression to match format tokens, literal text, and escaped characters
14+
const REGEXP = /\[(?:[^[\]]|\[[^[\]]*])*]|([A-Za-z])\1*|\.{3}|./g;
15+
516
/**
617
* Compiles a format string into a tokenized array for efficient parsing and formatting.
718
* @param formatString - The format string to compile
819
* @returns A compiled array where the first element is the original format string,
920
* followed by tokenized format components
1021
*/
1122
export const compile = (formatString: string): CompiledObject => {
12-
return [formatString, ...formatString.match(/\[(?:[^[\]]|\[[^[\]]*])*]|([A-Za-z])\1*|\.{3}|./g) || []];
23+
const array = formatString.replace(LBRACKET, '\uE000').replace(RBRACKET, '\uE001').match(REGEXP) || [];
24+
return [formatString, ...array.map(token => token.replace(PUA_LBRACKET, '[').replace(PUA_RBRACKET, ']'))];
1325
};

tests/compile.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,8 @@ test('[empty]', () => {
170170
const obj = [''] as string[];
171171
expect(compile('')).toEqual(obj);
172172
});
173+
174+
test('\\[YYYY-MM-DD\\]', () => {
175+
const obj = ['\\[YYYY-MM-DD\\]', '[', 'YYYY', '-', 'MM', '-', 'DD', ']'];
176+
expect(compile('\\[YYYY-MM-DD\\]')).toEqual(obj);
177+
});

tests/parse.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,11 @@ test(' ', () => {
489489
expect(Number.isNaN(parse('20151231235959900', ' ').getTime())).toBe(true);
490490
});
491491

492+
test('\\[YYYY-MM-DD HH:mm:ss\\]', () => {
493+
const now = new Date(2025, 7, 23, 14, 30, 45);
494+
expect(parse('[2025-08-23 14:30:45]', '\\[YYYY-MM-DD HH:mm:ss\\]')).toEqual(now);
495+
});
496+
492497
describe('options', () => {
493498
test('hour12: h11', () => {
494499
expect(parse('2000-01-01 00:00 AM', 'YYYY-MM-DD hh:mm A', { hour12: 'h11' })).toEqual(new Date(2000, 0, 1, 0, 0));

0 commit comments

Comments
 (0)