native-dates 0.0.0-stage → 0.0.6
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.
- package/LICENSE +7 -0
- package/README.md +41 -2
- package/package.json +46 -4
- package/src/calendar-date.js +393 -0
- package/src/date-time.js +433 -0
- package/src/duration.js +341 -0
- package/src/helpers/compare.js +126 -0
- package/src/helpers/errors.js +89 -0
- package/src/helpers/fields.js +194 -0
- package/src/helpers/format.js +93 -0
- package/src/helpers/identity.js +10 -0
- package/src/helpers/intl.js +45 -0
- package/src/helpers/types.js +20 -0
- package/src/helpers/units.js +147 -0
- package/src/helpers/zones.js +113 -0
- package/src/index.js +30 -0
- package/src/time.js +357 -0
- package/types/index.d.ts +538 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jack Lukic
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,42 @@
|
|
|
1
|
-
#
|
|
1
|
+
# <img alt="" src=".github/logo.svg" height="47" align="absmiddle"> Native Dates
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> A thin date library built on the browser's Temporal and Intl APIs
|
|
4
|
+
|
|
5
|
+
- Four nouns for what code actually means: `DateTime`, `CalendarDate`, `Time` and `Duration`
|
|
6
|
+
- Time zones, daylight transitions and calendar math handled by the platform
|
|
7
|
+
- Frozen values that lead in the console with their human-readable `text`
|
|
8
|
+
- Natural phrases (`'1h 30m'`, `today().plus(2, 'days').at('9am')`) and `Intl` presets
|
|
9
|
+
- Plugins you import only if you use them
|
|
10
|
+
- About 6 KB brotli, with zero dependencies
|
|
11
|
+
|
|
12
|
+
Native Dates leaves calendar math and formatting to the platform. The browser's [Temporal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal) and [Intl](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) APIs know every time zone, daylight transition and locale, and this library speaks them in plain English: four nouns instead of nine, one word per thought, and values that cannot be mutated in place.
|
|
13
|
+
|
|
14
|
+
```js
|
|
15
|
+
import { date, duration, now, today } from 'native-dates';
|
|
16
|
+
import '@native-dates/plugins/ranges';
|
|
17
|
+
import '@native-dates/plugins/tokens';
|
|
18
|
+
|
|
19
|
+
const standup = today().next('monday').at('9:30am', 'America/Los_Angeles');
|
|
20
|
+
standup.in('Europe/Berlin').format('dddd, MMM D [at] HH:mm z');
|
|
21
|
+
|
|
22
|
+
const sprint = date('2026-09-01').to(2, 'weeks');
|
|
23
|
+
sprint.contains(today());
|
|
24
|
+
sprint.points('day');
|
|
25
|
+
|
|
26
|
+
const remaining = now().until(standup);
|
|
27
|
+
remaining.total('hours');
|
|
28
|
+
duration('1h 30m').plus(15, 'minutes').format('short');
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Packages
|
|
32
|
+
|
|
33
|
+
| Package | Description |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| [native-dates](src) | The four nouns (`DateTime`, `CalendarDate`, `Time`, `Duration`), presets, comparisons and `configure()` |
|
|
36
|
+
| [@native-dates/plugins](packages/plugins) | The plugins (`ranges`, `tokens`, `relative`, `zones`, `loose`, `legacy-date`), each installed by importing it |
|
|
37
|
+
| [@native-dates/polyfill](packages/polyfill) | Loads `temporal-polyfill` only when `globalThis.Temporal` is absent |
|
|
38
|
+
| [@native-dates/core](packages/core) | Native Dates under its scoped name, a thin re-export |
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
[ISC](LICENSE)
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "native-dates",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"title": "Native Dates",
|
|
5
|
+
"description": "Temporal, spoken plainly. Datetimes, dates, times, durations and ranges on the browser's Temporal and Intl APIs",
|
|
6
|
+
"author": "Jack Lukic",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=22"
|
|
12
|
+
},
|
|
13
|
+
"main": "src/index.js",
|
|
14
|
+
"module": "src/index.js",
|
|
15
|
+
"types": "types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./types/index.d.ts",
|
|
19
|
+
"import": "./src/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./helpers/*": "./src/helpers/*.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"types",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"workspaces": [
|
|
30
|
+
".",
|
|
31
|
+
"packages/*"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "node --test test/*.test.js",
|
|
35
|
+
"size": "node tools/size.js",
|
|
36
|
+
"format": "npx dprint fmt",
|
|
37
|
+
"setup": "npx simple-git-hooks"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"dprint": "^0.50.0",
|
|
41
|
+
"esbuild": "^0.25.0",
|
|
42
|
+
"simple-git-hooks": "^2.11.1"
|
|
43
|
+
},
|
|
44
|
+
"simple-git-hooks": {
|
|
45
|
+
"pre-commit": "bash tools/hooks/pre-commit",
|
|
46
|
+
"commit-msg": "bash tools/hooks/commit-msg \"$1\""
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { DateTime } from './date-time.js';
|
|
2
|
+
import { anchored } from './duration.js';
|
|
3
|
+
import { guard, loosely, parseLoose, refuse, refuseType, requireLoose } from './helpers/errors.js';
|
|
4
|
+
import { fieldsFrom, temporalDurationOf } from './helpers/fields.js';
|
|
5
|
+
import { formatIntl, intlOptions, unknownFormat } from './helpers/format.js';
|
|
6
|
+
import { IS_CALENDAR_DATE } from './helpers/identity.js';
|
|
7
|
+
import { isDate, isDevelopment, isNumber, isPlainObject, isString } from './helpers/types.js';
|
|
8
|
+
import {
|
|
9
|
+
inspect,
|
|
10
|
+
isDigits,
|
|
11
|
+
isPlainDate,
|
|
12
|
+
isPlainDateTime,
|
|
13
|
+
isZonedDateTime,
|
|
14
|
+
plural,
|
|
15
|
+
quarterStart,
|
|
16
|
+
singularKeys,
|
|
17
|
+
stepOf,
|
|
18
|
+
timeUnits,
|
|
19
|
+
unit,
|
|
20
|
+
weekdayNumber,
|
|
21
|
+
wholeNumbers,
|
|
22
|
+
withParts,
|
|
23
|
+
} from './helpers/units.js';
|
|
24
|
+
import { weekStart, zoneId } from './helpers/zones.js';
|
|
25
|
+
import { Time } from './time.js';
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
a calendar date with no time and no zone: a due date, a birthday, a booking night. it is not a
|
|
29
|
+
moment, so it never shifts when read from another zone
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const hasTime = /[T ]\d{1,2}(?::?\d{2})?/;
|
|
33
|
+
const hasOffset = /(?:[zZ]|[+-]\d{2}(?::?\d{2})?)$/;
|
|
34
|
+
|
|
35
|
+
export class CalendarDate {
|
|
36
|
+
// brand calendar date
|
|
37
|
+
get [IS_CALENDAR_DATE]() {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
static [Symbol.hasInstance](value) {
|
|
41
|
+
return !!value?.[IS_CALENDAR_DATE];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#plain;
|
|
45
|
+
|
|
46
|
+
// date(2026, 9, 6), date(input, zone), or date(input, { zone, loose })
|
|
47
|
+
constructor(input, monthOrOptions, day) {
|
|
48
|
+
if (isDigits(input) && (isNumber(monthOrOptions) || isDigits(monthOrOptions))) {
|
|
49
|
+
[input, monthOrOptions, day] = [Number(input), Number(monthOrOptions), day === undefined ? day : Number(day)];
|
|
50
|
+
}
|
|
51
|
+
const settings = isPlainObject(monthOrOptions)
|
|
52
|
+
? monthOrOptions
|
|
53
|
+
: isString(monthOrOptions)
|
|
54
|
+
? { zone: monthOrOptions }
|
|
55
|
+
: {};
|
|
56
|
+
this.#plain =
|
|
57
|
+
isPlainDate(input) && settings.zone === undefined && !settings.loose && settings.dayFirst === undefined
|
|
58
|
+
? input
|
|
59
|
+
: CalendarDate.#read(input, isNumber(monthOrOptions) ? monthOrOptions : undefined, day, settings);
|
|
60
|
+
// the parts are own properties, so a value prints them in a console without a click, in the order a
|
|
61
|
+
// preview reads: the text first, filled once the parts it reads are in, then month, day, year
|
|
62
|
+
this.text = undefined;
|
|
63
|
+
const plain = this.#plain;
|
|
64
|
+
this.month = plain.month;
|
|
65
|
+
this.day = plain.day;
|
|
66
|
+
this.year = plain.year;
|
|
67
|
+
this.weekday = plain.dayOfWeek;
|
|
68
|
+
this.text = this.format();
|
|
69
|
+
Object.freeze(this);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static #read(input, month, day, settings) {
|
|
73
|
+
if (settings.zone !== undefined) {
|
|
74
|
+
zoneId(settings.zone);
|
|
75
|
+
}
|
|
76
|
+
if (settings.loose || settings.dayFirst !== undefined) {
|
|
77
|
+
requireLoose(settings.loose ? 'loose' : 'dayFirst');
|
|
78
|
+
}
|
|
79
|
+
if (input === undefined || input === null) {
|
|
80
|
+
return refuseType('notADate', String(input), {
|
|
81
|
+
explanation: isDevelopment
|
|
82
|
+
? 'today() is the current day. a missing value is refused so an absent field never silently becomes today'
|
|
83
|
+
: 0,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (isNumber(input) && isNumber(month)) {
|
|
87
|
+
return guard(
|
|
88
|
+
() => {
|
|
89
|
+
if (!wholeNumbers([input, month, day])) {
|
|
90
|
+
throw new RangeError('a date reads whole numbers');
|
|
91
|
+
}
|
|
92
|
+
return Temporal.PlainDate.from({ year: input, month, day: day ?? 1 }, { overflow: 'reject' });
|
|
93
|
+
},
|
|
94
|
+
'unreadableDate',
|
|
95
|
+
`${input}-${month}-${day}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
if (input instanceof CalendarDate) {
|
|
99
|
+
return input.toTemporal();
|
|
100
|
+
}
|
|
101
|
+
if (input instanceof DateTime) {
|
|
102
|
+
return input.toTemporal().toPlainDate();
|
|
103
|
+
}
|
|
104
|
+
if (isNumber(input)) {
|
|
105
|
+
return refuseType('notADate', String(input), {
|
|
106
|
+
explanation: isDevelopment
|
|
107
|
+
? 'a number alone is ambiguous. epoch milliseconds are a datetime, datetime(n).date, and a year needs its month and day, date(2026, 9, 6)'
|
|
108
|
+
: 0,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (isDate(input)) {
|
|
112
|
+
return new DateTime(input, settings).toTemporal().toPlainDate();
|
|
113
|
+
}
|
|
114
|
+
if (isString(input)) {
|
|
115
|
+
try {
|
|
116
|
+
return CalendarDate.#parse(input);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (!settings.loose || (error.code !== 'notADate' && error.code !== 'unreadableDate')) {
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
const zoned = parseLoose(input, settings.zone, settings.dayFirst);
|
|
123
|
+
if (!zoned) {
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
return zoned.toPlainDate();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (isZonedDateTime(input) || isPlainDateTime(input)) {
|
|
130
|
+
return input.toPlainDate();
|
|
131
|
+
}
|
|
132
|
+
if (isPlainObject(input)) {
|
|
133
|
+
return guard(
|
|
134
|
+
() => Temporal.PlainDate.from(singularKeys(input), { overflow: 'reject' }),
|
|
135
|
+
'unreadableDate',
|
|
136
|
+
JSON.stringify(input),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
return refuseType('notADate', String(input), {
|
|
140
|
+
explanation: isDevelopment
|
|
141
|
+
? "a date is an ISO string like '2026-09-06', three numbers, a fields object, a datetime, or a Temporal value"
|
|
142
|
+
: 0,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// a wall-clock string keeps its day and drops the clock. a string carrying Z or an offset is an
|
|
147
|
+
// instant, and which day it falls on depends on the zone, so it is refused with the two ways to choose
|
|
148
|
+
static #parse(text) {
|
|
149
|
+
const trimmed = text.trim();
|
|
150
|
+
if (hasTime.test(trimmed) && hasOffset.test(trimmed)) {
|
|
151
|
+
refuseType('notADate', text, {
|
|
152
|
+
explanation: isDevelopment
|
|
153
|
+
? 'that string is an instant. datetime(text, zone).date chooses its day in a zone, and so does { loose: true, zone } with plugins/loose'
|
|
154
|
+
: 0,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return guard(
|
|
158
|
+
() => Temporal.PlainDate.from(trimmed),
|
|
159
|
+
'unreadableDate',
|
|
160
|
+
text,
|
|
161
|
+
isDevelopment ? "write ISO 8601: '2026-09-06'. plugins/loose adds { loose: true } for what Date reads" : 0,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/*******************************
|
|
166
|
+
Reads
|
|
167
|
+
*******************************/
|
|
168
|
+
|
|
169
|
+
get quarter() {
|
|
170
|
+
return Math.ceil(this.#plain.month / 3);
|
|
171
|
+
}
|
|
172
|
+
get dayOfYear() {
|
|
173
|
+
return this.#plain.dayOfYear;
|
|
174
|
+
}
|
|
175
|
+
get weekOfYear() {
|
|
176
|
+
return this.#plain.weekOfYear;
|
|
177
|
+
}
|
|
178
|
+
get daysInMonth() {
|
|
179
|
+
return this.#plain.daysInMonth;
|
|
180
|
+
}
|
|
181
|
+
get daysInYear() {
|
|
182
|
+
return this.#plain.daysInYear;
|
|
183
|
+
}
|
|
184
|
+
isLeapYear() {
|
|
185
|
+
return this.#plain.inLeapYear;
|
|
186
|
+
}
|
|
187
|
+
isWeekend() {
|
|
188
|
+
return this.#plain.dayOfWeek >= 6;
|
|
189
|
+
}
|
|
190
|
+
isWeekday() {
|
|
191
|
+
return this.#plain.dayOfWeek < 6;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/*******************************
|
|
195
|
+
Arithmetic
|
|
196
|
+
*******************************/
|
|
197
|
+
|
|
198
|
+
plus(amount, name) {
|
|
199
|
+
return new CalendarDate(
|
|
200
|
+
guard(() => this.#plain.add(CalendarDate.#dateFields(amount, name)), 'cannotAdd', `${amount} ${name ?? ''}`),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
minus(amount, name) {
|
|
205
|
+
return new CalendarDate(
|
|
206
|
+
guard(
|
|
207
|
+
() => this.#plain.subtract(CalendarDate.#dateFields(amount, name)),
|
|
208
|
+
'cannotSubtract',
|
|
209
|
+
`${amount} ${name ?? ''}`,
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
static #dateFields(amount, name) {
|
|
215
|
+
const fields = fieldsFrom(amount, name);
|
|
216
|
+
const clock = timeUnits.find((time) => fields[plural(time)]);
|
|
217
|
+
if (clock) {
|
|
218
|
+
refuse('notADateUnit', `${clock}s`, {
|
|
219
|
+
explanation: isDevelopment
|
|
220
|
+
? 'a date has no clock. add days, or combine it with a time first: date.at(time, zone).plus(hours)'
|
|
221
|
+
: 0,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return temporalDurationOf(fields);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
set(fields, value) {
|
|
228
|
+
const changes = isString(fields) ? { [unit(fields)]: value } : singularKeys(fields);
|
|
229
|
+
return new CalendarDate(
|
|
230
|
+
guard(() => withParts(this.#plain, changes), 'cannotSet', JSON.stringify(changes)),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// a week starts on the configured first day, or on the one given: a picker's first day is a prop
|
|
235
|
+
startOf(name, firstDay) {
|
|
236
|
+
switch (unit(name)) {
|
|
237
|
+
case 'year':
|
|
238
|
+
return new CalendarDate(this.#plain.with({ month: 1, day: 1 }));
|
|
239
|
+
case 'quarter':
|
|
240
|
+
return new CalendarDate(this.#plain.with({ month: quarterStart(this.month), day: 1 }));
|
|
241
|
+
case 'month':
|
|
242
|
+
return new CalendarDate(this.#plain.with({ day: 1 }));
|
|
243
|
+
case 'week':
|
|
244
|
+
return new CalendarDate(this.#plain.subtract({ days: (this.weekday - weekStart(firstDay) + 7) % 7 }));
|
|
245
|
+
case 'day':
|
|
246
|
+
return this;
|
|
247
|
+
default:
|
|
248
|
+
return refuse('notADateUnit', String(name), {
|
|
249
|
+
explanation: isDevelopment ? 'a date starts at year, quarter, month, week or day' : 0,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
endOf(name, firstDay) {
|
|
255
|
+
return this.startOf(name, firstDay).plus(stepOf(unit(name))).minus({ days: 1 });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
next(weekday) {
|
|
259
|
+
const delta = (weekdayNumber(weekday) - this.weekday + 7) % 7 || 7;
|
|
260
|
+
return this.plus({ days: delta });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
previous(weekday) {
|
|
264
|
+
const delta = (this.weekday - weekdayNumber(weekday) + 7) % 7 || 7;
|
|
265
|
+
return this.minus({ days: delta });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/*******************************
|
|
269
|
+
Comparison
|
|
270
|
+
*******************************/
|
|
271
|
+
|
|
272
|
+
equals(other) {
|
|
273
|
+
return this.#compare(other) === 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
#compare(other) {
|
|
277
|
+
return Temporal.PlainDate.compare(this.#plain, new CalendarDate(other).toTemporal());
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
isBefore(other) {
|
|
281
|
+
return this.#compare(other) < 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
isAfter(other) {
|
|
285
|
+
return this.#compare(other) > 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
isSame(other, name, firstDay) {
|
|
289
|
+
if (name === undefined) {
|
|
290
|
+
return this.equals(other);
|
|
291
|
+
}
|
|
292
|
+
return this.startOf(name, firstDay).equals(new CalendarDate(other).startOf(name, firstDay));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
isPast(zone) {
|
|
296
|
+
return this.isBefore(today(zone));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
isFuture(zone) {
|
|
300
|
+
return this.isAfter(today(zone));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
isToday(zone) {
|
|
304
|
+
return this.equals(today(zone));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
isTomorrow(zone) {
|
|
308
|
+
return this.equals(today(zone).plus({ days: 1 }));
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
isYesterday(zone) {
|
|
312
|
+
return this.equals(today(zone).minus({ days: 1 }));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/*******************************
|
|
316
|
+
Measure
|
|
317
|
+
*******************************/
|
|
318
|
+
|
|
319
|
+
until(other, name) {
|
|
320
|
+
const end = new CalendarDate(other);
|
|
321
|
+
const span = anchored(this.#plain.until(end.toTemporal(), { largestUnit: 'year' }), this.#plain);
|
|
322
|
+
return name === undefined ? span : span.total(name);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
since(other, name) {
|
|
326
|
+
return new CalendarDate(other).until(this, name);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
at(when, zone) {
|
|
330
|
+
const plainTime = new Time(when ?? '00:00').toTemporal();
|
|
331
|
+
return new DateTime(this.#plain.toZonedDateTime({ timeZone: zoneId(zone), plainTime }));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/*******************************
|
|
335
|
+
Output
|
|
336
|
+
*******************************/
|
|
337
|
+
|
|
338
|
+
format(spec, locale) {
|
|
339
|
+
const options = intlOptions('date', spec);
|
|
340
|
+
return options ? formatIntl('date', this.#plain, options, locale) : unknownFormat(spec);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
toString() {
|
|
344
|
+
return this.#plain.toString();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
toJSON() {
|
|
348
|
+
return this.#plain.toString();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
toJSDate(zone) {
|
|
352
|
+
return this.at('00:00', zone).toJSDate();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
toTemporal() {
|
|
356
|
+
return this.#plain;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
valueOf() {
|
|
360
|
+
return refuse('notANumber', String(this), {
|
|
361
|
+
explanation: isDevelopment
|
|
362
|
+
? 'a date is not a point on the number line. compare with isBefore, isAfter or equals, or measure with until'
|
|
363
|
+
: 0,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// + gives the string, while < and - still refuse through valueOf
|
|
368
|
+
[Symbol.toPrimitive](hint) {
|
|
369
|
+
return hint === 'number' ? this.valueOf() : this.toString();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
[inspect]() {
|
|
373
|
+
return `CalendarDate(${this.#plain})`;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
CalendarDate.prototype.add = CalendarDate.prototype.plus;
|
|
378
|
+
CalendarDate.prototype.subtract = CalendarDate.prototype.minus;
|
|
379
|
+
CalendarDate.prototype.remove = CalendarDate.prototype.minus;
|
|
380
|
+
|
|
381
|
+
export const date = (input, monthOrOptions, day) => {
|
|
382
|
+
const build = () =>
|
|
383
|
+
input instanceof CalendarDate && monthOrOptions === undefined
|
|
384
|
+
? input
|
|
385
|
+
: new CalendarDate(input, monthOrOptions, day);
|
|
386
|
+
return isPlainObject(monthOrOptions) && monthOrOptions.loose ? loosely(build) : build();
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
export const today = (zone) => new CalendarDate(Temporal.Now.plainDateISO(zoneId(zone)));
|
|
390
|
+
export const tomorrow = (zone) => today(zone).plus({ days: 1 });
|
|
391
|
+
export const yesterday = (zone) => today(zone).minus({ days: 1 });
|
|
392
|
+
|
|
393
|
+
export const isCalendarDate = (value) => value instanceof CalendarDate;
|