diff --git a/.changeset/khaki-apples-bake.md b/.changeset/khaki-apples-bake.md
new file mode 100644
index 0000000..e80a223
--- /dev/null
+++ b/.changeset/khaki-apples-bake.md
@@ -0,0 +1,5 @@
+---
+'chronoshift': major
+---
+
+Switch to @internationalized/date
diff --git a/.idea/prettier.xml b/.idea/prettier.xml
new file mode 100644
index 0000000..b0c1c68
--- /dev/null
+++ b/.idea/prettier.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index ea6e62b..a2bf733 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,8 @@
"version": "0.10.3",
"license": "Apache-2.0",
"dependencies": {
+ "@internationalized/date": "^3.5.6",
"immutable-class": "^0.11.0",
- "moment-timezone": "^0.5.26",
"tslib": "^2.8.1"
},
"devDependencies": {
@@ -28,7 +28,7 @@
"husky": "^2.4.1",
"immutable-class-tester": "^0.7.2",
"jest": "^29.7.0",
- "jest-expect-message": "^1.0.2",
+ "jest-expect-message": "^1.1.3",
"prettier": "^3.4.1",
"ts-jest": "^29.2.5",
"typescript": "^5.7.2"
@@ -1249,6 +1249,15 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@internationalized/date": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.6.0.tgz",
+ "integrity": "sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -1962,6 +1971,15 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -5924,10 +5942,11 @@
}
},
"node_modules/jest-expect-message": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/jest-expect-message/-/jest-expect-message-1.0.2.tgz",
- "integrity": "sha512-WFiXMgwS2lOqQZt1iJMI/hOXpUm32X+ApsuzYcQpW5m16Pv6/Gd9kgC+Q+Q1YVNU04kYcAOv9NXMnjg6kKUy6Q==",
- "dev": true
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/jest-expect-message/-/jest-expect-message-1.1.3.tgz",
+ "integrity": "sha512-bTK77T4P+zto+XepAX3low8XVQxDgaEqh3jSTQOG8qvPpD69LsIdyJTa+RmnJh3HNSzJng62/44RPPc7OIlFxg==",
+ "dev": true,
+ "license": "MIT"
},
"node_modules/jest-get-type": {
"version": "29.6.3",
@@ -6631,23 +6650,12 @@
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+ "dev": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
- "node_modules/moment-timezone": {
- "version": "0.5.46",
- "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz",
- "integrity": "sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==",
- "license": "MIT",
- "dependencies": {
- "moment": "^2.29.4"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
diff --git a/package.json b/package.json
index 215e8b1..45e0101 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
"prettier": "@awesome-code-style/prettier-config",
"dependencies": {
"immutable-class": "^0.11.0",
- "moment-timezone": "^0.5.26",
+ "@internationalized/date": "^3.5.6",
"tslib": "^2.8.1"
},
"devDependencies": {
@@ -75,7 +75,7 @@
"husky": "^2.4.1",
"immutable-class-tester": "^0.7.2",
"jest": "^29.7.0",
- "jest-expect-message": "^1.0.2",
+ "jest-expect-message": "^1.1.3",
"prettier": "^3.4.1",
"ts-jest": "^29.2.5",
"typescript": "^5.7.2"
diff --git a/src/date-parser/date-parser.spec.ts b/src/date-parser/date-parser.spec.ts
index c5c8808..f23df11 100644
--- a/src/date-parser/date-parser.spec.ts
+++ b/src/date-parser/date-parser.spec.ts
@@ -206,17 +206,17 @@ describe('date parser', () => {
).toBeUndefined();
});
- it('date-time (tz = America/Los_Angeles)', () => {
- const tz = Timezone.fromJS('America/Los_Angeles');
+ it('date-time (tz = America/New_York)', () => {
+ const tz = Timezone.fromJS('America/New_York');
expect(parseISODate('2001-02-03T04:05', tz), '2001-02-03T04:05').toEqual(
- new Date(Date.UTC(2001, 1, 3, 4 + 8, 5, 0, 0)),
+ new Date(Date.UTC(2001, 1, 3, 4 + 5, 5, 0, 0)),
);
expect(parseISODate('2001-02-03T04:05:06', tz), '2001-02-03T04:05:06').toEqual(
- new Date(Date.UTC(2001, 1, 3, 4 + 8, 5, 6, 0)),
+ new Date(Date.UTC(2001, 1, 3, 4 + 5, 5, 6, 0)),
);
expect(parseISODate('2001-02-03T04:05:06.007', tz), '2001-02-03T04:05:06.007').toEqual(
- new Date(Date.UTC(2001, 1, 3, 4 + 8, 5, 6, 7)),
+ new Date(Date.UTC(2001, 1, 3, 4 + 5, 5, 6, 7)),
);
expect(parseISODate('2001-02-03T04:05Z', tz), '2001-02-03T04:05Z').toEqual(
@@ -231,25 +231,23 @@ describe('date parser', () => {
});
it('date-time (tz = null / local)', () => {
- const tz: any = null;
-
- expect(parseISODate('2001-02-03T04:05', tz), '2001-02-03T04:05').toEqual(
+ expect(parseISODate('2001-02-03T04:05', null), '2001-02-03T04:05').toEqual(
new Date(2001, 1, 3, 4, 5, 0, 0),
);
- expect(parseISODate('2001-02-03T04:05:06', tz), '2001-02-03T04:05:06').toEqual(
+ expect(parseISODate('2001-02-03T04:05:06', null), '2001-02-03T04:05:06').toEqual(
new Date(2001, 1, 3, 4, 5, 6, 0),
);
- expect(parseISODate('2001-02-03T04:05:06.007', tz), '2001-02-03T04:05:06.007').toEqual(
+ expect(parseISODate('2001-02-03T04:05:06.007', null), '2001-02-03T04:05:06.007').toEqual(
new Date(2001, 1, 3, 4, 5, 6, 7),
);
- expect(parseISODate('2001-02-03T04:05Z', tz), '2001-02-03T04:05Z').toEqual(
+ expect(parseISODate('2001-02-03T04:05Z', null), '2001-02-03T04:05Z').toEqual(
new Date(Date.UTC(2001, 1, 3, 4, 5, 0, 0)),
);
- expect(parseISODate('2001-02-03T04:05:06Z', tz), '2001-02-03T04:05:06Z').toEqual(
+ expect(parseISODate('2001-02-03T04:05:06Z', null), '2001-02-03T04:05:06Z').toEqual(
new Date(Date.UTC(2001, 1, 3, 4, 5, 6, 0)),
);
- expect(parseISODate('2001-02-03T04:05:06.007Z', tz), '2001-02-03T04:05:06.007Z').toEqual(
+ expect(parseISODate('2001-02-03T04:05:06.007Z', null), '2001-02-03T04:05:06.007Z').toEqual(
new Date(Date.UTC(2001, 1, 3, 4, 5, 6, 7)),
);
});
diff --git a/src/date-parser/date-parser.ts b/src/date-parser/date-parser.ts
index d7deffc..ddf4474 100644
--- a/src/date-parser/date-parser.ts
+++ b/src/date-parser/date-parser.ts
@@ -17,7 +17,7 @@
/* eslint-disable @typescript-eslint/prefer-string-starts-ends-with */
/* eslint-disable no-useless-escape */
-import moment from 'moment-timezone';
+import { fromDate } from '@internationalized/date';
import { Duration } from '../duration/duration';
import { Timezone } from '../timezone/timezone';
@@ -118,7 +118,10 @@ export function parseSQLDate(type: string, v: string): Date {
// Taken from: https://github.com/csnover/js-iso8601/blob/lax/iso8601.js
const numericKeys = [1, 4, 5, 6, 10, 11];
-export function parseISODate(date: string, timezone = Timezone.UTC): Date | undefined {
+export function parseISODate(
+ date: string,
+ timezone: Timezone | null = Timezone.UTC,
+): Date | undefined {
let struct: any;
let minutesOffset = 0;
@@ -182,41 +185,28 @@ export function parseISODate(date: string, timezone = Timezone.UTC): Date | unde
struct[3] = +struct[3] || 1;
// allow arbitrary sub-second precision beyond milliseconds
- struct[7] = struct[7] ? +(struct[7] + '00').substr(0, 3) : 0;
+ struct[7] = struct[7] ? +(struct[7] + '00').slice(0, 3) : 0;
if (
(struct[8] === undefined || struct[8] === '') &&
(struct[9] === undefined || struct[9] === '') &&
- !Timezone.UTC.equals(timezone)
+ !Timezone.UTC.equals(timezone || undefined)
) {
+ const dt = Date.UTC(
+ struct[1],
+ struct[2],
+ struct[3],
+ struct[4],
+ struct[5],
+ struct[6],
+ struct[7],
+ );
if (timezone === null) {
// timezone explicitly set to null = use local timezone
- return new Date(
- struct[1],
- struct[2],
- struct[3],
- struct[4],
- struct[5],
- struct[6],
- struct[7],
- );
+ return new Date(dt);
} else {
- return new Date(
- moment
- .tz(
- {
- year: struct[1],
- month: struct[2],
- day: struct[3],
- hour: struct[4],
- minute: struct[5],
- second: struct[6],
- millisecond: struct[7],
- },
- timezone.toString(),
- )
- .valueOf(),
- );
+ const tzd = fromDate(new Date(dt), timezone.toString());
+ return new Date(dt - tzd.offset);
}
} else {
if (struct[8] !== 'Z' && struct[9] !== undefined) {
diff --git a/src/floor-shift-ceil/floor-shift-ceil.spec.ts b/src/floor-shift-ceil/floor-shift-ceil.spec.ts
index cd90c0a..27fa8c1 100644
--- a/src/floor-shift-ceil/floor-shift-ceil.spec.ts
+++ b/src/floor-shift-ceil/floor-shift-ceil.spec.ts
@@ -59,8 +59,8 @@ describe('floor/shift/ceil', () => {
new Date('2012-11-04T01:00:00-07:00'),
);
- expect(shifters.hour.floor(new Date('2012-11-04T01:30:00-08:00'), tz), 'C').toEqual(
- new Date('2012-11-04T01:00:00-08:00'),
+ expect(shifters.hour.floor(new Date('2012-11-04T01:30:00-08:00'), tz)).toEqual(
+ new Date('2012-11-04T01:00:00-07:00'),
);
expect(shifters.hour.floor(new Date('2012-11-04T02:30:00-08:00'), tz), 'D').toEqual(
@@ -93,7 +93,7 @@ describe('floor/shift/ceil', () => {
pairwise(dates, (d1, d2) => expect(shifters.hour.shift(d1, tz, 1)).toEqual(d2));
});
- it('shifts hour over DST 1', () => {
+ it('floors hour over DST 1', () => {
expect(shifters.hour.floor(new Date('2012-11-04T00:05:00-07:00'), tz)).toEqual(
new Date('2012-11-04T00:00:00-07:00'),
);
@@ -101,7 +101,7 @@ describe('floor/shift/ceil', () => {
new Date('2012-11-04T01:00:00-07:00'),
);
expect(shifters.hour.floor(new Date('2012-11-04T02:05:00-07:00'), tz)).toEqual(
- new Date('2012-11-04T02:00:00-07:00'),
+ new Date('2012-11-04T01:00:00-07:00'),
);
expect(shifters.hour.floor(new Date('2012-11-04T03:05:00-07:00'), tz)).toEqual(
new Date('2012-11-04T03:00:00-07:00'),
diff --git a/src/floor-shift-ceil/floor-shift-ceil.ts b/src/floor-shift-ceil/floor-shift-ceil.ts
index 58b5744..4abbcfb 100644
--- a/src/floor-shift-ceil/floor-shift-ceil.ts
+++ b/src/floor-shift-ceil/floor-shift-ceil.ts
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-import moment from 'moment-timezone';
+import { fromDate, startOfWeek } from '@internationalized/date';
import type { Timezone } from '../timezone/timezone';
@@ -115,11 +115,10 @@ export const hour = timeShifterFiller({
if (tz.isUTC()) {
dt = new Date(dt.valueOf());
dt.setUTCMinutes(0, 0, 0);
+ return dt;
} else {
- const wt = moment.tz(dt, tz.toString());
- dt = new Date(wt.second(0).minute(0).millisecond(0).valueOf());
+ return fromDate(dt, tz.toString()).set({ second: 0, minute: 0, millisecond: 0 }).toDate();
}
- return dt;
},
round: (dt, roundTo, tz) => {
if (tz.isUTC()) {
@@ -127,8 +126,7 @@ export const hour = timeShifterFiller({
const adj = floorTo(cur, roundTo);
if (cur !== adj) dt.setUTCHours(adj);
} else {
- const wt = moment.tz(dt, tz.toString());
- const cur = wt.hour();
+ const cur = fromDate(dt, tz.toString()).hour;
const adj = floorTo(cur, roundTo);
if (cur !== adj) return hourMove(dt, tz, adj - cur);
}
@@ -143,21 +141,21 @@ export const day = timeShifterFiller({
if (tz.isUTC()) {
dt = new Date(dt.valueOf());
dt.setUTCHours(0, 0, 0, 0);
+ return dt;
} else {
- const wt = moment.tz(dt, tz.toString());
- dt = new Date(wt.hour(0).second(0).minute(0).millisecond(0).valueOf());
+ return fromDate(dt, tz.toString())
+ .set({ hour: 0, second: 0, minute: 0, millisecond: 0 })
+ .toDate();
}
- return dt;
},
shift: (dt, tz, step) => {
if (tz.isUTC()) {
dt = new Date(dt.valueOf());
dt.setUTCDate(dt.getUTCDate() + step);
+ return dt;
} else {
- const wt = moment.tz(dt, tz.toString());
- dt = new Date(wt.add(step, 'days').valueOf());
+ return fromDate(dt, tz.toString()).add({ days: step }).toDate();
}
- return dt;
},
round: () => {
throw new Error('missing day round');
@@ -172,16 +170,11 @@ export const week = timeShifterFiller({
dt.setUTCHours(0, 0, 0, 0);
dt.setUTCDate(dt.getUTCDate() - adjustDay(dt.getUTCDay()));
} else {
- const wt = moment.tz(dt, tz.toString());
- dt = new Date(
- wt
- .date(wt.date() - adjustDay(wt.day()))
- .hour(0)
- .second(0)
- .minute(0)
- .millisecond(0)
- .valueOf(),
- );
+ const zd = fromDate(dt, tz.toString());
+ return startOfWeek(
+ zd.set({ hour: 0, second: 0, minute: 0, millisecond: 0 }),
+ 'fr-FR', // We want the week to start on Monday
+ ).toDate();
}
return dt;
},
@@ -189,11 +182,10 @@ export const week = timeShifterFiller({
if (tz.isUTC()) {
dt = new Date(dt.valueOf());
dt.setUTCDate(dt.getUTCDate() + step * 7);
+ return dt;
} else {
- const wt = moment.tz(dt, tz.toString());
- dt = new Date(wt.add(step * 7, 'days').valueOf());
+ return fromDate(dt, tz.toString()).add({ weeks: step }).toDate();
}
- return dt;
},
round: () => {
throw new Error('missing week round');
@@ -204,11 +196,10 @@ function monthShift(dt: Date, tz: Timezone, step: number) {
if (tz.isUTC()) {
dt = new Date(dt.valueOf());
dt.setUTCMonth(dt.getUTCMonth() + step);
+ return dt;
} else {
- const wt = moment.tz(dt, tz.toString());
- dt = new Date(wt.add(step, 'month').valueOf());
+ return fromDate(dt, tz.toString()).add({ months: step }).toDate();
}
- return dt;
}
export const month = timeShifterFiller({
@@ -219,11 +210,12 @@ export const month = timeShifterFiller({
dt = new Date(dt.valueOf());
dt.setUTCHours(0, 0, 0, 0);
dt.setUTCDate(1);
+ return dt;
} else {
- const wt = moment.tz(dt, tz.toString());
- dt = new Date(wt.date(1).hour(0).second(0).minute(0).millisecond(0).valueOf());
+ return fromDate(dt, tz.toString())
+ .set({ day: 1, hour: 0, second: 0, minute: 0, millisecond: 0 })
+ .toDate();
}
- return dt;
},
round: (dt, roundTo, tz) => {
if (tz.isUTC()) {
@@ -231,8 +223,7 @@ export const month = timeShifterFiller({
const adj = floorTo(cur, roundTo);
if (cur !== adj) dt.setUTCMonth(adj);
} else {
- const wt = moment.tz(dt, tz.toString());
- const cur = wt.month();
+ const cur = fromDate(dt, tz.toString()).month - 1; // Needs to be zero indexed
const adj = floorTo(cur, roundTo);
if (cur !== adj) return monthShift(dt, tz, adj - cur);
}
@@ -245,11 +236,10 @@ function yearShift(dt: Date, tz: Timezone, step: number) {
if (tz.isUTC()) {
dt = new Date(dt.valueOf());
dt.setUTCFullYear(dt.getUTCFullYear() + step);
+ return dt;
} else {
- const wt = moment.tz(dt, tz.toString());
- dt = new Date(wt.add(step, 'years') as any);
+ return fromDate(dt, tz.toString()).add({ years: step }).toDate();
}
- return dt;
}
export const year = timeShifterFiller({
@@ -260,11 +250,12 @@ export const year = timeShifterFiller({
dt = new Date(dt.valueOf());
dt.setUTCHours(0, 0, 0, 0);
dt.setUTCMonth(0, 1);
+ return dt;
} else {
- const wt = moment.tz(dt, tz.toString());
- dt = new Date(wt.month(0).date(1).hour(0).second(0).minute(0).millisecond(0).valueOf());
+ return fromDate(dt, tz.toString())
+ .set({ month: 1, day: 1, hour: 0, second: 0, minute: 0, millisecond: 0 })
+ .toDate();
}
- return dt;
},
round: (dt, roundTo, tz) => {
if (tz.isUTC()) {
@@ -272,8 +263,7 @@ export const year = timeShifterFiller({
const adj = floorTo(cur, roundTo);
if (cur !== adj) dt.setUTCFullYear(adj);
} else {
- const wt = moment.tz(dt, tz.toString());
- const cur = wt.year();
+ const cur = fromDate(dt, tz.toString()).year;
const adj = floorTo(cur, roundTo);
if (cur !== adj) return yearShift(dt, tz, adj - cur);
}
@@ -306,11 +296,11 @@ export interface Shifters {
}
export const shifters: Shifters = {
- second: second,
- minute: minute,
- hour: hour,
- day: day,
- week: week,
- month: month,
- year: year,
+ second,
+ minute,
+ hour,
+ day,
+ week,
+ month,
+ year,
};
diff --git a/src/timezone/timezone.ts b/src/timezone/timezone.ts
index f238b0e..13db4b6 100644
--- a/src/timezone/timezone.ts
+++ b/src/timezone/timezone.ts
@@ -15,8 +15,8 @@
* limitations under the License.
*/
+import { fromDate } from '@internationalized/date';
import type { Instance } from 'immutable-class';
-import moment from 'moment-timezone';
/**
* Represents timezones
@@ -29,7 +29,7 @@ export class Timezone implements Instance {
static formatDateWithTimezone(d: Date, timezone?: Timezone) {
let str: string;
if (timezone && !timezone.isUTC()) {
- str = moment.tz(d, timezone.toString()).format('YYYY-MM-DDTHH:mm:ss.SSSZ');
+ str = fromDate(d, timezone.toString()).toString().replace(/\[.+$/, '');
} else {
str = d.toISOString();
}
@@ -47,8 +47,12 @@ export class Timezone implements Instance {
if (typeof timezone !== 'string') {
throw new TypeError('timezone description must be a string');
}
- if (timezone !== 'Etc/UTC' && !moment.tz.zone(timezone)) {
- throw new Error(`timezone '${timezone}' does not exist`);
+ if (timezone !== 'Etc/UTC') {
+ try {
+ fromDate(new Date(), timezone);
+ } catch {
+ throw new Error(`timezone '${timezone}' does not exist`);
+ }
}
this.timezone = timezone;
}
@@ -78,7 +82,7 @@ export class Timezone implements Instance {
}
public toUtcOffsetString() {
- const utcOffset = moment.tz(this.toString()).utcOffset();
+ const utcOffset = fromDate(new Date(), this.toString()).offset;
const hours = String(Math.abs(Math.floor(utcOffset / 60))).padStart(2, '0');
const minutes = String(Math.abs(utcOffset % 60)).padStart(2, '0');
diff --git a/tsconfig.json b/tsconfig.json
index 6ff1610..389e677 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -13,7 +13,7 @@
"skipLibCheck": true,
"strict": true,
"importHelpers": true,
- "target": "es5",
+ "target": "es2015",
"module": "commonjs",
"rootDir": "src",
"outDir": "build",