Skip to content

Commit 0323024

Browse files
authored
Merge pull request #71 from techniq/remove-date-fns
feat: Add new date utils including `parseDate`, `timeInterval`, `startOfInterval`, `endOfInterval`, `intervalOffset`, and more and replace `date-fns` usage
2 parents 5cc3eb9 + 21bf4be commit 0323024

23 files changed

Lines changed: 666 additions & 213 deletions

File tree

.changeset/cyan-guests-sip.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@layerstack/svelte-actions': patch
3+
'@layerstack/svelte-stores': patch
4+
'@layerstack/svelte-state': patch
5+
'@layerstack/svelte-table': patch
6+
'@layerstack/tailwind': patch
7+
'@layerstack/utils': patch
8+
---
9+
10+
refactor: Replace `date-fns` usage with new date utils (based on d3-time) to reduce bundle size

.changeset/odd-hands-tie.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@layerstack/svelte-actions': patch
3+
'@layerstack/svelte-stores': patch
4+
'@layerstack/svelte-state': patch
5+
'@layerstack/svelte-table': patch
6+
'@layerstack/tailwind': patch
7+
'@layerstack/utils': patch
8+
---
9+
10+
feat: Add new date utils including `parseDate`, `timeInterval`, `startOfInterval`, `endOfInterval`, `intervalOffset`, and more

packages/svelte-actions/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
"@layerstack/utils": "workspace:*",
4242
"d3-array": "^3.2.4",
4343
"d3-scale": "^4.0.2",
44-
"date-fns": "^4.1.0",
4544
"lodash-es": "^4.17.21"
4645
},
4746
"main": "./dist/index.js",

packages/svelte-stores/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"dependencies": {
4040
"@layerstack/utils": "workspace:*",
4141
"d3-array": "^3.2.4",
42-
"date-fns": "^4.1.0",
4342
"immer": "^10.1.1",
4443
"lodash-es": "^4.17.21",
4544
"zod": "^3.24.3"

packages/svelte-table/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"@layerstack/svelte-actions": "workspace:*",
4040
"@layerstack/utils": "workspace:*",
4141
"d3-array": "^3.2.4",
42-
"date-fns": "^4.1.0",
4342
"lodash-es": "^4.17.21"
4443
},
4544
"main": "./dist/index.js",

packages/svelte-table/src/lib/utils.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { isFunction, get } from 'lodash-es';
2-
import { parseISO } from 'date-fns';
32

4-
import { PeriodType } from '@layerstack/utils';
3+
import { PeriodType, parseDate } from '@layerstack/utils';
54

65
import type { ColumnDef } from './types.js';
76

@@ -118,7 +117,7 @@ export function getCellValue(column: ColumnDef, rowData: any, rowIndex?: number)
118117
// TODO: Should we handle date-only strings different?
119118
// value = new Date(value);
120119
// console.log({ column: column.name, value });
121-
value = parseISO(value);
120+
value = parseDate(value);
122121
}
123122

124123
return value;

packages/tailwind/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
"clsx": "^2.1.1",
4040
"culori": "^4.0.1",
4141
"d3-array": "^3.2.4",
42-
"date-fns": "^4.1.0",
4342
"lodash-es": "^4.17.21",
4443
"tailwind-merge": "^3.2.0",
4544
"tailwindcss": "^4.1.5"

packages/utils/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@sveltejs/package": "^2.3.11",
2222
"@sveltejs/vite-plugin-svelte": "^5.0.3",
2323
"@types/d3-array": "^3.2.1",
24+
"@types/d3-time": "^3.0.4",
2425
"@types/lodash-es": "^4.17.12",
2526
"@vitest/coverage-v8": "^3.1.2",
2627
"prettier": "^3.5.3",
@@ -36,7 +37,7 @@
3637
"type": "module",
3738
"dependencies": {
3839
"d3-array": "^3.2.4",
39-
"date-fns": "^4.1.0",
40+
"d3-time": "^3.1.0",
4041
"lodash-es": "^4.17.21"
4142
},
4243
"main": "./dist/index.js",

packages/utils/src/lib/date.test.ts

Lines changed: 212 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, it, expect } from 'vitest';
1+
import { describe, it, expect, test } from 'vitest';
22
import {
33
formatDate,
44
getMonthDaysByWeek,
@@ -12,6 +12,15 @@ import {
1212
hasDayOfWeek,
1313
replaceDayOfWeek,
1414
isStringDate,
15+
timeInterval,
16+
startOfInterval,
17+
endOfInterval,
18+
parseDate,
19+
intervalOffset,
20+
isSameInterval,
21+
intervalDifference,
22+
isLeapYear,
23+
isDateWithin,
1524
} from './date.js';
1625
import { createLocaleSettings, defaultLocale, LocaleSettings } from './locale.js';
1726
import {
@@ -21,12 +30,23 @@ import {
2130
type CustomIntlDateTimeFormatOptions,
2231
DateToken,
2332
PeriodTypeCode,
33+
TimeIntervalType,
2434
} from './date_types.js';
2535
import { getWeekStartsOnFromIntl } from './dateInternal.js';
26-
import { parseISO } from 'date-fns';
36+
import {
37+
timeDay,
38+
timeHour,
39+
timeMillisecond,
40+
timeMinute,
41+
timeMonth,
42+
timeYear,
43+
timeSecond,
44+
timeWeek,
45+
} from 'd3-time';
2746

2847
export const testDateStr = '2023-11-21'; // "good" default date as the day (21) is bigger than 12 (number of months). And november is a good month1 (because why not?)
29-
export const testDate = parseISO('2023-11-21'); // "good" default date as the day (21) is bigger than 12 (number of months). And november is a good month1 (because why not?)
48+
export const testDate = parseDate('2023-11-21'); // "good" default date as the day (21) is bigger than 12 (number of months). And november is a good month1 (because why not?)
49+
3050
const dt_2M_2d = new Date(2023, 10, 21);
3151
const dt_2M_1d = new Date(2023, 10, 7);
3252
const dt_1M_1d = new Date(2023, 2, 7);
@@ -1236,3 +1256,192 @@ describe('isStringDate()', () => {
12361256
expect(isStringDate('1982-03-30T11:25:59.1234567Z')).true;
12371257
});
12381258
});
1259+
1260+
describe('parseDate()', () => {
1261+
it('date only as locale date', () => {
1262+
expect(parseDate('1982-03-30')).toEqual(new Date('1982-03-30T04:00:00.000Z'));
1263+
});
1264+
1265+
it('date and time only (hour/minute) as locale date', () => {
1266+
expect(parseDate('1982-03-30T04:00')).toEqual(new Date('1982-03-30T08:00:00.000Z'));
1267+
});
1268+
1269+
it('date and time only (hour/minute/second) as locale date', () => {
1270+
expect(parseDate('1982-03-30T04:00:00')).toEqual(new Date('1982-03-30T08:00:00.000Z'));
1271+
});
1272+
1273+
it('should not equal UTC date', () => {
1274+
// Just an extra check
1275+
expect(parseDate('1982-03-30')).not.toEqual(new Date('1982-03-30'));
1276+
});
1277+
1278+
it('date with timezome (UTC)', () => {
1279+
expect(parseDate('1982-03-30T11:25:59Z')).toEqual(new Date('1982-03-30T11:25:59Z'));
1280+
});
1281+
1282+
it('date with time (offset)', () => {
1283+
expect(parseDate('1982-03-30T00:00:00-01:00')).toEqual(new Date('1982-03-30T00:00:00-01:00'));
1284+
});
1285+
1286+
it('date with time and 3 digit milliseconds (UTC)', () => {
1287+
expect(parseDate('1982-03-30T11:25:59.123Z')).toEqual(new Date('1982-03-30T11:25:59.123Z'));
1288+
});
1289+
1290+
it('date with time with 7 digit milliseconds (UTC)', () => {
1291+
expect(parseDate('1982-03-30T11:25:59.1234567Z')).toEqual(
1292+
new Date('1982-03-30T11:25:59.1234567Z')
1293+
);
1294+
});
1295+
1296+
it('invalid date string', () => {
1297+
expect(parseDate('some_string')).toEqual(new Date('Invalid Date'));
1298+
});
1299+
});
1300+
1301+
describe('timeInterval()', () => {
1302+
test.each([
1303+
['millisecond', timeMillisecond],
1304+
['second', timeSecond],
1305+
['minute', timeMinute],
1306+
['hour', timeHour],
1307+
['day', timeDay],
1308+
['week', timeWeek],
1309+
['month', timeMonth],
1310+
// ['quarter', timeMonth.every(3)], // TODO: how to verify this?
1311+
['year', timeYear],
1312+
])('timeInterval(%s) => %s', (interval, expected) => {
1313+
expect(timeInterval(interval as TimeIntervalType)).toEqual(expected);
1314+
});
1315+
});
1316+
1317+
describe('startOfInterval()', () => {
1318+
test.each([
1319+
['millisecond', '2023-03-07T14:02:03.004'],
1320+
['second', '2023-03-07T14:02:03'],
1321+
['minute', '2023-03-07T14:02'],
1322+
['hour', '2023-03-07T14:00:00.000'],
1323+
['day', '2023-03-07'],
1324+
['week', '2023-03-05'],
1325+
['month', '2023-03-01'],
1326+
['quarter', '2023-01-01'],
1327+
['year', '2023-01-01'],
1328+
])('startOfInterval(%s, %s) => %s', (interval, expected) => {
1329+
expect(startOfInterval(interval as TimeIntervalType, dt_1M_1d_time_pm)).toEqual(
1330+
parseDate(expected)
1331+
);
1332+
});
1333+
});
1334+
1335+
describe('endOfInterval()', () => {
1336+
test.each([
1337+
['millisecond', '2023-03-07T14:02:03.004'],
1338+
['second', '2023-03-07T14:02:03.999'],
1339+
['minute', '2023-03-07T14:02:59.999'],
1340+
['hour', '2023-03-07T14:59:59.999'],
1341+
['day', '2023-03-07T23:59:59.999'],
1342+
['week', '2023-03-11T23:59:59.999'],
1343+
['month', '2023-03-31T23:59:59.999'],
1344+
['quarter', '2023-03-31T23:59:59.999'],
1345+
['year', '2023-12-31T23:59:59.999'],
1346+
])('endOfInterval(%s, %s) => %s', (interval, expected) => {
1347+
expect(endOfInterval(interval as TimeIntervalType, dt_1M_1d_time_pm)).toEqual(
1348+
parseDate(expected)
1349+
);
1350+
});
1351+
});
1352+
1353+
describe('intervalOffset()', () => {
1354+
test.each([
1355+
['millisecond', 1, '2023-11-21T04:00:00.001Z'],
1356+
['millisecond', -1, '2023-11-21T03:59:59.999Z'],
1357+
['second', 1, '2023-11-21T04:00:01Z'],
1358+
['second', -1, '2023-11-21T03:59:59Z'],
1359+
['minute', 1, '2023-11-21T04:01:00Z'],
1360+
['minute', -1, '2023-11-21T03:59:00Z'],
1361+
['hour', 1, '2023-11-21T05:00:00Z'],
1362+
['hour', -1, '2023-11-21T03:00:00Z'],
1363+
['day', 1, '2023-11-22'],
1364+
['day', -1, '2023-11-20'],
1365+
['week', 1, '2023-11-28'],
1366+
['week', -1, '2023-11-14'],
1367+
['month', 1, '2023-12-21'],
1368+
['month', -1, '2023-10-21'],
1369+
['quarter', 1, '2024-02-01'],
1370+
['quarter', -1, '2023-08-01'],
1371+
['year', 1, '2024-11-21'],
1372+
['year', -1, '2022-11-21'],
1373+
])('intervalOffset(%s, %s, %s) => %s', (interval, offset, expected) => {
1374+
expect(intervalOffset(interval as TimeIntervalType, testDate, offset)).toEqual(
1375+
parseDate(expected)
1376+
);
1377+
});
1378+
});
1379+
1380+
describe('isSameInterval()', () => {
1381+
test.each([
1382+
['day', '2023-03-07T00:00', '2023-03-07T23:59', true],
1383+
['day', '2023-03-07T11:00', '2023-03-08T12:00', false],
1384+
['week', '2023-03-07', '2023-03-08', true],
1385+
['week', '2023-03-07', '2023-03-14', false],
1386+
['month', '2023-03-07', '2023-03-30', true],
1387+
['month', '2023-03-07', '2023-04-07', false],
1388+
['quarter', '2023-03-07', '2023-01-07', true],
1389+
['quarter', '2023-03-07', '2023-04-07', false],
1390+
['year', '2023-03-07', '2023-11-21', true],
1391+
['year', '2023-03-07', '2024-03-07', false],
1392+
])('isSameInterval(%s, %s, %s) => %s', (interval, date1, date2, expected) => {
1393+
expect(isSameInterval(interval as TimeIntervalType, parseDate(date1), parseDate(date2))).toBe(
1394+
expected
1395+
);
1396+
});
1397+
});
1398+
1399+
describe('intervalDifference()', () => {
1400+
test.each([
1401+
['day', '2023-03-07T00:00', '2023-03-07T23:59', 0], // Same day
1402+
['day', '2023-03-07', '2023-03-08', 1], // Next day
1403+
['day', '2023-01-01', '2023-12-31', 364], // Full year
1404+
['day', '2023-01-01', '2024-01-01', 365], // Next year
1405+
['week', '2023-03-05', '2023-03-11', 0], // Same week
1406+
['week', '2023-03-07', '2023-03-14', 1], // Next week
1407+
['month', '2023-01-01', '2023-01-31', 0], // Same month
1408+
['month', '2023-01-01', '2023-02-01', 1], // Next month
1409+
['quarter', '2023-01-01', '2023-03-31', 0], // Same quarter
1410+
['quarter', '2023-01-01', '2023-04-01', 1], // Next quarter
1411+
['year', '2023-01-01', '2023-12-31', 0], // Same year
1412+
['year', '2023-01-01', '2024-01-01', 1], // Next year
1413+
])('intervalDifference(%s, %s, %s) => %s', (interval, date1, date2, expected) => {
1414+
expect(
1415+
intervalDifference(interval as TimeIntervalType, parseDate(date1), parseDate(date2))
1416+
).toEqual(expected);
1417+
});
1418+
});
1419+
1420+
describe('isLeapYear()', () => {
1421+
test.each([
1422+
['2020-01-01', true],
1423+
['2021-01-01', false],
1424+
['2022-01-01', false],
1425+
['2023-01-01', false],
1426+
['2024-01-01', true],
1427+
])('isLeapYear(%s) => %s', (date, expected) => {
1428+
expect(isLeapYear(parseDate(date))).toBe(expected);
1429+
});
1430+
});
1431+
1432+
describe('isDateWithin()', () => {
1433+
test.each([
1434+
['2023-03-07', '2023-03-06', '2023-03-08', true], // between
1435+
['2023-03-07', '2023-03-07', '2023-03-08', true], // match start
1436+
['2023-03-07', '2023-03-06', '2023-03-07', true], // match end
1437+
['2023-03-07', '2023-03-08', '2023-03-09', false], // outside start
1438+
['2023-03-07', '2023-03-05', '2023-03-06', false], // outside end
1439+
])('isDateWithin(%s, %s) => %s', (date, start, end, expected) => {
1440+
expect(
1441+
isDateWithin(parseDate(date), {
1442+
start: parseDate(start),
1443+
end: parseDate(end),
1444+
})
1445+
).toBe(expected);
1446+
});
1447+
});

0 commit comments

Comments
 (0)