Skip to content

Commit 2e1dd22

Browse files
authored
fix: Handle more BC date cases (#7410)
1 parent 9170093 commit 2e1dd22

File tree

4 files changed

+103
-10
lines changed

4 files changed

+103
-10
lines changed

packages/@internationalized/date/src/conversion.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ export function fromAbsolute(ms: number, timeZone: string): ZonedDateTime {
187187
let second = date.getUTCSeconds();
188188
let millisecond = date.getUTCMilliseconds();
189189

190-
return new ZonedDateTime(year < 1 ? 'BC' : 'AD', year < 0 ? Math.abs(year) + 1 : year, month, day, timeZone, offset, hour, minute, second, millisecond);
190+
return new ZonedDateTime(year < 1 ? 'BC' : 'AD', year < 1 ? -year + 1 : year, month, day, timeZone, offset, hour, minute, second, millisecond);
191191
}
192192

193193
/**

packages/@internationalized/date/src/string.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import {GregorianCalendar} from './calendars/GregorianCalendar';
1818
import {Mutable} from './utils';
1919

2020
const TIME_RE = /^(\d{2})(?::(\d{2}))?(?::(\d{2}))?(\.\d+)?$/;
21-
const DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
22-
const DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}))?(?::(\d{2}))?(?::(\d{2}))?(\.\d+)?$/;
23-
const ZONED_DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}))?(?::(\d{2}))?(?::(\d{2}))?(\.\d+)?(?:([+-]\d{2})(?::?(\d{2}))?)?\[(.*?)\]$/;
24-
const ABSOLUTE_RE = /^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}))?(?::(\d{2}))?(?::(\d{2}))?(\.\d+)?(?:(?:([+-]\d{2})(?::?(\d{2}))?)|Z)$/;
21+
const DATE_RE = /^([+-]\d{6}|\d{4})-(\d{2})-(\d{2})$/;
22+
const DATE_TIME_RE = /^([+-]\d{6}|\d{4})-(\d{2})-(\d{2})(?:T(\d{2}))?(?::(\d{2}))?(?::(\d{2}))?(\.\d+)?$/;
23+
const ZONED_DATE_TIME_RE = /^([+-]\d{6}|\d{4})-(\d{2})-(\d{2})(?:T(\d{2}))?(?::(\d{2}))?(?::(\d{2}))?(\.\d+)?(?:([+-]\d{2})(?::?(\d{2}))?)?\[(.*?)\]$/;
24+
const ABSOLUTE_RE = /^([+-]\d{6}|\d{4})-(\d{2})-(\d{2})(?:T(\d{2}))?(?::(\d{2}))?(?::(\d{2}))?(\.\d+)?(?:(?:([+-]\d{2})(?::?(\d{2}))?)|Z)$/;
2525
const DATE_TIME_DURATION_RE =
2626
/^((?<negative>-)|\+)?P((?<years>\d*)Y)?((?<months>\d*)M)?((?<weeks>\d*)W)?((?<days>\d*)D)?((?<time>T)((?<hours>\d*[.,]?\d{1,9})H)?((?<minutes>\d*[.,]?\d{1,9})M)?((?<seconds>\d*[.,]?\d{1,9})S)?)?$/;
2727
const requiredDurationTimeGroups = ['hours', 'minutes', 'seconds'];
@@ -66,8 +66,12 @@ export function parseDateTime(value: string): CalendarDateTime {
6666
throw new Error('Invalid ISO 8601 date time string: ' + value);
6767
}
6868

69+
let year = parseNumber(m[1], -9999, 9999);
70+
let era = year < 1 ? 'BC' : 'AD';
71+
6972
let date: Mutable<CalendarDateTime> = new CalendarDateTime(
70-
parseNumber(m[1], 1, 9999),
73+
era,
74+
year < 1 ? -year + 1 : year,
7175
parseNumber(m[2], 1, 12),
7276
1,
7377
m[4] ? parseNumber(m[4], 0, 23) : 0,
@@ -92,8 +96,12 @@ export function parseZonedDateTime(value: string, disambiguation?: Disambiguatio
9296
throw new Error('Invalid ISO 8601 date time string: ' + value);
9397
}
9498

99+
let year = parseNumber(m[1], -9999, 9999);
100+
let era = year < 1 ? 'BC' : 'AD';
101+
95102
let date: Mutable<ZonedDateTime> = new ZonedDateTime(
96-
parseNumber(m[1], 1, 9999),
103+
era,
104+
year < 1 ? -year + 1 : year,
97105
parseNumber(m[2], 1, 12),
98106
1,
99107
m[10],
@@ -136,8 +144,12 @@ export function parseAbsolute(value: string, timeZone: string): ZonedDateTime {
136144
throw new Error('Invalid ISO 8601 date time string: ' + value);
137145
}
138146

147+
let year = parseNumber(m[1], -9999, 9999);
148+
let era = year < 1 ? 'BC' : 'AD';
149+
139150
let date: Mutable<ZonedDateTime> = new ZonedDateTime(
140-
parseNumber(m[1], 1, 9999),
151+
era,
152+
year < 1 ? -year + 1 : year,
141153
parseNumber(m[2], 1, 12),
142154
1,
143155
timeZone,
@@ -180,7 +192,15 @@ export function timeToString(time: Time): string {
180192

181193
export function dateToString(date: CalendarDate): string {
182194
let gregorianDate = toCalendar(date, new GregorianCalendar());
183-
return `${String(gregorianDate.year).padStart(4, '0')}-${String(gregorianDate.month).padStart(2, '0')}-${String(gregorianDate.day).padStart(2, '0')}`;
195+
let year: string;
196+
if (gregorianDate.era === 'BC') {
197+
year = gregorianDate.year === 1
198+
? '0000'
199+
: '-' + String(Math.abs(1 - gregorianDate.year)).padStart(6, '00');
200+
} else {
201+
year = String(gregorianDate.year).padStart(4, '0');
202+
}
203+
return `${year}-${String(gregorianDate.month).padStart(2, '0')}-${String(gregorianDate.day).padStart(2, '0')}`;
184204
}
185205

186206
export function dateTimeToString(date: AnyDateTime): string {

packages/@internationalized/date/tests/conversion.test.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ describe('CalendarDate conversion', function () {
5858
let date = new CalendarDate(2020, 2, 3);
5959
expect(toAbsolute(date, 'America/Los_Angeles')).toBe(new Date('2020-02-03T08:00Z').getTime());
6060
});
61+
62+
it('should support BC dates', function () {
63+
let date = new CalendarDateTime('BC', 2, 1, 1);
64+
expect(toAbsolute(date, 'UTC')).toEqual(new Date('-000001-01-01T00:00Z').getTime());
65+
});
6166
});
6267

6368
describe('toDate', function () {
@@ -104,6 +109,11 @@ describe('CalendarDate conversion', function () {
104109
let date = new CalendarDate(2020, 2, 3);
105110
expect(toDate(date, 'America/Los_Angeles')).toEqual(new Date('2020-02-03T08:00Z'));
106111
});
112+
113+
it('should support BC dates', function () {
114+
let date = new CalendarDateTime('BC', 2, 1, 1);
115+
expect(toDate(date, 'UTC')).toEqual(new Date('-000001-01-01T00:00Z'));
116+
});
107117
});
108118

109119
describe('possibleAbsolutes', function () {
@@ -142,7 +152,8 @@ describe('CalendarDate conversion', function () {
142152
expect(date).toEqual(new ZonedDateTime('BC', 1, 1, 1, 'UTC', 0, 0, 0, 0));
143153
date = fromAbsolute(new Date('0001-01-01T00:00:00.000Z').getTime(), 'UTC');
144154
expect(date).toEqual(new ZonedDateTime('AD', 1, 1, 1, 'UTC', 0, 0, 0, 0));
145-
155+
date = fromAbsolute(new Date('-000001-01-01T00:00:00.000Z').getTime(), 'UTC');
156+
expect(date).toEqual(new ZonedDateTime('BC', 2, 1, 1, 'UTC', 0, 0, 0, 0));
146157
date = fromAbsolute(new Date('-000009-01-01T00:00:00.000Z').getTime(), 'UTC');
147158
expect(date).toEqual(new ZonedDateTime('BC', 10, 1, 1, 'UTC', 0, 0, 0, 0));
148159
});

packages/@internationalized/date/tests/string.test.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ describe('string conversion', function () {
122122
let date = new CalendarDate(123, 2, 3);
123123
expect(date.toString()).toBe('0123-02-03');
124124
});
125+
126+
it('should stringify a BC date', function () {
127+
let date = new CalendarDate('BC', 1, 1, 1);
128+
expect(date.toString()).toBe('0000-01-01');
129+
130+
date = new CalendarDate('BC', 2, 1, 1);
131+
expect(date.toString()).toBe('-000001-01-01');
132+
});
125133
});
126134

127135
describe('parseDateTime', function () {
@@ -155,6 +163,16 @@ describe('string conversion', function () {
155163
expect(date).toEqual(expected);
156164
});
157165

166+
it('should parse BC dates', function () {
167+
let date = parseDateTime('0000-01-01');
168+
let expected = new CalendarDateTime('BC', 1, 1, 1);
169+
expect(date).toEqual(expected);
170+
171+
date = parseDateTime('-000002-01-01');
172+
expected = new CalendarDateTime('BC', 3, 1, 1);
173+
expect(date).toEqual(expected);
174+
});
175+
158176
it('should error if date is not padded', function () {
159177
expect(() => parseDateTime('123-02-03T12:24:45')).toThrow();
160178
expect(() => parseDateTime('2020-2-03T12:24:45')).toThrow();
@@ -193,6 +211,14 @@ describe('string conversion', function () {
193211
let date = new CalendarDateTime(2020, 2, 3, 12, 23, 45, 120);
194212
expect(date.toString()).toBe('2020-02-03T12:23:45.12');
195213
});
214+
215+
it('should stringify a BC date', function () {
216+
let date = new CalendarDateTime('BC', 1, 1, 1);
217+
expect(date.toString()).toBe('0000-01-01T00:00:00');
218+
219+
date = new CalendarDateTime('BC', 2, 1, 1);
220+
expect(date.toString()).toBe('-000001-01-01T00:00:00');
221+
});
196222
});
197223

198224
describe('parseZonedDateTime', function () {
@@ -274,6 +300,16 @@ describe('string conversion', function () {
274300
expect(date).toEqual(expected);
275301
});
276302

303+
it('should parse BC dates', function () {
304+
let date = parseZonedDateTime('0000-01-01T01:00[America/Los_Angeles]');
305+
let expected = new ZonedDateTime('BC', 1, 1, 1, 'America/Los_Angeles', -28378000, 1, 0, 0);
306+
expect(date).toEqual(expected);
307+
308+
date = parseZonedDateTime('-000002-01-01T01:00[America/Los_Angeles]');
309+
expected = new ZonedDateTime('BC', 3, 1, 1, 'America/Los_Angeles', -28378000, 1, 0, 0);
310+
expect(date).toEqual(expected);
311+
});
312+
277313
it('should error if parsing a date with an invalid offset', function () {
278314
expect(() => parseZonedDateTime('2020-02-03T12:24:45.12-04:00[America/Los_Angeles]')).toThrow();
279315
expect(() => parseZonedDateTime('2020-02-03T12:24:45.12-08:24[America/Los_Angeles]')).toThrow();
@@ -302,6 +338,14 @@ describe('string conversion', function () {
302338
let date = new ZonedDateTime(2020, 2, 3, 'America/Los_Angeles', -28800000, 12, 24, 45, 120);
303339
expect(date.toString()).toBe('2020-02-03T12:24:45.12-08:00[America/Los_Angeles]');
304340
});
341+
342+
it('should stringify a BC date', function () {
343+
let date = new ZonedDateTime('BC', 1, 1, 1, 'UTC', 0, 1, 0, 0);
344+
expect(date.toString()).toBe('0000-01-01T01:00:00+00:00[UTC]');
345+
346+
date = new ZonedDateTime('BC', 2, 1, 1, 'UTC', 0, 1, 0, 0);
347+
expect(date.toString()).toBe('-000001-01-01T01:00:00+00:00[UTC]');
348+
});
305349
});
306350

307351
describe('parseAbsolute', function () {
@@ -355,6 +399,16 @@ describe('string conversion', function () {
355399
expect(date).toEqual(expected);
356400
});
357401

402+
it('should parse BC dates', function () {
403+
let date = parseAbsolute('0000-01-01T01:00Z', 'UTC');
404+
let expected = new ZonedDateTime('BC', 1, 1, 1, 'UTC', 0, 1, 0, 0);
405+
expect(date).toEqual(expected);
406+
407+
date = parseAbsolute('-000002-01-01T01:00Z', 'UTC');
408+
expected = new ZonedDateTime('BC', 3, 1, 1, 'UTC', 0, 1, 0, 0);
409+
expect(date).toEqual(expected);
410+
});
411+
358412
it('should error if missing offset or Z', function () {
359413
expect(() => parseAbsolute('2020-02-03')).toThrow();
360414
});
@@ -375,6 +429,14 @@ describe('string conversion', function () {
375429
let date = new ZonedDateTime(2020, 2, 3, 'America/Los_Angeles', -28800000, 14, 32, 45);
376430
expect(date.toAbsoluteString()).toBe('2020-02-03T22:32:45.000Z');
377431
});
432+
433+
it('should stringify a BC date', function () {
434+
let date = new ZonedDateTime('BC', 1, 1, 1, 'UTC', 0, 1, 0, 0);
435+
expect(date.toAbsoluteString()).toBe('0000-01-01T01:00:00.000Z');
436+
437+
date = new ZonedDateTime('BC', 2, 1, 1, 'UTC', 0, 1, 0, 0);
438+
expect(date.toAbsoluteString()).toBe('-000001-01-01T01:00:00.000Z');
439+
});
378440
});
379441

380442
describe('parseDuration', function () {

0 commit comments

Comments
 (0)