@aglyn/shared-util-timestamp 1.0.0-beta.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,242 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Scientific notation values for decimal precision of time equivalents for
19
+ * conversions when calculating time. Small to big uses a positive exponent and
20
+ * big to small uses a negative exponent.
21
+ * @example
22
+ * `MILLI_TO_SEC` - 1e3 equals 1,000 i.e., 1,000 milliseconds in a second
23
+ * `SEC_TO_MILLI` - 1e-3 equals 0.001 i.e., 1/1,000 of a second is a millisecond
24
+ */
25
+ export declare enum TimeExchange {
26
+ NANO_TO_MICRO = 1000,
27
+ NANO_TO_MILLI = 1000000,
28
+ NANO_TO_SEC = 1000000000,
29
+ MICRO_TO_NANO = 0.001,
30
+ MICRO_TO_MILLI = 1000,
31
+ MICRO_TO_SEC = 1000000,
32
+ MILLI_TO_NANO = 0.000001,
33
+ MILLI_TO_MICRO = 0.001,
34
+ MILLI_TO_SEC = 1000,
35
+ SEC_TO_NANO = 1e-9,
36
+ SEC_TO_MICRO = 0.000001,
37
+ SEC_TO_MILLI = 0.001,
38
+ SEC_TO_MIN = 60,
39
+ SEC_TO_HR = 3600,
40
+ SEC_TO_DY = 86400,
41
+ SEC_TO_WK = 604800,
42
+ SEC_TO_MO = 2628000,
43
+ SEC_TO_YR = 31540000,
44
+ MIN_TO_SEC = 0.016667,
45
+ HR_TO_SEC = 0.00027778,
46
+ DY_TO_SEC = 0.000011574,
47
+ WK_TO_SEC = 0.0000016534,
48
+ MO_TO_SEC = 3.8052e-7,
49
+ YR_TO_SEC = 3.171e-8
50
+ }
51
+ /**
52
+ * The timestamp shape, declared structurally (AGL-1207).
53
+ *
54
+ * It used to be `extends FirestoreTimestamp`, which dragged the Firestore
55
+ * client in through TYPE space as well as value space. Everything that
56
+ * consumes an `ITimestamp` wants these five members; nothing wanted the SDK
57
+ * class's identity.
58
+ */
59
+ export interface ITimestamp {
60
+ readonly seconds: number;
61
+ readonly nanoseconds: number;
62
+ toDate(): Date;
63
+ toMillis(): number;
64
+ isEqual(other: ITimestamp): boolean;
65
+ }
66
+ /**
67
+ * A `Timestamp` represents a point in time independent of any time zone or
68
+ * calendar, represented as seconds and fractions of seconds at nanosecond
69
+ * resolution in UTC Epoch time.
70
+ *
71
+ * It is encoded using the Proleptic Gregorian Calendar which extends the
72
+ * Gregorian calendar backwards to year one. It is encoded assuming all minutes
73
+ * are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second
74
+ * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
75
+ * 9999-12-31T23:59:59.999999999Z.
76
+ *
77
+ * Model and logical flow inspired by `@firebase/firestore/lite/timestamp`
78
+ *
79
+ * ## Why it extends `Date` (AGL-1207)
80
+ *
81
+ * It used to extend the Firestore SDK's `Timestamp`. A class `extends` is a
82
+ * hard runtime dependency — not type-only, not tree-shakeable — so every
83
+ * module graph reaching this file loaded the whole Firestore client, and any
84
+ * spec that partially mocked `firebase/firestore` failed at IMPORT time.
85
+ *
86
+ * `Date` is a JS builtin, so extending it costs nothing. Crucially it also
87
+ * keeps every existing write working untouched: Firestore's client validates
88
+ * field values with `instanceof Date` and serialises them from the internal
89
+ * time slot via `getTime()`, so an instance of this class is still written as
90
+ * a real timestamp — not as a map. That was verified against the emulator
91
+ * before this change, including with `valueOf()` overridden below to return a
92
+ * string, which does NOT interfere: the SDK never consults it.
93
+ *
94
+ * A plain custom class would have been rejected outright with
95
+ * `invalid-argument: Unsupported field value`, which is why `Date` and not a
96
+ * bare object is the base.
97
+ */
98
+ /**
99
+ * `Date` at runtime, with two members hidden from its TYPE.
100
+ *
101
+ * `valueOf()` here returns a zero-padded ordering string, `toJSON()` returns
102
+ * `{seconds, nanoseconds, type}`, and `toString()` returns the
103
+ * `Timestamp(seconds=…, nanoseconds=…)` form — all three deliberate, all three
104
+ * predating this change, and all incompatible with `Date`'s own signatures.
105
+ * TypeScript is right to reject the override; the runtime is perfectly happy.
106
+ *
107
+ * Omitting them from the base type keeps every existing behaviour byte-for-byte
108
+ * rather than quietly changing what `JSON.stringify(timestamp)` or a `<`
109
+ * comparison produces — this change is about removing a dependency, not about
110
+ * altering semantics. `Timestamp` is still `Date` at runtime, so `instanceof
111
+ * Date` holds and Firestore serialises it as a timestamp.
112
+ */
113
+ declare const TimestampBase: {
114
+ new (milliseconds: number): Omit<Date, "valueOf" | "toJSON" | "toString">;
115
+ };
116
+ export declare class Timestamp extends TimestampBase implements ITimestamp {
117
+ /**
118
+ * The earliest date supported by Google Firestore timestamps
119
+ * (0001-01-01T00:00:00Z).
120
+ */
121
+ static MIN_SECONDS: number;
122
+ /**
123
+ * The latest date supported by Google Firestore timestamps
124
+ * (9999-12-31T23:59:59Z).
125
+ */
126
+ static MAX_SECONDS: number;
127
+ /**
128
+ * Creates a new {@link Timestamp}.
129
+ *
130
+ * @param seconds - The number of seconds of UTC time since Unix epoch
131
+ * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
132
+ * 9999-12-31T23:59:59Z inclusive.
133
+ * @param nanoseconds - The non-negative fractions of a second at nanosecond
134
+ * resolution. Negative second values with fractions must still have
135
+ * non-negative nanoseconds values that count forward in time. Must be
136
+ * from 0 to 999,999,999 inclusive.
137
+ */
138
+ /**
139
+ * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
140
+ */
141
+ readonly seconds: number;
142
+ /**
143
+ * The fractions of a second at nanosecond resolution.
144
+ *
145
+ * Held as its own field rather than derived from `Date`, which is only
146
+ * millisecond-resolution. Reads coming back from Firestore carry real
147
+ * nanoseconds and are the SDK's own class, not this one; nothing in the repo
148
+ * constructs a sub-millisecond value (`new Timestamp(` has no call sites,
149
+ * and `now()`/`fromMillis()`/`fromDate()` are all millisecond-sourced), so
150
+ * the internal slot losing sub-millisecond precision is not reachable today.
151
+ * The field keeps the public shape exact regardless.
152
+ */
153
+ readonly nanoseconds: number;
154
+ constructor(seconds: number, nanoseconds: number);
155
+ /**
156
+ * Primitive comparator
157
+ * @param left - left operand
158
+ * @param right - right operand
159
+ * @returns -1 if smaller, 0 if equal, 1 if greater
160
+ */
161
+ static comparator<T>(left: T, right: T): number;
162
+ /**
163
+ * Creates a new {@link Timestamp} with the current date, with millisecond
164
+ * precision.
165
+ *
166
+ * @returns a new {@link Timestamp} representing the current date.
167
+ */
168
+ static now(): Timestamp;
169
+ /**
170
+ * Creates a new timestamp from the given date.
171
+ *
172
+ * @param date - The date to convert to a {@link Timestamp} instance
173
+ * @returns {@link Timestamp} equivalent as the provided {@link Date}
174
+ */
175
+ static fromDate(date: Date): ITimestamp;
176
+ /**
177
+ * Creates a new timestamp from the given number of milliseconds, since Unix
178
+ * epoch 1970-01-01T00:00:00Z
179
+ *
180
+ * @param milliseconds - Number of milliseconds
181
+ * @returns New {@link Timestamp} instance from the provided milliseconds
182
+ */
183
+ static fromMillis(milliseconds: number): Timestamp;
184
+ /**
185
+ * Converts a {@link Timestamp} to a JavaScript {@link Date} object. This
186
+ * conversion causes a loss of precision since `Date` objects only support
187
+ * millisecond precision.
188
+ *
189
+ * @returns JavaScript {@link Date} object representing the same point in time
190
+ * as this {@link Timestamp}, with millisecond precision.
191
+ */
192
+ toDate(): Date;
193
+ /**
194
+ * Converts a {@link Timestamp} to a numeric timestamp (in milliseconds since
195
+ * epoch). This operation causes a loss of precision.
196
+ *
197
+ * @returns The time corresponding to this {@link Timestamp}, represented as
198
+ * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
199
+ */
200
+ toMillis(): number;
201
+ /**
202
+ * Returns true if this {@link Timestamp} is equal to the provided one.
203
+ *
204
+ * @param other - The {@link Timestamp} to compare against.
205
+ * @returns true if this {@link Timestamp} is equal to the provided one.
206
+ */
207
+ isEqual(other: ITimestamp): boolean;
208
+ /**
209
+ * Returns a textual representation of this Timestamp.
210
+ */
211
+ toString(): string;
212
+ /**
213
+ * Converts this object to a primitive string, which allows Timestamp objects
214
+ * to be compared using the `>`, `<=`, `>=` and `>` operators.
215
+ *
216
+ * This method returns a string of the form <seconds>.<nanoseconds> where
217
+ * <seconds> is translated to have a non-negative value and both <seconds>
218
+ * and <nanoseconds> are left-padded with zeroes to be a consistent length.
219
+ * Strings with this format then have a lexicographical ordering that matches
220
+ * the expected ordering. The <seconds> translation is done to avoid having
221
+ * a leading negative sign (i.e. a leading '-' character) in its string
222
+ * representation, which would affect its lexicographical ordering.
223
+ */
224
+ valueOf(): string;
225
+ /**
226
+ * Returns a JSON-serializable representation of this Timestamp.
227
+ */
228
+ toJSON(): {
229
+ seconds: number;
230
+ nanoseconds: number;
231
+ type: string;
232
+ };
233
+ /**
234
+ * Returns the difference of `a` to `b` in seconds unless they are equal, in
235
+ * which case it will compare the difference in nanoseconds
236
+ * @param a - an instance of {@link Timestamp} to compare with {@link b}
237
+ * @param b - an instance of {@link Timestamp} to compare against {@link a}
238
+ */
239
+ static difference(a: Timestamp, b: Timestamp): number;
240
+ _compareTo(other: ITimestamp): number;
241
+ }
242
+ export default Timestamp;
@@ -0,0 +1,241 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2022 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * Scientific notation values for decimal precision of time equivalents for
18
+ * conversions when calculating time. Small to big uses a positive exponent and
19
+ * big to small uses a negative exponent.
20
+ * @example
21
+ * `MILLI_TO_SEC` - 1e3 equals 1,000 i.e., 1,000 milliseconds in a second
22
+ * `SEC_TO_MILLI` - 1e-3 equals 0.001 i.e., 1/1,000 of a second is a millisecond
23
+ */ // Multiple members intentionally share the same value: base-1000 metric
24
+ // prefix conversions (nano/micro/milli/sec) recur at the same magnitude.
25
+ /* eslint-disable @typescript-eslint/no-duplicate-enum-values */ export var TimeExchange = /*#__PURE__*/ function(TimeExchange) {
26
+ TimeExchange[TimeExchange["NANO_TO_MICRO"] = 1e3] = "NANO_TO_MICRO";
27
+ TimeExchange[TimeExchange["NANO_TO_MILLI"] = 1e6] = "NANO_TO_MILLI";
28
+ TimeExchange[TimeExchange["NANO_TO_SEC"] = 1e9] = "NANO_TO_SEC";
29
+ TimeExchange[TimeExchange["MICRO_TO_NANO"] = 1e-3] = "MICRO_TO_NANO";
30
+ TimeExchange[TimeExchange["MICRO_TO_MILLI"] = 1e3] = "MICRO_TO_MILLI";
31
+ TimeExchange[TimeExchange["MICRO_TO_SEC"] = 1e6] = "MICRO_TO_SEC";
32
+ TimeExchange[TimeExchange["MILLI_TO_NANO"] = 1e-6] = "MILLI_TO_NANO";
33
+ TimeExchange[TimeExchange["MILLI_TO_MICRO"] = 1e-3] = "MILLI_TO_MICRO";
34
+ TimeExchange[TimeExchange["MILLI_TO_SEC"] = 1e3] = "MILLI_TO_SEC";
35
+ TimeExchange[TimeExchange["SEC_TO_NANO"] = 1e-9] = "SEC_TO_NANO";
36
+ TimeExchange[TimeExchange["SEC_TO_MICRO"] = 1e-6] = "SEC_TO_MICRO";
37
+ TimeExchange[TimeExchange["SEC_TO_MILLI"] = 1e-3] = "SEC_TO_MILLI";
38
+ TimeExchange[TimeExchange["SEC_TO_MIN"] = 6e1] = "SEC_TO_MIN";
39
+ TimeExchange[TimeExchange["SEC_TO_HR"] = 3.6e3] = "SEC_TO_HR";
40
+ TimeExchange[TimeExchange["SEC_TO_DY"] = 8.64e4] = "SEC_TO_DY";
41
+ TimeExchange[TimeExchange["SEC_TO_WK"] = 6.048e5] = "SEC_TO_WK";
42
+ TimeExchange[TimeExchange["SEC_TO_MO"] = 2.628e6] = "SEC_TO_MO";
43
+ TimeExchange[TimeExchange["SEC_TO_YR"] = 3.154e7] = "SEC_TO_YR";
44
+ TimeExchange[TimeExchange["MIN_TO_SEC"] = 1.6667e-2] = "MIN_TO_SEC";
45
+ TimeExchange[TimeExchange["HR_TO_SEC"] = 2.7778e-4] = "HR_TO_SEC";
46
+ TimeExchange[TimeExchange["DY_TO_SEC"] = 1.1574e-5] = "DY_TO_SEC";
47
+ TimeExchange[TimeExchange["WK_TO_SEC"] = 1.6534e-6] = "WK_TO_SEC";
48
+ TimeExchange[TimeExchange["MO_TO_SEC"] = 3.8052e-7] = "MO_TO_SEC";
49
+ TimeExchange[TimeExchange["YR_TO_SEC"] = 3.1710e-8] = "YR_TO_SEC";
50
+ return TimeExchange;
51
+ }({});
52
+ /**
53
+ * A `Timestamp` represents a point in time independent of any time zone or
54
+ * calendar, represented as seconds and fractions of seconds at nanosecond
55
+ * resolution in UTC Epoch time.
56
+ *
57
+ * It is encoded using the Proleptic Gregorian Calendar which extends the
58
+ * Gregorian calendar backwards to year one. It is encoded assuming all minutes
59
+ * are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second
60
+ * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
61
+ * 9999-12-31T23:59:59.999999999Z.
62
+ *
63
+ * Model and logical flow inspired by `@firebase/firestore/lite/timestamp`
64
+ *
65
+ * ## Why it extends `Date` (AGL-1207)
66
+ *
67
+ * It used to extend the Firestore SDK's `Timestamp`. A class `extends` is a
68
+ * hard runtime dependency — not type-only, not tree-shakeable — so every
69
+ * module graph reaching this file loaded the whole Firestore client, and any
70
+ * spec that partially mocked `firebase/firestore` failed at IMPORT time.
71
+ *
72
+ * `Date` is a JS builtin, so extending it costs nothing. Crucially it also
73
+ * keeps every existing write working untouched: Firestore's client validates
74
+ * field values with `instanceof Date` and serialises them from the internal
75
+ * time slot via `getTime()`, so an instance of this class is still written as
76
+ * a real timestamp — not as a map. That was verified against the emulator
77
+ * before this change, including with `valueOf()` overridden below to return a
78
+ * string, which does NOT interfere: the SDK never consults it.
79
+ *
80
+ * A plain custom class would have been rejected outright with
81
+ * `invalid-argument: Unsupported field value`, which is why `Date` and not a
82
+ * bare object is the base.
83
+ */ /**
84
+ * `Date` at runtime, with two members hidden from its TYPE.
85
+ *
86
+ * `valueOf()` here returns a zero-padded ordering string, `toJSON()` returns
87
+ * `{seconds, nanoseconds, type}`, and `toString()` returns the
88
+ * `Timestamp(seconds=…, nanoseconds=…)` form — all three deliberate, all three
89
+ * predating this change, and all incompatible with `Date`'s own signatures.
90
+ * TypeScript is right to reject the override; the runtime is perfectly happy.
91
+ *
92
+ * Omitting them from the base type keeps every existing behaviour byte-for-byte
93
+ * rather than quietly changing what `JSON.stringify(timestamp)` or a `<`
94
+ * comparison produces — this change is about removing a dependency, not about
95
+ * altering semantics. `Timestamp` is still `Date` at runtime, so `instanceof
96
+ * Date` holds and Firestore serialises it as a timestamp.
97
+ */ const TimestampBase = Date;
98
+ export class Timestamp extends TimestampBase {
99
+ /**
100
+ * Primitive comparator
101
+ * @param left - left operand
102
+ * @param right - right operand
103
+ * @returns -1 if smaller, 0 if equal, 1 if greater
104
+ */ static comparator(left, right) {
105
+ return left < right ? -1 : left > right ? 1 : 0;
106
+ }
107
+ /**
108
+ * Creates a new {@link Timestamp} with the current date, with millisecond
109
+ * precision.
110
+ *
111
+ * @returns a new {@link Timestamp} representing the current date.
112
+ */ static now() {
113
+ return this.fromMillis(Date.now());
114
+ }
115
+ /**
116
+ * Creates a new timestamp from the given date.
117
+ *
118
+ * @param date - The date to convert to a {@link Timestamp} instance
119
+ * @returns {@link Timestamp} equivalent as the provided {@link Date}
120
+ */ static fromDate(date) {
121
+ return this.fromMillis(date.getTime());
122
+ }
123
+ /**
124
+ * Creates a new timestamp from the given number of milliseconds, since Unix
125
+ * epoch 1970-01-01T00:00:00Z
126
+ *
127
+ * @param milliseconds - Number of milliseconds
128
+ * @returns New {@link Timestamp} instance from the provided milliseconds
129
+ */ static fromMillis(milliseconds) {
130
+ const seconds = Math.floor(milliseconds / 1e3);
131
+ const nanoseconds = Math.floor((milliseconds - seconds * 1e3) * 1e6);
132
+ return new this(seconds, nanoseconds);
133
+ }
134
+ /**
135
+ * Converts a {@link Timestamp} to a JavaScript {@link Date} object. This
136
+ * conversion causes a loss of precision since `Date` objects only support
137
+ * millisecond precision.
138
+ *
139
+ * @returns JavaScript {@link Date} object representing the same point in time
140
+ * as this {@link Timestamp}, with millisecond precision.
141
+ */ toDate() {
142
+ return new Date(this.toMillis());
143
+ }
144
+ /**
145
+ * Converts a {@link Timestamp} to a numeric timestamp (in milliseconds since
146
+ * epoch). This operation causes a loss of precision.
147
+ *
148
+ * @returns The time corresponding to this {@link Timestamp}, represented as
149
+ * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
150
+ */ toMillis() {
151
+ return this.seconds * 1e3 + this.nanoseconds / 1e6;
152
+ }
153
+ /**
154
+ * Returns true if this {@link Timestamp} is equal to the provided one.
155
+ *
156
+ * @param other - The {@link Timestamp} to compare against.
157
+ * @returns true if this {@link Timestamp} is equal to the provided one.
158
+ */ isEqual(other) {
159
+ return other.seconds === this.seconds && other.nanoseconds === this.nanoseconds;
160
+ }
161
+ /**
162
+ * Returns a textual representation of this Timestamp.
163
+ */ toString() {
164
+ return `Timestamp(seconds=${this.seconds}, nanoseconds=${this.nanoseconds})`;
165
+ }
166
+ /**
167
+ * Converts this object to a primitive string, which allows Timestamp objects
168
+ * to be compared using the `>`, `<=`, `>=` and `>` operators.
169
+ *
170
+ * This method returns a string of the form <seconds>.<nanoseconds> where
171
+ * <seconds> is translated to have a non-negative value and both <seconds>
172
+ * and <nanoseconds> are left-padded with zeroes to be a consistent length.
173
+ * Strings with this format then have a lexicographical ordering that matches
174
+ * the expected ordering. The <seconds> translation is done to avoid having
175
+ * a leading negative sign (i.e. a leading '-' character) in its string
176
+ * representation, which would affect its lexicographical ordering.
177
+ */ valueOf() {
178
+ const adjustedSeconds = this.seconds - Timestamp.MIN_SECONDS;
179
+ // Note: Up to 12 decimal digits are required to represent all valid
180
+ // 'seconds' values.
181
+ const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
182
+ const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
183
+ return formattedSeconds + '.' + formattedNanoseconds;
184
+ }
185
+ /**
186
+ * Returns a JSON-serializable representation of this Timestamp.
187
+ */ toJSON() {
188
+ return {
189
+ seconds: this.seconds,
190
+ nanoseconds: this.nanoseconds,
191
+ type: 'firestore/timestamp/1.0'
192
+ };
193
+ }
194
+ /**
195
+ * Returns the difference of `a` to `b` in seconds unless they are equal, in
196
+ * which case it will compare the difference in nanoseconds
197
+ * @param a - an instance of {@link Timestamp} to compare with {@link b}
198
+ * @param b - an instance of {@link Timestamp} to compare against {@link a}
199
+ */ static difference(a, b) {
200
+ return a._compareTo(b);
201
+ }
202
+ _compareTo(other) {
203
+ if (this.seconds === other.seconds) {
204
+ return Timestamp.comparator(this.nanoseconds, other.nanoseconds);
205
+ }
206
+ return Timestamp.comparator(this.seconds, other.seconds);
207
+ }
208
+ constructor(seconds, nanoseconds){
209
+ // Validation runs BEFORE `super()`, which is legal because it reads only
210
+ // the parameters and never `this`. Order matters: seeding Date with an
211
+ // out-of-range value would otherwise produce an Invalid Date before the
212
+ // error that explains why.
213
+ if (nanoseconds < 0) {
214
+ throw new Error('invalid-argument: timestamp nanoseconds out of range: ' + nanoseconds);
215
+ }
216
+ if (nanoseconds >= 1e9) {
217
+ throw new Error('invalid-argument: timestamp nanoseconds out of range: ' + nanoseconds);
218
+ }
219
+ if (seconds < Timestamp.MIN_SECONDS) {
220
+ throw new Error('invalid-argument: timestamp seconds out of range: ' + seconds);
221
+ }
222
+ // This will break in the year 10,000.
223
+ if (seconds > Timestamp.MAX_SECONDS) {
224
+ throw new Error('invalid-argument: timestamp seconds out of range: ' + seconds);
225
+ }
226
+ super(seconds * 1e3 + nanoseconds / 1e6);
227
+ this.seconds = seconds;
228
+ this.nanoseconds = nanoseconds;
229
+ }
230
+ }
231
+ /**
232
+ * The earliest date supported by Google Firestore timestamps
233
+ * (0001-01-01T00:00:00Z).
234
+ */ Timestamp.MIN_SECONDS = -62135596800;
235
+ /**
236
+ * The latest date supported by Google Firestore timestamps
237
+ * (9999-12-31T23:59:59Z).
238
+ */ Timestamp.MAX_SECONDS = 253402322399;
239
+ export default Timestamp;
240
+
241
+ //# sourceMappingURL=timestamp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/shared/util/timestamp/src/lib/timestamp.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2022 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\n/**\n * Scientific notation values for decimal precision of time equivalents for\n * conversions when calculating time. Small to big uses a positive exponent and\n * big to small uses a negative exponent.\n * @example\n * `MILLI_TO_SEC` - 1e3 equals 1,000 i.e., 1,000 milliseconds in a second\n * `SEC_TO_MILLI` - 1e-3 equals 0.001 i.e., 1/1,000 of a second is a millisecond\n */\n// Multiple members intentionally share the same value: base-1000 metric\n// prefix conversions (nano/micro/milli/sec) recur at the same magnitude.\n/* eslint-disable @typescript-eslint/no-duplicate-enum-values */\nexport enum TimeExchange {\n NANO_TO_MICRO = 1e3,\n NANO_TO_MILLI = 1e6,\n NANO_TO_SEC = 1e9,\n MICRO_TO_NANO = 1e-3,\n MICRO_TO_MILLI = 1e3,\n MICRO_TO_SEC = 1e6,\n MILLI_TO_NANO = 1e-6,\n MILLI_TO_MICRO = 1e-3,\n MILLI_TO_SEC = 1e3,\n SEC_TO_NANO = 1e-9,\n SEC_TO_MICRO = 1e-6,\n SEC_TO_MILLI = 1e-3,\n SEC_TO_MIN = 6e1,\n SEC_TO_HR = 3.6e3,\n SEC_TO_DY = 8.64e4,\n SEC_TO_WK = 6.048e5,\n SEC_TO_MO = 2.628e6,\n SEC_TO_YR = 3.154e7,\n MIN_TO_SEC = 1.6667e-2,\n HR_TO_SEC = 2.7778e-4,\n DY_TO_SEC = 1.1574e-5,\n WK_TO_SEC = 1.6534e-6,\n MO_TO_SEC = 3.8052e-7,\n YR_TO_SEC = 3.1710e-8,\n}\n/* eslint-enable @typescript-eslint/no-duplicate-enum-values */\n\n/**\n * The timestamp shape, declared structurally (AGL-1207).\n *\n * It used to be `extends FirestoreTimestamp`, which dragged the Firestore\n * client in through TYPE space as well as value space. Everything that\n * consumes an `ITimestamp` wants these five members; nothing wanted the SDK\n * class's identity.\n */\nexport interface ITimestamp {\n readonly seconds: number\n readonly nanoseconds: number\n toDate(): Date\n toMillis(): number\n isEqual(other: ITimestamp): boolean\n}\n\n/**\n * A `Timestamp` represents a point in time independent of any time zone or\n * calendar, represented as seconds and fractions of seconds at nanosecond\n * resolution in UTC Epoch time.\n *\n * It is encoded using the Proleptic Gregorian Calendar which extends the\n * Gregorian calendar backwards to year one. It is encoded assuming all minutes\n * are 60 seconds long, i.e. leap seconds are \"smeared\" so that no leap second\n * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to\n * 9999-12-31T23:59:59.999999999Z.\n *\n * Model and logical flow inspired by `@firebase/firestore/lite/timestamp`\n *\n * ## Why it extends `Date` (AGL-1207)\n *\n * It used to extend the Firestore SDK's `Timestamp`. A class `extends` is a\n * hard runtime dependency — not type-only, not tree-shakeable — so every\n * module graph reaching this file loaded the whole Firestore client, and any\n * spec that partially mocked `firebase/firestore` failed at IMPORT time.\n *\n * `Date` is a JS builtin, so extending it costs nothing. Crucially it also\n * keeps every existing write working untouched: Firestore's client validates\n * field values with `instanceof Date` and serialises them from the internal\n * time slot via `getTime()`, so an instance of this class is still written as\n * a real timestamp — not as a map. That was verified against the emulator\n * before this change, including with `valueOf()` overridden below to return a\n * string, which does NOT interfere: the SDK never consults it.\n *\n * A plain custom class would have been rejected outright with\n * `invalid-argument: Unsupported field value`, which is why `Date` and not a\n * bare object is the base.\n */\n/**\n * `Date` at runtime, with two members hidden from its TYPE.\n *\n * `valueOf()` here returns a zero-padded ordering string, `toJSON()` returns\n * `{seconds, nanoseconds, type}`, and `toString()` returns the\n * `Timestamp(seconds=…, nanoseconds=…)` form — all three deliberate, all three\n * predating this change, and all incompatible with `Date`'s own signatures.\n * TypeScript is right to reject the override; the runtime is perfectly happy.\n *\n * Omitting them from the base type keeps every existing behaviour byte-for-byte\n * rather than quietly changing what `JSON.stringify(timestamp)` or a `<`\n * comparison produces — this change is about removing a dependency, not about\n * altering semantics. `Timestamp` is still `Date` at runtime, so `instanceof\n * Date` holds and Firestore serialises it as a timestamp.\n */\nconst TimestampBase = Date as unknown as {\n new (milliseconds: number): Omit<Date, 'valueOf' | 'toJSON' | 'toString'>\n}\n\nexport class Timestamp extends TimestampBase implements ITimestamp {\n /**\n * The earliest date supported by Google Firestore timestamps\n * (0001-01-01T00:00:00Z).\n */\n public static MIN_SECONDS = -62135596800\n\n /**\n * The latest date supported by Google Firestore timestamps\n * (9999-12-31T23:59:59Z).\n */\n public static MAX_SECONDS = 253402322399\n\n /**\n * Creates a new {@link Timestamp}.\n *\n * @param seconds - The number of seconds of UTC time since Unix epoch\n * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to\n * 9999-12-31T23:59:59Z inclusive.\n * @param nanoseconds - The non-negative fractions of a second at nanosecond\n * resolution. Negative second values with fractions must still have\n * non-negative nanoseconds values that count forward in time. Must be\n * from 0 to 999,999,999 inclusive.\n */\n /**\n * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.\n */\n readonly seconds: number\n /**\n * The fractions of a second at nanosecond resolution.\n *\n * Held as its own field rather than derived from `Date`, which is only\n * millisecond-resolution. Reads coming back from Firestore carry real\n * nanoseconds and are the SDK's own class, not this one; nothing in the repo\n * constructs a sub-millisecond value (`new Timestamp(` has no call sites,\n * and `now()`/`fromMillis()`/`fromDate()` are all millisecond-sourced), so\n * the internal slot losing sub-millisecond precision is not reachable today.\n * The field keeps the public shape exact regardless.\n */\n readonly nanoseconds: number\n\n constructor(seconds: number, nanoseconds: number) {\n // Validation runs BEFORE `super()`, which is legal because it reads only\n // the parameters and never `this`. Order matters: seeding Date with an\n // out-of-range value would otherwise produce an Invalid Date before the\n // error that explains why.\n if (nanoseconds < 0) {\n throw new Error(\n 'invalid-argument: timestamp nanoseconds out of range: ' + nanoseconds,\n )\n }\n if (nanoseconds >= 1e9) {\n throw new Error(\n 'invalid-argument: timestamp nanoseconds out of range: ' + nanoseconds,\n )\n }\n if (seconds < Timestamp.MIN_SECONDS) {\n throw new Error(\n 'invalid-argument: timestamp seconds out of range: ' + seconds,\n )\n }\n // This will break in the year 10,000.\n if (seconds > Timestamp.MAX_SECONDS) {\n throw new Error(\n 'invalid-argument: timestamp seconds out of range: ' + seconds,\n )\n }\n super(seconds * TimeExchange.MILLI_TO_SEC + nanoseconds / TimeExchange.NANO_TO_MILLI)\n this.seconds = seconds\n this.nanoseconds = nanoseconds\n }\n\n /**\n * Primitive comparator\n * @param left - left operand\n * @param right - right operand\n * @returns -1 if smaller, 0 if equal, 1 if greater\n */\n public static comparator<T>(left: T, right: T): number {\n return left < right ? -1 : left > right ? 1 : 0\n }\n\n /**\n * Creates a new {@link Timestamp} with the current date, with millisecond\n * precision.\n *\n * @returns a new {@link Timestamp} representing the current date.\n */\n public static now(): Timestamp {\n return this.fromMillis(Date.now())\n }\n\n /**\n * Creates a new timestamp from the given date.\n *\n * @param date - The date to convert to a {@link Timestamp} instance\n * @returns {@link Timestamp} equivalent as the provided {@link Date}\n */\n public static fromDate(date: Date): ITimestamp {\n return this.fromMillis(date.getTime())\n }\n\n /**\n * Creates a new timestamp from the given number of milliseconds, since Unix\n * epoch 1970-01-01T00:00:00Z\n *\n * @param milliseconds - Number of milliseconds\n * @returns New {@link Timestamp} instance from the provided milliseconds\n */\n public static fromMillis(milliseconds: number): Timestamp {\n const seconds = Math.floor(milliseconds / TimeExchange.MILLI_TO_SEC)\n const nanoseconds = Math.floor(\n (milliseconds - seconds * TimeExchange.MILLI_TO_SEC) *\n TimeExchange.NANO_TO_MILLI,\n )\n return new this(seconds, nanoseconds)\n }\n\n /**\n * Converts a {@link Timestamp} to a JavaScript {@link Date} object. This\n * conversion causes a loss of precision since `Date` objects only support\n * millisecond precision.\n *\n * @returns JavaScript {@link Date} object representing the same point in time\n * as this {@link Timestamp}, with millisecond precision.\n */\n public toDate(): Date {\n return new Date(this.toMillis())\n }\n\n /**\n * Converts a {@link Timestamp} to a numeric timestamp (in milliseconds since\n * epoch). This operation causes a loss of precision.\n *\n * @returns The time corresponding to this {@link Timestamp}, represented as\n * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.\n */\n public toMillis(): number {\n return (\n this.seconds * TimeExchange.MILLI_TO_SEC +\n this.nanoseconds / TimeExchange.NANO_TO_MILLI\n )\n }\n\n /**\n * Returns true if this {@link Timestamp} is equal to the provided one.\n *\n * @param other - The {@link Timestamp} to compare against.\n * @returns true if this {@link Timestamp} is equal to the provided one.\n */\n public isEqual(other: ITimestamp): boolean {\n return (\n other.seconds === this.seconds && other.nanoseconds === this.nanoseconds\n )\n }\n\n /**\n * Returns a textual representation of this Timestamp.\n */\n public toString(): string {\n return `Timestamp(seconds=${this.seconds}, nanoseconds=${this.nanoseconds})`\n }\n\n /**\n * Converts this object to a primitive string, which allows Timestamp objects\n * to be compared using the `>`, `<=`, `>=` and `>` operators.\n *\n * This method returns a string of the form <seconds>.<nanoseconds> where\n * <seconds> is translated to have a non-negative value and both <seconds>\n * and <nanoseconds> are left-padded with zeroes to be a consistent length.\n * Strings with this format then have a lexicographical ordering that matches\n * the expected ordering. The <seconds> translation is done to avoid having\n * a leading negative sign (i.e. a leading '-' character) in its string\n * representation, which would affect its lexicographical ordering.\n */\n public valueOf(): string {\n const adjustedSeconds = this.seconds - Timestamp.MIN_SECONDS\n // Note: Up to 12 decimal digits are required to represent all valid\n // 'seconds' values.\n const formattedSeconds = String(adjustedSeconds).padStart(12, '0')\n const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0')\n return formattedSeconds + '.' + formattedNanoseconds\n }\n\n /**\n * Returns a JSON-serializable representation of this Timestamp.\n */\n public toJSON(): { seconds: number; nanoseconds: number; type: string } {\n return {\n seconds: this.seconds,\n nanoseconds: this.nanoseconds,\n type: 'firestore/timestamp/1.0',\n }\n }\n\n /**\n * Returns the difference of `a` to `b` in seconds unless they are equal, in\n * which case it will compare the difference in nanoseconds\n * @param a - an instance of {@link Timestamp} to compare with {@link b}\n * @param b - an instance of {@link Timestamp} to compare against {@link a}\n */\n public static difference(a: Timestamp, b: Timestamp): number {\n return a._compareTo(b)\n }\n\n public _compareTo(other: ITimestamp): number {\n if (this.seconds === other.seconds) {\n return Timestamp.comparator(this.nanoseconds, other.nanoseconds)\n }\n return Timestamp.comparator(this.seconds, other.seconds)\n }\n}\n\nexport default Timestamp\n"],"names":["TimeExchange","TimestampBase","Date","Timestamp","comparator","left","right","now","fromMillis","fromDate","date","getTime","milliseconds","seconds","Math","floor","nanoseconds","toDate","toMillis","isEqual","other","toString","valueOf","adjustedSeconds","MIN_SECONDS","formattedSeconds","String","padStart","formattedNanoseconds","toJSON","type","difference","a","b","_compareTo","Error","MAX_SECONDS"],"mappings":"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"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /** Minutes in a day: the exclusive end of a window that runs to midnight. */
18
+ export declare const MINUTES_PER_DAY: number;
19
+ /** Monday through Friday, in `Date#getUTCDay` numbering. */
20
+ export declare const MONDAY_TO_FRIDAY: readonly number[];
21
+ /** The calendar fields a wall clock in a zone shows for one instant. */
22
+ export interface ZonedDateTime {
23
+ year: number;
24
+ /** 1 January through 12 December. */
25
+ month: number;
26
+ /** 1 through 31. */
27
+ day: number;
28
+ /** 0 through 23. */
29
+ hour: number;
30
+ minute: number;
31
+ second: number;
32
+ /** 0 Sunday through 6 Saturday. */
33
+ weekday: number;
34
+ }
35
+ /**
36
+ * A wall time to find the instant of. Fields overflow the way `Date.UTC`
37
+ * lets them — day 32 of a month is the first of the next — so a caller can
38
+ * step a calendar by adding to `day`.
39
+ */
40
+ export interface ZonedWallTime {
41
+ year: number;
42
+ /** 1 January through 12 December; overflow rolls into the next year. */
43
+ month: number;
44
+ day: number;
45
+ hour?: number;
46
+ minute?: number;
47
+ second?: number;
48
+ }
49
+ /** Whether `Intl` knows the zone, so a stored name can be checked before use. */
50
+ export declare function isValidTimeZone(value: unknown): value is string;
51
+ /**
52
+ * The wall clock in `timeZone` at `atMs`.
53
+ *
54
+ * @throws RangeError for a zone `Intl` does not know, or an instant that is
55
+ * not a finite number.
56
+ */
57
+ export declare function zonedDateTime(atMs: number, timeZone: string): ZonedDateTime;
58
+ /** `YYYY-MM-DD` of the calendar day `atMs` falls in, in `timeZone`. */
59
+ export declare function zonedDayKey(atMs: number, timeZone: string): string;
60
+ /** The wall clock's minute of the day, `hour * 60 + minute`, 0 through 1439. */
61
+ export declare function zonedMinuteOfDay(atMs: number, timeZone: string): number;
62
+ /** The zone's offset from UTC at `atMs`, in milliseconds, positive east. */
63
+ export declare function zoneOffsetMs(atMs: number, timeZone: string): number;
64
+ /**
65
+ * The instant a wall time happens in `timeZone` — see the module note for
66
+ * the two days a year it is ambiguous. Seconds are whole: a millisecond part
67
+ * of `second` is dropped.
68
+ */
69
+ export declare function zonedWallTimeToInstant(wall: ZonedWallTime, timeZone: string): number;
70
+ /** Midnight at the start of the calendar day `atMs` falls in, in `timeZone`. */
71
+ export declare function startOfZonedDay(atMs: number, timeZone: string): number;
72
+ /**
73
+ * Midnight at the start of the NEXT calendar day in `timeZone` — built from
74
+ * the calendar rather than by adding 24 hours, because a day a clock change
75
+ * falls in is 23 or 25 hours long.
76
+ */
77
+ export declare function startOfNextZonedDay(atMs: number, timeZone: string): number;
78
+ /**
79
+ * How many calendar days separate the day `fromMs` falls in from the day
80
+ * `toMs` falls in, in `timeZone`: `0` for the same day, negative when `toMs`
81
+ * is on an earlier one. Counted on the calendar, so a 23-hour day is a day.
82
+ */
83
+ export declare function zonedCalendarDaysBetween(fromMs: number, toMs: number, timeZone: string): number;
84
+ /**
85
+ * `count` business days after `atMs`, at the same wall-clock time, in
86
+ * `timeZone`.
87
+ *
88
+ * The calendar is walked a day at a time from the day after `atMs`, and each
89
+ * day whose weekday is in `businessDays` counts one. The time of day is the
90
+ * wall time `atMs` showed, so a 10:00 send is followed at 10:00 after a clock
91
+ * change as well; a wall time the destination day skips moves forward with
92
+ * the clock. `0` is `atMs` itself, whatever day that is.
93
+ *
94
+ * No holidays: a calendar that knew some countries' holidays would quietly
95
+ * shorten the wait in every country it did not know.
96
+ *
97
+ * @throws RangeError for a count that is not a whole number of days, or a
98
+ * `businessDays` list with no weekday in it.
99
+ */
100
+ export declare function addZonedBusinessDays(atMs: number, count: number, timeZone: string, businessDays?: readonly number[]): number;
101
+ /**
102
+ * One open stretch of a day, in minutes after local midnight: `start`
103
+ * inclusive, `end` exclusive, `0 <= start < end <= 1440`.
104
+ */
105
+ export interface WeeklyInterval {
106
+ start: number;
107
+ end: number;
108
+ }
109
+ /**
110
+ * Open stretches by weekday (`0` Sunday through `6` Saturday), read in one
111
+ * zone. A weekday with no entry is closed.
112
+ */
113
+ export type WeeklySchedule = Readonly<Partial<Record<number, readonly WeeklyInterval[]>>>;
114
+ /** A stretch of a schedule as instants: `startMs` inclusive, `endMs` exclusive. */
115
+ export interface ZonedInterval {
116
+ startMs: number;
117
+ endMs: number;
118
+ }
119
+ /**
120
+ * The stretch of `schedule` that contains `atMs`, or else the next one to
121
+ * open after it — `startMs <= atMs` tells the two apart — or `null` when the
122
+ * schedule never opens.
123
+ *
124
+ * Each end of a stretch is placed on the calendar by
125
+ * {@link zonedWallTimeToInstant}, so the day a clock changes is measured as
126
+ * it is lived: 09:00–17:00 is eight hours of wall clock either way, a time
127
+ * the spring gap skips is read the way that function reads it, and a stretch
128
+ * that comes out empty — it starts inside the gap and ends as the gap ends —
129
+ * does not open that day.
130
+ */
131
+ export declare function nextWeeklyOpening(atMs: number, schedule: WeeklySchedule, timeZone: string): ZonedInterval | null;
132
+ /** Whether `atMs` falls inside an open stretch of `schedule`. */
133
+ export declare function isWithinWeeklySchedule(atMs: number, schedule: WeeklySchedule, timeZone: string): boolean;