Skip to content

Commit b573654

Browse files
facugaichptomato
authored andcommitted
Regex: tighten matching of month and day values in datesplit
1 parent 5b1bc5e commit b573654

File tree

2 files changed

+128
-88
lines changed

2 files changed

+128
-88
lines changed

lib/regex.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ const calComponent = /[A-Za-z0-9]{3,8}/;
88
export const calendarID = new RegExp(`(?:${calComponent.source}(?:-${calComponent.source})*)`);
99

1010
const yearpart = /(?:[+\u2212-]\d{6}|\d{4})/;
11-
export const datesplit = new RegExp(`(${yearpart.source})(?:-(\\d{2})-(\\d{2})|(\\d{2})(\\d{2}))`);
11+
const monthpart = /(?:0[1-9]|1[0-2])/;
12+
const daypart = /(?:0[1-9]|[12]\d|3[01])/;
13+
export const datesplit = new RegExp(
14+
`(${yearpart.source})(?:-(${monthpart.source})-(${daypart.source})|(${monthpart.source})(${daypart.source}))`
15+
);
1216
const timesplit = /(\d{2})(?::(\d{2})(?::(\d{2})(?:[.,](\d{1,9}))?)?|(\d{2})(?:(\d{2})(?:[.,](\d{1,9}))?)?)?/;
1317
export const offset = /([+\u2212-])([01][0-9]|2[0-3])(?::?([0-5][0-9])(?::?([0-5][0-9])(?:[.,](\d{1,9}))?)?)?/;
1418
const zonesplit = new RegExp(`(?:([zZ])|(?:${offset.source})?)(?:\\[(${timeZoneID.source})\\])?`);
@@ -30,8 +34,8 @@ export const time = new RegExp(`^${timesplit.source}(?:${zonesplit.source})?(?:$
3034
// with the reference fields.
3135
// YYYYMM forbidden by ISO 8601, but since it is not ambiguous with anything
3236
// else we could parse in a YearMonth context, we allow it
33-
export const yearmonth = new RegExp(`^(${yearpart.source})-?(\\d{2})$`);
34-
export const monthday = /^(?:--)?(\d{2})-?(\d{2})$/;
37+
export const yearmonth = new RegExp(`^(${yearpart.source})-?(${monthpart.source})$`);
38+
export const monthday = new RegExp(`^(?:--)?(${monthpart.source})-?(${daypart.source})$`);
3539

3640
const fraction = /(\d+)(?:[.,](\d{1,9}))?/;
3741

test/regex.mjs

Lines changed: 121 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -33,47 +33,65 @@ describe('fromString regex', () => {
3333
test(`${dateTimeString}:30${zoneString}`, components.slice(0, 6));
3434
test(`${dateTimeString}:30.123456789${zoneString}`, components);
3535
}
36-
// Without time component
37-
test('2020-01-01Z', [2020, 1, 1, 0, 0, 0]);
38-
// Time separators
39-
['T', 't', ' '].forEach((timeSep) =>
40-
generateTest(`1976-11-18${timeSep}15:23`, 'Z', [1976, 11, 18, 15, 23, 30, 123, 456, 789])
41-
);
42-
// Time zone with bracketed name
43-
['+01:00', '+01', '+0100', '+01:00:00', '+010000', '+01:00:00.000000000', '+010000.0'].forEach((zoneString) => {
44-
generateTest('1976-11-18T15:23', `${zoneString}[Europe/Vienna]`, [1976, 11, 18, 14, 23, 30, 123, 456, 789]);
45-
generateTest('1976-11-18T15:23', `+01:00[${zoneString}]`, [1976, 11, 18, 14, 23, 30, 123, 456, 789]);
36+
describe('valid', () => {
37+
// Without time component
38+
test('2020-01-01Z', [2020, 1, 1, 0, 0, 0]);
39+
// Time separators
40+
['T', 't', ' '].forEach((timeSep) =>
41+
generateTest(`1976-11-18${timeSep}15:23`, 'Z', [1976, 11, 18, 15, 23, 30, 123, 456, 789])
42+
);
43+
// Time zone with bracketed name
44+
['+01:00', '+01', '+0100', '+01:00:00', '+010000', '+01:00:00.000000000', '+010000.0'].forEach((zoneString) => {
45+
generateTest('1976-11-18T15:23', `${zoneString}[Europe/Vienna]`, [1976, 11, 18, 14, 23, 30, 123, 456, 789]);
46+
generateTest('1976-11-18T15:23', `+01:00[${zoneString}]`, [1976, 11, 18, 14, 23, 30, 123, 456, 789]);
47+
});
48+
// Time zone with only offset
49+
['-04:00', '-04', '-0400', '-04:00:00', '-040000', '-04:00:00.000000000', '-040000.0'].forEach((zoneString) =>
50+
generateTest('1976-11-18T15:23', zoneString, [1976, 11, 18, 19, 23, 30, 123, 456, 789])
51+
);
52+
// Various numbers of decimal places
53+
test('1976-11-18T15:23:30.1Z', [1976, 11, 18, 15, 23, 30, 100]);
54+
test('1976-11-18T15:23:30.12Z', [1976, 11, 18, 15, 23, 30, 120]);
55+
test('1976-11-18T15:23:30.123Z', [1976, 11, 18, 15, 23, 30, 123]);
56+
test('1976-11-18T15:23:30.1234Z', [1976, 11, 18, 15, 23, 30, 123, 400]);
57+
test('1976-11-18T15:23:30.12345Z', [1976, 11, 18, 15, 23, 30, 123, 450]);
58+
test('1976-11-18T15:23:30.123456Z', [1976, 11, 18, 15, 23, 30, 123, 456]);
59+
test('1976-11-18T15:23:30.1234567Z', [1976, 11, 18, 15, 23, 30, 123, 456, 700]);
60+
test('1976-11-18T15:23:30.12345678Z', [1976, 11, 18, 15, 23, 30, 123, 456, 780]);
61+
// Lowercase UTC designator
62+
generateTest('1976-11-18T15:23', 'z', [1976, 11, 18, 15, 23, 30, 123, 456, 789]);
63+
// Comma decimal separator
64+
test('1976-11-18T15:23:30,1234Z', [1976, 11, 18, 15, 23, 30, 123, 400]);
65+
// Unicode minus sign
66+
['\u221204:00', '\u221204', '\u22120400'].forEach((offset) =>
67+
test(`1976-11-18T15:23:30.1234${offset}`, [1976, 11, 18, 19, 23, 30, 123, 400])
68+
);
69+
test('\u2212009999-11-18T15:23:30.1234Z', [-9999, 11, 18, 15, 23, 30, 123, 400]);
70+
// Mixture of basic and extended format
71+
test('1976-11-18T152330Z', [1976, 11, 18, 15, 23, 30]);
72+
test('1976-11-18T152330.1234Z', [1976, 11, 18, 15, 23, 30, 123, 400]);
73+
test('19761118T15:23:30Z', [1976, 11, 18, 15, 23, 30]);
74+
test('19761118T152330Z', [1976, 11, 18, 15, 23, 30]);
75+
test('19761118T152330.1234Z', [1976, 11, 18, 15, 23, 30, 123, 400]);
76+
// Representations with reduced precision
77+
test('1976-11-18T15Z', [1976, 11, 18, 15]);
78+
});
79+
describe('not valid', () => {
80+
// Invalid month values
81+
['00', '13', '20', '99'].forEach((monthString) => {
82+
const invalidIsoString = `1976-${monthString}-18T00:00:00Z`;
83+
it(invalidIsoString, () => {
84+
throws(() => Temporal.Instant.from(invalidIsoString), RangeError);
85+
});
86+
});
87+
// Invalid day values
88+
['00', '32', '40', '99'].forEach((dayString) => {
89+
const invalidIsoString = `1976-11-${dayString}T00:00:00Z`;
90+
it(invalidIsoString, () => {
91+
throws(() => Temporal.Instant.from(invalidIsoString), RangeError);
92+
});
93+
});
4694
});
47-
// Time zone with only offset
48-
['-04:00', '-04', '-0400', '-04:00:00', '-040000', '-04:00:00.000000000', '-040000.0'].forEach((zoneString) =>
49-
generateTest('1976-11-18T15:23', zoneString, [1976, 11, 18, 19, 23, 30, 123, 456, 789])
50-
);
51-
// Various numbers of decimal places
52-
test('1976-11-18T15:23:30.1Z', [1976, 11, 18, 15, 23, 30, 100]);
53-
test('1976-11-18T15:23:30.12Z', [1976, 11, 18, 15, 23, 30, 120]);
54-
test('1976-11-18T15:23:30.123Z', [1976, 11, 18, 15, 23, 30, 123]);
55-
test('1976-11-18T15:23:30.1234Z', [1976, 11, 18, 15, 23, 30, 123, 400]);
56-
test('1976-11-18T15:23:30.12345Z', [1976, 11, 18, 15, 23, 30, 123, 450]);
57-
test('1976-11-18T15:23:30.123456Z', [1976, 11, 18, 15, 23, 30, 123, 456]);
58-
test('1976-11-18T15:23:30.1234567Z', [1976, 11, 18, 15, 23, 30, 123, 456, 700]);
59-
test('1976-11-18T15:23:30.12345678Z', [1976, 11, 18, 15, 23, 30, 123, 456, 780]);
60-
// Lowercase UTC designator
61-
generateTest('1976-11-18T15:23', 'z', [1976, 11, 18, 15, 23, 30, 123, 456, 789]);
62-
// Comma decimal separator
63-
test('1976-11-18T15:23:30,1234Z', [1976, 11, 18, 15, 23, 30, 123, 400]);
64-
// Unicode minus sign
65-
['\u221204:00', '\u221204', '\u22120400'].forEach((offset) =>
66-
test(`1976-11-18T15:23:30.1234${offset}`, [1976, 11, 18, 19, 23, 30, 123, 400])
67-
);
68-
test('\u2212009999-11-18T15:23:30.1234Z', [-9999, 11, 18, 15, 23, 30, 123, 400]);
69-
// Mixture of basic and extended format
70-
test('1976-11-18T152330Z', [1976, 11, 18, 15, 23, 30]);
71-
test('1976-11-18T152330.1234Z', [1976, 11, 18, 15, 23, 30, 123, 400]);
72-
test('19761118T15:23:30Z', [1976, 11, 18, 15, 23, 30]);
73-
test('19761118T152330Z', [1976, 11, 18, 15, 23, 30]);
74-
test('19761118T152330.1234Z', [1976, 11, 18, 15, 23, 30, 123, 400]);
75-
// Representations with reduced precision
76-
test('1976-11-18T15Z', [1976, 11, 18, 15]);
7795
});
7896

7997
describe('datetime', () => {
@@ -99,51 +117,69 @@ describe('fromString regex', () => {
99117
test(`${dateTimeString}:30${zoneString}`, components.slice(0, 6));
100118
test(`${dateTimeString}:30.123456789${zoneString}`, components);
101119
}
102-
// Time separators
103-
['T', 't', ' '].forEach((timeSep) => generateTest(`1976-11-18${timeSep}15:23`, ''));
104-
// Various forms of time zone
105-
[
106-
'+0100[Europe/Vienna]',
107-
'+01:00[Europe/Vienna]',
108-
'[Europe/Vienna]',
109-
'+01:00[Custom/Vienna]',
110-
'-0400',
111-
'-04:00',
112-
'-04:00:00.000000000',
113-
'+010000.0[Europe/Vienna]',
114-
'+01:00[+01:00]',
115-
'+01:00[+0100]',
116-
''
117-
].forEach((zoneString) => generateTest('1976-11-18T15:23', zoneString));
118-
// Various numbers of decimal places
119-
test('1976-11-18T15:23:30.1', [1976, 11, 18, 15, 23, 30, 100]);
120-
test('1976-11-18T15:23:30.12', [1976, 11, 18, 15, 23, 30, 120]);
121-
test('1976-11-18T15:23:30.123', [1976, 11, 18, 15, 23, 30, 123]);
122-
test('1976-11-18T15:23:30.1234', [1976, 11, 18, 15, 23, 30, 123, 400]);
123-
test('1976-11-18T15:23:30.12345', [1976, 11, 18, 15, 23, 30, 123, 450]);
124-
test('1976-11-18T15:23:30.123456', [1976, 11, 18, 15, 23, 30, 123, 456]);
125-
test('1976-11-18T15:23:30.1234567', [1976, 11, 18, 15, 23, 30, 123, 456, 700]);
126-
test('1976-11-18T15:23:30.12345678', [1976, 11, 18, 15, 23, 30, 123, 456, 780]);
127-
// Comma decimal separator
128-
test('1976-11-18T15:23:30,1234', [1976, 11, 18, 15, 23, 30, 123, 400]);
129-
// Unicode minus sign
130-
['\u221204:00', '\u221204', '\u22120400'].forEach((offset) =>
131-
test(`1976-11-18T15:23:30.1234${offset}`, [1976, 11, 18, 15, 23, 30, 123, 400])
132-
);
133-
test('\u2212009999-11-18T15:23:30.1234', [-9999, 11, 18, 15, 23, 30, 123, 400]);
134-
// Mixture of basic and extended format
135-
test('1976-11-18T152330', [1976, 11, 18, 15, 23, 30]);
136-
test('1976-11-18T152330.1234', [1976, 11, 18, 15, 23, 30, 123, 400]);
137-
test('19761118T15:23:30', [1976, 11, 18, 15, 23, 30]);
138-
test('19761118T152330', [1976, 11, 18, 15, 23, 30]);
139-
test('19761118T152330.1234', [1976, 11, 18, 15, 23, 30, 123, 400]);
140-
// Representations with reduced precision
141-
test('1976-11-18T15', [1976, 11, 18, 15]);
142-
test('1976-11-18', [1976, 11, 18]);
143-
// Representations with calendar
144-
['', 'Z', '+01:00[Europe/Vienna]', '+01:00[Custom/Vienna]', '[Europe/Vienna]'].forEach((zoneString) =>
145-
test(`1976-11-18T15:23:30.123456789${zoneString}[u-ca=iso8601]`, [1976, 11, 18, 15, 23, 30, 123, 456, 789])
146-
);
120+
describe('valid', () => {
121+
// Time separators
122+
['T', 't', ' '].forEach((timeSep) => generateTest(`1976-11-18${timeSep}15:23`, ''));
123+
// Various forms of time zone
124+
[
125+
'+0100[Europe/Vienna]',
126+
'+01:00[Europe/Vienna]',
127+
'[Europe/Vienna]',
128+
'+01:00[Custom/Vienna]',
129+
'-0400',
130+
'-04:00',
131+
'-04:00:00.000000000',
132+
'+010000.0[Europe/Vienna]',
133+
'+01:00[+01:00]',
134+
'+01:00[+0100]',
135+
''
136+
].forEach((zoneString) => generateTest('1976-11-18T15:23', zoneString));
137+
// Various numbers of decimal places
138+
test('1976-11-18T15:23:30.1', [1976, 11, 18, 15, 23, 30, 100]);
139+
test('1976-11-18T15:23:30.12', [1976, 11, 18, 15, 23, 30, 120]);
140+
test('1976-11-18T15:23:30.123', [1976, 11, 18, 15, 23, 30, 123]);
141+
test('1976-11-18T15:23:30.1234', [1976, 11, 18, 15, 23, 30, 123, 400]);
142+
test('1976-11-18T15:23:30.12345', [1976, 11, 18, 15, 23, 30, 123, 450]);
143+
test('1976-11-18T15:23:30.123456', [1976, 11, 18, 15, 23, 30, 123, 456]);
144+
test('1976-11-18T15:23:30.1234567', [1976, 11, 18, 15, 23, 30, 123, 456, 700]);
145+
test('1976-11-18T15:23:30.12345678', [1976, 11, 18, 15, 23, 30, 123, 456, 780]);
146+
// Comma decimal separator
147+
test('1976-11-18T15:23:30,1234', [1976, 11, 18, 15, 23, 30, 123, 400]);
148+
// Unicode minus sign
149+
['\u221204:00', '\u221204', '\u22120400'].forEach((offset) =>
150+
test(`1976-11-18T15:23:30.1234${offset}`, [1976, 11, 18, 15, 23, 30, 123, 400])
151+
);
152+
test('\u2212009999-11-18T15:23:30.1234', [-9999, 11, 18, 15, 23, 30, 123, 400]);
153+
// Mixture of basic and extended format
154+
test('1976-11-18T152330', [1976, 11, 18, 15, 23, 30]);
155+
test('1976-11-18T152330.1234', [1976, 11, 18, 15, 23, 30, 123, 400]);
156+
test('19761118T15:23:30', [1976, 11, 18, 15, 23, 30]);
157+
test('19761118T152330', [1976, 11, 18, 15, 23, 30]);
158+
test('19761118T152330.1234', [1976, 11, 18, 15, 23, 30, 123, 400]);
159+
// Representations with reduced precision
160+
test('1976-11-18T15', [1976, 11, 18, 15]);
161+
test('1976-11-18', [1976, 11, 18]);
162+
// Representations with calendar
163+
['', 'Z', '+01:00[Europe/Vienna]', '+01:00[Custom/Vienna]', '[Europe/Vienna]'].forEach((zoneString) =>
164+
test(`1976-11-18T15:23:30.123456789${zoneString}[u-ca=iso8601]`, [1976, 11, 18, 15, 23, 30, 123, 456, 789])
165+
);
166+
});
167+
describe('not valid', () => {
168+
// Invalid month values
169+
['00', '13', '20', '99'].forEach((monthString) => {
170+
const invalidIsoString = `1976-${monthString}-18T00:00:00`;
171+
it(invalidIsoString, () => {
172+
throws(() => Temporal.PlainDateTime.from(invalidIsoString), RangeError);
173+
});
174+
});
175+
// Invalid day values
176+
['00', '32', '40', '99'].forEach((dayString) => {
177+
const invalidIsoString = `1976-11-${dayString}T00:00:00`;
178+
it(invalidIsoString, () => {
179+
throws(() => Temporal.PlainDateTime.from(invalidIsoString), RangeError);
180+
});
181+
});
182+
});
147183
});
148184

149185
describe('date', () => {

0 commit comments

Comments
 (0)