svelte-date-utils 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,132 @@
1
+ const padStart = (value, length = 2) => String(value).padStart(length, '0');
2
+ const isDateString = (value) => typeof value === 'string';
3
+ /** Piece `index` (0 hours, 1 minutes, 2 seconds) of a time string that has at least `hh:mm`. */
4
+ const timePiece = (value, index) => {
5
+ const pieces = value.split(':');
6
+ return pieces.length >= 2 ? pieces[index] || (index === 0 ? undefined : '0') : undefined;
7
+ };
8
+ const parseTimePiece = (piece, name, value) => {
9
+ const parsed = Number.parseInt(piece ?? '', 10);
10
+ if (Number.isNaN(parsed))
11
+ throw new Error(`Failed to get ${name} from date: ${value}.`);
12
+ return parsed;
13
+ };
14
+ /**
15
+ * Builds the helpers of `@wojtekmaj/date-utils` (start, end, previous, next and range of a century,
16
+ * decade, year, month and day, plus ISO-like local formatting) on top of any `DateAdapter`.
17
+ */
18
+ export function createDateUtils(adapter) {
19
+ const ops = adapter;
20
+ const yearOf = (value) => {
21
+ if (typeof value === 'number')
22
+ return value;
23
+ if (isDateString(value)) {
24
+ const year = Number.parseInt(value, 10);
25
+ if (!Number.isNaN(year))
26
+ return year;
27
+ throw new Error(`Failed to get year from date: ${value}.`);
28
+ }
29
+ return ops.getYear(value);
30
+ };
31
+ /** A date to start from: the given one, or today moved to the given year. */
32
+ const dateOf = (value) => typeof value === 'number' || isDateString(value)
33
+ ? ops.setYear(ops.date(), yearOf(value))
34
+ : value;
35
+ // ---- getters
36
+ const getYear = yearOf;
37
+ const getMonth = (date) => ops.getMonth(date);
38
+ const getMonthHuman = (date) => ops.getMonth(date) + 1;
39
+ const getDate = (date) => ops.getDate(date);
40
+ const getHours = (date) => isDateString(date) ? parseTimePiece(timePiece(date, 0), 'hours', date) : ops.getHours(date);
41
+ const getMinutes = (date) => isDateString(date) ? parseTimePiece(timePiece(date, 1), 'minutes', date) : ops.getMinutes(date);
42
+ const getSeconds = (date) => isDateString(date) ? parseTimePiece(timePiece(date, 2), 'seconds', date) : ops.getSeconds(date);
43
+ const getMilliseconds = (date) => isDateString(date)
44
+ ? parseTimePiece(timePiece(date, 2) === undefined ? undefined : timePiece(date, 2).split('.')[1] || '0', 'milliseconds', date)
45
+ : ops.getMilliseconds(date);
46
+ const range = (start, end) => (date) => [start(date), end(date)];
47
+ /** Builds start, end, previous, next and range helpers of a period measured in `unit`s. */
48
+ const period = (unit, startOf, endOf, shift, alignStart = startOf) => {
49
+ const start = (date) => alignStart(dateOf(date));
50
+ const end = (date) => endOf(alignStart(dateOf(date)));
51
+ const previous = (edge, offset) => {
52
+ return (date, amount = offset) => edge(shift(alignStart(dateOf(date)), amount));
53
+ };
54
+ return {
55
+ start,
56
+ end,
57
+ previousStart: previous(start, -unit),
58
+ nextStart: previous(start, unit),
59
+ previousEnd: previous(end, -unit),
60
+ nextEnd: previous(end, unit),
61
+ range: range(start, end),
62
+ };
63
+ };
64
+ // A century runs from year 1 to 100, 101 to 200, ...: 2001 to 2100 is the 21st. Same for decades.
65
+ const alignedYear = (year, size) => year + ((-year + 1) % size);
66
+ const alignedStart = (size) => (date) => ops.startOfYear(ops.setYear(date, alignedYear(ops.getYear(date), size)));
67
+ const alignedEnd = (size) => (date) => ops.endOfYear(ops.addYears(ops.setYear(date, alignedYear(ops.getYear(date), size)), size - 1));
68
+ const century = period(100, alignedStart(100), alignedEnd(100), ops.addYears, alignedStart(100));
69
+ const decade = period(10, alignedStart(10), alignedEnd(10), ops.addYears, alignedStart(10));
70
+ const year = period(1, ops.startOfYear, ops.endOfYear, ops.addYears);
71
+ const month = period(1, ops.startOfMonth, ops.endOfMonth, ops.addMonths);
72
+ const day = period(1, ops.startOfDay, ops.endOfDay, ops.addDays);
73
+ const getDaysInMonth = (date) => ops.getDaysInMonth(date);
74
+ // ---- ISO-like local formatting
75
+ const getHoursMinutes = (date) => `${padStart(getHours(date))}:${padStart(getMinutes(date))}`;
76
+ const getHoursMinutesSeconds = (date) => `${getHoursMinutes(date)}:${padStart(getSeconds(date))}`;
77
+ const getISOLocalMonth = (date) => `${padStart(ops.getYear(date), 4)}-${padStart(getMonthHuman(date))}`;
78
+ const getISOLocalDate = (date) => `${getISOLocalMonth(date)}-${padStart(ops.getDate(date))}`;
79
+ const getISOLocalDateTime = (date) => `${getISOLocalDate(date)}T${getHoursMinutesSeconds(date)}`;
80
+ return {
81
+ adapter,
82
+ getYear,
83
+ getMonth,
84
+ getMonthHuman,
85
+ getDate,
86
+ getHours,
87
+ getMinutes,
88
+ getSeconds,
89
+ getMilliseconds,
90
+ getCenturyStart: century.start,
91
+ getPreviousCenturyStart: century.previousStart,
92
+ getNextCenturyStart: century.nextStart,
93
+ getCenturyEnd: century.end,
94
+ getPreviousCenturyEnd: century.previousEnd,
95
+ getNextCenturyEnd: century.nextEnd,
96
+ getCenturyRange: century.range,
97
+ getDecadeStart: decade.start,
98
+ getPreviousDecadeStart: decade.previousStart,
99
+ getNextDecadeStart: decade.nextStart,
100
+ getDecadeEnd: decade.end,
101
+ getPreviousDecadeEnd: decade.previousEnd,
102
+ getNextDecadeEnd: decade.nextEnd,
103
+ getDecadeRange: decade.range,
104
+ getYearStart: year.start,
105
+ getPreviousYearStart: year.previousStart,
106
+ getNextYearStart: year.nextStart,
107
+ getYearEnd: year.end,
108
+ getPreviousYearEnd: year.previousEnd,
109
+ getNextYearEnd: year.nextEnd,
110
+ getYearRange: year.range,
111
+ getMonthStart: month.start,
112
+ getPreviousMonthStart: month.previousStart,
113
+ getNextMonthStart: month.nextStart,
114
+ getMonthEnd: month.end,
115
+ getPreviousMonthEnd: month.previousEnd,
116
+ getNextMonthEnd: month.nextEnd,
117
+ getMonthRange: month.range,
118
+ getDayStart: day.start,
119
+ getPreviousDayStart: day.previousStart,
120
+ getNextDayStart: day.nextStart,
121
+ getDayEnd: day.end,
122
+ getPreviousDayEnd: day.previousEnd,
123
+ getNextDayEnd: day.nextEnd,
124
+ getDayRange: day.range,
125
+ getDaysInMonth,
126
+ getHoursMinutes,
127
+ getHoursMinutesSeconds,
128
+ getISOLocalMonth,
129
+ getISOLocalDate,
130
+ getISOLocalDateTime,
131
+ };
132
+ }
@@ -0,0 +1,2 @@
1
+ export { AdapterDateFns } from './AdapterDateFns.js';
2
+ export type { AdapterDateFnsOptions } from './AdapterDateFns.js';
@@ -0,0 +1 @@
1
+ export { AdapterDateFns } from './AdapterDateFns.js';
@@ -0,0 +1,63 @@
1
+ export { AdapterDate } from './AdapterDate.js';
2
+ export type { AdapterDateOptions } from './AdapterDate.js';
3
+ export { createDateUtils } from './createDateUtils.js';
4
+ export type { DateUtils, TimeLike, YearLike } from './createDateUtils.js';
5
+ export type { DateAdapter } from './types.js';
6
+ /**
7
+ * The helpers on the native `Date`, with the names and behavior of `@wojtekmaj/date-utils`.
8
+ * For date-fns or Luxon, call `createDateUtils` with `AdapterDateFns` or `AdapterLuxon`
9
+ * from `svelte-date-utils/date-fns` or `svelte-date-utils/luxon`.
10
+ */
11
+ export declare const dateUtils: {
12
+ adapter: import("./types.js").DateAdapter<Date>;
13
+ getYear: (value: import("./createDateUtils.js").YearLike<Date>) => number;
14
+ getMonth: (date: Date) => number;
15
+ getMonthHuman: (date: Date) => number;
16
+ getDate: (date: Date) => number;
17
+ getHours: (date: import("./createDateUtils.js").TimeLike<Date>) => number;
18
+ getMinutes: (date: import("./createDateUtils.js").TimeLike<Date>) => number;
19
+ getSeconds: (date: import("./createDateUtils.js").TimeLike<Date>) => number;
20
+ getMilliseconds: (date: import("./createDateUtils.js").TimeLike<Date>) => number;
21
+ getCenturyStart: (date: import("./createDateUtils.js").YearLike<Date>) => Date;
22
+ getPreviousCenturyStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
23
+ getNextCenturyStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
24
+ getCenturyEnd: (date: import("./createDateUtils.js").YearLike<Date>) => Date;
25
+ getPreviousCenturyEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
26
+ getNextCenturyEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
27
+ getCenturyRange: (date: import("./createDateUtils.js").YearLike<Date>) => [Date, Date];
28
+ getDecadeStart: (date: import("./createDateUtils.js").YearLike<Date>) => Date;
29
+ getPreviousDecadeStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
30
+ getNextDecadeStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
31
+ getDecadeEnd: (date: import("./createDateUtils.js").YearLike<Date>) => Date;
32
+ getPreviousDecadeEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
33
+ getNextDecadeEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
34
+ getDecadeRange: (date: import("./createDateUtils.js").YearLike<Date>) => [Date, Date];
35
+ getYearStart: (date: import("./createDateUtils.js").YearLike<Date>) => Date;
36
+ getPreviousYearStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
37
+ getNextYearStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
38
+ getYearEnd: (date: import("./createDateUtils.js").YearLike<Date>) => Date;
39
+ getPreviousYearEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
40
+ getNextYearEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
41
+ getYearRange: (date: import("./createDateUtils.js").YearLike<Date>) => [Date, Date];
42
+ getMonthStart: (date: import("./createDateUtils.js").YearLike<Date>) => Date;
43
+ getPreviousMonthStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
44
+ getNextMonthStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
45
+ getMonthEnd: (date: import("./createDateUtils.js").YearLike<Date>) => Date;
46
+ getPreviousMonthEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
47
+ getNextMonthEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
48
+ getMonthRange: (date: import("./createDateUtils.js").YearLike<Date>) => [Date, Date];
49
+ getDayStart: (date: import("./createDateUtils.js").YearLike<Date>) => Date;
50
+ getPreviousDayStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
51
+ getNextDayStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
52
+ getDayEnd: (date: import("./createDateUtils.js").YearLike<Date>) => Date;
53
+ getPreviousDayEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
54
+ getNextDayEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date;
55
+ getDayRange: (date: import("./createDateUtils.js").YearLike<Date>) => [Date, Date];
56
+ getDaysInMonth: (date: Date) => number;
57
+ getHoursMinutes: (date: import("./createDateUtils.js").TimeLike<Date>) => string;
58
+ getHoursMinutesSeconds: (date: import("./createDateUtils.js").TimeLike<Date>) => string;
59
+ getISOLocalMonth: (date: Date) => string;
60
+ getISOLocalDate: (date: Date) => string;
61
+ getISOLocalDateTime: (date: Date) => string;
62
+ };
63
+ export declare const getYear: (value: import("./createDateUtils.js").YearLike<Date>) => number, getMonth: (date: Date) => number, getMonthHuman: (date: Date) => number, getDate: (date: Date) => number, getHours: (date: import("./createDateUtils.js").TimeLike<Date>) => number, getMinutes: (date: import("./createDateUtils.js").TimeLike<Date>) => number, getSeconds: (date: import("./createDateUtils.js").TimeLike<Date>) => number, getMilliseconds: (date: import("./createDateUtils.js").TimeLike<Date>) => number, getCenturyStart: (date: import("./createDateUtils.js").YearLike<Date>) => Date, getPreviousCenturyStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getNextCenturyStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getCenturyEnd: (date: import("./createDateUtils.js").YearLike<Date>) => Date, getPreviousCenturyEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getNextCenturyEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getCenturyRange: (date: import("./createDateUtils.js").YearLike<Date>) => [Date, Date], getDecadeStart: (date: import("./createDateUtils.js").YearLike<Date>) => Date, getPreviousDecadeStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getNextDecadeStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getDecadeEnd: (date: import("./createDateUtils.js").YearLike<Date>) => Date, getPreviousDecadeEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getNextDecadeEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getDecadeRange: (date: import("./createDateUtils.js").YearLike<Date>) => [Date, Date], getYearStart: (date: import("./createDateUtils.js").YearLike<Date>) => Date, getPreviousYearStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getNextYearStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getYearEnd: (date: import("./createDateUtils.js").YearLike<Date>) => Date, getPreviousYearEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getNextYearEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getYearRange: (date: import("./createDateUtils.js").YearLike<Date>) => [Date, Date], getMonthStart: (date: import("./createDateUtils.js").YearLike<Date>) => Date, getPreviousMonthStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getNextMonthStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getMonthEnd: (date: import("./createDateUtils.js").YearLike<Date>) => Date, getPreviousMonthEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getNextMonthEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getMonthRange: (date: import("./createDateUtils.js").YearLike<Date>) => [Date, Date], getDayStart: (date: import("./createDateUtils.js").YearLike<Date>) => Date, getPreviousDayStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getNextDayStart: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getDayEnd: (date: import("./createDateUtils.js").YearLike<Date>) => Date, getPreviousDayEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getNextDayEnd: (date: import("./createDateUtils.js").YearLike<Date>, offset?: number) => Date, getDayRange: (date: import("./createDateUtils.js").YearLike<Date>) => [Date, Date], getDaysInMonth: (date: Date) => number, getHoursMinutes: (date: import("./createDateUtils.js").TimeLike<Date>) => string, getHoursMinutesSeconds: (date: import("./createDateUtils.js").TimeLike<Date>) => string, getISOLocalMonth: (date: Date) => string, getISOLocalDate: (date: Date) => string, getISOLocalDateTime: (date: Date) => string;
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import { AdapterDate } from './AdapterDate.js';
2
+ import { createDateUtils } from './createDateUtils.js';
3
+ export { AdapterDate } from './AdapterDate.js';
4
+ export { createDateUtils } from './createDateUtils.js';
5
+ /**
6
+ * The helpers on the native `Date`, with the names and behavior of `@wojtekmaj/date-utils`.
7
+ * For date-fns or Luxon, call `createDateUtils` with `AdapterDateFns` or `AdapterLuxon`
8
+ * from `svelte-date-utils/date-fns` or `svelte-date-utils/luxon`.
9
+ */
10
+ export const dateUtils = createDateUtils(new AdapterDate());
11
+ export const { getYear, getMonth, getMonthHuman, getDate, getHours, getMinutes, getSeconds, getMilliseconds, getCenturyStart, getPreviousCenturyStart, getNextCenturyStart, getCenturyEnd, getPreviousCenturyEnd, getNextCenturyEnd, getCenturyRange, getDecadeStart, getPreviousDecadeStart, getNextDecadeStart, getDecadeEnd, getPreviousDecadeEnd, getNextDecadeEnd, getDecadeRange, getYearStart, getPreviousYearStart, getNextYearStart, getYearEnd, getPreviousYearEnd, getNextYearEnd, getYearRange, getMonthStart, getPreviousMonthStart, getNextMonthStart, getMonthEnd, getPreviousMonthEnd, getNextMonthEnd, getMonthRange, getDayStart, getPreviousDayStart, getNextDayStart, getDayEnd, getPreviousDayEnd, getNextDayEnd, getDayRange, getDaysInMonth, getHoursMinutes, getHoursMinutesSeconds, getISOLocalMonth, getISOLocalDate, getISOLocalDateTime, } = dateUtils;
@@ -0,0 +1,2 @@
1
+ export { AdapterLuxon } from './AdapterLuxon.js';
2
+ export type { AdapterLuxonOptions } from './AdapterLuxon.js';
package/dist/luxon.js ADDED
@@ -0,0 +1 @@
1
+ export { AdapterLuxon } from './AdapterLuxon.js';
@@ -0,0 +1,60 @@
1
+ /**
2
+ * The date operations the utilities need, over any date type `T` (`Date`, a date-fns `Date`, a
3
+ * Luxon `DateTime`, ...). The shape follows `MuiPickersAdapter` from `@mui/x-date-pickers`, cut
4
+ * down to what a calendar and a date input use. Months are 0-based in every adapter.
5
+ */
6
+ export interface DateAdapter<T> {
7
+ /** Name of the library behind the adapter. */
8
+ readonly lib: string;
9
+ /** The current time, or the date parsed from an ISO string. */
10
+ date(value?: string): T;
11
+ getInvalidDate(): T;
12
+ isValid(value: T | null | undefined): value is T;
13
+ toJsDate(value: T): Date;
14
+ formatByString(value: T, format: string): string;
15
+ parse(value: string, format: string): T | null;
16
+ isEqual(value: T | null, comparing: T | null): boolean;
17
+ isSameYear(value: T, comparing: T): boolean;
18
+ isSameMonth(value: T, comparing: T): boolean;
19
+ isSameDay(value: T, comparing: T): boolean;
20
+ isAfter(value: T, comparing: T): boolean;
21
+ isBefore(value: T, comparing: T): boolean;
22
+ isWithinRange(value: T, range: [T, T]): boolean;
23
+ startOfYear(value: T): T;
24
+ startOfMonth(value: T): T;
25
+ startOfWeek(value: T): T;
26
+ startOfDay(value: T): T;
27
+ endOfYear(value: T): T;
28
+ endOfMonth(value: T): T;
29
+ endOfWeek(value: T): T;
30
+ endOfDay(value: T): T;
31
+ addYears(value: T, amount: number): T;
32
+ addMonths(value: T, amount: number): T;
33
+ addWeeks(value: T, amount: number): T;
34
+ addDays(value: T, amount: number): T;
35
+ addHours(value: T, amount: number): T;
36
+ addMinutes(value: T, amount: number): T;
37
+ addSeconds(value: T, amount: number): T;
38
+ getYear(value: T): number;
39
+ /** 0-based month. */
40
+ getMonth(value: T): number;
41
+ getDate(value: T): number;
42
+ getHours(value: T): number;
43
+ getMinutes(value: T): number;
44
+ getSeconds(value: T): number;
45
+ getMilliseconds(value: T): number;
46
+ setYear(value: T, year: number): T;
47
+ /** 0-based month. */
48
+ setMonth(value: T, month: number): T;
49
+ setDate(value: T, date: number): T;
50
+ setHours(value: T, hours: number): T;
51
+ setMinutes(value: T, minutes: number): T;
52
+ setSeconds(value: T, seconds: number): T;
53
+ setMilliseconds(value: T, milliseconds: number): T;
54
+ getDaysInMonth(value: T): number;
55
+ /** Weeks of the month of `value`, padded with days of the neighboring months. */
56
+ getWeekArray(value: T): T[][];
57
+ getWeekNumber(value: T): number;
58
+ /** 1 to 7, from the first day of the week of the locale. */
59
+ getDayOfWeek(value: T): number;
60
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "svelte-date-utils",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "Date helpers for calendars and date inputs (start, end, previous, next and range of a century, decade, year, month and day) that work on native Date, date-fns or Luxon through adapters.",
5
+ "keywords": [
6
+ "date",
7
+ "date-fns",
8
+ "luxon",
9
+ "svelte",
10
+ "utils"
11
+ ],
12
+ "license": "MIT",
13
+ "author": {
14
+ "name": "Adhi Ravishankar",
15
+ "email": "adhiravishankar@gmail.com"
16
+ },
17
+ "files": [
18
+ "dist/**/*",
19
+ "src/**/*",
20
+ "!**/*.spec.*"
21
+ ],
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "./date-fns": {
31
+ "types": "./dist/date-fns.d.ts",
32
+ "default": "./dist/date-fns.js"
33
+ },
34
+ "./luxon": {
35
+ "types": "./dist/luxon.d.ts",
36
+ "default": "./dist/luxon.js"
37
+ }
38
+ },
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "provenance": true
42
+ },
43
+ "scripts": {
44
+ "build": "tsc -p tsconfig.build.json",
45
+ "clean": "node -e \"fs.rmSync('./dist', { recursive: true, force: true })\"",
46
+ "format": "vp fmt --check",
47
+ "lint": "vp lint",
48
+ "prepack": "npm run clean && npm run build",
49
+ "test": "npm run tsc && npm run format && npm run unit",
50
+ "tsc": "tsc",
51
+ "unit": "vp test",
52
+ "watch": "tsc -p tsconfig.build.json --watch"
53
+ },
54
+ "devDependencies": {
55
+ "@types/luxon": "^3.7.1",
56
+ "@vitest/browser-playwright": "5.0.5",
57
+ "date-fns": "^4.1.0",
58
+ "luxon": "^3.7.2",
59
+ "playwright": "^1.64.1",
60
+ "typescript": "^6.0.3",
61
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
62
+ "vite-plus": "1.4.0",
63
+ "vitest": "5.0.5"
64
+ },
65
+ "peerDependencies": {
66
+ "date-fns": "^3.2.0 || ^4.0.0",
67
+ "luxon": "^3.0.2"
68
+ },
69
+ "peerDependenciesMeta": {
70
+ "date-fns": {
71
+ "optional": true
72
+ },
73
+ "luxon": {
74
+ "optional": true
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,209 @@
1
+ import type { DateAdapter } from './types.js';
2
+
3
+ export interface AdapterDateOptions {
4
+ /** First day of the week, 0 (Sunday) to 6 (Saturday). Defaults to 0. */
5
+ weekStartsOn?: number;
6
+ }
7
+
8
+ const copy = (value: Date) => new Date(value.getTime());
9
+
10
+ /** Adapter for the native `Date`, with no dependency. The default adapter of the package. */
11
+ export class AdapterDate implements DateAdapter<Date> {
12
+ public readonly lib = 'native';
13
+
14
+ private readonly weekStartsOn: number;
15
+
16
+ constructor({ weekStartsOn = 0 }: AdapterDateOptions = {}) {
17
+ this.weekStartsOn = weekStartsOn;
18
+ }
19
+
20
+ public date = (value?: string) => (value === undefined ? new Date() : new Date(value));
21
+ public getInvalidDate = () => new Date('Invalid Date');
22
+ public isValid = (value: Date | null | undefined): value is Date =>
23
+ value instanceof Date && !Number.isNaN(value.getTime());
24
+ public toJsDate = (value: Date) => value;
25
+
26
+ public formatByString = (value: Date, format: string): string => {
27
+ const pad = (n: number, length = 2) => String(n).padStart(length, '0');
28
+ const tokens: Record<string, string> = {
29
+ yyyy: pad(value.getFullYear(), 4),
30
+ MM: pad(value.getMonth() + 1),
31
+ dd: pad(value.getDate()),
32
+ HH: pad(value.getHours()),
33
+ mm: pad(value.getMinutes()),
34
+ ss: pad(value.getSeconds()),
35
+ SSS: pad(value.getMilliseconds(), 3),
36
+ };
37
+ return format.replace(/yyyy|MM|dd|HH|mm|ss|SSS/g, (token) => tokens[token] ?? token);
38
+ };
39
+
40
+ /** Parses the numeric tokens `yyyy MM dd HH mm ss`. Use another adapter for locale formats. */
41
+ public parse = (value: string, format: string): Date | null => {
42
+ if (value === '') return null;
43
+ const order: string[] = [];
44
+ const source = format
45
+ .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
46
+ .replace(/yyyy|MM|dd|HH|mm|ss/g, (token) => {
47
+ order.push(token);
48
+ return `(\\d{${token.length}})`;
49
+ });
50
+ const match = new RegExp(`^${source}$`).exec(value);
51
+ if (!match) return this.getInvalidDate();
52
+ const part = (token: string, fallback: number) => {
53
+ const index = order.indexOf(token);
54
+ return index === -1 ? fallback : Number(match[index + 1]);
55
+ };
56
+ const result = new Date(
57
+ part('yyyy', 1970),
58
+ part('MM', 1) - 1,
59
+ part('dd', 1),
60
+ part('HH', 0),
61
+ part('mm', 0),
62
+ part('ss', 0),
63
+ );
64
+ // new Date(99, ...) means 1999: keep the year as written
65
+ result.setFullYear(part('yyyy', 1970));
66
+ return result;
67
+ };
68
+
69
+ public isEqual = (value: Date | null, comparing: Date | null) =>
70
+ value === null || comparing === null ? value === comparing : +value === +comparing;
71
+ public isSameYear = (value: Date, comparing: Date) =>
72
+ value.getFullYear() === comparing.getFullYear();
73
+ public isSameMonth = (value: Date, comparing: Date) =>
74
+ this.isSameYear(value, comparing) && value.getMonth() === comparing.getMonth();
75
+ public isSameDay = (value: Date, comparing: Date) =>
76
+ this.isSameMonth(value, comparing) && value.getDate() === comparing.getDate();
77
+ public isAfter = (value: Date, comparing: Date) => value > comparing;
78
+ public isBefore = (value: Date, comparing: Date) => value < comparing;
79
+ public isWithinRange = (value: Date, [start, end]: [Date, Date]) =>
80
+ value >= start && value <= end;
81
+
82
+ public startOfYear = (value: Date) => {
83
+ const result = new Date(0);
84
+ result.setFullYear(value.getFullYear(), 0, 1);
85
+ result.setHours(0, 0, 0, 0);
86
+ return result;
87
+ };
88
+ public startOfMonth = (value: Date) => this.startOfDay(this.setDate(value, 1));
89
+ public startOfWeek = (value: Date) => {
90
+ const diff = (value.getDay() - this.weekStartsOn + 7) % 7;
91
+ return this.startOfDay(this.addDays(value, -diff));
92
+ };
93
+ public startOfDay = (value: Date) => {
94
+ const result = copy(value);
95
+ result.setHours(0, 0, 0, 0);
96
+ return result;
97
+ };
98
+ public endOfYear = (value: Date) => {
99
+ const result = copy(value);
100
+ result.setMonth(11, 31);
101
+ result.setHours(23, 59, 59, 999);
102
+ return result;
103
+ };
104
+ public endOfMonth = (value: Date) => {
105
+ const result = this.setDate(value, this.getDaysInMonth(value));
106
+ result.setHours(23, 59, 59, 999);
107
+ return result;
108
+ };
109
+ public endOfWeek = (value: Date) => this.endOfDay(this.addDays(this.startOfWeek(value), 6));
110
+ public endOfDay = (value: Date) => {
111
+ const result = copy(value);
112
+ result.setHours(23, 59, 59, 999);
113
+ return result;
114
+ };
115
+
116
+ public addYears = (value: Date, amount: number) => this.addMonths(value, amount * 12);
117
+ /** Clamps to the last day of the target month, like date-fns and Luxon: Jan 31 + 1 month is Feb 28. */
118
+ public addMonths = (value: Date, amount: number) => {
119
+ const result = copy(value);
120
+ result.setDate(1);
121
+ result.setMonth(result.getMonth() + amount);
122
+ result.setDate(Math.min(value.getDate(), this.getDaysInMonth(result)));
123
+ return result;
124
+ };
125
+ public addWeeks = (value: Date, amount: number) => this.addDays(value, amount * 7);
126
+ public addDays = (value: Date, amount: number) => {
127
+ const result = copy(value);
128
+ result.setDate(result.getDate() + amount);
129
+ return result;
130
+ };
131
+ public addHours = (value: Date, amount: number) => new Date(value.getTime() + amount * 3_600_000);
132
+ public addMinutes = (value: Date, amount: number) => new Date(value.getTime() + amount * 60_000);
133
+ public addSeconds = (value: Date, amount: number) => new Date(value.getTime() + amount * 1000);
134
+
135
+ public getYear = (value: Date) => value.getFullYear();
136
+ public getMonth = (value: Date) => value.getMonth();
137
+ public getDate = (value: Date) => value.getDate();
138
+ public getHours = (value: Date) => value.getHours();
139
+ public getMinutes = (value: Date) => value.getMinutes();
140
+ public getSeconds = (value: Date) => value.getSeconds();
141
+ public getMilliseconds = (value: Date) => value.getMilliseconds();
142
+
143
+ /** Clamps the day when the target month is shorter (Feb 29 to a common year is Feb 28). */
144
+ public setYear = (value: Date, year: number) => {
145
+ const result = copy(value);
146
+ result.setFullYear(year, value.getMonth(), 1);
147
+ result.setDate(Math.min(value.getDate(), this.getDaysInMonth(result)));
148
+ return result;
149
+ };
150
+ public setMonth = (value: Date, month: number) => {
151
+ const result = copy(value);
152
+ result.setMonth(month, 1);
153
+ result.setDate(Math.min(value.getDate(), this.getDaysInMonth(result)));
154
+ return result;
155
+ };
156
+ public setDate = (value: Date, date: number) => {
157
+ const result = copy(value);
158
+ result.setDate(date);
159
+ return result;
160
+ };
161
+ public setHours = (value: Date, hours: number) => {
162
+ const result = copy(value);
163
+ result.setHours(hours);
164
+ return result;
165
+ };
166
+ public setMinutes = (value: Date, minutes: number) => {
167
+ const result = copy(value);
168
+ result.setMinutes(minutes);
169
+ return result;
170
+ };
171
+ public setSeconds = (value: Date, seconds: number) => {
172
+ const result = copy(value);
173
+ result.setSeconds(seconds);
174
+ return result;
175
+ };
176
+ public setMilliseconds = (value: Date, milliseconds: number) => {
177
+ const result = copy(value);
178
+ result.setMilliseconds(milliseconds);
179
+ return result;
180
+ };
181
+
182
+ public getDaysInMonth = (value: Date) =>
183
+ new Date(value.getFullYear(), value.getMonth() + 1, 0).getDate();
184
+
185
+ public getWeekArray = (value: Date) => {
186
+ const start = this.startOfWeek(this.startOfMonth(value));
187
+ const end = this.endOfWeek(this.endOfMonth(value));
188
+ const weeks: Date[][] = [];
189
+ let current = start;
190
+ for (let count = 0; current < end; count += 1) {
191
+ const week = (weeks[Math.floor(count / 7)] ??= []);
192
+ week.push(current);
193
+ current = this.addDays(current, 1);
194
+ }
195
+ return weeks;
196
+ };
197
+
198
+ /** Week of the year, where week 1 holds January 1 (the date-fns default for `getWeek`). */
199
+ public getWeekNumber = (value: Date) => {
200
+ const yearStart = this.startOfWeek(this.startOfYear(value));
201
+ const weekStart = this.startOfWeek(value);
202
+ const nextYearStart = this.startOfWeek(this.startOfYear(this.addYears(value, 1)));
203
+ // the last days of December can belong to week 1 of the next year
204
+ if (weekStart >= nextYearStart) return 1;
205
+ return Math.round((weekStart.getTime() - yearStart.getTime()) / (7 * 86_400_000)) + 1;
206
+ };
207
+
208
+ public getDayOfWeek = (value: Date) => ((value.getDay() - this.weekStartsOn + 7) % 7) + 1;
209
+ }