qrono 1.1.2 → 1.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.
- package/README.md +18 -10
- package/dist/qrono.cjs +1 -1
- package/dist/qrono.cjs.map +1 -1
- package/dist/qrono.js +259 -296
- package/dist/qrono.js.map +1 -1
- package/dist/qrono.min.js +1 -1
- package/dist/qrono.min.js.map +1 -1
- package/package.json +3 -1
- package/src/helpers.js +0 -72
- package/src/qrono.js +129 -148
- package/types/qrono.d.ts +23 -12
package/src/helpers.js
CHANGED
|
@@ -28,14 +28,6 @@ export const millisecondsPerHour = secondsPerHour * millisecondsPerSecond
|
|
|
28
28
|
export const millisecondsPerDay = secondsPerDay * millisecondsPerSecond
|
|
29
29
|
export const millisecondsPerWeek = secondsPerWeek * millisecondsPerSecond
|
|
30
30
|
|
|
31
|
-
export const monday = 1
|
|
32
|
-
export const tuesday = 2
|
|
33
|
-
export const wednesday = 3
|
|
34
|
-
export const thursday = 4
|
|
35
|
-
export const friday = 5
|
|
36
|
-
export const saturday = 6
|
|
37
|
-
export const sunday = 7
|
|
38
|
-
|
|
39
31
|
export function has(object, ...keys) {
|
|
40
32
|
return keys.flat().some(object.hasOwnProperty, object)
|
|
41
33
|
}
|
|
@@ -85,67 +77,3 @@ export function hasDatetimeField(object) {
|
|
|
85
77
|
'millisecond',
|
|
86
78
|
])
|
|
87
79
|
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Resolve a local time that falls on or near a DST transition boundary.
|
|
91
|
-
*
|
|
92
|
-
* Handles two distinct cases that arise when constructing a local Date:
|
|
93
|
-
*
|
|
94
|
-
* GAP (spring-forward, isGap = true):
|
|
95
|
-
* A range of local times is skipped entirely. JavaScript automatically advances
|
|
96
|
-
* the time to the next valid moment (post-transition / DST side), adding the gap
|
|
97
|
-
* size to the UTC timestamp. The caller detects a gap by comparing the constructed
|
|
98
|
-
* Date's local fields against the originally requested values.
|
|
99
|
-
* - interpretAsDst = true → accept JS's forward-shift as-is (DST side)
|
|
100
|
-
* - interpretAsDst = false → shift UTC back by the gap size (pre-transition side)
|
|
101
|
-
*
|
|
102
|
-
* OVERLAP (fall-back, isGap = false):
|
|
103
|
-
* A range of local times occurs twice. JavaScript always picks the DST side
|
|
104
|
-
* (first occurrence). If the time is not actually in an overlap, the adjustment
|
|
105
|
-
* will not preserve the original local fields and the original Date is returned.
|
|
106
|
-
* - interpretAsDst = true → accept JS's DST-side interpretation as-is
|
|
107
|
-
* - interpretAsDst = false → shift UTC by the offset difference (standard-time side)
|
|
108
|
-
*
|
|
109
|
-
* In both cases the UTC adjustment uses the same formula:
|
|
110
|
-
* adjustedUTC = date.getTime() + adjust * millisecondsPerMinute
|
|
111
|
-
* where adjust = nextDay.timezoneOffset - prevDay.timezoneOffset.
|
|
112
|
-
* For a gap the adjust is negative (offsets decrease going forward),
|
|
113
|
-
* so subtracting it moves UTC backward to the pre-transition side.
|
|
114
|
-
* For an overlap the adjust is also negative in the same direction,
|
|
115
|
-
* and the same subtraction moves to the standard-time side.
|
|
116
|
-
*
|
|
117
|
-
* @param {boolean} interpretAsDst
|
|
118
|
-
* @param {Date} date - The Date constructed from the requested local fields.
|
|
119
|
-
* @param {boolean} isGap - true if the requested time fell in a DST gap (spring-forward).
|
|
120
|
-
* @returns {Date}
|
|
121
|
-
*/
|
|
122
|
-
export function resolveDstTime(interpretAsDst, date, isGap) {
|
|
123
|
-
const numeric = date.getTime()
|
|
124
|
-
const original = new Date(numeric)
|
|
125
|
-
if (interpretAsDst) {
|
|
126
|
-
return original
|
|
127
|
-
}
|
|
128
|
-
const nextDay = new Date(numeric)
|
|
129
|
-
nextDay.setDate(date.getDate() + 1)
|
|
130
|
-
const prevDay = new Date(numeric)
|
|
131
|
-
prevDay.setDate(date.getDate() - 1)
|
|
132
|
-
const adjust = nextDay.getTimezoneOffset() - prevDay.getTimezoneOffset()
|
|
133
|
-
if (adjust === 0) {
|
|
134
|
-
return original
|
|
135
|
-
}
|
|
136
|
-
const adjustedUTC = new Date(
|
|
137
|
-
new Date(numeric).setUTCMinutes(date.getUTCMinutes() + adjust)
|
|
138
|
-
)
|
|
139
|
-
if (isGap) {
|
|
140
|
-
return adjustedUTC
|
|
141
|
-
}
|
|
142
|
-
// For an overlap, verify the candidate preserves the original local fields.
|
|
143
|
-
// If it does not, the time is not actually in an overlap — return as-is.
|
|
144
|
-
if (
|
|
145
|
-
adjustedUTC.getHours() !== date.getHours() ||
|
|
146
|
-
adjustedUTC.getMinutes() !== date.getMinutes()
|
|
147
|
-
) {
|
|
148
|
-
return original
|
|
149
|
-
}
|
|
150
|
-
return adjustedUTC
|
|
151
|
-
}
|
package/src/qrono.js
CHANGED
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
isObject,
|
|
7
7
|
isString,
|
|
8
8
|
isValidDate,
|
|
9
|
-
resolveDstTime,
|
|
10
9
|
hasDatetimeField,
|
|
11
10
|
initialSafeDate,
|
|
12
11
|
daysPerWeek,
|
|
@@ -14,13 +13,6 @@ import {
|
|
|
14
13
|
minutesPerHour,
|
|
15
14
|
millisecondsPerMinute,
|
|
16
15
|
millisecondsPerDay,
|
|
17
|
-
monday,
|
|
18
|
-
tuesday,
|
|
19
|
-
wednesday,
|
|
20
|
-
thursday,
|
|
21
|
-
friday,
|
|
22
|
-
saturday,
|
|
23
|
-
sunday,
|
|
24
16
|
} from './helpers.js'
|
|
25
17
|
|
|
26
18
|
// -----------------------------------------------------------------------------
|
|
@@ -30,16 +22,6 @@ const qrono = Qrono
|
|
|
30
22
|
|
|
31
23
|
export { qrono }
|
|
32
24
|
|
|
33
|
-
export {
|
|
34
|
-
monday,
|
|
35
|
-
tuesday,
|
|
36
|
-
wednesday,
|
|
37
|
-
thursday,
|
|
38
|
-
friday,
|
|
39
|
-
saturday,
|
|
40
|
-
sunday,
|
|
41
|
-
} from './helpers'
|
|
42
|
-
|
|
43
25
|
// -----------------------------------------------------------------------------
|
|
44
26
|
// Static
|
|
45
27
|
// -----------------------------------------------------------------------------
|
|
@@ -48,7 +30,7 @@ Qrono.date = QronoDate
|
|
|
48
30
|
// NOTE Must be flat object for shallow cloning.
|
|
49
31
|
const defaultContext = {
|
|
50
32
|
localtime: false,
|
|
51
|
-
|
|
33
|
+
disambiguation: 'compatible',
|
|
52
34
|
}
|
|
53
35
|
|
|
54
36
|
for (const key of fields(defaultContext)) {
|
|
@@ -84,6 +66,14 @@ Qrono.asLocaltime = function () {
|
|
|
84
66
|
return this
|
|
85
67
|
}
|
|
86
68
|
|
|
69
|
+
const monday = 1
|
|
70
|
+
const tuesday = 2
|
|
71
|
+
const wednesday = 3
|
|
72
|
+
const thursday = 4
|
|
73
|
+
const friday = 5
|
|
74
|
+
const saturday = 6
|
|
75
|
+
const sunday = 7
|
|
76
|
+
|
|
87
77
|
Object.assign(Qrono, {
|
|
88
78
|
monday,
|
|
89
79
|
tuesday,
|
|
@@ -97,7 +87,7 @@ Object.assign(Qrono, {
|
|
|
97
87
|
// -----------------------------------------------------------------------------
|
|
98
88
|
// Constructor
|
|
99
89
|
// -----------------------------------------------------------------------------
|
|
100
|
-
const internal = Symbol(
|
|
90
|
+
const internal = Symbol()
|
|
101
91
|
|
|
102
92
|
function Qrono(...args) {
|
|
103
93
|
if (!new.target) {
|
|
@@ -107,7 +97,7 @@ function Qrono(...args) {
|
|
|
107
97
|
// properties
|
|
108
98
|
nativeDate: null,
|
|
109
99
|
localtime: false,
|
|
110
|
-
|
|
100
|
+
disambiguation: 'compatible',
|
|
111
101
|
// methods
|
|
112
102
|
set,
|
|
113
103
|
parse,
|
|
@@ -147,8 +137,9 @@ function Qrono(...args) {
|
|
|
147
137
|
} else if (Number.isFinite(first) && !Number.isFinite(second)) {
|
|
148
138
|
self.nativeDate = new Date(first)
|
|
149
139
|
} else if (Number.isFinite(first) || Array.isArray(first)) {
|
|
150
|
-
const
|
|
151
|
-
|
|
140
|
+
const flat = args.flat()
|
|
141
|
+
const values = flat.filter(v => Number.isSafeInteger(v))
|
|
142
|
+
if (values.length !== flat.length) {
|
|
152
143
|
throw RangeError('Should be safe integers')
|
|
153
144
|
}
|
|
154
145
|
if (values.length > 7) {
|
|
@@ -195,43 +186,7 @@ function getNative(name) {
|
|
|
195
186
|
function set(values) {
|
|
196
187
|
const args = { ...values }
|
|
197
188
|
args.month = args.month && args.month - 1
|
|
198
|
-
if (this.localtime) {
|
|
199
|
-
const dateOnly = !has(values, 'hour', 'minute', 'second', 'millisecond')
|
|
200
|
-
const interpretAsDst = dateOnly ? true : this.interpretAsDst
|
|
201
|
-
const baseDate = this.nativeDate ?? new Date(0, 0)
|
|
202
|
-
const newDate = new Date(initialSafeDate.getTime())
|
|
203
|
-
const requested = {
|
|
204
|
-
year: args.year ?? baseDate.getFullYear(),
|
|
205
|
-
month: args.month ?? baseDate.getMonth(),
|
|
206
|
-
day: args.day ?? baseDate.getDate(),
|
|
207
|
-
hour: args.hour ?? (dateOnly ? 0 : baseDate.getHours()),
|
|
208
|
-
minute: args.minute ?? (dateOnly ? 0 : baseDate.getMinutes()),
|
|
209
|
-
second: args.second ?? (dateOnly ? 0 : baseDate.getSeconds()),
|
|
210
|
-
millisecond:
|
|
211
|
-
args.millisecond ?? (dateOnly ? 0 : baseDate.getMilliseconds()),
|
|
212
|
-
}
|
|
213
|
-
newDate.setFullYear(requested.year, requested.month, requested.day)
|
|
214
|
-
newDate.setHours(
|
|
215
|
-
requested.hour,
|
|
216
|
-
requested.minute,
|
|
217
|
-
requested.second,
|
|
218
|
-
requested.millisecond
|
|
219
|
-
)
|
|
220
|
-
// Detect whether the constructed Date landed in a DST gap (missing time).
|
|
221
|
-
// In a gap, JavaScript silently shifts the time forward.
|
|
222
|
-
const isGap =
|
|
223
|
-
requested.year * 1e8 +
|
|
224
|
-
requested.month * 1e6 +
|
|
225
|
-
requested.day * 1e4 +
|
|
226
|
-
requested.hour * 1e2 +
|
|
227
|
-
requested.minute <
|
|
228
|
-
newDate.getFullYear() * 1e8 +
|
|
229
|
-
newDate.getMonth() * 1e6 +
|
|
230
|
-
newDate.getDate() * 1e4 +
|
|
231
|
-
newDate.getHours() * 1e2 +
|
|
232
|
-
newDate.getMinutes()
|
|
233
|
-
this.nativeDate = resolveDstTime(interpretAsDst, newDate, isGap)
|
|
234
|
-
} else {
|
|
189
|
+
if (!this.localtime) {
|
|
235
190
|
const baseDate = this.nativeDate ?? new Date(0)
|
|
236
191
|
const newDate = new Date(0)
|
|
237
192
|
newDate.setUTCFullYear(
|
|
@@ -246,7 +201,71 @@ function set(values) {
|
|
|
246
201
|
args.millisecond ?? baseDate.getUTCMilliseconds()
|
|
247
202
|
)
|
|
248
203
|
this.nativeDate = newDate
|
|
204
|
+
return this
|
|
205
|
+
}
|
|
206
|
+
const dateOnly = !has(values, 'hour', 'minute', 'second', 'millisecond')
|
|
207
|
+
const disambig = dateOnly ? 'later' : this.disambiguation
|
|
208
|
+
const baseDate = this.nativeDate ?? new Date(0, 0)
|
|
209
|
+
const newDate = new Date(initialSafeDate.getTime())
|
|
210
|
+
const requested = {
|
|
211
|
+
year: args.year ?? baseDate.getFullYear(),
|
|
212
|
+
month: args.month ?? baseDate.getMonth(),
|
|
213
|
+
day: args.day ?? baseDate.getDate(),
|
|
214
|
+
hour: args.hour ?? (dateOnly ? 0 : baseDate.getHours()),
|
|
215
|
+
minute: args.minute ?? (dateOnly ? 0 : baseDate.getMinutes()),
|
|
216
|
+
second: args.second ?? (dateOnly ? 0 : baseDate.getSeconds()),
|
|
217
|
+
millisecond:
|
|
218
|
+
args.millisecond ?? (dateOnly ? 0 : baseDate.getMilliseconds()),
|
|
219
|
+
}
|
|
220
|
+
newDate.setFullYear(requested.year, requested.month, requested.day)
|
|
221
|
+
newDate.setHours(
|
|
222
|
+
requested.hour,
|
|
223
|
+
requested.minute,
|
|
224
|
+
requested.second,
|
|
225
|
+
requested.millisecond
|
|
226
|
+
)
|
|
227
|
+
// Compute the offset-delta between the day before and after.
|
|
228
|
+
const numeric = newDate.getTime()
|
|
229
|
+
const nextDay = new Date(numeric)
|
|
230
|
+
nextDay.setDate(newDate.getDate() + 1)
|
|
231
|
+
const prevDay = new Date(numeric)
|
|
232
|
+
prevDay.setDate(newDate.getDate() - 1)
|
|
233
|
+
const adjust = nextDay.getTimezoneOffset() - prevDay.getTimezoneOffset()
|
|
234
|
+
// No DST transition nearby — nothing to resolve.
|
|
235
|
+
if (disambig === 'compatible' || adjust === 0) {
|
|
236
|
+
this.nativeDate = newDate
|
|
237
|
+
return this
|
|
238
|
+
}
|
|
239
|
+
// Detect whether the constructed Date landed in a DST gap (missing time).
|
|
240
|
+
// In a gap, JavaScript silently shifts the time forward.
|
|
241
|
+
const isGap =
|
|
242
|
+
requested.year * 1e8 +
|
|
243
|
+
requested.month * 1e6 +
|
|
244
|
+
requested.day * 1e4 +
|
|
245
|
+
requested.hour * 1e2 +
|
|
246
|
+
requested.minute <
|
|
247
|
+
newDate.getFullYear() * 1e8 +
|
|
248
|
+
newDate.getMonth() * 1e6 +
|
|
249
|
+
newDate.getDate() * 1e4 +
|
|
250
|
+
newDate.getHours() * 1e2 +
|
|
251
|
+
newDate.getMinutes()
|
|
252
|
+
// Compute the standard-time candidate.
|
|
253
|
+
// Verify the candidate actually preserves the original local fields.
|
|
254
|
+
// If it doesn't, the time isn't truly in an overlap.
|
|
255
|
+
const adjustedUTC = new Date(
|
|
256
|
+
new Date(numeric).setUTCMinutes(newDate.getUTCMinutes() + adjust)
|
|
257
|
+
)
|
|
258
|
+
const isOverlap =
|
|
259
|
+
adjustedUTC.getHours() === newDate.getHours() &&
|
|
260
|
+
adjustedUTC.getMinutes() === newDate.getMinutes()
|
|
261
|
+
if (!isGap && !isOverlap) {
|
|
262
|
+
this.nativeDate = newDate
|
|
263
|
+
return this
|
|
249
264
|
}
|
|
265
|
+
if (disambig === 'reject') {
|
|
266
|
+
throw new RangeError(`Requested local time ${requested} is ambiguous.`)
|
|
267
|
+
}
|
|
268
|
+
this.nativeDate = disambig === 'later' ? newDate : adjustedUTC
|
|
250
269
|
return this
|
|
251
270
|
}
|
|
252
271
|
|
|
@@ -303,24 +322,26 @@ function parse(str) {
|
|
|
303
322
|
// -----------------------------------------------------------------------------
|
|
304
323
|
// Public methods
|
|
305
324
|
// -----------------------------------------------------------------------------
|
|
325
|
+
const p = (v, n) => String(v).padStart(n, '0')
|
|
326
|
+
|
|
306
327
|
// Basic
|
|
307
328
|
Qrono.prototype.toString = function () {
|
|
308
329
|
if (this[internal].localtime) {
|
|
309
330
|
const t = this[internal].nativeDate
|
|
310
331
|
const offset = -t.getTimezoneOffset()
|
|
311
332
|
const offsetAbs = Math.abs(offset)
|
|
312
|
-
return
|
|
313
|
-
t.
|
|
314
|
-
|
|
315
|
-
t.
|
|
316
|
-
|
|
317
|
-
t.
|
|
318
|
-
|
|
319
|
-
(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
333
|
+
return (
|
|
334
|
+
`${p(t.getFullYear(), 4)}-` +
|
|
335
|
+
`${p(t.getMonth() + 1, 2)}-` +
|
|
336
|
+
`${p(t.getDate(), 2)}T` +
|
|
337
|
+
`${p(t.getHours(), 2)}:` +
|
|
338
|
+
`${p(t.getMinutes(), 2)}:` +
|
|
339
|
+
`${p(t.getSeconds(), 2)}.` +
|
|
340
|
+
`${p(t.getMilliseconds(), 3)}` +
|
|
341
|
+
`${offset >= 0 ? '+' : '-'}` +
|
|
342
|
+
`${p(Math.trunc(offsetAbs / minutesPerHour), 2)}:` +
|
|
343
|
+
`${p(offsetAbs % minutesPerHour, 2)}`
|
|
344
|
+
)
|
|
324
345
|
}
|
|
325
346
|
return this[internal].nativeDate.toISOString()
|
|
326
347
|
}
|
|
@@ -338,7 +359,7 @@ Qrono.prototype.context = function (context) {
|
|
|
338
359
|
? this.clone(context)
|
|
339
360
|
: {
|
|
340
361
|
localtime: this[internal].localtime,
|
|
341
|
-
|
|
362
|
+
disambiguation: this[internal].disambiguation,
|
|
342
363
|
}
|
|
343
364
|
}
|
|
344
365
|
|
|
@@ -356,10 +377,10 @@ Qrono.prototype.localtime = function (arg) {
|
|
|
356
377
|
return given(arg) ? this.clone({ localtime: arg }) : this[internal].localtime
|
|
357
378
|
}
|
|
358
379
|
|
|
359
|
-
Qrono.prototype.
|
|
380
|
+
Qrono.prototype.disambiguation = function (arg) {
|
|
360
381
|
return given(arg)
|
|
361
|
-
? this.clone({
|
|
362
|
-
: this[internal].
|
|
382
|
+
? this.clone({ disambiguation: arg })
|
|
383
|
+
: this[internal].disambiguation
|
|
363
384
|
}
|
|
364
385
|
|
|
365
386
|
Qrono.prototype.valid = function () {
|
|
@@ -409,46 +430,20 @@ Qrono.prototype.asLocaltime = function () {
|
|
|
409
430
|
}
|
|
410
431
|
|
|
411
432
|
// Accessor
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
? this.clone({ day: value })
|
|
427
|
-
: this[internal].getNative('Date')
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
Qrono.prototype.hour = function (value) {
|
|
431
|
-
return given(value)
|
|
432
|
-
? this.clone({ hour: value })
|
|
433
|
-
: this[internal].getNative('Hours')
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
Qrono.prototype.minute = function (value) {
|
|
437
|
-
return given(value)
|
|
438
|
-
? this.clone({ minute: value })
|
|
439
|
-
: this[internal].getNative('Minutes')
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
Qrono.prototype.second = function (value) {
|
|
443
|
-
return given(value)
|
|
444
|
-
? this.clone({ second: value })
|
|
445
|
-
: this[internal].getNative('Seconds')
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
Qrono.prototype.millisecond = function (value) {
|
|
449
|
-
return given(value)
|
|
450
|
-
? this.clone({ millisecond: value })
|
|
451
|
-
: this[internal].getNative('Milliseconds')
|
|
433
|
+
for (const [field, native, offset] of [
|
|
434
|
+
['year', 'FullYear', 0],
|
|
435
|
+
['month', 'Month', 1],
|
|
436
|
+
['day', 'Date', 0],
|
|
437
|
+
['hour', 'Hours', 0],
|
|
438
|
+
['minute', 'Minutes', 0],
|
|
439
|
+
['second', 'Seconds', 0],
|
|
440
|
+
['millisecond', 'Milliseconds', 0],
|
|
441
|
+
]) {
|
|
442
|
+
Qrono.prototype[field] = function (value) {
|
|
443
|
+
return given(value)
|
|
444
|
+
? this.clone({ [field]: value })
|
|
445
|
+
: this[internal].getNative(native) + offset
|
|
446
|
+
}
|
|
452
447
|
}
|
|
453
448
|
|
|
454
449
|
// Getter
|
|
@@ -517,7 +512,7 @@ Qrono.prototype.minutesInDay = function () {
|
|
|
517
512
|
if (!this[internal].localtime) {
|
|
518
513
|
return minutesPerDay
|
|
519
514
|
}
|
|
520
|
-
const startOfDay = this.context({
|
|
515
|
+
const startOfDay = this.context({ disambiguation: 'later' }).startOfDay()
|
|
521
516
|
const nextDay = startOfDay.plus({ day: 1 }).startOfDay()
|
|
522
517
|
if (startOfDay.day() === nextDay.day()) {
|
|
523
518
|
return minutesPerDay
|
|
@@ -547,41 +542,26 @@ Qrono.prototype.weeksInYear = function () {
|
|
|
547
542
|
return 52
|
|
548
543
|
}
|
|
549
544
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
Qrono.prototype.startOfMonth = function () {
|
|
562
|
-
return this.clone({ day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 })
|
|
545
|
+
for (const [name, cloneArg] of [
|
|
546
|
+
['Year', { month: 1, day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 }],
|
|
547
|
+
['Month', { day: 1, hour: 0, minute: 0, second: 0, millisecond: 0 }],
|
|
548
|
+
['Hour', { minute: 0, second: 0, millisecond: 0 }],
|
|
549
|
+
['Minute', { second: 0, millisecond: 0 }],
|
|
550
|
+
['Second', { millisecond: 0 }],
|
|
551
|
+
]) {
|
|
552
|
+
Qrono.prototype[`startOf${name}`] = function () {
|
|
553
|
+
return this.clone(cloneArg)
|
|
554
|
+
}
|
|
563
555
|
}
|
|
564
556
|
|
|
565
557
|
Qrono.prototype.startOfDay = function () {
|
|
566
558
|
const timestamp = this.clone(
|
|
567
|
-
{
|
|
559
|
+
{ disambiguation: 'later' },
|
|
568
560
|
{ hour: 0, minute: 0, second: 0, millisecond: 0 }
|
|
569
561
|
).numeric()
|
|
570
562
|
return this.clone(timestamp)
|
|
571
563
|
}
|
|
572
564
|
|
|
573
|
-
Qrono.prototype.startOfHour = function () {
|
|
574
|
-
return this.clone({ minute: 0, second: 0, millisecond: 0 })
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
Qrono.prototype.startOfMinute = function () {
|
|
578
|
-
return this.clone({ second: 0, millisecond: 0 })
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
Qrono.prototype.startOfSecond = function () {
|
|
582
|
-
return this.clone({ millisecond: 0 })
|
|
583
|
-
}
|
|
584
|
-
|
|
585
565
|
Qrono.prototype.isSame = function (another) {
|
|
586
566
|
return +this === +another
|
|
587
567
|
}
|
|
@@ -626,8 +606,9 @@ function plus(sign, ...args) {
|
|
|
626
606
|
}
|
|
627
607
|
timeFields = arg0
|
|
628
608
|
} else if (Number.isFinite(arg0) || Array.isArray(arg0)) {
|
|
629
|
-
const
|
|
630
|
-
|
|
609
|
+
const flat = args.flat()
|
|
610
|
+
const values = flat.filter(v => Number.isSafeInteger(v))
|
|
611
|
+
if (values.length !== flat.length) {
|
|
631
612
|
throw RangeError('Should be safe integers')
|
|
632
613
|
}
|
|
633
614
|
if (values.length > 7) {
|
|
@@ -675,13 +656,13 @@ function plus(sign, ...args) {
|
|
|
675
656
|
date[`getUTC${nativeKey}`]() + sign * timeFields[key]
|
|
676
657
|
)
|
|
677
658
|
}
|
|
678
|
-
return this.clone(
|
|
659
|
+
return this.clone(date)
|
|
679
660
|
}
|
|
680
661
|
|
|
681
662
|
// -----------------------------------------------------------------------------
|
|
682
663
|
// QronoDate Class
|
|
683
664
|
// -----------------------------------------------------------------------------
|
|
684
|
-
const internalDate = Symbol(
|
|
665
|
+
const internalDate = Symbol()
|
|
685
666
|
|
|
686
667
|
function QronoDate(...args) {
|
|
687
668
|
if (!new.target) {
|
|
@@ -781,7 +762,7 @@ for (const method of [
|
|
|
781
762
|
for (const method of ['minutesInDay', 'hasDstInYear', 'isDstTransitionDay']) {
|
|
782
763
|
QronoDate.prototype[method] = function () {
|
|
783
764
|
return qrono(
|
|
784
|
-
{
|
|
765
|
+
{ disambiguation: 'later' },
|
|
785
766
|
this[internalDate].datetime.toArray().slice(0, 3)
|
|
786
767
|
)[method]()
|
|
787
768
|
}
|
package/types/qrono.d.ts
CHANGED
|
@@ -230,16 +230,16 @@ declare namespace qrono {
|
|
|
230
230
|
export function localtime(value: boolean): typeof qrono
|
|
231
231
|
|
|
232
232
|
/**
|
|
233
|
-
* Returns
|
|
233
|
+
* Returns the current global disambiguation option.
|
|
234
234
|
*/
|
|
235
|
-
export function
|
|
235
|
+
export function disambiguation(): Disambiguation
|
|
236
236
|
|
|
237
237
|
/**
|
|
238
|
-
* Sets
|
|
239
|
-
* @param value -
|
|
238
|
+
* Sets the global disambiguation option.
|
|
239
|
+
* @param value - The disambiguation option to set.
|
|
240
240
|
* @returns The qrono object.
|
|
241
241
|
*/
|
|
242
|
-
export function
|
|
242
|
+
export function disambiguation(value: Disambiguation): typeof qrono
|
|
243
243
|
|
|
244
244
|
/**
|
|
245
245
|
* Returns new qrono instance with UTC context.
|
|
@@ -251,9 +251,20 @@ declare namespace qrono {
|
|
|
251
251
|
*/
|
|
252
252
|
export function asLocaltime(): typeof qrono
|
|
253
253
|
|
|
254
|
+
/**
|
|
255
|
+
* The four disambiguation options for resolving ambiguous local times,
|
|
256
|
+
* mirroring the Temporal API.
|
|
257
|
+
*
|
|
258
|
+
* - `'compatible'`: Gap → later (JS native forward-shift). Overlap → earlier (standard-time side).
|
|
259
|
+
* - `'earlier'`: Gap or overlap → the earlier (standard-time / pre-transition) instant.
|
|
260
|
+
* - `'later'`: Gap or overlap → the later (DST side / post-transition) instant.
|
|
261
|
+
* - `'reject'`: Throws `RangeError` if the time falls in a gap or overlap.
|
|
262
|
+
*/
|
|
263
|
+
export type Disambiguation = 'compatible' | 'earlier' | 'later' | 'reject'
|
|
264
|
+
|
|
254
265
|
export type Context = {
|
|
255
266
|
localtime?: boolean
|
|
256
|
-
|
|
267
|
+
disambiguation?: Disambiguation
|
|
257
268
|
}
|
|
258
269
|
|
|
259
270
|
export type TimeFields = {
|
|
@@ -322,16 +333,16 @@ declare namespace qrono {
|
|
|
322
333
|
localtime(yes: boolean): Qrono
|
|
323
334
|
|
|
324
335
|
/**
|
|
325
|
-
* Returns
|
|
336
|
+
* Returns the current disambiguation option of the Qrono instance.
|
|
326
337
|
*/
|
|
327
|
-
|
|
338
|
+
disambiguation(): Disambiguation
|
|
328
339
|
|
|
329
340
|
/**
|
|
330
|
-
* Sets
|
|
331
|
-
* @param
|
|
332
|
-
* @returns
|
|
341
|
+
* Sets the disambiguation option of the Qrono instance.
|
|
342
|
+
* @param value - The disambiguation option.
|
|
343
|
+
* @returns A new Qrono instance with the updated disambiguation.
|
|
333
344
|
*/
|
|
334
|
-
|
|
345
|
+
disambiguation(value: Disambiguation): Qrono
|
|
335
346
|
|
|
336
347
|
/**
|
|
337
348
|
* Returns whether the Qrono instance is valid.
|