@magmacomputing/tempo 1.0.0 → 1.0.1
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/dist/cipher.class.js +117 -50
- package/dist/enumerate.library.js +55 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -2
- package/dist/logify.class.js +88 -39
- package/dist/pledge.class.js +185 -115
- package/dist/tempo.class.d.ts +1 -0
- package/dist/tempo.class.js +1325 -1292
- package/package.json +5 -1
package/dist/tempo.class.js
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
|
+
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2
|
+
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
|
3
|
+
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
4
|
+
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
5
|
+
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
6
|
+
var _, done = false;
|
|
7
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
8
|
+
var context = {};
|
|
9
|
+
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
10
|
+
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
11
|
+
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
|
12
|
+
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
|
13
|
+
if (kind === "accessor") {
|
|
14
|
+
if (result === void 0) continue;
|
|
15
|
+
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
16
|
+
if (_ = accept(result.get)) descriptor.get = _;
|
|
17
|
+
if (_ = accept(result.set)) descriptor.set = _;
|
|
18
|
+
if (_ = accept(result.init)) initializers.unshift(_);
|
|
19
|
+
}
|
|
20
|
+
else if (_ = accept(result)) {
|
|
21
|
+
if (kind === "field") initializers.unshift(_);
|
|
22
|
+
else descriptor[key] = _;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
26
|
+
done = true;
|
|
27
|
+
};
|
|
28
|
+
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
|
29
|
+
var useValue = arguments.length > 2;
|
|
30
|
+
for (var i = 0; i < initializers.length; i++) {
|
|
31
|
+
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
32
|
+
}
|
|
33
|
+
return useValue ? value : void 0;
|
|
34
|
+
};
|
|
35
|
+
var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
|
|
36
|
+
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
|
37
|
+
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
|
38
|
+
};
|
|
39
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
40
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
41
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
42
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
43
|
+
};
|
|
44
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
45
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
46
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
47
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
48
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
49
|
+
};
|
|
50
|
+
import './temporal.polyfill.js';
|
|
1
51
|
// #region library modules~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
2
52
|
import { Logify } from './logify.class.js';
|
|
3
53
|
import { ifDefined } from './object.library.js';
|
|
@@ -51,1357 +101,1340 @@ const Context = getContext(); // get current execution context
|
|
|
51
101
|
* })
|
|
52
102
|
* ```
|
|
53
103
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
/** some useful Dates */ static get LIMIT() { return enums.LIMIT; }
|
|
69
|
-
// #endregion
|
|
70
|
-
// #region Static private properties~~~~~~~~~~~~~~~~~~~~~~
|
|
71
|
-
static #dbg = new Logify('Tempo', {
|
|
72
|
-
debug: Default?.debug ?? false,
|
|
73
|
-
catch: Default?.catch ?? false
|
|
74
|
-
});
|
|
75
|
-
static #global = {};
|
|
76
|
-
static #pending = void 0; // collect the parse rule-match results
|
|
77
|
-
static #usrCount = 0; // cache for next-available 'usr' Token key
|
|
78
|
-
// #endregion
|
|
79
|
-
// #region Static private methods~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
80
|
-
//** prototype handlers */
|
|
81
|
-
/** return the Prototype parent of an object */ static #proto(obj) { return Object.getPrototypeOf(obj); }
|
|
82
|
-
/** test object has own property with the given key */ static #hasOwn(obj, key) { return Object.hasOwn(obj, key); }
|
|
83
|
-
/** test object is Frozen */ static #isFrozen(obj) { return isDefined(obj) && Object.isFrozen(obj); }
|
|
84
|
-
/** return whether the shape is 'local' or 'global' */ static #isLocal(shape) { return shape.config.scope === 'local'; }
|
|
85
|
-
/** create an object based on a prototype */ static #create(obj, name) { return Object.create(Tempo.#proto(obj)[name]); }
|
|
86
|
-
/**
|
|
87
|
-
* {dt} is a layout that combines date-related {snippets} (e.g. dd, mm -or- evt) into a pattern against which a string can be tested.
|
|
88
|
-
* because it will also include a list of events (e.g. 'new_years' | 'xmas'), we need to rebuild {dt} if the user adds a new event
|
|
89
|
-
*/
|
|
90
|
-
// TODO: check all Layouts which reference "{evt}" and update them
|
|
91
|
-
static #setEvents(shape) {
|
|
92
|
-
const events = ownEntries(shape.parse.event, true);
|
|
93
|
-
if (Tempo.#isLocal(shape) && !Tempo.#hasOwn(shape.parse, 'event') && !Tempo.#hasOwn(shape.parse, 'isMonthDay'))
|
|
94
|
-
return; // no local change needed
|
|
95
|
-
const src = shape.config.scope.substring(0, 1); // 'g'lobal or 'l'ocal
|
|
96
|
-
const groups = events
|
|
97
|
-
.map(([pat, _], idx) => `(?<${src}evt${idx}>${pat})`) // assign a number to the pattern
|
|
98
|
-
.join('|'); // make an 'Or' pattern for the event-keys
|
|
99
|
-
if (groups) {
|
|
100
|
-
const protoEvt = Tempo.#proto(shape.parse.snippet)[Token.evt]?.source;
|
|
101
|
-
if (!Tempo.#isLocal(shape) || groups !== protoEvt) {
|
|
102
|
-
if (Tempo.#isLocal(shape) && !Tempo.#hasOwn(shape.parse, 'snippet'))
|
|
103
|
-
shape.parse.snippet = Tempo.#create(shape.parse, 'snippet');
|
|
104
|
-
Object.defineProperty(shape.parse.snippet, Token.evt, {
|
|
105
|
-
value: new RegExp(groups),
|
|
106
|
-
enumerable: true,
|
|
107
|
-
writable: true,
|
|
108
|
-
configurable: true
|
|
109
|
-
});
|
|
110
|
-
}
|
|
104
|
+
let Tempo = (() => {
|
|
105
|
+
var _a, _Tempo_dbg, _Tempo_global, _Tempo_pending, _Tempo_usrCount, _Tempo_proto, _Tempo_hasOwn, _Tempo_isFrozen, _Tempo_isLocal, _Tempo_create, _Tempo_setEvents, _Tempo_setPeriods, _Tempo_setSphere, _Tempo_isMonthDay, _Tempo_swapLayout, _Tempo_prefix, _Tempo_locale, _Tempo_setConfig, _Tempo_mdyLocales, _Tempo_setPatterns;
|
|
106
|
+
let _classDecorators = [Serializable, Immutable];
|
|
107
|
+
let _classDescriptor;
|
|
108
|
+
let _classExtraInitializers = [];
|
|
109
|
+
let _classThis;
|
|
110
|
+
var Tempo = _a = class {
|
|
111
|
+
static { _classThis = this; }
|
|
112
|
+
static { __setFunctionName(this, "Tempo"); }
|
|
113
|
+
static {
|
|
114
|
+
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
|
|
115
|
+
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
|
116
|
+
Tempo = _classThis = _classDescriptor.value;
|
|
117
|
+
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
111
118
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
// #region Static enum properties~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
120
|
+
/** Weekday names (short-form) */ static get WEEKDAY() { return enums.WEEKDAY; }
|
|
121
|
+
/** Weekday names (long-form) */ static get WEEKDAYS() { return enums.WEEKDAYS; }
|
|
122
|
+
/** Month names (short-form) */ static get MONTH() { return enums.MONTH; }
|
|
123
|
+
/** Month names (long-form) */ static get MONTHS() { return enums.MONTHS; }
|
|
124
|
+
/** Time durations as seconds (singular) */ static get DURATION() { return enums.DURATION; }
|
|
125
|
+
/** Time durations as milliseconds (plural) */ static get DURATIONS() { return enums.DURATIONS; }
|
|
126
|
+
/** Quarterly Seasons */ static get SEASON() { return enums.SEASON; }
|
|
127
|
+
/** Compass cardinal points */ static get COMPASS() { return enums.COMPASS; }
|
|
128
|
+
/** Tempo to Temporal DateTime Units map */ static get ELEMENT() { return enums.ELEMENT; }
|
|
129
|
+
/** Pre-configured format {name -> string} pairs */ static get FORMAT() { return enums.FORMAT; }
|
|
130
|
+
/** some useful Dates */ static get LIMIT() { return enums.LIMIT; }
|
|
131
|
+
static {
|
|
132
|
+
// #endregion
|
|
133
|
+
// #region Static private properties~~~~~~~~~~~~~~~~~~~~~~
|
|
134
|
+
_Tempo_dbg = { value: new Logify('Tempo', {
|
|
135
|
+
debug: Default?.debug ?? false,
|
|
136
|
+
catch: Default?.catch ?? false
|
|
137
|
+
}) };
|
|
125
138
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
static {
|
|
140
|
+
_Tempo_global = { value: {} };
|
|
141
|
+
}
|
|
142
|
+
static {
|
|
143
|
+
_Tempo_pending = { value: void 0 }; // collect the parse rule-match results
|
|
144
|
+
}
|
|
145
|
+
static {
|
|
146
|
+
_Tempo_usrCount = { value: 0 }; // cache for next-available 'usr' Token key
|
|
147
|
+
}
|
|
148
|
+
static {
|
|
149
|
+
/** try to infer hemisphere using the timezone's daylight-savings setting */
|
|
150
|
+
_Tempo_setSphere = { value: (shape, options) => {
|
|
151
|
+
if (isUndefined(shape.config.timeZone) || __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, options, 'sphere'))
|
|
152
|
+
return shape.config.sphere; // already specified or no timeZone to calculate from
|
|
153
|
+
const zdt = Temporal.Now.zonedDateTimeISO(shape.config.timeZone);
|
|
154
|
+
const jan = zdt.with({ day: 1, month: 1 }).offsetNanoseconds;
|
|
155
|
+
const jun = zdt.with({ day: 1, month: 6 }).offsetNanoseconds;
|
|
156
|
+
const dst = Math.sign(jan - jun); // timeZone offset difference between Jan and Jun
|
|
157
|
+
switch (dst) {
|
|
158
|
+
case -1:
|
|
159
|
+
return Tempo.COMPASS.North; // clock moves backward in Northern hemisphere
|
|
160
|
+
case 1:
|
|
161
|
+
return Tempo.COMPASS.South; // clock moves forward in Southern hemisphere
|
|
162
|
+
case 0:
|
|
163
|
+
return void 0; // timeZone does not observe DST
|
|
164
|
+
default:
|
|
165
|
+
return Default.sphere ?? Tempo.COMPASS.North; // timeZone does not observe DST
|
|
166
|
+
}
|
|
167
|
+
} };
|
|
168
|
+
}
|
|
169
|
+
static {
|
|
170
|
+
/** properCase week-day / calendar-month */
|
|
171
|
+
_Tempo_prefix = { value: (str) => toProperCase(str.substring(0, 3)) };
|
|
172
|
+
}
|
|
173
|
+
static {
|
|
174
|
+
/** get first Canonical name of a supplied locale */
|
|
175
|
+
_Tempo_locale = { value: (locale) => {
|
|
176
|
+
let language;
|
|
177
|
+
try { // lookup locale
|
|
178
|
+
language = Intl.getCanonicalLocales(locale.replace('_', '-'))[0];
|
|
179
|
+
}
|
|
180
|
+
catch (error) { } // catch unknown locale
|
|
181
|
+
const global = Context.global;
|
|
182
|
+
return language ??
|
|
183
|
+
global?.navigator?.languages?.[0] ?? // fallback to current first navigator.languages[]
|
|
184
|
+
global?.navigator?.language ?? // else navigator.language
|
|
185
|
+
Default.locale ?? // else default locale
|
|
186
|
+
locale; // cannot determine locale
|
|
187
|
+
} };
|
|
188
|
+
}
|
|
189
|
+
// #endregion Static private methods
|
|
190
|
+
// #region Static public methods~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
191
|
+
/**
|
|
192
|
+
* Initializes the global default configuration for all subsequent `Tempo` instances.
|
|
193
|
+
*
|
|
194
|
+
* Settings are inherited in this priority:
|
|
195
|
+
* 1. Reasonable library defaults (defined in tempo.config.js)
|
|
196
|
+
* 2. Persistent storage (e.g. localStorage), if available.
|
|
197
|
+
* 3. `options` provided to this method.
|
|
198
|
+
*
|
|
199
|
+
* @param options - Configuration overrides to apply globally.
|
|
200
|
+
* @returns The resolved global configuration.
|
|
201
|
+
*/
|
|
202
|
+
static init(options = {}) {
|
|
203
|
+
if (isEmpty(options)) { // if no options supplied, reset all
|
|
204
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).config = {}; // remove previous config
|
|
205
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).parse = {
|
|
206
|
+
snippet: { ...Snippet },
|
|
207
|
+
layout: { ...Layout },
|
|
208
|
+
event: { ...Event },
|
|
209
|
+
period: { ...Period },
|
|
210
|
+
mdyLocales: __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_mdyLocales).call(Tempo, Default.mdyLocales),
|
|
211
|
+
mdyLayouts: asArray(Default.mdyLayouts),
|
|
212
|
+
}; // remove previous parsing rules
|
|
213
|
+
for (const key of Object.keys(Token)) // purge user-allocated Tokens
|
|
214
|
+
if (key.startsWith('usr.')) // only remove 'usr.' prefixed keys
|
|
215
|
+
delete Token[key];
|
|
216
|
+
__classPrivateFieldSet(Tempo, _classThis, 0, "f", _Tempo_usrCount); // reset user-key counter
|
|
217
|
+
const dateTime = Intl.DateTimeFormat().resolvedOptions();
|
|
218
|
+
Object.assign(__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).config, {
|
|
219
|
+
calendar: dateTime.calendar,
|
|
220
|
+
timeZone: dateTime.timeZone,
|
|
221
|
+
locale: __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_locale).call(Tempo), // get from browser, if possible
|
|
150
222
|
});
|
|
223
|
+
__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_setConfig).call(Tempo, __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global), { store: STORAGEKEY, scope: 'global' }, Default, // set Tempo defaults
|
|
224
|
+
Tempo.readStore());
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
// if (options.store !== STORAGEKEY)
|
|
228
|
+
// Tempo.#setConfig(Tempo.#global, Tempo.readStore(options.store)); // user-defined local storage
|
|
229
|
+
__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_setConfig).call(Tempo, __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global), options); // overload with init() argument (options)
|
|
151
230
|
}
|
|
231
|
+
if (Context.type === CONTEXT.Browser || options.debug === true)
|
|
232
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).info(Tempo.config, 'Tempo:', __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).config);
|
|
233
|
+
return __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).config;
|
|
152
234
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const jan = zdt.with({ day: 1, month: 1 }).offsetNanoseconds;
|
|
160
|
-
const jun = zdt.with({ day: 1, month: 6 }).offsetNanoseconds;
|
|
161
|
-
const dst = Math.sign(jan - jun); // timeZone offset difference between Jan and Jun
|
|
162
|
-
switch (dst) {
|
|
163
|
-
case -1:
|
|
164
|
-
return Tempo.COMPASS.North; // clock moves backward in Northern hemisphere
|
|
165
|
-
case 1:
|
|
166
|
-
return Tempo.COMPASS.South; // clock moves forward in Southern hemisphere
|
|
167
|
-
case 0:
|
|
168
|
-
return void 0; // timeZone does not observe DST
|
|
169
|
-
default:
|
|
170
|
-
return Default.sphere ?? this.COMPASS.North; // timeZone does not observe DST
|
|
235
|
+
/**
|
|
236
|
+
* Reads `Tempo` options from persistent storage (e.g., localStorage).
|
|
237
|
+
* @returns The stored configuration or an empty object.
|
|
238
|
+
*/
|
|
239
|
+
static readStore(key = __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).config.store) {
|
|
240
|
+
return getStorage(key, {});
|
|
171
241
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return monthDay.some(mdy => mdy.timeZones?.includes(shape.config.timeZone));
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* swap parsing-order of layouts to suit different timeZones
|
|
182
|
-
* this allows the parser to try to interpret '04012023' as Apr-01-2023 before trying 04-Jan-2023
|
|
183
|
-
*/
|
|
184
|
-
static #swapLayout(shape) {
|
|
185
|
-
const layouts = ownEntries(shape.parse.layout); // get entries of Layout Record
|
|
186
|
-
const swap = shape.parse.mdyLayouts; // get the swap-tuple
|
|
187
|
-
let chg = false; // no need to rebuild, if no change
|
|
188
|
-
swap
|
|
189
|
-
.forEach(([dmy, mdy]) => {
|
|
190
|
-
const idx1 = layouts.findIndex(([key]) => key.description === dmy); // 1st swap element exists in {layouts}
|
|
191
|
-
const idx2 = layouts.findIndex(([key]) => key.description === mdy); // 2nd swap element exists in {layouts}
|
|
192
|
-
if (idx1 === -1 || idx2 === -1)
|
|
193
|
-
return; // no pair to swap
|
|
194
|
-
const swap1 = (idx1 < idx2) && shape.parse.isMonthDay; // we prefer {mdy} and the 1st tuple was found earlier than the 2nd
|
|
195
|
-
const swap2 = (idx1 > idx2) && !shape.parse.isMonthDay; // we dont prefer {mdy} and the 1st tuple was found later than the 2nd
|
|
196
|
-
if (swap1 || swap2) { // since {layouts} is an array, ok to swap by-reference
|
|
197
|
-
[layouts[idx1], layouts[idx2]] = [layouts[idx2], layouts[idx1]];
|
|
198
|
-
chg = true;
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
if (chg)
|
|
202
|
-
shape.parse.layout = Object.fromEntries(layouts); // rebuild Layout in new parse order
|
|
203
|
-
}
|
|
204
|
-
/** properCase week-day / calendar-month */
|
|
205
|
-
static #prefix = (str) => toProperCase(str.substring(0, 3));
|
|
206
|
-
/** get first Canonical name of a supplied locale */
|
|
207
|
-
static #locale = (locale) => {
|
|
208
|
-
let language;
|
|
209
|
-
try { // lookup locale
|
|
210
|
-
language = Intl.getCanonicalLocales(locale.replace('_', '-'))[0];
|
|
242
|
+
/**
|
|
243
|
+
* Writes the provided configuration into persistent storage.
|
|
244
|
+
* @param config - The options to save.
|
|
245
|
+
*/
|
|
246
|
+
static writeStore(config, key = __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).config.store) {
|
|
247
|
+
return setStorage(key, config);
|
|
211
248
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
* for example:
|
|
224
|
-
```
|
|
225
|
-
Tempo.init({ snippet: {'yy': /20\d{2}/, 'mm': /[0-9|1|2]\d/ } })
|
|
226
|
-
Tempo.init({ layout: {'ddmm': '{dd}{sep}?{mm}'} })
|
|
227
|
-
Tempo.init({ layout: '{wkd}' }) (can be a single string)
|
|
228
|
-
Tempo.init({ layout: '({wkd})? {tm}' }) (or a string combination of snippets)
|
|
229
|
-
Tempo.init({ layout: new Map([['name1', '{wkd} {yy}']], ['name2', '{mm}{sep}{dd}']]]) })
|
|
230
|
-
Tempo.init({ layout: [ {name1: '{wkd} {yy}'}, {name2: '{mm}{sep}{dd}'} ]
|
|
231
|
-
|
|
232
|
-
Tempo.init({event: {'canada ?day': '01-Jun', 'aus(tralia)? ?day': '26-Jan'} })
|
|
233
|
-
Tempo.init({period: [{'morning tea': '09:30' }, {'elevenses': '11:00' }]})
|
|
234
|
-
new Tempo('birthday', {event: [["birth(day)?", "20-May"], ["anniversary", "01-Jul"] ]})
|
|
235
|
-
```
|
|
236
|
-
*/
|
|
237
|
-
static #setConfig(shape, ...options) {
|
|
238
|
-
/** helper to normalize snippet/layout Options into the target Config */
|
|
239
|
-
const collect = (target, value, convert) => {
|
|
240
|
-
const itm = asType(value);
|
|
241
|
-
target ??= {};
|
|
242
|
-
switch (itm.type) {
|
|
243
|
-
case 'Object':
|
|
244
|
-
ownEntries(itm.value)
|
|
245
|
-
.forEach(([k, v]) => target[Tempo.getSymbol(k)] = convert(v));
|
|
246
|
-
break;
|
|
247
|
-
case 'String':
|
|
248
|
-
case 'RegExp':
|
|
249
|
-
target[Tempo.getSymbol()] = convert(itm.value);
|
|
250
|
-
break;
|
|
251
|
-
case 'Array':
|
|
252
|
-
itm.value.forEach(elm => collect(target, elm, convert));
|
|
253
|
-
break;
|
|
249
|
+
/**
|
|
250
|
+
* looks-up or registers a new `Symbol` for a given key.
|
|
251
|
+
* auto-maintains the `Token` registry for internal consistency.
|
|
252
|
+
*
|
|
253
|
+
* @param key - The description for which to retrieve/create a Symbol.
|
|
254
|
+
*/
|
|
255
|
+
static getSymbol(key) {
|
|
256
|
+
var _b, _c;
|
|
257
|
+
if (isUndefined(key)) {
|
|
258
|
+
const usr = `usr.${__classPrivateFieldSet(_b = Tempo, _classThis, (_c = __classPrivateFieldGet(_b, _classThis, "f", _Tempo_usrCount), ++_c), "f", _Tempo_usrCount)}`; // allocate a prefixed 'user' key
|
|
259
|
+
return Token[usr] = Symbol(usr); // add to Symbol register
|
|
254
260
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (
|
|
260
|
-
return; //
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
261
|
+
if (isSymbol(key)) {
|
|
262
|
+
const name = key.description ?? Cipher.randomKey(); // get Symbol description, else allocate random string
|
|
263
|
+
return Token[name] ??= key;
|
|
264
|
+
}
|
|
265
|
+
if (isDefined(Token[key])) // already registered (internal)
|
|
266
|
+
return Token[key]; // return existing Symbol
|
|
267
|
+
const usr = `usr.${key}`;
|
|
268
|
+
if (isDefined(Token[usr])) // already registered (user)
|
|
269
|
+
return Token[usr]; // return existing Symbol
|
|
270
|
+
const description = key
|
|
271
|
+
.split(Match.separator)
|
|
272
|
+
.filter(s => !isEmpty(s)).pop() || key;
|
|
273
|
+
return Token[usr] = Symbol(description); // add to Symbol register
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* translates a {layout} string into an anchored, case-insensitive regular expression.
|
|
277
|
+
* supports placeholder expansion using the {snippet} registry (e.g., `{yy}`, `{mm}`).
|
|
278
|
+
*/
|
|
279
|
+
static regexp(layout, snippet) {
|
|
280
|
+
// helper function to replace {name} placeholders with their corresponding snippets
|
|
281
|
+
function matcher(str) {
|
|
282
|
+
let source = isRegExp(str) ? str.source : str;
|
|
283
|
+
if (isRegExpLike(source)) // string that looks like a RegExp
|
|
284
|
+
source = source.substring(1, source.length - 1); // remove the leading/trailing "/"
|
|
285
|
+
if (source.startsWith('^') && source.endsWith('$'))
|
|
286
|
+
source = source.substring(1, source.length - 1); // remove the leading/trailing anchors (^ $)
|
|
287
|
+
return source.replace(Match.braces, (match, name) => {
|
|
288
|
+
const token = Tempo.getSymbol(name); // get the symbol for this {name}
|
|
289
|
+
let snip = snippet?.[token]?.source // get the snippet source (custom)
|
|
290
|
+
?? Snippet[token]?.source // else get the snippet source (global)
|
|
291
|
+
?? Layout[token]; // else get the layout source
|
|
292
|
+
if (isNullish(snip) && name.includes('.')) { // if no definition found, try fallback
|
|
293
|
+
const prefix = name.split('.')[0]; // get the base token name
|
|
294
|
+
const pToken = Tempo.getSymbol(prefix);
|
|
295
|
+
snip = snippet?.[pToken]?.source
|
|
296
|
+
?? Snippet[pToken]?.source
|
|
297
|
+
?? Layout[pToken];
|
|
298
|
+
if (snip) {
|
|
299
|
+
const safeName = name.replace(/\./g, '_'); // e.g. aaa.bbb -> aaa_bbb
|
|
300
|
+
snip = `(?<${safeName}>${snip.replace(Match.captures, '(?:$2)')})`;
|
|
301
|
+
}
|
|
279
302
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
case 'mdyLayouts': // these are the 'layouts' that need to swap parse-order
|
|
285
|
-
shape.parse[optKey] = asArray(arg.value);
|
|
286
|
-
break;
|
|
287
|
-
case 'timeZone':
|
|
288
|
-
const zone = String(arg.value).toLowerCase();
|
|
289
|
-
shape.config.timeZone = TimeZone[zone] ?? arg.value;
|
|
290
|
-
break;
|
|
291
|
-
default: // else just add to config
|
|
292
|
-
Object.assign(shape.config, { [optKey]: optVal });
|
|
293
|
-
break;
|
|
303
|
+
return (isNullish(snip) || snip === match) // if no definition found,
|
|
304
|
+
? match // return the original match
|
|
305
|
+
: matcher(snip); // else recurse to see if snippet contains embedded "{}" pairs
|
|
306
|
+
});
|
|
294
307
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
// The google-apps-script types package provides its own Intl.Locale interface that doesn't include getTimeZones(),
|
|
310
|
-
// and it takes priority over the ESNext.Intl augmentation in tsconfig.
|
|
311
|
-
// The "(mdy as any).getTimeZones?.()" can be replaced with "mdy.getTimeZones()" after google-apps-script is corrected
|
|
312
|
-
static #mdyLocales(value) {
|
|
313
|
-
return asArray(value)
|
|
314
|
-
.map(mdy => new Intl.Locale(mdy))
|
|
315
|
-
.map(mdy => ({ locale: mdy.baseName, timeZones: mdy.getTimeZones?.() ?? [] }));
|
|
316
|
-
}
|
|
317
|
-
/** build RegExp patterns */
|
|
318
|
-
static #setPatterns(shape) {
|
|
319
|
-
const snippet = shape.parse.snippet;
|
|
320
|
-
// if local and no snippet or layout overrides, we can just use the prototype's patterns
|
|
321
|
-
if (Tempo.#isLocal(shape) && !Tempo.#hasOwn(shape.parse, 'snippet') && !Tempo.#hasOwn(shape.parse, 'layout'))
|
|
322
|
-
return;
|
|
323
|
-
// ensure we have our own Map to mutate (shadow if local)
|
|
324
|
-
if (!Tempo.#hasOwn(shape.parse, 'pattern'))
|
|
325
|
-
shape.parse.pattern = new Map();
|
|
326
|
-
shape.parse.pattern.clear(); // reset {pattern} Map
|
|
327
|
-
for (const [sym, layout] of ownEntries(shape.parse.layout, true))
|
|
328
|
-
shape.parse.pattern.set(sym, Tempo.regexp(layout, snippet));
|
|
329
|
-
}
|
|
330
|
-
// #endregion Static private methods
|
|
331
|
-
// #region Static public methods~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
332
|
-
/**
|
|
333
|
-
* Initializes the global default configuration for all subsequent `Tempo` instances.
|
|
334
|
-
*
|
|
335
|
-
* Settings are inherited in this priority:
|
|
336
|
-
* 1. Reasonable library defaults (defined in tempo.config.js)
|
|
337
|
-
* 2. Persistent storage (e.g. localStorage), if available.
|
|
338
|
-
* 3. `options` provided to this method.
|
|
339
|
-
*
|
|
340
|
-
* @param options - Configuration overrides to apply globally.
|
|
341
|
-
* @returns The resolved global configuration.
|
|
342
|
-
*/
|
|
343
|
-
static init(options = {}) {
|
|
344
|
-
if (isEmpty(options)) { // if no options supplied, reset all
|
|
345
|
-
Tempo.#global.config = {}; // remove previous config
|
|
346
|
-
Tempo.#global.parse = {
|
|
347
|
-
snippet: { ...Snippet },
|
|
348
|
-
layout: { ...Layout },
|
|
349
|
-
event: { ...Event },
|
|
350
|
-
period: { ...Period },
|
|
351
|
-
mdyLocales: Tempo.#mdyLocales(Default.mdyLocales),
|
|
352
|
-
mdyLayouts: asArray(Default.mdyLayouts),
|
|
353
|
-
}; // remove previous parsing rules
|
|
354
|
-
for (const key of Object.keys(Token)) // purge user-allocated Tokens
|
|
355
|
-
if (key.startsWith('usr.')) // only remove 'usr.' prefixed keys
|
|
356
|
-
delete Token[key];
|
|
357
|
-
Tempo.#usrCount = 0; // reset user-key counter
|
|
358
|
-
const dateTime = Intl.DateTimeFormat().resolvedOptions();
|
|
359
|
-
Object.assign(Tempo.#global.config, {
|
|
360
|
-
calendar: dateTime.calendar,
|
|
361
|
-
timeZone: dateTime.timeZone,
|
|
362
|
-
locale: Tempo.#locale(), // get from browser, if possible
|
|
363
|
-
});
|
|
364
|
-
Tempo.#setConfig(Tempo.#global, { store: STORAGEKEY, scope: 'global' }, Default, // set Tempo defaults
|
|
365
|
-
Tempo.readStore());
|
|
308
|
+
// helper to check for duplicate named capture-groups
|
|
309
|
+
function checker(source) {
|
|
310
|
+
names.clear(); // clear the set of names
|
|
311
|
+
return source.replace(Match.captures, (match, name) => {
|
|
312
|
+
if (names.has(name))
|
|
313
|
+
return `(\\k<${name}>)`; // replace with a back-reference to the {name}
|
|
314
|
+
names.add(name); // add {name} to the set of names
|
|
315
|
+
return match;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const names = new Set(); // track the regex named capture-groups
|
|
319
|
+
layout = matcher(layout); // initiate the layout-parse
|
|
320
|
+
layout = checker(layout); // check for named capture-groups
|
|
321
|
+
return new RegExp(`^(${layout})$`, 'i'); // translate the source into a regex
|
|
366
322
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Compares two `Tempo` instances or date-time values.
|
|
325
|
+
* Useful for sorting or determining chronological order.
|
|
326
|
+
*
|
|
327
|
+
* @param tempo1 - The first value to compare.
|
|
328
|
+
* @param tempo2 - The second value to compare (defaults to 'now').
|
|
329
|
+
* @returns `-1` if `tempo1 < tempo2`, `0` if tempo1 == tempo2, `1` if `tempo1 > tempo2`.
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```typescript
|
|
333
|
+
* const sorted = [t1, t2].sort(Tempo.compare);
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
static compare(tempo1, tempo2) {
|
|
337
|
+
const one = new Tempo(tempo1), two = new Tempo(tempo2);
|
|
338
|
+
return Number((one.nano > two.nano) || -(one.nano < two.nano)) + 0;
|
|
371
339
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
return
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
*/
|
|
380
|
-
static readStore(key = Tempo.#global.config.store) {
|
|
381
|
-
return getStorage(key, {});
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Writes the provided configuration into persistent storage.
|
|
385
|
-
* @param config - The options to save.
|
|
386
|
-
*/
|
|
387
|
-
static writeStore(config, key = Tempo.#global.config.store) {
|
|
388
|
-
return setStorage(key, config);
|
|
389
|
-
}
|
|
390
|
-
/**
|
|
391
|
-
* looks-up or registers a new `Symbol` for a given key.
|
|
392
|
-
* auto-maintains the `Token` registry for internal consistency.
|
|
393
|
-
*
|
|
394
|
-
* @param key - The description for which to retrieve/create a Symbol.
|
|
395
|
-
*/
|
|
396
|
-
static getSymbol(key) {
|
|
397
|
-
if (isUndefined(key)) {
|
|
398
|
-
const usr = `usr.${++Tempo.#usrCount}`; // allocate a prefixed 'user' key
|
|
399
|
-
return Token[usr] = Symbol(usr); // add to Symbol register
|
|
340
|
+
static from(tempo, options) { return new Tempo(tempo, options); }
|
|
341
|
+
/** Returns current time as epoch nanoseconds (BigInt). */
|
|
342
|
+
static now() { return Temporal.Now.instant().epochNanoseconds; }
|
|
343
|
+
/** static Tempo.terms getter */
|
|
344
|
+
static get terms() {
|
|
345
|
+
return secure(terms
|
|
346
|
+
.map(({ define, ...rest }) => rest)); // omit the 'define' method
|
|
400
347
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return
|
|
348
|
+
/** static Tempo properties getter */
|
|
349
|
+
static get properties() {
|
|
350
|
+
return secure(getAccessors(Tempo)
|
|
351
|
+
.filter(acc => getType(acc) !== 'Symbol')); // omit any Symbol properties
|
|
404
352
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (isDefined(Token[usr])) // already registered (user)
|
|
409
|
-
return Token[usr]; // return existing Symbol
|
|
410
|
-
const description = key
|
|
411
|
-
.split(Match.separator)
|
|
412
|
-
.filter(s => !isEmpty(s)).pop() || key;
|
|
413
|
-
return Token[usr] = Symbol(description); // add to Symbol register
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* translates a {layout} string into an anchored, case-insensitive regular expression.
|
|
417
|
-
* supports placeholder expansion using the {snippet} registry (e.g., `{yy}`, `{mm}`).
|
|
418
|
-
*/
|
|
419
|
-
static regexp(layout, snippet) {
|
|
420
|
-
// helper function to replace {name} placeholders with their corresponding snippets
|
|
421
|
-
function matcher(str) {
|
|
422
|
-
let source = isRegExp(str) ? str.source : str;
|
|
423
|
-
if (isRegExpLike(source)) // string that looks like a RegExp
|
|
424
|
-
source = source.substring(1, source.length - 1); // remove the leading/trailing "/"
|
|
425
|
-
if (source.startsWith('^') && source.endsWith('$'))
|
|
426
|
-
source = source.substring(1, source.length - 1); // remove the leading/trailing anchors (^ $)
|
|
427
|
-
return source.replace(Match.braces, (match, name) => {
|
|
428
|
-
const token = Tempo.getSymbol(name); // get the symbol for this {name}
|
|
429
|
-
let snip = snippet?.[token]?.source // get the snippet source (custom)
|
|
430
|
-
?? Snippet[token]?.source // else get the snippet source (global)
|
|
431
|
-
?? Layout[token]; // else get the layout source
|
|
432
|
-
if (isNullish(snip) && name.includes('.')) { // if no definition found, try fallback
|
|
433
|
-
const prefix = name.split('.')[0]; // get the base token name
|
|
434
|
-
const pToken = Tempo.getSymbol(prefix);
|
|
435
|
-
snip = snippet?.[pToken]?.source
|
|
436
|
-
?? Snippet[pToken]?.source
|
|
437
|
-
?? Layout[pToken];
|
|
438
|
-
if (snip) {
|
|
439
|
-
const safeName = name.replace(/\./g, '_'); // e.g. aaa.bbb -> aaa_bbb
|
|
440
|
-
snip = `(?<${safeName}>${snip.replace(Match.captures, '(?:$2)')})`;
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
return (isNullish(snip) || snip === match) // if no definition found,
|
|
444
|
-
? match // return the original match
|
|
445
|
-
: matcher(snip); // else recurse to see if snippet contains embedded "{}" pairs
|
|
446
|
-
});
|
|
353
|
+
/** Tempo global config settings */
|
|
354
|
+
static get config() {
|
|
355
|
+
return secure({ ...__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).config });
|
|
447
356
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
return source.replace(Match.captures, (match, name) => {
|
|
452
|
-
if (names.has(name))
|
|
453
|
-
return `(\\k<${name}>)`; // replace with a back-reference to the {name}
|
|
454
|
-
names.add(name); // add {name} to the set of names
|
|
455
|
-
return match;
|
|
456
|
-
});
|
|
357
|
+
/** Tempo initial default settings */
|
|
358
|
+
static get default() {
|
|
359
|
+
return secure(Default);
|
|
457
360
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
*
|
|
471
|
-
* @example
|
|
472
|
-
* ```typescript
|
|
473
|
-
* const sorted = [t1, t2].sort(Tempo.compare);
|
|
474
|
-
* ```
|
|
475
|
-
*/
|
|
476
|
-
static compare(tempo1, tempo2) {
|
|
477
|
-
const one = new Tempo(tempo1), two = new Tempo(tempo2);
|
|
478
|
-
return Number((one.nano > two.nano) || -(one.nano < two.nano)) + 0;
|
|
479
|
-
}
|
|
480
|
-
static from(tempo, options) { return new Tempo(tempo, options); }
|
|
481
|
-
/** Returns current time as epoch nanoseconds (BigInt). */
|
|
482
|
-
static now() { return Temporal.Now.instant().epochNanoseconds; }
|
|
483
|
-
/** static Tempo.terms getter */
|
|
484
|
-
static get terms() {
|
|
485
|
-
return secure(terms
|
|
486
|
-
.map(({ define, ...rest }) => rest)); // omit the 'define' method
|
|
487
|
-
}
|
|
488
|
-
/** static Tempo properties getter */
|
|
489
|
-
static get properties() {
|
|
490
|
-
return secure(getAccessors(Tempo)
|
|
491
|
-
.filter(acc => getType(acc) !== 'Symbol')); // omit any Symbol properties
|
|
492
|
-
}
|
|
493
|
-
/** Tempo global config settings */
|
|
494
|
-
static get config() {
|
|
495
|
-
return secure({ ...Tempo.#global.config });
|
|
496
|
-
}
|
|
497
|
-
/** Tempo initial default settings */
|
|
498
|
-
static get default() {
|
|
499
|
-
return secure(Default);
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* configuration governing the static 'rules' used when parsing Tempo.DateTime argument
|
|
503
|
-
*/
|
|
504
|
-
static get parse() {
|
|
505
|
-
const parse = Tempo.#global.parse;
|
|
506
|
-
return secure({
|
|
507
|
-
...parse,
|
|
508
|
-
snippet: { ...parse.snippet },
|
|
509
|
-
layout: { ...parse.layout },
|
|
510
|
-
event: { ...parse.event },
|
|
511
|
-
period: { ...parse.period },
|
|
512
|
-
// token: { ...parse.token }, // I don't believe the Token needs to be exposed
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
/** iterate over Tempo properties */
|
|
516
|
-
static [Symbol.iterator]() {
|
|
517
|
-
return Tempo.properties[Symbol.iterator](); // static Iterator over array of 'getters'
|
|
518
|
-
}
|
|
519
|
-
// #endregion Static public methods
|
|
520
|
-
// #region Instance symbols~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
521
|
-
/** allow for auto-convert of Tempo to BigInt */
|
|
522
|
-
[Symbol.toPrimitive](hint) {
|
|
523
|
-
Tempo.#dbg.info(this.config, getType(this), '.hint: ', hint);
|
|
524
|
-
return this.nano;
|
|
525
|
-
}
|
|
526
|
-
/** iterate over instance formats */
|
|
527
|
-
[Symbol.iterator]() {
|
|
528
|
-
return ownEntries(this.#fmt)[Symbol.iterator](); // instance Iterator over tuple of FormatType[]
|
|
529
|
-
}
|
|
530
|
-
get [Symbol.toStringTag]() {
|
|
531
|
-
return 'Tempo'; // hard-coded to avoid minification mangling
|
|
532
|
-
}
|
|
533
|
-
// #endregion Instance symbols
|
|
534
|
-
// #region Instance properties~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
535
|
-
/** constructor tempo */ #tempo;
|
|
536
|
-
/** constructor options */ #options = {};
|
|
537
|
-
/** instantiation Temporal Instant */ #now;
|
|
538
|
-
/** underlying Temporal ZonedDateTime */ #zdt;
|
|
539
|
-
/** prebuilt formats, for convenience */ #fmt = {};
|
|
540
|
-
/** instance term plugins */ #term = {};
|
|
541
|
-
/** instance values to complement static values */ #local = {
|
|
542
|
-
/** instance configuration */ config: {},
|
|
543
|
-
/** instance parse rules (only populated when local overrides exist) */ parse: {}
|
|
544
|
-
};
|
|
545
|
-
constructor(tempo, options = {}) {
|
|
546
|
-
this.#now = Temporal.Now.instant(); // stash current Instant
|
|
547
|
-
// swap arguments around, if arg1=Options or Temporal-like
|
|
548
|
-
[this.#tempo, this.#options] = (this.#isOptions(tempo) || this.#isZonedDateTimeLike(tempo))
|
|
549
|
-
? [tempo?.value, tempo]
|
|
550
|
-
: [tempo, { ...options }];
|
|
551
|
-
// parse the local options looking for overrides to Tempo.#global.config
|
|
552
|
-
this.#setLocal(this.#options);
|
|
553
|
-
// we now have all the info we need to instantiate a new Tempo
|
|
554
|
-
try {
|
|
555
|
-
this.#zdt = this.#parse(this.#tempo); // attempt to interpret the DateTime arg
|
|
556
|
-
if (['iso8601', 'gregory'].includes(this.#local.config['calendar'])) {
|
|
557
|
-
const formats = ownEntries(Tempo.FORMAT);
|
|
558
|
-
for (const [key, val] of formats)
|
|
559
|
-
this.#fmt[key] = this.format(val);
|
|
560
|
-
}
|
|
561
|
-
terms // add the plug-in getters for the pre-defined Terms to the instance
|
|
562
|
-
.forEach(({ key, scope, define }) => {
|
|
563
|
-
this.#setTerm(this, key, define, true); // add a getter which returns the key-field only
|
|
564
|
-
this.#setTerm(this, scope, define, false); // add a getter which returns a range-object
|
|
361
|
+
/**
|
|
362
|
+
* configuration governing the static 'rules' used when parsing Tempo.DateTime argument
|
|
363
|
+
*/
|
|
364
|
+
static get parse() {
|
|
365
|
+
const parse = __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).parse;
|
|
366
|
+
return secure({
|
|
367
|
+
...parse,
|
|
368
|
+
snippet: { ...parse.snippet },
|
|
369
|
+
layout: { ...parse.layout },
|
|
370
|
+
event: { ...parse.event },
|
|
371
|
+
period: { ...parse.period },
|
|
372
|
+
// token: { ...parse.token }, // I don't believe the Token needs to be exposed
|
|
565
373
|
});
|
|
566
|
-
if (isDefined(Tempo.#pending)) { // are we mutating with 'set()' ?
|
|
567
|
-
this.#local.parse.result = Tempo.#pending; // stash collected parse-matches
|
|
568
|
-
Tempo.#pending = void 0; // and reset mutating-flag
|
|
569
|
-
}
|
|
570
|
-
secure(this.#fmt); // prevent mutations
|
|
571
|
-
secure(this.#local.config);
|
|
572
|
-
secure(this.#local.parse);
|
|
573
374
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
375
|
+
/** iterate over Tempo properties */
|
|
376
|
+
static [(_Tempo_proto = function _Tempo_proto(obj) { return Object.getPrototypeOf(obj); }, _Tempo_hasOwn = function _Tempo_hasOwn(obj, key) { return Object.hasOwn(obj, key); }, _Tempo_isFrozen = function _Tempo_isFrozen(obj) { return isDefined(obj) && Object.isFrozen(obj); }, _Tempo_isLocal = function _Tempo_isLocal(shape) { return shape.config.scope === 'local'; }, _Tempo_create = function _Tempo_create(obj, name) { return Object.create(__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_proto).call(Tempo, obj)[name]); }, _Tempo_setEvents = function _Tempo_setEvents(shape) {
|
|
377
|
+
const events = ownEntries(shape.parse.event, true);
|
|
378
|
+
if (__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isLocal).call(Tempo, shape) && !__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, 'event') && !__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, 'isMonthDay'))
|
|
379
|
+
return; // no local change needed
|
|
380
|
+
const src = shape.config.scope.substring(0, 1); // 'g'lobal or 'l'ocal
|
|
381
|
+
const groups = events
|
|
382
|
+
.map(([pat, _], idx) => `(?<${src}evt${idx}>${pat})`) // assign a number to the pattern
|
|
383
|
+
.join('|'); // make an 'Or' pattern for the event-keys
|
|
384
|
+
if (groups) {
|
|
385
|
+
const protoEvt = __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_proto).call(Tempo, shape.parse.snippet)[Token.evt]?.source;
|
|
386
|
+
if (!__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isLocal).call(Tempo, shape) || groups !== protoEvt) {
|
|
387
|
+
if (__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isLocal).call(Tempo, shape) && !__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, 'snippet'))
|
|
388
|
+
shape.parse.snippet = __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_create).call(Tempo, shape.parse, 'snippet');
|
|
389
|
+
Object.defineProperty(shape.parse.snippet, Token.evt, {
|
|
390
|
+
value: new RegExp(groups),
|
|
391
|
+
enumerable: true,
|
|
392
|
+
writable: true,
|
|
393
|
+
configurable: true
|
|
593
394
|
});
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (shape.parse.isMonthDay) {
|
|
398
|
+
const protoDt = __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_proto).call(Tempo, shape.parse.layout)[Token.dt];
|
|
399
|
+
const localDt = '{mm}{sep}?{dd}({sep}?{yy})?|{mod}?({evt})';
|
|
400
|
+
if (!__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isLocal).call(Tempo, shape) || localDt !== protoDt) {
|
|
401
|
+
if (__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isLocal).call(Tempo, shape) && !__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, 'layout'))
|
|
402
|
+
shape.parse.layout = __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_create).call(Tempo, shape.parse, 'layout');
|
|
403
|
+
Object.defineProperty(shape.parse.layout, Token.dt, {
|
|
404
|
+
value: localDt,
|
|
599
405
|
enumerable: true,
|
|
406
|
+
writable: true,
|
|
407
|
+
configurable: true
|
|
600
408
|
});
|
|
601
|
-
|
|
602
|
-
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}, _Tempo_setPeriods = function _Tempo_setPeriods(shape) {
|
|
412
|
+
const periods = ownEntries(shape.parse.period, true);
|
|
413
|
+
if (__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isLocal).call(Tempo, shape) && !__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, 'period'))
|
|
414
|
+
return; // no local change needed
|
|
415
|
+
const src = shape.config.scope.substring(0, 1); // 'g'lobal or 'l'ocal
|
|
416
|
+
const groups = periods
|
|
417
|
+
.map(([pat, _], idx) => `(?<${src}per${idx}>${pat})`) // {pattern} is the 1st element of the tuple
|
|
418
|
+
.join('|'); // make an 'or' pattern for the period-keys
|
|
419
|
+
if (groups) {
|
|
420
|
+
const protoPer = __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_proto).call(Tempo, shape.parse.snippet)[Token.per]?.source;
|
|
421
|
+
if (!__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isLocal).call(Tempo, shape) || groups !== protoPer) {
|
|
422
|
+
if (__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isLocal).call(Tempo, shape) && !__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, 'snippet'))
|
|
423
|
+
shape.parse.snippet = __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_create).call(Tempo, shape.parse, 'snippet');
|
|
424
|
+
Object.defineProperty(shape.parse.snippet, Token.per, {
|
|
425
|
+
value: new RegExp(groups),
|
|
426
|
+
enumerable: true,
|
|
427
|
+
writable: true,
|
|
428
|
+
configurable: true
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}, _Tempo_isMonthDay = function _Tempo_isMonthDay(shape) {
|
|
433
|
+
const monthDay = [...asArray(__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).parse.mdyLocales)];
|
|
434
|
+
if (__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isLocal).call(Tempo, shape) && __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, 'mdyLocales'))
|
|
435
|
+
monthDay.push(...shape.parse.mdyLocales); // append local mdyLocales (not overwrite global)
|
|
436
|
+
return monthDay.some(mdy => mdy.timeZones?.includes(shape.config.timeZone));
|
|
437
|
+
}, _Tempo_swapLayout = function _Tempo_swapLayout(shape) {
|
|
438
|
+
const layouts = ownEntries(shape.parse.layout); // get entries of Layout Record
|
|
439
|
+
const swap = shape.parse.mdyLayouts; // get the swap-tuple
|
|
440
|
+
let chg = false; // no need to rebuild, if no change
|
|
441
|
+
swap
|
|
442
|
+
.forEach(([dmy, mdy]) => {
|
|
443
|
+
const idx1 = layouts.findIndex(([key]) => key.description === dmy); // 1st swap element exists in {layouts}
|
|
444
|
+
const idx2 = layouts.findIndex(([key]) => key.description === mdy); // 2nd swap element exists in {layouts}
|
|
445
|
+
if (idx1 === -1 || idx2 === -1)
|
|
446
|
+
return; // no pair to swap
|
|
447
|
+
const swap1 = (idx1 < idx2) && shape.parse.isMonthDay; // we prefer {mdy} and the 1st tuple was found earlier than the 2nd
|
|
448
|
+
const swap2 = (idx1 > idx2) && !shape.parse.isMonthDay; // we dont prefer {mdy} and the 1st tuple was found later than the 2nd
|
|
449
|
+
if (swap1 || swap2) { // since {layouts} is an array, ok to swap by-reference
|
|
450
|
+
[layouts[idx1], layouts[idx2]] = [layouts[idx2], layouts[idx1]];
|
|
451
|
+
chg = true;
|
|
603
452
|
}
|
|
604
453
|
});
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
/** Full month name (e.g., 'January') */ get mon() { return Tempo.MONTHS.keyOf(this.#zdt.month); }
|
|
625
|
-
/** Short weekday name (e.g., 'Mon') */ get www() { return Tempo.WEEKDAY.keyOf(this.#zdt.dayOfWeek); }
|
|
626
|
-
/** Full weekday name (e.g., 'Monday') */ get wkd() { return Tempo.WEEKDAYS.keyOf(this.#zdt.dayOfWeek); }
|
|
627
|
-
/** ISO weekday number: Mon=1, Sun=7 */ get dow() { return this.#zdt.dayOfWeek; }
|
|
628
|
-
/** Nanoseconds since Unix epoch (BigInt) */ get nano() { return this.#zdt.epochNanoseconds; }
|
|
629
|
-
/** Instance-specific configuration settings */ get config() { return this.#local.config; }
|
|
630
|
-
/** Instance-specific parse rules (merged with global) */ get parse() { return this.#local.parse; }
|
|
631
|
-
/** Object containing results from all term plugins */ get term() { return this.#term; }
|
|
632
|
-
/** Formatted results for all pre-defined format codes */ get fmt() { return this.#fmt; }
|
|
633
|
-
/** units since epoch */ get epoch() {
|
|
634
|
-
return secure({
|
|
635
|
-
/** seconds since epoch */ ss: Math.trunc(this.#zdt.epochMilliseconds / 1_000),
|
|
636
|
-
/** milliseconds since epoch */ ms: this.#zdt.epochMilliseconds,
|
|
637
|
-
/** microseconds since epoch */ us: Number(this.#zdt.epochNanoseconds / BigInt(1_000)),
|
|
638
|
-
/** nanoseconds since epoch */ ns: this.#zdt.epochNanoseconds,
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
/** time duration until another date-time */ until(optsOrDate, optsOrUntil) { return this.#until(optsOrDate, optsOrUntil); }
|
|
642
|
-
/** time elapsed since another date-time */ since(optsOrDate, optsOrUntil) { return this.#since(optsOrDate, optsOrUntil); }
|
|
643
|
-
/** applies a format to the current `Tempo` instance. */ format(fmt) { return this.#format(fmt); }
|
|
644
|
-
/** returns a new `Tempo` with specific duration added. */ add(mutate) { return this.#add(mutate); }
|
|
645
|
-
/** returns a new `Tempo` with specific offsets. */ set(offset) { return this.#set(offset); }
|
|
646
|
-
/** `true` if the underlying date-time is valid. */ isValid() { return !isEmpty(this); }
|
|
647
|
-
/** the underlying `Temporal.ZonedDateTime` object. */ toDateTime() { return this.#zdt; }
|
|
648
|
-
/** the date-time as a `Temporal.Instant`. */ toInstant() { return this.#now; }
|
|
649
|
-
/** the date-time as a standard `Date` object. */ toDate() { return new Date(this.#zdt.round({ smallestUnit: 'millisecond' }).epochMilliseconds); }
|
|
650
|
-
/** the ISO8601 string representation of the date-time. */ toString() { return this.#zdt.toString(); }
|
|
651
|
-
/** Custom JSON serialization for `JSON.stringify`. */ toJSON() { return { ...this.#local.config, value: this.toString() }; }
|
|
652
|
-
// #endregion Instance public methods
|
|
653
|
-
// #region Instance private methods~~~~~~~~~~~~~~~~~~~~~~~
|
|
654
|
-
/**
|
|
655
|
-
* setup local 'config' and 'parse' rules (with prototype set to global)
|
|
656
|
-
* we copy down the current global config to the local instance, then apply any options provided.
|
|
657
|
-
* in this way, we preserve immutability of this instance, in case the user later changes the global config.
|
|
658
|
-
*
|
|
659
|
-
* we do not copy down the current global parse rules, but instead create a new parse object
|
|
660
|
-
* that prototypes the global parse object. this way, we can add new parse rules to the local
|
|
661
|
-
* parse object without affecting the global parse object.
|
|
662
|
-
*/
|
|
663
|
-
#setLocal(options) {
|
|
664
|
-
// copy down current global config to local instance
|
|
665
|
-
this.#local.config = Object.create(Tempo.#global.config); // set prototype-;ink to global config
|
|
666
|
-
const { mdyLocales, mdyLayouts, ...config } = Tempo.#global.config;
|
|
667
|
-
Object.assign(this.#local.config, config, { level: 'local' });
|
|
668
|
-
// setup effective parse rules for this instance (prototype-link to global)
|
|
669
|
-
this.#local.parse = Object.create(Tempo.#global.parse); // set prototype to global parse
|
|
670
|
-
this.#local.parse.result = []; // start with empty result
|
|
671
|
-
Tempo.#setConfig(this.#local, options); // set #local config
|
|
672
|
-
}
|
|
673
|
-
/** parse DateTime input */
|
|
674
|
-
#parse(tempo, dateTime) {
|
|
675
|
-
const timeZone = this.#local.config['timeZone'];
|
|
676
|
-
const calendar = this.#local.config['calendar'];
|
|
677
|
-
const today = dateTime ?? this.#now // use provided ZonedDateTime, else cast instantiation to current timeZone
|
|
678
|
-
.toZonedDateTimeISO(timeZone);
|
|
679
|
-
const { type, value } = this.#conform(tempo, today); // if String or Number, conform the input against known patterns
|
|
680
|
-
if (isEmpty(this.#local.parse.result)) // #conform() didn't find any matches
|
|
681
|
-
this.#local.parse.result = [{ type, value }];
|
|
682
|
-
Tempo.#dbg.info(this.#local.config, 'parse', `{type: ${type}, value: ${value}}`); // show what we're parsing
|
|
683
|
-
switch (type) {
|
|
684
|
-
case 'Null':
|
|
685
|
-
case 'Void':
|
|
686
|
-
case 'Empty':
|
|
687
|
-
case 'Undefined':
|
|
688
|
-
return today;
|
|
689
|
-
case 'String': // String which didn't conform to a Tempo.pattern
|
|
690
|
-
case 'Temporal.ZonedDateTime':
|
|
691
|
-
try {
|
|
692
|
-
return Temporal.ZonedDateTime.from(value); // see if Temporal can parse value
|
|
454
|
+
if (chg)
|
|
455
|
+
shape.parse.layout = Object.fromEntries(layouts); // rebuild Layout in new parse order
|
|
456
|
+
}, _Tempo_setConfig = function _Tempo_setConfig(shape, ...options) {
|
|
457
|
+
/** helper to normalize snippet/layout Options into the target Config */
|
|
458
|
+
const collect = (target, value, convert) => {
|
|
459
|
+
const itm = asType(value);
|
|
460
|
+
target ??= {};
|
|
461
|
+
switch (itm.type) {
|
|
462
|
+
case 'Object':
|
|
463
|
+
ownEntries(itm.value)
|
|
464
|
+
.forEach(([k, v]) => target[Tempo.getSymbol(k)] = convert(v));
|
|
465
|
+
break;
|
|
466
|
+
case 'String':
|
|
467
|
+
case 'RegExp':
|
|
468
|
+
target[Tempo.getSymbol()] = convert(itm.value);
|
|
469
|
+
break;
|
|
470
|
+
case 'Array':
|
|
471
|
+
itm.value.forEach(elm => collect(target, elm, convert));
|
|
472
|
+
break;
|
|
693
473
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
474
|
+
};
|
|
475
|
+
const mergedOptions = Object.assign({}, ...options);
|
|
476
|
+
ownEntries(mergedOptions)
|
|
477
|
+
.forEach(([optKey, optVal]) => {
|
|
478
|
+
if (isUndefined(optVal))
|
|
479
|
+
return; // skip undefined values
|
|
480
|
+
const arg = asType(optVal);
|
|
481
|
+
switch (optKey) {
|
|
482
|
+
case 'snippet':
|
|
483
|
+
case 'layout':
|
|
484
|
+
case 'event':
|
|
485
|
+
case 'period':
|
|
486
|
+
// lazy-shadowing: only create local object if it doesn't already exist on local shape
|
|
487
|
+
if (!__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, optKey))
|
|
488
|
+
shape.parse[optKey] = __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_create).call(Tempo, shape.parse, optKey);
|
|
489
|
+
const rule = shape.parse[optKey];
|
|
490
|
+
if (['snippet', 'layout'].includes(optKey)) {
|
|
491
|
+
collect(rule, arg.value, v => optKey === 'snippet'
|
|
492
|
+
? isRegExp(v) ? v : new RegExp(v)
|
|
493
|
+
: isRegExp(v) ? v.source : v);
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
asArray(arg.value)
|
|
497
|
+
.forEach(elm => ownEntries(elm).forEach(([key, val]) => rule[key] = val));
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
case 'mdyLocales':
|
|
501
|
+
shape.parse[optKey] = __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_mdyLocales).call(Tempo, arg.value);
|
|
502
|
+
break;
|
|
503
|
+
case 'mdyLayouts': // these are the 'layouts' that need to swap parse-order
|
|
504
|
+
shape.parse[optKey] = asArray(arg.value);
|
|
505
|
+
break;
|
|
506
|
+
case 'timeZone':
|
|
507
|
+
const zone = String(arg.value).toLowerCase();
|
|
508
|
+
shape.config.timeZone = TimeZone[zone] ?? arg.value;
|
|
509
|
+
break;
|
|
510
|
+
default: // else just add to config
|
|
511
|
+
Object.assign(shape.config, { [optKey]: optVal });
|
|
512
|
+
break;
|
|
701
513
|
}
|
|
702
|
-
case 'Temporal.PlainDate':
|
|
703
|
-
case 'Temporal.PlainDateTime':
|
|
704
|
-
return value
|
|
705
|
-
.toZonedDateTime(timeZone)
|
|
706
|
-
.withCalendar(calendar);
|
|
707
|
-
case 'Temporal.PlainTime':
|
|
708
|
-
return today.withPlainTime(value);
|
|
709
|
-
case 'Temporal.PlainYearMonth': // assume current day, else end-of-month
|
|
710
|
-
return value
|
|
711
|
-
.toPlainDate({ day: Math.min(today.day, value.daysInMonth) })
|
|
712
|
-
.toZonedDateTime(timeZone)
|
|
713
|
-
.withCalendar(calendar);
|
|
714
|
-
case 'Temporal.PlainMonthDay': // assume current year
|
|
715
|
-
return value
|
|
716
|
-
.toPlainDate({ year: today.year })
|
|
717
|
-
.toZonedDateTime(timeZone)
|
|
718
|
-
.withCalendar(calendar);
|
|
719
|
-
case 'Temporal.Instant':
|
|
720
|
-
return value
|
|
721
|
-
.toZonedDateTimeISO(timeZone)
|
|
722
|
-
.withCalendar(calendar);
|
|
723
|
-
case 'Tempo':
|
|
724
|
-
return value
|
|
725
|
-
.toDateTime(); // clone provided Tempo
|
|
726
|
-
case 'Date':
|
|
727
|
-
return new Temporal.ZonedDateTime(BigInt(value.getTime() * 1_000_000), timeZone, calendar);
|
|
728
|
-
case 'Number': // Number which didn't conform to a Tempo.pattern
|
|
729
|
-
const [seconds = BigInt(0), suffix = BigInt(0)] = value.toString().split('.').map(BigInt);
|
|
730
|
-
const nano = BigInt(suffix.toString().substring(0, 9).padEnd(9, '0'));
|
|
731
|
-
return new Temporal.ZonedDateTime(seconds * BigInt(1_000_000_000) + nano, timeZone, calendar);
|
|
732
|
-
case 'BigInt': // BigInt is not conformed against a Tempo.pattern
|
|
733
|
-
return new Temporal.ZonedDateTime(value, timeZone, calendar);
|
|
734
|
-
default:
|
|
735
|
-
Tempo.#dbg.catch(this.#local.config, `Unexpected Tempo parameter type: ${type}, ${String(value)}`);
|
|
736
|
-
return today;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
/** check if we've been given a Tempo Options object */
|
|
740
|
-
#isOptions(arg) {
|
|
741
|
-
return isObject(arg) && ownKeys(arg)
|
|
742
|
-
.some(key => ['snippet', 'layout', 'event', 'period', 'mdyLocales', 'mdyLayouts', 'debug', 'catch', 'store', 'pivot'].includes(key));
|
|
743
|
-
}
|
|
744
|
-
/** check if we've been given a ZonedDateTimeLike object */
|
|
745
|
-
#isZonedDateTimeLike(tempo) {
|
|
746
|
-
if (!isObject(tempo) || isEmpty(tempo))
|
|
747
|
-
return false;
|
|
748
|
-
// if it contains any 'options' keys, it's not a ZonedDateTime
|
|
749
|
-
const keys = ownKeys(tempo);
|
|
750
|
-
if (keys.some(key => ['snippet', 'layout', 'event', 'period', 'mdyLocales', 'mdyLayouts', 'debug', 'catch', 'store', 'pivot'].includes(key)))
|
|
751
|
-
return false;
|
|
752
|
-
// we include {value} to allow for Tempo instances
|
|
753
|
-
return keys
|
|
754
|
-
.filter(isString)
|
|
755
|
-
.every(key => ['value', 'timeZoneId', 'calendarId', 'year', 'month', 'monthCode', 'day', 'hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond', 'offset', 'timeZone'].includes(key)); // if every key in tempo-object is included in this array
|
|
756
|
-
}
|
|
757
|
-
/** trace the initial instance pattern-match */
|
|
758
|
-
#result(base, ...rest) {
|
|
759
|
-
(Tempo.#pending ?? this.#local.parse.result)
|
|
760
|
-
.push(Object.assign({}, base, ...rest));
|
|
761
|
-
}
|
|
762
|
-
/** evaluate 'string | number' input against known patterns */
|
|
763
|
-
#conform(tempo, dateTime) {
|
|
764
|
-
const arg = asType(tempo);
|
|
765
|
-
if (this.#isZonedDateTimeLike(tempo)) { // tempo is ZonedDateTime-ish object (throw away 'value' property)
|
|
766
|
-
const { timeZone, calendar, value, ...options } = tempo;
|
|
767
|
-
let zdt = !isEmpty(options)
|
|
768
|
-
? dateTime.with({ ...options })
|
|
769
|
-
: dateTime;
|
|
770
|
-
if (timeZone)
|
|
771
|
-
zdt = zdt.withTimeZone(timeZone); // optionally set timeZone
|
|
772
|
-
if (calendar)
|
|
773
|
-
zdt = zdt.withCalendar(calendar); // optionally set calendar
|
|
774
|
-
this.#result({ type: 'Temporal.ZonedDateTimeLike', value: zdt, match: 'Temporal.ZonedDateTimeLike' });
|
|
775
|
-
return Object.assign(arg, {
|
|
776
|
-
type: 'Temporal.ZonedDateTime', // override {arg.type}
|
|
777
|
-
value: zdt,
|
|
778
514
|
});
|
|
515
|
+
const isMonthDay = __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isMonthDay).call(Tempo, shape);
|
|
516
|
+
if (isMonthDay !== __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_proto).call(Tempo, shape.parse).isMonthDay) // this will always set on 'global', conditionally on 'local'
|
|
517
|
+
shape.parse.isMonthDay = isMonthDay;
|
|
518
|
+
shape.config.sphere = __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_setSphere).call(Tempo, shape, mergedOptions);
|
|
519
|
+
if (isDefined(shape.parse.mdyLayouts))
|
|
520
|
+
__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_swapLayout).call(Tempo, shape);
|
|
521
|
+
if (isDefined(shape.parse.event))
|
|
522
|
+
__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_setEvents).call(Tempo, shape);
|
|
523
|
+
if (isDefined(shape.parse.period))
|
|
524
|
+
__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_setPeriods).call(Tempo, shape);
|
|
525
|
+
__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_setPatterns).call(Tempo, shape); // setup Regex DateTime patterns
|
|
526
|
+
}, _Tempo_mdyLocales = function _Tempo_mdyLocales(value) {
|
|
527
|
+
return asArray(value)
|
|
528
|
+
.map(mdy => new Intl.Locale(mdy))
|
|
529
|
+
.map(mdy => ({ locale: mdy.baseName, timeZones: mdy.getTimeZones?.() ?? [] }));
|
|
530
|
+
}, _Tempo_setPatterns = function _Tempo_setPatterns(shape) {
|
|
531
|
+
const snippet = shape.parse.snippet;
|
|
532
|
+
// if local and no snippet or layout overrides, we can just use the prototype's patterns
|
|
533
|
+
if (__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_isLocal).call(Tempo, shape) && !__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, 'snippet') && !__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, 'layout'))
|
|
534
|
+
return;
|
|
535
|
+
// ensure we have our own Map to mutate (shadow if local)
|
|
536
|
+
if (!__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, shape.parse, 'pattern'))
|
|
537
|
+
shape.parse.pattern = new Map();
|
|
538
|
+
shape.parse.pattern.clear(); // reset {pattern} Map
|
|
539
|
+
for (const [sym, layout] of ownEntries(shape.parse.layout, true))
|
|
540
|
+
shape.parse.pattern.set(sym, Tempo.regexp(layout, snippet));
|
|
541
|
+
}, Symbol.iterator)]() {
|
|
542
|
+
return Tempo.properties[Symbol.iterator](); // static Iterator over array of 'getters'
|
|
543
|
+
}
|
|
544
|
+
// #endregion Static public methods
|
|
545
|
+
// #region Instance symbols~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
546
|
+
/** allow for auto-convert of Tempo to BigInt */
|
|
547
|
+
[Symbol.toPrimitive](hint) {
|
|
548
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).info(this.config, getType(this), '.hint: ', hint);
|
|
549
|
+
return this.nano;
|
|
550
|
+
}
|
|
551
|
+
/** iterate over instance formats */
|
|
552
|
+
[Symbol.iterator]() {
|
|
553
|
+
return ownEntries(this.#fmt)[Symbol.iterator](); // instance Iterator over tuple of FormatType[]
|
|
554
|
+
}
|
|
555
|
+
get [Symbol.toStringTag]() {
|
|
556
|
+
return 'Tempo'; // hard-coded to avoid minification mangling
|
|
779
557
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
558
|
+
// #endregion Instance symbols
|
|
559
|
+
// #region Instance properties~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
560
|
+
/** constructor tempo */ #tempo;
|
|
561
|
+
/** constructor options */ #options = {};
|
|
562
|
+
/** instantiation Temporal Instant */ #now;
|
|
563
|
+
/** underlying Temporal ZonedDateTime */ #zdt;
|
|
564
|
+
/** prebuilt formats, for convenience */ #fmt = {};
|
|
565
|
+
/** instance term plugins */ #term = {};
|
|
566
|
+
/** instance values to complement static values */ #local = {
|
|
567
|
+
/** instance configuration */ config: {},
|
|
568
|
+
/** instance parse rules (only populated when local overrides exist) */ parse: {}
|
|
569
|
+
};
|
|
570
|
+
constructor(tempo, options = {}) {
|
|
571
|
+
this.#now = Temporal.Now.instant(); // stash current Instant
|
|
572
|
+
// swap arguments around, if arg1=Options or Temporal-like
|
|
573
|
+
[this.#tempo, this.#options] = (this.#isOptions(tempo) || this.#isZonedDateTimeLike(tempo))
|
|
574
|
+
? [tempo?.value, tempo]
|
|
575
|
+
: [tempo, { ...options }];
|
|
576
|
+
// parse the local options looking for overrides to Tempo.#global.config
|
|
577
|
+
this.#setLocal(this.#options);
|
|
578
|
+
// we now have all the info we need to instantiate a new Tempo
|
|
579
|
+
try {
|
|
580
|
+
this.#zdt = this.#parse(this.#tempo); // attempt to interpret the DateTime arg
|
|
581
|
+
if (['iso8601', 'gregory'].includes(this.#local.config['calendar'])) {
|
|
582
|
+
const formats = ownEntries(Tempo.FORMAT);
|
|
583
|
+
for (const [key, val] of formats)
|
|
584
|
+
this.#fmt[key] = this.format(val);
|
|
585
|
+
}
|
|
586
|
+
terms // add the plug-in getters for the pre-defined Terms to the instance
|
|
587
|
+
.forEach(({ key, scope, define }) => {
|
|
588
|
+
this.#setTerm(this, key, define, true); // add a getter which returns the key-field only
|
|
589
|
+
this.#setTerm(this, scope, define, false); // add a getter which returns a range-object
|
|
590
|
+
});
|
|
591
|
+
if (isDefined(__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_pending))) { // are we mutating with 'set()' ?
|
|
592
|
+
this.#local.parse.result = __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_pending); // stash collected parse-matches
|
|
593
|
+
__classPrivateFieldSet(Tempo, _classThis, void 0, "f", _Tempo_pending); // and reset mutating-flag
|
|
594
|
+
}
|
|
595
|
+
secure(this.#fmt); // prevent mutations
|
|
596
|
+
secure(this.#local.config);
|
|
597
|
+
secure(this.#local.parse);
|
|
787
598
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
return
|
|
599
|
+
catch (err) {
|
|
600
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).catch(this.config, `Cannot create Tempo: ${err.message}\n${err.stack}`);
|
|
601
|
+
return {}; // return empty Object
|
|
791
602
|
}
|
|
792
603
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
604
|
+
// This function has be defined within the Tempo class (and not imported from another module) because it references a private-variable
|
|
605
|
+
/** this will add the self-updating {getter} on the Tempo.term object */
|
|
606
|
+
#setTerm(self, name, define, isKeyOnly) {
|
|
607
|
+
if (isDefined(name) && isDefined(define)) {
|
|
608
|
+
Object.defineProperty(self.#term, name, {
|
|
609
|
+
configurable: false,
|
|
610
|
+
enumerable: false,
|
|
611
|
+
get: function () {
|
|
612
|
+
const props = Object.getOwnPropertyDescriptors(self.#term);
|
|
613
|
+
self.#term = {}; // wipe down the 'term'
|
|
614
|
+
ownEntries(props)
|
|
615
|
+
.forEach(([prop, desc]) => {
|
|
616
|
+
if (prop !== name) // except the current one
|
|
617
|
+
Object.defineProperty(self.#term, prop, desc);
|
|
618
|
+
});
|
|
619
|
+
const value = define.call(self, isKeyOnly); // evaluate the term range-lookup
|
|
620
|
+
Object.defineProperty(self.#term, name, {
|
|
621
|
+
value,
|
|
622
|
+
configurable: false,
|
|
623
|
+
writable: false,
|
|
624
|
+
enumerable: true,
|
|
625
|
+
});
|
|
626
|
+
secure(self.#term);
|
|
627
|
+
return secure(value);
|
|
628
|
+
}
|
|
629
|
+
});
|
|
797
630
|
}
|
|
798
631
|
}
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
632
|
+
// #endregion Constructor
|
|
633
|
+
// #region Instance public accessors~~~~~~~~~~~~~~~~~~~~~~
|
|
634
|
+
/** 4-digit year (e.g., 2024) */ get yy() { return this.#zdt.year; }
|
|
635
|
+
/** Month number: Jan=1, Dec=12 */ get mm() { return this.#zdt.month; }
|
|
636
|
+
/** ISO week number of the year */ get ww() { return this.#zdt.weekOfYear; }
|
|
637
|
+
/** Day of the month (1-31) */ get dd() { return this.#zdt.day; }
|
|
638
|
+
/** Day of the month (alias for `dd`) */ get day() { return this.#zdt.day; }
|
|
639
|
+
/** Hour of the day (0-23) */ get hh() { return this.#zdt.hour; }
|
|
640
|
+
/** Minutes of the hour (0-59) */ get mi() { return this.#zdt.minute; }
|
|
641
|
+
/** Seconds of the minute (0-59) */ get ss() { return this.#zdt.second; }
|
|
642
|
+
/** Milliseconds of the second (0-999) */ get ms() { return this.#zdt.millisecond; }
|
|
643
|
+
/** Microseconds of the millisecond (0-999) */ get us() { return this.#zdt.microsecond; }
|
|
644
|
+
/** Nanoseconds of the microsecond (0-999) */ get ns() { return this.#zdt.nanosecond; }
|
|
645
|
+
/** Fractional seconds (e.g., 0.123456789) */ get ff() { return +(`0.${pad(this.ms, 3)}${pad(this.us, 3)}${pad(this.ns, 3)}`); }
|
|
646
|
+
/** IANA Time Zone ID (e.g., 'Australia/Sydney') */ get tz() { return this.#zdt.timeZoneId; }
|
|
647
|
+
/** Unix timestamp (defaults to milliseconds) */ get ts() { return this.epoch[this.#local.config['timeStamp']]; }
|
|
648
|
+
/** Short month name (e.g., 'Jan') */ get mmm() { return Tempo.MONTH.keyOf(this.#zdt.month); }
|
|
649
|
+
/** Full month name (e.g., 'January') */ get mon() { return Tempo.MONTHS.keyOf(this.#zdt.month); }
|
|
650
|
+
/** Short weekday name (e.g., 'Mon') */ get www() { return Tempo.WEEKDAY.keyOf(this.#zdt.dayOfWeek); }
|
|
651
|
+
/** Full weekday name (e.g., 'Monday') */ get wkd() { return Tempo.WEEKDAYS.keyOf(this.#zdt.dayOfWeek); }
|
|
652
|
+
/** ISO weekday number: Mon=1, Sun=7 */ get dow() { return this.#zdt.dayOfWeek; }
|
|
653
|
+
/** Nanoseconds since Unix epoch (BigInt) */ get nano() { return this.#zdt.epochNanoseconds; }
|
|
654
|
+
/** Instance-specific configuration settings */ get config() { return this.#local.config; }
|
|
655
|
+
/** Instance-specific parse rules (merged with global) */ get parse() { return this.#local.parse; }
|
|
656
|
+
/** Object containing results from all term plugins */ get term() { return this.#term; }
|
|
657
|
+
/** Formatted results for all pre-defined format codes */ get fmt() { return this.#fmt; }
|
|
658
|
+
/** units since epoch */ get epoch() {
|
|
659
|
+
return secure({
|
|
660
|
+
/** seconds since epoch */ ss: Math.trunc(this.#zdt.epochMilliseconds / 1_000),
|
|
661
|
+
/** milliseconds since epoch */ ms: this.#zdt.epochMilliseconds,
|
|
662
|
+
/** microseconds since epoch */ us: Number(this.#zdt.epochNanoseconds / BigInt(1_000)),
|
|
663
|
+
/** nanoseconds since epoch */ ns: this.#zdt.epochNanoseconds,
|
|
664
|
+
});
|
|
819
665
|
}
|
|
820
|
-
return
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
return
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
666
|
+
/** time duration until another date-time */ until(optsOrDate, optsOrUntil) { return this.#until(optsOrDate, optsOrUntil); }
|
|
667
|
+
/** time elapsed since another date-time */ since(optsOrDate, optsOrUntil) { return this.#since(optsOrDate, optsOrUntil); }
|
|
668
|
+
/** applies a format to the current `Tempo` instance. */ format(fmt) { return this.#format(fmt); }
|
|
669
|
+
/** returns a new `Tempo` with specific duration added. */ add(mutate) { return this.#add(mutate); }
|
|
670
|
+
/** returns a new `Tempo` with specific offsets. */ set(offset) { return this.#set(offset); }
|
|
671
|
+
/** `true` if the underlying date-time is valid. */ isValid() { return !isEmpty(this); }
|
|
672
|
+
/** the underlying `Temporal.ZonedDateTime` object. */ toDateTime() { return this.#zdt; }
|
|
673
|
+
/** the date-time as a `Temporal.Instant`. */ toInstant() { return this.#now; }
|
|
674
|
+
/** the date-time as a standard `Date` object. */ toDate() { return new Date(this.#zdt.round({ smallestUnit: 'millisecond' }).epochMilliseconds); }
|
|
675
|
+
/** the ISO8601 string representation of the date-time. */ toString() { return this.#zdt.toString(); }
|
|
676
|
+
/** Custom JSON serialization for `JSON.stringify`. */ toJSON() { return { ...this.#local.config, value: this.toString() }; }
|
|
677
|
+
// #endregion Instance public methods
|
|
678
|
+
// #region Instance private methods~~~~~~~~~~~~~~~~~~~~~~~
|
|
679
|
+
/**
|
|
680
|
+
* setup local 'config' and 'parse' rules (with prototype set to global)
|
|
681
|
+
* we copy down the current global config to the local instance, then apply any options provided.
|
|
682
|
+
* in this way, we preserve immutability of this instance, in case the user later changes the global config.
|
|
683
|
+
*
|
|
684
|
+
* we do not copy down the current global parse rules, but instead create a new parse object
|
|
685
|
+
* that prototypes the global parse object. this way, we can add new parse rules to the local
|
|
686
|
+
* parse object without affecting the global parse object.
|
|
687
|
+
*/
|
|
688
|
+
#setLocal(options) {
|
|
689
|
+
// copy down current global config to local instance
|
|
690
|
+
this.#local.config = Object.create(__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).config); // set prototype-;ink to global config
|
|
691
|
+
const { mdyLocales, mdyLayouts, ...config } = __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).config;
|
|
692
|
+
Object.assign(this.#local.config, config, { level: 'local' });
|
|
693
|
+
// setup effective parse rules for this instance (prototype-link to global)
|
|
694
|
+
this.#local.parse = Object.create(__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).parse); // set prototype to global parse
|
|
695
|
+
this.#local.parse.result = []; // start with empty result
|
|
696
|
+
__classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_setConfig).call(Tempo, this.#local, options); // set #local config
|
|
847
697
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
const
|
|
852
|
-
const
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
698
|
+
/** parse DateTime input */
|
|
699
|
+
#parse(tempo, dateTime) {
|
|
700
|
+
const timeZone = this.#local.config['timeZone'];
|
|
701
|
+
const calendar = this.#local.config['calendar'];
|
|
702
|
+
const today = dateTime ?? this.#now // use provided ZonedDateTime, else cast instantiation to current timeZone
|
|
703
|
+
.toZonedDateTimeISO(timeZone);
|
|
704
|
+
const { type, value } = this.#conform(tempo, today); // if String or Number, conform the input against known patterns
|
|
705
|
+
if (isEmpty(this.#local.parse.result)) // #conform() didn't find any matches
|
|
706
|
+
this.#local.parse.result = [{ type, value }];
|
|
707
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).info(this.#local.config, 'parse', `{type: ${type}, value: ${value}}`); // show what we're parsing
|
|
708
|
+
switch (type) {
|
|
709
|
+
case 'Null':
|
|
710
|
+
case 'Void':
|
|
711
|
+
case 'Empty':
|
|
712
|
+
case 'Undefined':
|
|
713
|
+
return today;
|
|
714
|
+
case 'String': // String which didn't conform to a Tempo.pattern
|
|
715
|
+
case 'Temporal.ZonedDateTime':
|
|
716
|
+
try {
|
|
717
|
+
return Temporal.ZonedDateTime.from(value); // see if Temporal can parse value
|
|
718
|
+
}
|
|
719
|
+
catch { // else see if Date.parse can parse value
|
|
720
|
+
const fallback = { match: 'Date.parse' };
|
|
721
|
+
this.#result({ type, value }, fallback);
|
|
722
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).warn(this.#local.config, 'Cannot detect DateTime; fallback to Date.parse');
|
|
723
|
+
return Temporal.ZonedDateTime
|
|
724
|
+
.from(`${new Date(value.toString()).toISOString()}[${timeZone}]`)
|
|
725
|
+
.withCalendar(calendar);
|
|
726
|
+
}
|
|
727
|
+
case 'Temporal.PlainDate':
|
|
728
|
+
case 'Temporal.PlainDateTime':
|
|
729
|
+
return value
|
|
730
|
+
.toZonedDateTime(timeZone)
|
|
731
|
+
.withCalendar(calendar);
|
|
732
|
+
case 'Temporal.PlainTime':
|
|
733
|
+
return today.withPlainTime(value);
|
|
734
|
+
case 'Temporal.PlainYearMonth': // assume current day, else end-of-month
|
|
735
|
+
return value
|
|
736
|
+
.toPlainDate({ day: Math.min(today.day, value.daysInMonth) })
|
|
737
|
+
.toZonedDateTime(timeZone)
|
|
738
|
+
.withCalendar(calendar);
|
|
739
|
+
case 'Temporal.PlainMonthDay': // assume current year
|
|
740
|
+
return value
|
|
741
|
+
.toPlainDate({ year: today.year })
|
|
742
|
+
.toZonedDateTime(timeZone)
|
|
743
|
+
.withCalendar(calendar);
|
|
744
|
+
case 'Temporal.Instant':
|
|
745
|
+
return value
|
|
746
|
+
.toZonedDateTimeISO(timeZone)
|
|
747
|
+
.withCalendar(calendar);
|
|
748
|
+
case 'Tempo':
|
|
749
|
+
return value
|
|
750
|
+
.toDateTime(); // clone provided Tempo
|
|
751
|
+
case 'Date':
|
|
752
|
+
return new Temporal.ZonedDateTime(BigInt(value.getTime() * 1_000_000), timeZone, calendar);
|
|
753
|
+
case 'Number': // Number which didn't conform to a Tempo.pattern
|
|
754
|
+
const [seconds = BigInt(0), suffix = BigInt(0)] = value.toString().split('.').map(BigInt);
|
|
755
|
+
const nano = BigInt(suffix.toString().substring(0, 9).padEnd(9, '0'));
|
|
756
|
+
return new Temporal.ZonedDateTime(seconds * BigInt(1_000_000_000) + nano, timeZone, calendar);
|
|
757
|
+
case 'BigInt': // BigInt is not conformed against a Tempo.pattern
|
|
758
|
+
return new Temporal.ZonedDateTime(value, timeZone, calendar);
|
|
759
|
+
default:
|
|
760
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).catch(this.#local.config, `Unexpected Tempo parameter type: ${type}, ${String(value)}`);
|
|
761
|
+
return today;
|
|
762
|
+
}
|
|
858
763
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
.findIndex(el => el === mm) // resolve month-name into a month-number
|
|
864
|
-
.toString() // (some browsers do not allow month-names when parsing a Date)
|
|
865
|
-
.padStart(2, '0');
|
|
764
|
+
/** check if we've been given a Tempo Options object */
|
|
765
|
+
#isOptions(arg) {
|
|
766
|
+
return isObject(arg) && ownKeys(arg)
|
|
767
|
+
.some(key => ['snippet', 'layout', 'event', 'period', 'mdyLocales', 'mdyLayouts', 'debug', 'catch', 'store', 'pivot'].includes(key));
|
|
866
768
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
769
|
+
/** check if we've been given a ZonedDateTimeLike object */
|
|
770
|
+
#isZonedDateTimeLike(tempo) {
|
|
771
|
+
if (!isObject(tempo) || isEmpty(tempo))
|
|
772
|
+
return false;
|
|
773
|
+
// if it contains any 'options' keys, it's not a ZonedDateTime
|
|
774
|
+
const keys = ownKeys(tempo);
|
|
775
|
+
if (keys.some(key => ['snippet', 'layout', 'event', 'period', 'mdyLocales', 'mdyLayouts', 'debug', 'catch', 'store', 'pivot'].includes(key)))
|
|
776
|
+
return false;
|
|
777
|
+
// we include {value} to allow for Tempo instances
|
|
778
|
+
return keys
|
|
779
|
+
.filter(isString)
|
|
780
|
+
.every(key => ['value', 'timeZoneId', 'calendarId', 'year', 'month', 'monthCode', 'day', 'hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond', 'offset', 'timeZone'].includes(key)); // if every key in tempo-object is included in this array
|
|
878
781
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
return
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
782
|
+
/** trace the initial instance pattern-match */
|
|
783
|
+
#result(base, ...rest) {
|
|
784
|
+
(__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_pending) ?? this.#local.parse.result)
|
|
785
|
+
.push(Object.assign({}, base, ...rest));
|
|
786
|
+
}
|
|
787
|
+
/** evaluate 'string | number' input against known patterns */
|
|
788
|
+
#conform(tempo, dateTime) {
|
|
789
|
+
const arg = asType(tempo);
|
|
790
|
+
if (this.#isZonedDateTimeLike(tempo)) { // tempo is ZonedDateTime-ish object (throw away 'value' property)
|
|
791
|
+
const { timeZone, calendar, value, ...options } = tempo;
|
|
792
|
+
let zdt = !isEmpty(options)
|
|
793
|
+
? dateTime.with({ ...options })
|
|
794
|
+
: dateTime;
|
|
795
|
+
if (timeZone)
|
|
796
|
+
zdt = zdt.withTimeZone(timeZone); // optionally set timeZone
|
|
797
|
+
if (calendar)
|
|
798
|
+
zdt = zdt.withCalendar(calendar); // optionally set calendar
|
|
799
|
+
this.#result({ type: 'Temporal.ZonedDateTimeLike', value: zdt, match: 'Temporal.ZonedDateTimeLike' });
|
|
800
|
+
return Object.assign(arg, {
|
|
801
|
+
type: 'Temporal.ZonedDateTime', // override {arg.type}
|
|
802
|
+
value: zdt,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
if (!isType(arg.value, 'String', 'Number'))
|
|
806
|
+
return arg; // only conform String or Number (not BigInt, etc) against known patterns
|
|
807
|
+
const value = trimAll(arg.value, Match.strips); // cast as String, remove \( \) and control-characters
|
|
808
|
+
if (isString(arg.value)) { // if original value is String
|
|
809
|
+
if (isEmpty(value)) { // don't conform empty string
|
|
810
|
+
this.#result(arg, { match: 'Empty' });
|
|
811
|
+
return Object.assign(arg, { type: 'Empty' });
|
|
812
|
+
}
|
|
813
|
+
if (isIntegerLike(value)) { // if string representation of BigInt literal
|
|
814
|
+
this.#result(arg, { match: 'BigInt' });
|
|
815
|
+
return Object.assign(arg, { type: 'BigInt', value: asInteger(value) });
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
else { // else it is a Number
|
|
819
|
+
if (value.length <= 7) { // cannot reliably interpret small numbers: might be {ss} or {yymmdd} or {dmmyyyy}
|
|
820
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).catch(this.#local.config, 'Cannot safely interpret number with less than 8-digits: use string instead');
|
|
821
|
+
return arg;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
if (isUndefined(this.#zdt)) // if first pass
|
|
825
|
+
dateTime = dateTime.withPlainTime('00:00:00'); // strip out all time-components
|
|
826
|
+
const map = this.#local.parse.pattern;
|
|
827
|
+
for (const [sym, pat] of map) {
|
|
828
|
+
const groups = this.#parseMatch(pat, value); // determine pattern-match groups
|
|
829
|
+
if (isEmpty(groups))
|
|
830
|
+
continue; // no match, so skip this iteration
|
|
831
|
+
this.#result(arg, { match: sym.description, groups: cleanify(groups) }); // stash the {key} of the pattern that was matched
|
|
832
|
+
this.#parseGroups(groups); // mutate the {groups} object
|
|
833
|
+
dateTime = this.#parseWeekday(groups, dateTime); // if {weekDay} pattern, calculate a calendar value
|
|
834
|
+
dateTime = this.#parseDate(groups, dateTime); // if {calendar}|{event} pattern, translate to date value
|
|
835
|
+
dateTime = this.#parseTime(groups, dateTime); // if {clock}|{period} pattern, translate to a time value
|
|
836
|
+
/**
|
|
837
|
+
* finished analyzing a matched pattern.
|
|
838
|
+
* rebuild {arg.value} into a ZonedDateTime
|
|
839
|
+
*/
|
|
840
|
+
Object.assign(arg, { type: 'Temporal.ZonedDateTime', value: dateTime });
|
|
841
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).info(this.#local.config, 'groups', groups); // show the resolved date-time elements
|
|
842
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).info(this.#local.config, 'pattern', sym.description); // show the pattern that was matched
|
|
843
|
+
break; // stop checking patterns
|
|
844
|
+
}
|
|
845
|
+
return arg;
|
|
846
|
+
}
|
|
847
|
+
/** apply a regex-match against a value, and clean the result */
|
|
848
|
+
#parseMatch(pat, value) {
|
|
849
|
+
const groups = value.toString().match(pat)?.groups || {};
|
|
850
|
+
ownEntries(groups) // remove undefined, NaN, null and empty values
|
|
851
|
+
.forEach(([key, val]) => isEmpty(val) && delete groups[key]);
|
|
852
|
+
return groups;
|
|
925
853
|
}
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* if named-group 'wkd' detected (with optional 'mod', 'nbr', or time-units), then calc relative weekday offset
|
|
929
|
-
* | Example | Result | Note |
|
|
930
|
-
* | :--- | :---- | :---- |
|
|
931
|
-
* | `Wed` | Wed this week | might be earlier or later or equal to current day |
|
|
932
|
-
* | `-Wed` | Wed last week | same as new Tempo('Wed').add({ weeks: -1 }) |
|
|
933
|
-
* | `+Wed` | Wed next week | same as new Tempo('Wed').add({ weeks: 1 }) |
|
|
934
|
-
* | `-3Wed` | Wed three weeks ago | same as new Tempo('Wed').add({ weeks: -3 }) |
|
|
935
|
-
* | `<Wed` | Wed prior to today | might be current or previous week |
|
|
936
|
-
* | `<=Wed` | Wed prior to tomorrow | might be current or previous week |
|
|
937
|
-
* | `Wed noon` | Wed this week at 12:00pm | even though time-periods may be present, ignore them in this method |
|
|
938
|
-
*
|
|
939
|
-
* @returns ZonedDateTime with computed date-offset
|
|
940
|
-
*/
|
|
941
|
-
#parseWeekday(groups, dateTime) {
|
|
942
|
-
const { wkd, mod, nbr = '1', sfx, ...rest } = groups;
|
|
943
|
-
if (isUndefined(wkd)) // this is not a true {weekDay} pattern match
|
|
944
|
-
return dateTime;
|
|
945
854
|
/**
|
|
946
|
-
*
|
|
947
|
-
*
|
|
948
|
-
*
|
|
855
|
+
* resolve any {event} | {period} to their date | time values,
|
|
856
|
+
* intercept any {month} string,
|
|
857
|
+
* set default {nbr} if {mod} present,
|
|
858
|
+
* Note: this will mutate the {groups} object
|
|
949
859
|
*/
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
.
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
860
|
+
#parseGroups(groups) {
|
|
861
|
+
// fix {event}
|
|
862
|
+
const event = ownKeys(groups).find(key => key.match(Match.event));
|
|
863
|
+
if (event) {
|
|
864
|
+
const idx = +event.substring(4); // number index of the {event}
|
|
865
|
+
const src = event.startsWith('g') ? __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).parse.event : this.#local.parse.event;
|
|
866
|
+
const [_key, evt] = ownEntries(src, true)[idx]; // fetch the indexed tuple's value
|
|
867
|
+
Object.assign(groups, this.#parseEvent(evt)); // determine the date-values for the {event}
|
|
868
|
+
delete groups[event];
|
|
869
|
+
const { yy, mm, dd } = groups;
|
|
870
|
+
if (isEmpty(yy) && isEmpty(mm) && isEmpty(dd))
|
|
871
|
+
return __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).catch(this.#local.config, `Cannot determine a {date} or {event} from "${evt}"`);
|
|
872
|
+
}
|
|
873
|
+
// fix {period}
|
|
874
|
+
const period = ownKeys(groups).find(key => key.match(Match.period));
|
|
875
|
+
if (period) {
|
|
876
|
+
const idx = +period.substring(4); // number index of the {period}
|
|
877
|
+
const src = period.startsWith('g') ? __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_global).parse.period : this.#local.parse.period;
|
|
878
|
+
const [_key, per] = ownEntries(src, true)[idx]; // fetch the indexed tuple's value
|
|
879
|
+
Object.assign(groups, this.#parsePeriod(per)); // determine the time-values for the {period}
|
|
880
|
+
delete groups[period];
|
|
881
|
+
if (isEmpty(groups["hh"])) // must have at-least {hh} time-component
|
|
882
|
+
return __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).catch(this.#local.config, `Cannot determine a {time} or {period} from "${per}"`);
|
|
883
|
+
}
|
|
884
|
+
// fix {mm}
|
|
885
|
+
if (isDefined(groups["mm"]) && !isNumeric(groups["mm"])) {
|
|
886
|
+
const mm = __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_prefix).call(Tempo, groups["mm"]); // conform month-name
|
|
887
|
+
groups["mm"] = Tempo.MONTH.keys()
|
|
888
|
+
.findIndex(el => el === mm) // resolve month-name into a month-number
|
|
889
|
+
.toString() // (some browsers do not allow month-names when parsing a Date)
|
|
890
|
+
.padStart(2, '0');
|
|
891
|
+
}
|
|
892
|
+
// fix {rdt}
|
|
893
|
+
if (isDefined(groups["rdt"])) {
|
|
894
|
+
const idx = ['yesterday', 'tomorrow', 'today'].indexOf(groups["rdt"]);
|
|
895
|
+
const val = [Event['yesterday'], Event['tomorrow'], Event['today']][idx];
|
|
896
|
+
const zdt = val.bind(this)();
|
|
897
|
+
Object.assign(groups, {
|
|
898
|
+
yy: zdt.year.toString(),
|
|
899
|
+
mm: zdt.month.toString().padStart(2, '0'),
|
|
900
|
+
dd: zdt.day.toString().padStart(2, '0'),
|
|
901
|
+
});
|
|
902
|
+
delete groups["rdt"];
|
|
903
|
+
}
|
|
904
|
+
return groups;
|
|
957
905
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
906
|
+
/**
|
|
907
|
+
* We expect similar offset-logic to apply to 'modifiers' when parsing a string DateTime.
|
|
908
|
+
* returns {adjust} to make, based on {modifier}, {offset}, and {period}
|
|
909
|
+
* - previous period
|
|
910
|
+
* + next period
|
|
911
|
+
* -3 three periods ago
|
|
912
|
+
* < prior to base-date (asIs)
|
|
913
|
+
* <= prior to base-date (plus one)
|
|
914
|
+
*/
|
|
915
|
+
#parseModifier({ mod, adjust, offset, period }) {
|
|
916
|
+
adjust = Math.abs(adjust);
|
|
917
|
+
switch (mod) {
|
|
918
|
+
case void 0: // no adjustment
|
|
919
|
+
case '=':
|
|
920
|
+
case 'this': // current period
|
|
921
|
+
return 0;
|
|
922
|
+
case '+': // next period
|
|
923
|
+
case 'next':
|
|
924
|
+
return adjust;
|
|
925
|
+
case '-': // previous period
|
|
926
|
+
case 'prev':
|
|
927
|
+
case 'last':
|
|
928
|
+
return -adjust;
|
|
929
|
+
case '<': // period before base-date
|
|
930
|
+
case 'ago':
|
|
931
|
+
return (period <= offset)
|
|
932
|
+
? -adjust
|
|
933
|
+
: -(adjust - 1);
|
|
934
|
+
case '<=': // period before or including base-date
|
|
935
|
+
return (period < offset)
|
|
936
|
+
? -adjust
|
|
937
|
+
: -(adjust - 1);
|
|
938
|
+
case '>': // period after base-date
|
|
939
|
+
case 'hence':
|
|
940
|
+
return (period > offset)
|
|
941
|
+
? adjust
|
|
942
|
+
: (adjust - 1);
|
|
943
|
+
case '>=': // period after or including base-date
|
|
944
|
+
case '+=':
|
|
945
|
+
return (period >= offset)
|
|
946
|
+
? adjust
|
|
947
|
+
: (adjust - 1);
|
|
948
|
+
default: // unexpected modifier
|
|
949
|
+
return 0;
|
|
950
|
+
}
|
|
982
951
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
952
|
+
/**
|
|
953
|
+
* if named-group 'wkd' detected (with optional 'mod', 'nbr', or time-units), then calc relative weekday offset
|
|
954
|
+
* | Example | Result | Note |
|
|
955
|
+
* | :--- | :---- | :---- |
|
|
956
|
+
* | `Wed` | Wed this week | might be earlier or later or equal to current day |
|
|
957
|
+
* | `-Wed` | Wed last week | same as new Tempo('Wed').add({ weeks: -1 }) |
|
|
958
|
+
* | `+Wed` | Wed next week | same as new Tempo('Wed').add({ weeks: 1 }) |
|
|
959
|
+
* | `-3Wed` | Wed three weeks ago | same as new Tempo('Wed').add({ weeks: -3 }) |
|
|
960
|
+
* | `<Wed` | Wed prior to today | might be current or previous week |
|
|
961
|
+
* | `<=Wed` | Wed prior to tomorrow | might be current or previous week |
|
|
962
|
+
* | `Wed noon` | Wed this week at 12:00pm | even though time-periods may be present, ignore them in this method |
|
|
963
|
+
*
|
|
964
|
+
* @returns ZonedDateTime with computed date-offset
|
|
965
|
+
*/
|
|
966
|
+
#parseWeekday(groups, dateTime) {
|
|
967
|
+
const { wkd, mod, nbr = '1', sfx, ...rest } = groups;
|
|
968
|
+
if (isUndefined(wkd)) // this is not a true {weekDay} pattern match
|
|
969
|
+
return dateTime;
|
|
970
|
+
/**
|
|
971
|
+
* the {weekDay} pattern should only have keys of {wkd}, {mod}, {nbr}, {sfx} (and optionally time-units)
|
|
972
|
+
* for example: {wkd: 'Wed', mod: '>', hh: '10', mer: 'pm'}
|
|
973
|
+
* we early-exit if we find anything other than time-units
|
|
974
|
+
*/
|
|
975
|
+
const time = ['hh', 'mi', 'ss', 'ms', 'us', 'ns', 'ff', 'mer'];
|
|
976
|
+
if (!ownKeys(rest)
|
|
977
|
+
.every(key => time.includes(key))) // non 'time-unit' keys detected
|
|
978
|
+
return dateTime; // this is not a true {weekDay} pattern, so early-exit
|
|
979
|
+
if (!isEmpty(mod) && !isEmpty(sfx)) {
|
|
980
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).warn(`Cannot provide both a modifier '${mod}' and suffix '${sfx}'`);
|
|
981
|
+
return dateTime; // cannot provide both 'modifier' and 'suffix'
|
|
982
|
+
}
|
|
983
|
+
const weekday = __classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_prefix).call(Tempo, wkd); // conform weekday-name
|
|
984
|
+
const adjust = +nbr; // how many weeks to adjust
|
|
985
|
+
const offset = Tempo.WEEKDAY.keys() // how far weekday is from today
|
|
986
|
+
.findIndex(el => el === weekday);
|
|
987
|
+
const days = offset - dateTime.dayOfWeek // number of days to offset from dateTime
|
|
988
|
+
+ (this.#parseModifier({ mod: mod ?? sfx, adjust, offset, period: dateTime.dayOfWeek }) * dateTime.daysInWeek);
|
|
989
|
+
delete groups["wkd"];
|
|
997
990
|
delete groups["mod"];
|
|
998
|
-
|
|
991
|
+
delete groups["nbr"];
|
|
992
|
+
delete groups["sfx"];
|
|
993
|
+
return dateTime
|
|
994
|
+
.add({ days }); // set new {day}
|
|
999
995
|
}
|
|
1000
996
|
/**
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1003
|
-
* century = Int(currYear / 100) // for example: Int(2024 / 100) => 20
|
|
1004
|
-
* 22 => 2022 // 22 is less than pivot, so use {century}
|
|
1005
|
-
* 57 => 1957 // 57 is more than pivot, so use {century - 1}
|
|
997
|
+
* match input against date patterns
|
|
998
|
+
* @returns adjusted ZonedDateTime with resolved time-components
|
|
1006
999
|
*/
|
|
1007
|
-
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1000
|
+
#parseDate(groups, dateTime) {
|
|
1001
|
+
const { mod, nbr = '1', afx, unt, yy, mm, dd } = groups;
|
|
1002
|
+
if (isEmpty(yy) && isEmpty(mm) && isEmpty(dd) && isUndefined(unt))
|
|
1003
|
+
return dateTime; // return default
|
|
1004
|
+
if (!isEmpty(mod) && !isEmpty(afx)) {
|
|
1005
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).warn(`Cannot provide both a modifier '${mod}' and suffix '${afx}'`);
|
|
1006
|
+
return dateTime;
|
|
1007
|
+
}
|
|
1008
|
+
let { year, month, day } = this.#num({
|
|
1009
|
+
year: yy ?? dateTime.year, // supplied year, else current year
|
|
1010
|
+
month: mm ?? dateTime.month, // supplied month, else current month
|
|
1011
|
+
day: dd ?? dateTime.day, // supplied day, else current day
|
|
1012
|
+
});
|
|
1013
|
+
// handle {unt} relative offset (e.g. '2 days ago')
|
|
1014
|
+
if (unt) {
|
|
1015
|
+
const adjust = +nbr;
|
|
1016
|
+
const direction = (mod === '<' || mod === '-' || afx === 'ago') ? -1 : 1;
|
|
1017
|
+
const plural = singular(unt) + 's';
|
|
1018
|
+
dateTime = dateTime.add({ [plural]: adjust * direction });
|
|
1019
|
+
delete groups["unt"];
|
|
1020
|
+
delete groups["nbr"];
|
|
1021
|
+
delete groups["afx"];
|
|
1022
|
+
delete groups["mod"];
|
|
1023
|
+
return dateTime;
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* change two-digit year into four-digits using 'pivot-year' (defaulted to '75' years) to determine century
|
|
1027
|
+
* pivot = (currYear - Tempo.pivot) % 100 // for example: Rem((2024 - 75) / 100) => 49
|
|
1028
|
+
* century = Int(currYear / 100) // for example: Int(2024 / 100) => 20
|
|
1029
|
+
* 22 => 2022 // 22 is less than pivot, so use {century}
|
|
1030
|
+
* 57 => 1957 // 57 is more than pivot, so use {century - 1}
|
|
1031
|
+
*/
|
|
1032
|
+
if (year.toString().match(Match.twoDigit)) { // if {year} match just-two digits
|
|
1033
|
+
const pivot = dateTime
|
|
1034
|
+
.subtract({ years: this.#local.config['pivot'] }) // pivot cutoff to determine century
|
|
1035
|
+
.year % 100; // remainder
|
|
1036
|
+
const century = Math.trunc(dateTime.year / 100); // current century
|
|
1037
|
+
year += (century - Number(year >= pivot)) * 100; // now a four-digit year
|
|
1038
|
+
}
|
|
1039
|
+
// adjust the {year} if a Modifier is present
|
|
1040
|
+
const adjust = +nbr; // how many years to adjust
|
|
1041
|
+
const offset = Number(pad(month) + '.' + pad(day)); // the event month.day
|
|
1042
|
+
const period = Number(pad(dateTime.month) + '.' + pad(dateTime.day + 1));
|
|
1043
|
+
year += this.#parseModifier({ mod: mod ?? afx, adjust, offset, period });
|
|
1044
|
+
Object.assign(groups, { yy: year, mm: month, dd: day });
|
|
1045
|
+
delete groups["mod"];
|
|
1046
|
+
delete groups["nbr"];
|
|
1047
|
+
delete groups["afx"];
|
|
1048
|
+
// all date-components are now set; check for overflow in case past end-of-month
|
|
1049
|
+
return Temporal.PlainDate.from({ year, month, day }, { overflow: 'constrain' })
|
|
1050
|
+
.toZonedDateTime(dateTime.timeZoneId) // adjust to constrained date
|
|
1051
|
+
.withPlainTime(dateTime.toPlainTime()); // restore the time
|
|
1046
1052
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
const groups = {};
|
|
1060
|
-
const val = isFunction(evt)
|
|
1061
|
-
? evt.bind(this)().toString()
|
|
1062
|
-
: evt;
|
|
1063
|
-
const pats = this.#local.parse.isMonthDay // first find out if we have a US-format timeZone
|
|
1064
|
-
? ['mdy', 'dmy', 'ymd'] // try {mdy} before {dmy} if US-format
|
|
1065
|
-
: ['dmy', 'mdy', 'ymd']; // else try {dmy} before {mdy}
|
|
1066
|
-
for (const pat of pats) {
|
|
1067
|
-
const reg = this.#local.parse.pattern.get(Tempo.getSymbol(pat)); // get the RegExp for the date-pattern
|
|
1068
|
-
if (isUndefined(reg)) {
|
|
1069
|
-
Tempo.#dbg.catch(this.#local.config, `Cannot find pattern: "${pat}"`);
|
|
1053
|
+
/**
|
|
1054
|
+
* match input against 'tm' pattern.
|
|
1055
|
+
* {groups} is expected to contain time-components (like {hh:'3', mi:'30', mer:'pm'}).
|
|
1056
|
+
* returns an adjusted ZonedDateTime
|
|
1057
|
+
*/
|
|
1058
|
+
#parseTime(groups = {}, dateTime) {
|
|
1059
|
+
if (isUndefined(groups["hh"])) // must contain 'time' with at least {hh}
|
|
1060
|
+
return dateTime;
|
|
1061
|
+
let { hh = 0, mi = 0, ss = 0, ms = 0, us = 0, ns = 0 } = this.#num(groups);
|
|
1062
|
+
if (hh >= 24) {
|
|
1063
|
+
dateTime = dateTime.add({ days: Math.trunc(hh / 24) }); // move the date forward number of days to offset
|
|
1064
|
+
hh %= 24; // midnight is '00:00' on the next-day
|
|
1070
1065
|
}
|
|
1071
|
-
|
|
1072
|
-
const
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1066
|
+
if (isDefined(groups["ff"])) { // {ff} is fractional seconds and overrides {ms|us|ns}
|
|
1067
|
+
const ff = groups["ff"].substring(0, 9).padEnd(9, '0');
|
|
1068
|
+
ms = +ff.substring(0, 3);
|
|
1069
|
+
us = +ff.substring(3, 6);
|
|
1070
|
+
ns = +ff.substring(6, 9);
|
|
1076
1071
|
}
|
|
1077
|
-
if (
|
|
1078
|
-
|
|
1072
|
+
if (groups["mer"]?.toLowerCase() === 'pm' && hh < 12 && (hh + mi + ss + ms + us + ns) > 0)
|
|
1073
|
+
hh += 12; // anything after midnight and before midday
|
|
1074
|
+
if (groups["mer"]?.toLowerCase() === 'am' && hh >= 12)
|
|
1075
|
+
hh -= 12; // anything after midday
|
|
1076
|
+
return dateTime // return the computed time-values
|
|
1077
|
+
.withPlainTime({ hour: hh, minute: mi, second: ss, millisecond: ms, microsecond: us, nanosecond: ns });
|
|
1079
1078
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1079
|
+
/**
|
|
1080
|
+
* match an {event} string against a date pattern
|
|
1081
|
+
* if {evt} is a function, it is bound to the current instance (to allow access to properties/methods)
|
|
1082
|
+
*/
|
|
1083
|
+
#parseEvent(evt) {
|
|
1084
|
+
const groups = {};
|
|
1085
|
+
const val = isFunction(evt)
|
|
1086
|
+
? evt.bind(this)().toString()
|
|
1087
|
+
: evt;
|
|
1088
|
+
const pats = this.#local.parse.isMonthDay // first find out if we have a US-format timeZone
|
|
1089
|
+
? ['mdy', 'dmy', 'ymd'] // try {mdy} before {dmy} if US-format
|
|
1090
|
+
: ['dmy', 'mdy', 'ymd']; // else try {dmy} before {mdy}
|
|
1091
|
+
for (const pat of pats) {
|
|
1092
|
+
const reg = this.#local.parse.pattern.get(Tempo.getSymbol(pat)); // get the RegExp for the date-pattern
|
|
1093
|
+
if (isUndefined(reg)) {
|
|
1094
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).catch(this.#local.config, `Cannot find pattern: "${pat}"`);
|
|
1095
|
+
}
|
|
1096
|
+
else {
|
|
1097
|
+
const match = this.#parseMatch(reg, val);
|
|
1098
|
+
if (!isEmpty(match))
|
|
1099
|
+
this.#result({ type: 'Event', value: val, match: pat, groups: cleanify(match) });
|
|
1100
|
+
Object.assign(groups, match);
|
|
1101
|
+
}
|
|
1102
|
+
if (!isEmpty(groups))
|
|
1103
|
+
break; // return on the first matched pattern
|
|
1104
|
+
}
|
|
1105
|
+
return groups; // overlay the match date-components
|
|
1092
1106
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
#num = (groups) => {
|
|
1104
|
-
return ownEntries(groups)
|
|
1105
|
-
.reduce((acc, [key, val]) => {
|
|
1106
|
-
if (isNumeric(val))
|
|
1107
|
-
acc[key] = ifNumeric(val);
|
|
1108
|
-
return acc;
|
|
1109
|
-
}, {});
|
|
1110
|
-
};
|
|
1111
|
-
/** create new Tempo with {offset} property */
|
|
1112
|
-
#add = (arg) => {
|
|
1113
|
-
Tempo.#pending ??= [...this.#local.parse.result]; // collected parse-results so-far
|
|
1114
|
-
const mutate = 'add';
|
|
1115
|
-
const zdt = ownEntries(arg) // loop through each mutation
|
|
1116
|
-
.reduce((zdt, [unit, offset]) => {
|
|
1117
|
-
const single = singular(unit);
|
|
1118
|
-
const plural = single + 's';
|
|
1119
|
-
switch (`${mutate}.${single}`) {
|
|
1120
|
-
case 'add.year':
|
|
1121
|
-
case 'add.month':
|
|
1122
|
-
case 'add.week':
|
|
1123
|
-
case 'add.day':
|
|
1124
|
-
case 'add.hour':
|
|
1125
|
-
case 'add.minute':
|
|
1126
|
-
case 'add.second':
|
|
1127
|
-
case 'add.millisecond':
|
|
1128
|
-
case 'add.microsecond':
|
|
1129
|
-
case 'add.nanosecond':
|
|
1130
|
-
return zdt
|
|
1131
|
-
.add({ [plural]: offset });
|
|
1132
|
-
default:
|
|
1133
|
-
Tempo.#dbg.catch(this.#local.config, `Unexpected method(${mutate}), unit(${unit}) and offset(${offset})`);
|
|
1134
|
-
return zdt;
|
|
1107
|
+
/**
|
|
1108
|
+
* match a {period} string against a time pattern
|
|
1109
|
+
* if {per} is a function, it is bound to the current instance (to allow access to properties/methods)
|
|
1110
|
+
*/
|
|
1111
|
+
#parsePeriod(per) {
|
|
1112
|
+
const groups = {};
|
|
1113
|
+
const tm = this.#local.parse.pattern.get(Tempo.getSymbol('tm')); // get the RegExp for the time-pattern
|
|
1114
|
+
if (isUndefined(tm)) {
|
|
1115
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).catch(this.#local.config, `Cannot find pattern "tm"`);
|
|
1116
|
+
return;
|
|
1135
1117
|
}
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1118
|
+
const val = isFunction(per)
|
|
1119
|
+
? per.bind(this)().toString()
|
|
1120
|
+
: per;
|
|
1121
|
+
const match = this.#parseMatch(tm, val);
|
|
1122
|
+
if (!isEmpty(match))
|
|
1123
|
+
this.#result({ type: 'Period', value: val, match: 'tm', groups: cleanify(match) });
|
|
1124
|
+
Object.assign(groups, match);
|
|
1125
|
+
return groups;
|
|
1126
|
+
}
|
|
1127
|
+
/** return a new object, with only numeric values */
|
|
1128
|
+
#num = (groups) => {
|
|
1129
|
+
return ownEntries(groups)
|
|
1130
|
+
.reduce((acc, [key, val]) => {
|
|
1131
|
+
if (isNumeric(val))
|
|
1132
|
+
acc[key] = ifNumeric(val);
|
|
1133
|
+
return acc;
|
|
1134
|
+
}, {});
|
|
1135
|
+
};
|
|
1136
|
+
/** create new Tempo with {offset} property */
|
|
1137
|
+
#add = (arg) => {
|
|
1138
|
+
var _b;
|
|
1139
|
+
__classPrivateFieldSet(_b = Tempo, _classThis, __classPrivateFieldGet(_b, _classThis, "f", _Tempo_pending) ?? [...this.#local.parse.result], "f", _Tempo_pending); // collected parse-results so-far
|
|
1140
|
+
const mutate = 'add';
|
|
1141
|
+
const zdt = ownEntries(arg) // loop through each mutation
|
|
1142
|
+
.reduce((zdt, [unit, offset]) => {
|
|
1143
|
+
const single = singular(unit);
|
|
1144
|
+
const plural = single + 's';
|
|
1145
|
+
switch (`${mutate}.${single}`) {
|
|
1146
|
+
case 'add.year':
|
|
1147
|
+
case 'add.month':
|
|
1148
|
+
case 'add.week':
|
|
1149
|
+
case 'add.day':
|
|
1150
|
+
case 'add.hour':
|
|
1151
|
+
case 'add.minute':
|
|
1152
|
+
case 'add.second':
|
|
1153
|
+
case 'add.millisecond':
|
|
1154
|
+
case 'add.microsecond':
|
|
1155
|
+
case 'add.nanosecond':
|
|
1156
|
+
return zdt
|
|
1157
|
+
.add({ [plural]: offset });
|
|
1150
1158
|
default:
|
|
1151
|
-
|
|
1159
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).catch(this.#local.config, `Unexpected method(${mutate}), unit(${unit}) and offset(${offset})`);
|
|
1160
|
+
return zdt;
|
|
1152
1161
|
}
|
|
1153
|
-
})
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1162
|
+
}, this.#zdt);
|
|
1163
|
+
return new Tempo(zdt, this.#options);
|
|
1164
|
+
};
|
|
1165
|
+
/** create a new Tempo with {adjust} property */
|
|
1166
|
+
#set = (args) => {
|
|
1167
|
+
var _b;
|
|
1168
|
+
__classPrivateFieldSet(_b = Tempo, _classThis, __classPrivateFieldGet(_b, _classThis, "f", _Tempo_pending) ?? [...this.#local.parse.result], "f", _Tempo_pending); // collected parse-results so-far
|
|
1169
|
+
const zdt = ownEntries(args) // loop through each mutation
|
|
1170
|
+
.reduce((zdt, [key, adjust]) => {
|
|
1171
|
+
const { mutate, offset, single } = ((key) => {
|
|
1172
|
+
switch (key) {
|
|
1173
|
+
case 'start':
|
|
1174
|
+
case 'mid':
|
|
1175
|
+
case 'end':
|
|
1176
|
+
return { mutate: key, offset: 0, single: singular(adjust?.toString() ?? '') };
|
|
1177
|
+
default:
|
|
1178
|
+
return { mutate: 'set', offset: adjust, single: singular(key) };
|
|
1179
|
+
}
|
|
1180
|
+
})(key); // IIFE to analyze arguments
|
|
1181
|
+
switch (`${mutate}.${single}`) {
|
|
1182
|
+
case 'set.period':
|
|
1183
|
+
case 'set.time':
|
|
1184
|
+
case 'set.date':
|
|
1185
|
+
case 'set.event':
|
|
1186
|
+
case 'set.dow': // set day-of-week by number
|
|
1187
|
+
case 'set.wkd': // set day-of-week by name
|
|
1188
|
+
return this.#parse(offset, zdt);
|
|
1189
|
+
case 'set.year':
|
|
1190
|
+
case 'set.month':
|
|
1191
|
+
// case 'set.week': // not defined
|
|
1192
|
+
case 'set.day':
|
|
1193
|
+
case 'set.hour':
|
|
1194
|
+
case 'set.minute':
|
|
1195
|
+
case 'set.second':
|
|
1196
|
+
case 'set.millisecond':
|
|
1197
|
+
case 'set.microsecond':
|
|
1198
|
+
case 'set.nanosecond':
|
|
1199
|
+
return zdt
|
|
1200
|
+
.with({ [single]: offset });
|
|
1201
|
+
case 'set.yy':
|
|
1202
|
+
case 'set.mm':
|
|
1203
|
+
// case 'set.ww': // not defined
|
|
1204
|
+
case 'set.dd':
|
|
1205
|
+
case 'set.hh':
|
|
1206
|
+
case 'set.mi':
|
|
1207
|
+
case 'set.ss':
|
|
1208
|
+
case 'set.ms':
|
|
1209
|
+
case 'set.us':
|
|
1210
|
+
case 'set.ns':
|
|
1211
|
+
const value = Tempo.ELEMENT[single];
|
|
1212
|
+
return zdt
|
|
1213
|
+
.with({ [value]: offset });
|
|
1214
|
+
case 'start.year':
|
|
1215
|
+
return zdt
|
|
1216
|
+
.with({ month: Tempo.MONTH.Jan, day: 1 })
|
|
1217
|
+
.startOfDay();
|
|
1218
|
+
case 'start.term': // TODO
|
|
1219
|
+
return zdt;
|
|
1220
|
+
case 'start.month':
|
|
1221
|
+
return zdt
|
|
1222
|
+
.with({ day: 1 })
|
|
1223
|
+
.startOfDay();
|
|
1224
|
+
case 'start.week':
|
|
1225
|
+
return zdt
|
|
1226
|
+
.add({ days: -(this.dow - Tempo.WEEKDAY.Mon) })
|
|
1227
|
+
.startOfDay();
|
|
1228
|
+
case 'start.day':
|
|
1229
|
+
return zdt
|
|
1230
|
+
.startOfDay();
|
|
1231
|
+
case 'start.hour':
|
|
1232
|
+
case 'start.minute':
|
|
1233
|
+
case 'start.second':
|
|
1234
|
+
return zdt
|
|
1235
|
+
.round({ smallestUnit: offset, roundingMode: 'trunc' });
|
|
1236
|
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
1237
|
+
case 'mid.year':
|
|
1238
|
+
return zdt
|
|
1239
|
+
.with({ month: Tempo.MONTH.Jul, day: 1 })
|
|
1240
|
+
.startOfDay();
|
|
1241
|
+
case 'mid.term': // TODO: relevant?
|
|
1242
|
+
return zdt;
|
|
1243
|
+
case 'mid.month':
|
|
1244
|
+
return zdt
|
|
1245
|
+
.with({ day: Math.trunc(zdt.daysInMonth / 2) })
|
|
1246
|
+
.startOfDay();
|
|
1247
|
+
case 'mid.week':
|
|
1248
|
+
return zdt
|
|
1249
|
+
.add({ days: -(this.dow - Tempo.WEEKDAY.Thu) })
|
|
1250
|
+
.startOfDay();
|
|
1251
|
+
case 'mid.day':
|
|
1252
|
+
return zdt
|
|
1253
|
+
.round({ smallestUnit: 'day', roundingMode: 'trunc' })
|
|
1254
|
+
.add({ hours: 12 });
|
|
1255
|
+
case 'mid.hour':
|
|
1256
|
+
return zdt
|
|
1257
|
+
.round({ smallestUnit: 'hour', roundingMode: 'trunc' })
|
|
1258
|
+
.add({ minutes: 30 });
|
|
1259
|
+
case 'mid.minute':
|
|
1260
|
+
return zdt
|
|
1261
|
+
.round({ smallestUnit: 'minute', roundingMode: 'trunc' })
|
|
1262
|
+
.add({ seconds: 30 });
|
|
1263
|
+
case 'mid.second':
|
|
1264
|
+
return zdt
|
|
1265
|
+
.round({ smallestUnit: 'second', roundingMode: 'trunc' })
|
|
1266
|
+
.add({ milliseconds: 500 });
|
|
1267
|
+
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
1268
|
+
case 'end.year':
|
|
1269
|
+
return zdt
|
|
1270
|
+
.add({ years: 1 })
|
|
1271
|
+
.with({ month: Tempo.MONTH.Jan, day: 1 })
|
|
1272
|
+
.startOfDay()
|
|
1273
|
+
.subtract({ nanoseconds: 1 });
|
|
1274
|
+
case 'end.term': // TODO
|
|
1275
|
+
return zdt
|
|
1276
|
+
.subtract({ nanoseconds: 1 });
|
|
1277
|
+
case 'end.month':
|
|
1278
|
+
return zdt
|
|
1279
|
+
.add({ months: 1 })
|
|
1280
|
+
.with({ day: 1 })
|
|
1281
|
+
.startOfDay()
|
|
1282
|
+
.subtract({ nanoseconds: 1 });
|
|
1283
|
+
case 'end.week':
|
|
1284
|
+
return zdt
|
|
1285
|
+
.add({ days: (Tempo.WEEKDAY.Sun - this.dow) + 1 })
|
|
1286
|
+
.startOfDay()
|
|
1287
|
+
.subtract({ nanoseconds: 1 });
|
|
1288
|
+
case 'end.day':
|
|
1289
|
+
case 'end.hour':
|
|
1290
|
+
case 'end.minute':
|
|
1291
|
+
case 'end.second':
|
|
1292
|
+
return zdt
|
|
1293
|
+
.round({ smallestUnit: offset, roundingMode: 'ceil' })
|
|
1294
|
+
.subtract({ nanoseconds: 1 });
|
|
1295
|
+
default:
|
|
1296
|
+
__classPrivateFieldGet(Tempo, _classThis, "f", _Tempo_dbg).catch(this.#local.config, `Unexpected method(${mutate}), unit(${adjust}) and offset(${single})`);
|
|
1297
|
+
return zdt;
|
|
1298
|
+
}
|
|
1299
|
+
}, this.#zdt); // start reduce with the Tempo zonedDateTime
|
|
1300
|
+
return new Tempo(zdt, this.#options);
|
|
1301
|
+
};
|
|
1302
|
+
#format = (fmt) => {
|
|
1303
|
+
if (isNull(this.#tempo))
|
|
1304
|
+
return void 0; // don't format <null> dates
|
|
1305
|
+
const obj = Tempo.FORMAT;
|
|
1306
|
+
const template = isString(fmt) && __classPrivateFieldGet(Tempo, _classThis, "m", _Tempo_hasOwn).call(Tempo, obj, fmt)
|
|
1307
|
+
? obj[fmt]
|
|
1308
|
+
: fmt;
|
|
1309
|
+
const sTemplate = String(template);
|
|
1310
|
+
switch (sTemplate) {
|
|
1311
|
+
case obj.yearWeek:
|
|
1312
|
+
const offset = this.ww === 1 && this.mm === Tempo.MONTH.Dec; // if late-Dec, add 1 to yy
|
|
1313
|
+
return +`${this.yy + +offset}${pad(this.ww)}`;
|
|
1314
|
+
case obj.yearMonth:
|
|
1315
|
+
return +`${this.yy}${pad(this.mm)}`;
|
|
1316
|
+
case obj.yearMonthDay:
|
|
1317
|
+
return +`${this.yy}${pad(this.mm)}${pad(this.dd)}`;
|
|
1268
1318
|
default:
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
case 'dow': return this.dow.toString();
|
|
1302
|
-
case 'wkd': return this.wkd;
|
|
1303
|
-
case 'www': return this.www;
|
|
1304
|
-
case 'ww': return pad(this.ww);
|
|
1305
|
-
case 'hh': return pad(this.hh);
|
|
1306
|
-
case 'HH': return pad(this.hh > 12 ? this.hh % 12 : this.hh || 12);
|
|
1307
|
-
case 'mer': return this.hh >= 12 ? 'pm' : 'am';
|
|
1308
|
-
case 'MER': return this.hh >= 12 ? 'PM' : 'AM';
|
|
1309
|
-
case 'mi': return pad(this.mi);
|
|
1310
|
-
case 'ss': return pad(this.ss);
|
|
1311
|
-
case 'ms': return pad(this.ms, 3);
|
|
1312
|
-
case 'us': return pad(this.us, 3);
|
|
1313
|
-
case 'ns': return pad(this.ns, 3);
|
|
1314
|
-
case 'ff': return `${pad(this.ms, 3)}${pad(this.us, 3)}${pad(this.ns, 3)}`;
|
|
1315
|
-
case 'hhmiss': return pad(this.hh) + pad(this.mi) + pad(this.ss);
|
|
1316
|
-
case 'ts': return this.ts.toString();
|
|
1317
|
-
case 'nano': return this.nano.toString();
|
|
1318
|
-
case 'tz': return this.tz;
|
|
1319
|
-
default: {
|
|
1320
|
-
return token.startsWith('term.')
|
|
1321
|
-
? stringify(this.term[token.slice(5)])
|
|
1322
|
-
: `{${token}}`; // unknown format-code, return as-is
|
|
1319
|
+
return sTemplate.replace(Match.braces, (_match, token) => {
|
|
1320
|
+
switch (token) {
|
|
1321
|
+
case 'yyyy': return pad(this.yy, 4);
|
|
1322
|
+
case 'yy': return pad(this.yy % 100);
|
|
1323
|
+
case 'mon': return this.mon;
|
|
1324
|
+
case 'mmm': return this.mmm;
|
|
1325
|
+
case 'mm': return pad(this.mm);
|
|
1326
|
+
case 'dd': return pad(this.dd);
|
|
1327
|
+
case 'day': return this.day.toString();
|
|
1328
|
+
case 'dow': return this.dow.toString();
|
|
1329
|
+
case 'wkd': return this.wkd;
|
|
1330
|
+
case 'www': return this.www;
|
|
1331
|
+
case 'ww': return pad(this.ww);
|
|
1332
|
+
case 'hh': return pad(this.hh);
|
|
1333
|
+
case 'HH': return pad(this.hh > 12 ? this.hh % 12 : this.hh || 12);
|
|
1334
|
+
case 'mer': return this.hh >= 12 ? 'pm' : 'am';
|
|
1335
|
+
case 'MER': return this.hh >= 12 ? 'PM' : 'AM';
|
|
1336
|
+
case 'mi': return pad(this.mi);
|
|
1337
|
+
case 'ss': return pad(this.ss);
|
|
1338
|
+
case 'ms': return pad(this.ms, 3);
|
|
1339
|
+
case 'us': return pad(this.us, 3);
|
|
1340
|
+
case 'ns': return pad(this.ns, 3);
|
|
1341
|
+
case 'ff': return `${pad(this.ms, 3)}${pad(this.us, 3)}${pad(this.ns, 3)}`;
|
|
1342
|
+
case 'hhmiss': return pad(this.hh) + pad(this.mi) + pad(this.ss);
|
|
1343
|
+
case 'ts': return this.ts.toString();
|
|
1344
|
+
case 'nano': return this.nano.toString();
|
|
1345
|
+
case 'tz': return this.tz;
|
|
1346
|
+
default: {
|
|
1347
|
+
return token.startsWith('term.')
|
|
1348
|
+
? stringify(this.term[token.slice(5)])
|
|
1349
|
+
: `{${token}}`; // unknown format-code, return as-is
|
|
1350
|
+
}
|
|
1323
1351
|
}
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
#until(arg, until = {}, since = false) {
|
|
1356
|
+
let value, opts = {}, unit = void 0;
|
|
1357
|
+
switch (true) {
|
|
1358
|
+
case isString(arg) && Tempo.ELEMENT.includes(singular(arg)):
|
|
1359
|
+
unit = arg; // e.g. tempo.until('hours')
|
|
1360
|
+
({ value, ...opts } = until);
|
|
1361
|
+
break;
|
|
1362
|
+
case isString(arg): // assume 'arg' is a dateTime string
|
|
1363
|
+
value = arg; // e.g. tempo.until('20-May-1957', {unit: 'years'})
|
|
1364
|
+
if (isObject(until))
|
|
1365
|
+
({ unit, ...opts } = until);
|
|
1366
|
+
else
|
|
1367
|
+
unit = until; // assume the 'until' arg is a 'unit' string
|
|
1368
|
+
break;
|
|
1369
|
+
case isObject(arg) && isString(until): // assume 'until' is a Unit
|
|
1370
|
+
unit = until; // e.g. tempo.until({value:'20-May-1957}, 'years'})
|
|
1371
|
+
({ value, ...opts } = arg);
|
|
1372
|
+
break;
|
|
1373
|
+
case isObject(arg) && isObject(until): // assume combination of Tempo.Options and Tempo.Until
|
|
1374
|
+
({ value, unit, ...opts } = Object.assign({}, arg, until));
|
|
1375
|
+
break;
|
|
1376
|
+
case isString(until):
|
|
1377
|
+
unit = until;
|
|
1378
|
+
value = arg;
|
|
1379
|
+
break;
|
|
1380
|
+
case isObject(until):
|
|
1381
|
+
unit = until.unit;
|
|
1382
|
+
value = arg;
|
|
1383
|
+
break;
|
|
1384
|
+
default:
|
|
1385
|
+
value = arg; // assume 'arg' is a DateTime
|
|
1386
|
+
}
|
|
1387
|
+
const offset = new Tempo(value, opts); // create the offset Tempo
|
|
1388
|
+
const diffZone = this.#zdt.timeZoneId !== offset.#zdt.timeZoneId;
|
|
1389
|
+
const duration = this.#zdt.until(offset.#zdt, { largestUnit: diffZone ? 'hours' : (unit ?? 'years') });
|
|
1390
|
+
if (isDefined(unit))
|
|
1391
|
+
unit = `${singular(unit)}s`; // coerce to plural
|
|
1392
|
+
return (isUndefined(unit) || since) // if no 'unit' provided, or if called via #since()
|
|
1393
|
+
? getAccessors(duration) // return an Object with all the duration accessors
|
|
1394
|
+
.reduce((acc, dur) => Object.assign(acc, { [dur]: duration[dur] }), ifDefined({ iso: duration.toString(), unit }))
|
|
1395
|
+
: duration.total({ relativeTo: this.#zdt, unit }); // sum-up the duration components
|
|
1326
1396
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
(
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
({
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1397
|
+
/** format the elapsed time between two Tempos (to nanosecond) */
|
|
1398
|
+
#since(arg, until = {}) {
|
|
1399
|
+
const dur = this.#until(arg, until, true); // get a Tempo.Duration object
|
|
1400
|
+
const date = [dur.years, dur.months, dur.days];
|
|
1401
|
+
const time = [dur.hours, dur.minutes, dur.seconds];
|
|
1402
|
+
const fraction = [dur.milliseconds, dur.microseconds, dur.nanoseconds]
|
|
1403
|
+
.map(nbr => nbr.toString().padStart(3, '0'))
|
|
1404
|
+
.join('');
|
|
1405
|
+
const rtf = new Intl.RelativeTimeFormat(this.#local.config['locale'], { style: 'narrow' });
|
|
1406
|
+
switch (dur.unit) {
|
|
1407
|
+
case void 0:
|
|
1408
|
+
return `${date.join('.')}T${time.join(':')}.${fraction}`;
|
|
1409
|
+
case 'years':
|
|
1410
|
+
return rtf.format(date[0], 'years');
|
|
1411
|
+
case 'months':
|
|
1412
|
+
return rtf.format(date[1], 'months');
|
|
1413
|
+
case 'weeks':
|
|
1414
|
+
return rtf.format(date[1], 'weeks');
|
|
1415
|
+
case 'days':
|
|
1416
|
+
return rtf.format(date[2], 'days');
|
|
1417
|
+
case 'hours':
|
|
1418
|
+
return rtf.format(time[0], 'hours');
|
|
1419
|
+
case 'minutes':
|
|
1420
|
+
return rtf.format(time[1], 'minutes');
|
|
1421
|
+
case 'seconds':
|
|
1422
|
+
return rtf.format(time[2], 'seconds');
|
|
1423
|
+
case 'milliseconds':
|
|
1424
|
+
case 'microseconds':
|
|
1425
|
+
case 'nanoseconds':
|
|
1426
|
+
return `${fraction}`;
|
|
1427
|
+
default:
|
|
1428
|
+
return dur.iso;
|
|
1429
|
+
}
|
|
1359
1430
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
const duration = this.#zdt.until(offset.#zdt, { largestUnit: diffZone ? 'hours' : (unit ?? 'years') });
|
|
1363
|
-
if (isDefined(unit))
|
|
1364
|
-
unit = `${singular(unit)}s`; // coerce to plural
|
|
1365
|
-
return (isUndefined(unit) || since) // if no 'unit' provided, or if called via #since()
|
|
1366
|
-
? getAccessors(duration) // return an Object with all the duration accessors
|
|
1367
|
-
.reduce((acc, dur) => Object.assign(acc, { [dur]: duration[dur] }), ifDefined({ iso: duration.toString(), unit }))
|
|
1368
|
-
: duration.total({ relativeTo: this.#zdt, unit }); // sum-up the duration components
|
|
1369
|
-
}
|
|
1370
|
-
/** format the elapsed time between two Tempos (to nanosecond) */
|
|
1371
|
-
#since(arg, until = {}) {
|
|
1372
|
-
const dur = this.#until(arg, until, true); // get a Tempo.Duration object
|
|
1373
|
-
const date = [dur.years, dur.months, dur.days];
|
|
1374
|
-
const time = [dur.hours, dur.minutes, dur.seconds];
|
|
1375
|
-
const fraction = [dur.milliseconds, dur.microseconds, dur.nanoseconds]
|
|
1376
|
-
.map(nbr => nbr.toString().padStart(3, '0'))
|
|
1377
|
-
.join('');
|
|
1378
|
-
const rtf = new Intl.RelativeTimeFormat(this.#local.config['locale'], { style: 'narrow' });
|
|
1379
|
-
switch (dur.unit) {
|
|
1380
|
-
case void 0:
|
|
1381
|
-
return `${date.join('.')}T${time.join(':')}.${fraction}`;
|
|
1382
|
-
case 'years':
|
|
1383
|
-
return rtf.format(date[0], 'years');
|
|
1384
|
-
case 'months':
|
|
1385
|
-
return rtf.format(date[1], 'months');
|
|
1386
|
-
case 'weeks':
|
|
1387
|
-
return rtf.format(date[1], 'weeks');
|
|
1388
|
-
case 'days':
|
|
1389
|
-
return rtf.format(date[2], 'days');
|
|
1390
|
-
case 'hours':
|
|
1391
|
-
return rtf.format(time[0], 'hours');
|
|
1392
|
-
case 'minutes':
|
|
1393
|
-
return rtf.format(time[1], 'minutes');
|
|
1394
|
-
case 'seconds':
|
|
1395
|
-
return rtf.format(time[2], 'seconds');
|
|
1396
|
-
case 'milliseconds':
|
|
1397
|
-
case 'microseconds':
|
|
1398
|
-
case 'nanoseconds':
|
|
1399
|
-
return `${fraction}`;
|
|
1400
|
-
default:
|
|
1401
|
-
return dur.iso;
|
|
1431
|
+
static {
|
|
1432
|
+
__runInitializers(_classThis, _classExtraInitializers);
|
|
1402
1433
|
}
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1434
|
+
};
|
|
1435
|
+
return Tempo = _classThis;
|
|
1436
|
+
})();
|
|
1437
|
+
export { Tempo };
|
|
1405
1438
|
// #endregion Namespace
|
|
1406
1439
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
1407
1440
|
Tempo.init(); // initialize default global configuration
|