@js-joda/core 6.3.0 → 6.3.2
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/js-joda.cjs.js +2469 -2287
- package/dist/js-joda.cjs.js.map +1 -1
- package/dist/js-joda.esm.js +2469 -2287
- package/dist/js-joda.esm.js.map +1 -1
- package/dist/js-joda.js +2469 -2287
- package/dist/js-joda.js.map +1 -1
- package/dist/js-joda.min.js +2 -2
- package/package.json +3 -3
- package/src/Duration.js +98 -7
- package/src/LocalDate.js +2 -1
- package/src/Period.js +1 -1
- package/src/format/DateTimeBuilder.js +94 -65
- package/src/format/EnumMap.js +94 -24
- package/src/zone/SystemDefaultZoneRules.js +27 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@js-joda/core",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.2",
|
|
4
4
|
"description": "a date and time library for javascript",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"test": "NODE_ENV=test npx mocha --timeout 5000 --require @babel/register ./test/*Test_mochaOnly.js ./test/*Test.js ./test/**/*Test.js ./test/**/**/*Test.js",
|
|
32
32
|
"test-coverage": "NODE_ENV=test COVERAGE=1 npx nyc --report-dir=build/coverage --reporter=lcov --reporter html npx mocha --timeout 5000 --require @babel/register --reporter progress ./test/*Test.js ./test/**/*Test.js ./test/**/**/*Test.js",
|
|
33
33
|
"test-browser": "npx karma start --reporters=dots --single-run",
|
|
34
|
-
"test-ci": "npm run build-dist && npm run test && npm run test-browser && npm run test-ts-definitions && npm run test-coverage",
|
|
34
|
+
"test-ci": "npm run build-dist && npm run lint && npm run test && npm run test-browser && npm run test-ts-definitions && npm run test-coverage",
|
|
35
35
|
"test-ts-definitions": "TS_NODE_PROJECT='../../shared/ts-test-config.json' NODE_ENV=test mocha --timeout 5000 --require ts-node/register ./test/typescript_definitions/*.ts",
|
|
36
36
|
"build-dist": "npx rollup -c rollup.config.js",
|
|
37
37
|
"build-md-toc": "npx markdown-toc -i CheatSheet.md",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"sourceMap": false,
|
|
48
48
|
"instrument": false
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "2364adefa7985123ce6d17d4181bf93d0e179351"
|
|
51
51
|
}
|
package/src/Duration.js
CHANGED
|
@@ -315,7 +315,7 @@ export class Duration extends TemporalAmount /*implements TemporalAmount, Compar
|
|
|
315
315
|
/**
|
|
316
316
|
* The pattern for parsing.
|
|
317
317
|
*/
|
|
318
|
-
const PATTERN = new RegExp('([-+]?)P(?:([-+]?[0-9]+)D)?(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)
|
|
318
|
+
const PATTERN = new RegExp('^([-+]?)P(?:([-+]?[0-9]+)D)?(T(?:([-+]?[0-9]+)H)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?$', 'i');
|
|
319
319
|
const matches = PATTERN.exec(text);
|
|
320
320
|
if (matches !== null) {
|
|
321
321
|
// check for letter T but no time sections
|
|
@@ -879,8 +879,11 @@ export class Duration extends TemporalAmount /*implements TemporalAmount, Compar
|
|
|
879
879
|
/**
|
|
880
880
|
* Returns a copy of this duration divided by the specified value.
|
|
881
881
|
*
|
|
882
|
-
*
|
|
883
|
-
*
|
|
882
|
+
* The result is truncated toward zero to the nanosecond, as in threeten.
|
|
883
|
+
* For an integer divisor up to `MAX_SAFE_INTEGER` (positive or negative) the division is exact.
|
|
884
|
+
* Other divisors, with a fraction or beyond `MAX_SAFE_INTEGER`, are not exactly representable
|
|
885
|
+
* or not supported by threeten; they are divided by floating point arithmetic, expect floating
|
|
886
|
+
* point rounding errors.
|
|
884
887
|
*
|
|
885
888
|
* @param {Number} divisor - the value to divide the duration by, positive or negative, not zero
|
|
886
889
|
* @return {Duration} based on this duration divided by the specified divisor, not null
|
|
@@ -902,10 +905,30 @@ export class Duration extends TemporalAmount /*implements TemporalAmount, Compar
|
|
|
902
905
|
seconds += 1;
|
|
903
906
|
nanos -= LocalTime.NANOS_PER_SECOND;
|
|
904
907
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
908
|
+
if (!Number.isInteger(divisor) || Math.abs(divisor) > MAX_SAFE_INTEGER) {
|
|
909
|
+
const secs = MathUtil.intDiv(seconds, divisor);
|
|
910
|
+
const remainder = MathUtil.intMod(seconds, divisor);
|
|
911
|
+
const nos = MathUtil.intDiv(remainder * LocalTime.NANOS_PER_SECOND + nanos, divisor);
|
|
912
|
+
return Duration.ofSeconds(secs, nos);
|
|
913
|
+
}
|
|
914
|
+
// the remainder operator is exact for integers, so are secs and the remainder
|
|
915
|
+
const remainder = seconds % divisor;
|
|
916
|
+
const secs = MathUtil.safeZero((seconds - remainder) / divisor);
|
|
917
|
+
const sign = ((seconds < 0 || nanos < 0) ? -1 : 1) * (divisor < 0 ? -1 : 1);
|
|
918
|
+
const absRemainder = Math.abs(remainder);
|
|
919
|
+
const absNanos = Math.abs(nanos);
|
|
920
|
+
const absDivisor = Math.abs(divisor);
|
|
921
|
+
// the largest remainder for which remainder * NANOS_PER_SECOND + nanos + divisor <= MAX_SAFE_INTEGER
|
|
922
|
+
const headroom = MAX_SAFE_INTEGER - absDivisor - absNanos;
|
|
923
|
+
let quotientNanos;
|
|
924
|
+
if (headroom >= 0 && absRemainder <= MathUtil.intDiv(headroom, LocalTime.NANOS_PER_SECOND)) {
|
|
925
|
+
// the usual case, e.g. dividedBy(60): remainder * NANOS_PER_SECOND + nanos is exact
|
|
926
|
+
quotientNanos = exactFloorDiv(absRemainder * LocalTime.NANOS_PER_SECOND + absNanos, absDivisor);
|
|
927
|
+
} else {
|
|
928
|
+
// remainder * NANOS_PER_SECOND exceeds MAX_SAFE_INTEGER, divide it without computing it
|
|
929
|
+
quotientNanos = divideScaledByNanos(absRemainder, absNanos, absDivisor);
|
|
930
|
+
}
|
|
931
|
+
return Duration.ofSeconds(secs, MathUtil.safeZero(sign * quotientNanos));
|
|
909
932
|
}
|
|
910
933
|
|
|
911
934
|
//-----------------------------------------------------------------------
|
|
@@ -1308,6 +1331,74 @@ export class Duration extends TemporalAmount /*implements TemporalAmount, Compar
|
|
|
1308
1331
|
|
|
1309
1332
|
}
|
|
1310
1333
|
|
|
1334
|
+
/**
|
|
1335
|
+
* `floor(value / divisor)` by floating point division, exact if `value + divisor <= MAX_SAFE_INTEGER`:
|
|
1336
|
+
* with `value = q * divisor + r` and `0 <= r < divisor`, the quotient `value / divisor` is at least
|
|
1337
|
+
* `1 / divisor` below `q + 1`. Rounding it up to `q + 1` would need that distance to be at most half
|
|
1338
|
+
* an ulp of `q + 1`, at most `(q + 1) / 2^53`, so `divisor * (q + 1) >= 2^53`; but
|
|
1339
|
+
* `divisor * (q + 1) <= value + divisor < 2^53`.
|
|
1340
|
+
*
|
|
1341
|
+
* @param {number} value - integer, `0 <= value`
|
|
1342
|
+
* @param {number} divisor - positive integer, `value + divisor <= MAX_SAFE_INTEGER`
|
|
1343
|
+
* @return {number}
|
|
1344
|
+
* @private
|
|
1345
|
+
*/
|
|
1346
|
+
function exactFloorDiv(value, divisor) {
|
|
1347
|
+
return Math.floor(value / divisor);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
/**
|
|
1351
|
+
* Computes `floor((remainder * NANOS_PER_SECOND + nanos) / divisor)` exactly, by long division over
|
|
1352
|
+
* the nine decimal digits of `nanos`, without the intermediate values exceeding `MAX_SAFE_INTEGER`.
|
|
1353
|
+
* Each step divides `rem * 10 + digit`, with `rem < divisor`, by `divisor`:
|
|
1354
|
+
*
|
|
1355
|
+
* * up to `MAX_SAFE_INTEGER / 11`, `rem * 10 + digit + divisor < 11 * divisor` is small enough
|
|
1356
|
+
* for {@link exactFloorDiv}
|
|
1357
|
+
* * above, `rem * 10` itself may exceed `MAX_SAFE_INTEGER`; it is computed as ten additions of `rem`
|
|
1358
|
+
* modulo `divisor`, counting the wrap-arounds, so that all values stay below `divisor`
|
|
1359
|
+
*
|
|
1360
|
+
* @param {number} remainder - integer, `0 <= remainder < divisor`
|
|
1361
|
+
* @param {number} nanos - integer, `0 <= nanos < NANOS_PER_SECOND`
|
|
1362
|
+
* @param {number} divisor - positive integer, at most `MAX_SAFE_INTEGER`
|
|
1363
|
+
* @return {number} the quotient, `0 <= quotient < NANOS_PER_SECOND`
|
|
1364
|
+
* @private
|
|
1365
|
+
*/
|
|
1366
|
+
function divideScaledByNanos(remainder, nanos, divisor) {
|
|
1367
|
+
const directLimit = MathUtil.intDiv(MAX_SAFE_INTEGER, 11);
|
|
1368
|
+
let rem = remainder;
|
|
1369
|
+
let quotient = 0;
|
|
1370
|
+
for (let scale = 100000000; scale >= 1; scale /= 10) {
|
|
1371
|
+
const digit = MathUtil.intDiv(nanos, scale) % 10;
|
|
1372
|
+
let digitQuotient;
|
|
1373
|
+
if (divisor <= directLimit) {
|
|
1374
|
+
const value = rem * 10 + digit;
|
|
1375
|
+
digitQuotient = exactFloorDiv(value, divisor);
|
|
1376
|
+
rem = value - digitQuotient * divisor;
|
|
1377
|
+
} else {
|
|
1378
|
+
// digit < 10 < divisor, so divisor - digit and divisor - rem are positive and exact
|
|
1379
|
+
let acc = 0;
|
|
1380
|
+
digitQuotient = 0;
|
|
1381
|
+
for (let i = 0; i < 10; i++) {
|
|
1382
|
+
if (acc >= divisor - rem) {
|
|
1383
|
+
acc -= divisor - rem;
|
|
1384
|
+
digitQuotient++;
|
|
1385
|
+
} else {
|
|
1386
|
+
acc += rem;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (acc >= divisor - digit) {
|
|
1390
|
+
acc -= divisor - digit;
|
|
1391
|
+
digitQuotient++;
|
|
1392
|
+
} else {
|
|
1393
|
+
acc += digit;
|
|
1394
|
+
}
|
|
1395
|
+
rem = acc;
|
|
1396
|
+
}
|
|
1397
|
+
quotient = quotient * 10 + digitQuotient;
|
|
1398
|
+
}
|
|
1399
|
+
return quotient;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1311
1402
|
export function _init() {
|
|
1312
1403
|
/**
|
|
1313
1404
|
* Constant for a duration of zero.
|
package/src/LocalDate.js
CHANGED
|
@@ -178,9 +178,10 @@ export class LocalDate extends ChronoLocalDate{
|
|
|
178
178
|
*
|
|
179
179
|
* @param {number} [epochDay=0] - the Epoch Day to convert, based on the epoch 1970-01-01
|
|
180
180
|
* @return {LocalDate} the local date, not null
|
|
181
|
-
* @throws {
|
|
181
|
+
* @throws {DateTimeException} if the epoch days exceeds the supported date range
|
|
182
182
|
*/
|
|
183
183
|
static ofEpochDay(epochDay=0) {
|
|
184
|
+
ChronoField.EPOCH_DAY.checkValidValue(epochDay);
|
|
184
185
|
let adjust, adjustCycles, doyEst, yearEst, zeroDay;
|
|
185
186
|
zeroDay = epochDay + DAYS_0000_TO_1970;
|
|
186
187
|
zeroDay -= 60;
|
package/src/Period.js
CHANGED
|
@@ -18,7 +18,7 @@ import { LocalDate } from './LocalDate';
|
|
|
18
18
|
/**
|
|
19
19
|
* The pattern for parsing.
|
|
20
20
|
*/
|
|
21
|
-
const PATTERN =
|
|
21
|
+
const PATTERN = /^([-+]?)P(?:([-+]?[0-9]+)Y)?(?:([-+]?[0-9]+)M)?(?:([-+]?[0-9]+)W)?(?:([-+]?[0-9]+)D)?$/i;
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* A date-based amount of time, such as '2 years, 3 months and 4 days'.
|
|
@@ -13,8 +13,8 @@ import { ResolverStyle } from './ResolverStyle';
|
|
|
13
13
|
|
|
14
14
|
import { IsoChronology } from '../chrono/IsoChronology';
|
|
15
15
|
import { ChronoLocalDate } from '../chrono/ChronoLocalDate';
|
|
16
|
+
import { ChronoLocalDateTime } from '../chrono/ChronoLocalDateTime';
|
|
16
17
|
import { ChronoField } from '../temporal/ChronoField';
|
|
17
|
-
import { IsoFields } from '../temporal/IsoFields';
|
|
18
18
|
import { TemporalAccessor } from '../temporal/TemporalAccessor';
|
|
19
19
|
import { TemporalQueries } from '../temporal/TemporalQueries';
|
|
20
20
|
|
|
@@ -149,9 +149,14 @@ export class DateTimeBuilder extends TemporalAccessor {
|
|
|
149
149
|
}
|
|
150
150
|
// handle standard fields
|
|
151
151
|
this._mergeInstantFields();
|
|
152
|
-
this._resolveFields(resolverStyle);
|
|
153
152
|
this._mergeDate(resolverStyle);
|
|
154
153
|
this._mergeTime(resolverStyle);
|
|
154
|
+
// the other fields may need fields produced by merging, e.g. YEAR from YEAR_OF_ERA
|
|
155
|
+
if (this._resolveFields(resolverStyle)) {
|
|
156
|
+
this._mergeInstantFields();
|
|
157
|
+
this._mergeDate(resolverStyle);
|
|
158
|
+
this._mergeTime(resolverStyle);
|
|
159
|
+
}
|
|
155
160
|
this._resolveTimeInferZeroes(resolverStyle);
|
|
156
161
|
this._crossCheck();
|
|
157
162
|
if (this.excessDays != null && this.excessDays.isZero() === false && this.date != null && this.time != null) {
|
|
@@ -164,28 +169,71 @@ export class DateTimeBuilder extends TemporalAccessor {
|
|
|
164
169
|
}
|
|
165
170
|
|
|
166
171
|
/**
|
|
167
|
-
* Resolves the
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
+
* Resolves the fields that are not {@link ChronoField}s, such as {@link IsoFields} or the
|
|
173
|
+
* fields of `@js-joda/locale` WeekFields, by delegating to their own `resolve()`, as
|
|
174
|
+
* java.time does. A field may resolve to a date, a time or a date-time, or replace itself
|
|
175
|
+
* by other fields (for example a localized day-of-week by {@link ChronoField.DAY_OF_WEEK}),
|
|
176
|
+
* so the fields are visited again after each change. Returns whether anything changed.
|
|
172
177
|
*
|
|
173
178
|
* @param {ResolverStyle} resolverStyle
|
|
174
179
|
* @return {boolean}
|
|
175
180
|
* @private
|
|
176
181
|
*/
|
|
177
182
|
_resolveFields(resolverStyle) {
|
|
178
|
-
let
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
let changes = 0;
|
|
184
|
+
let changed = true;
|
|
185
|
+
while (changed && changes < 100) {
|
|
186
|
+
changed = false;
|
|
187
|
+
const fields = this.fieldValues.keys();
|
|
188
|
+
for (let i = 0; i < fields.length; i++) {
|
|
189
|
+
const field = fields[i];
|
|
190
|
+
if (field instanceof ChronoField || typeof field.resolve !== 'function') {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const resolved = field.resolve(this.fieldValues, this, resolverStyle);
|
|
194
|
+
if (resolved != null) {
|
|
195
|
+
if (resolved instanceof ChronoLocalDateTime) {
|
|
196
|
+
this._resolveMakeChanges(field, resolved.toLocalDate());
|
|
197
|
+
this._resolveMakeChanges(field, resolved.toLocalTime());
|
|
198
|
+
} else if (resolved instanceof ChronoLocalDate || resolved instanceof LocalTime) {
|
|
199
|
+
this._resolveMakeChanges(field, resolved);
|
|
200
|
+
} else {
|
|
201
|
+
throw new DateTimeException(`Unknown type: ${resolved}`);
|
|
202
|
+
}
|
|
203
|
+
changed = true;
|
|
204
|
+
} else if (this.fieldValues.containsKey(field) === false) {
|
|
205
|
+
changed = true;
|
|
206
|
+
}
|
|
207
|
+
if (changed) {
|
|
208
|
+
changes++;
|
|
209
|
+
break; // start again, the fields have changed
|
|
185
210
|
}
|
|
186
211
|
}
|
|
187
212
|
}
|
|
188
|
-
|
|
213
|
+
if (changes === 100) {
|
|
214
|
+
throw new DateTimeException('Badly written field');
|
|
215
|
+
}
|
|
216
|
+
return changes > 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Adds a date or time resolved by a field as {@link ChronoField.EPOCH_DAY} or
|
|
221
|
+
* {@link ChronoField.NANO_OF_DAY}, to be merged with the other fields.
|
|
222
|
+
*
|
|
223
|
+
* @param {TemporalField} targetField
|
|
224
|
+
* @param {ChronoLocalDate|LocalTime} dateOrTime
|
|
225
|
+
* @private
|
|
226
|
+
*/
|
|
227
|
+
_resolveMakeChanges(targetField, dateOrTime) {
|
|
228
|
+
const isDate = dateOrTime instanceof ChronoLocalDate;
|
|
229
|
+
const field = isDate ? ChronoField.EPOCH_DAY : ChronoField.NANO_OF_DAY;
|
|
230
|
+
const value = isDate ? dateOrTime.toEpochDay() : dateOrTime.toNanoOfDay();
|
|
231
|
+
const old = this.fieldValues.get(field);
|
|
232
|
+
if (old != null && old !== value) {
|
|
233
|
+
const oldDateOrTime = isDate ? LocalDate.ofEpochDay(old) : LocalTime.ofNanoOfDay(old);
|
|
234
|
+
throw new DateTimeException(`Conflict found: ${oldDateOrTime} differs from ${dateOrTime} while resolving ${targetField}`);
|
|
235
|
+
}
|
|
236
|
+
this.fieldValues.put(field, value);
|
|
189
237
|
}
|
|
190
238
|
|
|
191
239
|
/**
|
|
@@ -211,28 +259,26 @@ export class DateTimeBuilder extends TemporalAccessor {
|
|
|
211
259
|
*/
|
|
212
260
|
_checkDate(date) {
|
|
213
261
|
if (date != null) {
|
|
262
|
+
if (this.date != null && this.date.equals(date) === false) {
|
|
263
|
+
throw new DateTimeException(`Conflict found: Fields resolved to two different dates: ${this.date} ${date}`);
|
|
264
|
+
}
|
|
214
265
|
this._addObject(date);
|
|
215
|
-
for (const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
continue;
|
|
226
|
-
} else {
|
|
227
|
-
throw ex;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
const val2 = this.fieldValues.get(field);
|
|
231
|
-
if (val1 !== val2) {
|
|
232
|
-
throw new DateTimeException(`Conflict found: Field ${field} ${val1} differs from ${field} ${val2} derived from ${date}`);
|
|
233
|
-
}
|
|
266
|
+
for (const field of this.fieldValues.keys()) {
|
|
267
|
+
if (field instanceof ChronoField && field.isDateBased()) {
|
|
268
|
+
let val1;
|
|
269
|
+
try {
|
|
270
|
+
val1 = date.getLong(field);
|
|
271
|
+
} catch (ex) {
|
|
272
|
+
if (ex instanceof DateTimeException) {
|
|
273
|
+
continue;
|
|
274
|
+
} else {
|
|
275
|
+
throw ex;
|
|
234
276
|
}
|
|
235
277
|
}
|
|
278
|
+
const val2 = this.fieldValues.get(field);
|
|
279
|
+
if (val1 !== val2) {
|
|
280
|
+
throw new DateTimeException(`Conflict found: Field ${field} ${val1} differs from ${field} ${val2} derived from ${date}`);
|
|
281
|
+
}
|
|
236
282
|
}
|
|
237
283
|
}
|
|
238
284
|
}
|
|
@@ -505,20 +551,6 @@ export class DateTimeBuilder extends TemporalAccessor {
|
|
|
505
551
|
this._addFieldValue(ChronoField.SECOND_OF_DAY, zdt.toLocalTime().toSecondOfDay());
|
|
506
552
|
}
|
|
507
553
|
|
|
508
|
-
/**
|
|
509
|
-
* @param {TemporalField} targetField
|
|
510
|
-
* @param {LocalDate} date
|
|
511
|
-
* @private
|
|
512
|
-
*/
|
|
513
|
-
_resolveMakeChanges(targetField, date) {
|
|
514
|
-
const epochDay = date.toEpochDay();
|
|
515
|
-
const old = this.fieldValues.get(ChronoField.EPOCH_DAY);
|
|
516
|
-
this.fieldValues.put(ChronoField.EPOCH_DAY, epochDay);
|
|
517
|
-
if (old != null && old !== epochDay) {
|
|
518
|
-
throw new DateTimeException(`Conflict found: ${LocalDate.ofEpochDay(old)} differs from ${LocalDate.ofEpochDay(epochDay)} while resolving ${targetField}`);
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
554
|
/**
|
|
523
555
|
* Checks the remaining field values against the resolved date and time, and removes the
|
|
524
556
|
* ones that match.
|
|
@@ -540,25 +572,22 @@ export class DateTimeBuilder extends TemporalAccessor {
|
|
|
540
572
|
* @private
|
|
541
573
|
*/
|
|
542
574
|
_crossCheck0(temporal) {
|
|
543
|
-
for (const
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
if (ex instanceof DateTimeException) {
|
|
552
|
-
continue;
|
|
553
|
-
}
|
|
554
|
-
throw ex;
|
|
575
|
+
for (const field of this.fieldValues.keys()) {
|
|
576
|
+
if (temporal.isSupported(field)) {
|
|
577
|
+
let temporalValue;
|
|
578
|
+
try {
|
|
579
|
+
temporalValue = temporal.getLong(field);
|
|
580
|
+
} catch (ex) {
|
|
581
|
+
if (ex instanceof DateTimeException) {
|
|
582
|
+
continue;
|
|
555
583
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
584
|
+
throw ex;
|
|
585
|
+
}
|
|
586
|
+
const value = this.fieldValues.get(field);
|
|
587
|
+
if (temporalValue !== value) {
|
|
588
|
+
throw new DateTimeException(`Cross check failed: ${field} ${temporalValue} vs ${field} ${value}`);
|
|
561
589
|
}
|
|
590
|
+
this.fieldValues.remove(field);
|
|
562
591
|
}
|
|
563
592
|
}
|
|
564
593
|
}
|
package/src/format/EnumMap.js
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
+
* A map from {@link TemporalField} to value.
|
|
8
|
+
*
|
|
9
|
+
* Fields are matched by identity, like the `HashMap` used by java.time. Entries are bucketed
|
|
10
|
+
* by `field.name()`, but fields with the same name are distinct keys: for example the
|
|
11
|
+
* `WeekBasedYear` field of `@js-joda/locale` WeekFields is not `IsoFields.WEEK_BASED_YEAR`.
|
|
12
|
+
*
|
|
7
13
|
* @private
|
|
8
14
|
*/
|
|
9
15
|
export class EnumMap {
|
|
@@ -11,20 +17,49 @@ export class EnumMap {
|
|
|
11
17
|
this._map = {};
|
|
12
18
|
}
|
|
13
19
|
|
|
20
|
+
/**
|
|
21
|
+
* @param {TemporalField} key
|
|
22
|
+
* @return {?Array<{key: TemporalField, value: *}>} the entries with the name of key, or undefined
|
|
23
|
+
* @private
|
|
24
|
+
*/
|
|
25
|
+
_bucket(key){
|
|
26
|
+
const name = key.name();
|
|
27
|
+
return Object.prototype.hasOwnProperty.call(this._map, name) ? this._map[name] : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {TemporalField} key
|
|
32
|
+
* @return {?{key: TemporalField, value: *}} the entry of key, or undefined
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
_entry(key){
|
|
36
|
+
const bucket = this._bucket(key);
|
|
37
|
+
if (bucket != null) {
|
|
38
|
+
for (let i = 0; i < bucket.length; i++) {
|
|
39
|
+
if (bucket[i].key === key) {
|
|
40
|
+
return bucket[i];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
14
47
|
putAll(otherMap){
|
|
15
|
-
|
|
16
|
-
|
|
48
|
+
const keys = otherMap.keys();
|
|
49
|
+
for (let i = 0; i < keys.length; i++) {
|
|
50
|
+
this.set(keys[i], otherMap.get(keys[i]));
|
|
17
51
|
}
|
|
18
52
|
return this;
|
|
19
53
|
}
|
|
20
54
|
|
|
21
55
|
containsKey(key){
|
|
22
|
-
|
|
23
|
-
return
|
|
56
|
+
const entry = this._entry(key);
|
|
57
|
+
return entry != null && entry.value !== undefined;
|
|
24
58
|
}
|
|
25
59
|
|
|
26
60
|
get(key) {
|
|
27
|
-
|
|
61
|
+
const entry = this._entry(key);
|
|
62
|
+
return entry != null ? entry.value : undefined;
|
|
28
63
|
}
|
|
29
64
|
|
|
30
65
|
put(key, val) {
|
|
@@ -32,39 +67,74 @@ export class EnumMap {
|
|
|
32
67
|
}
|
|
33
68
|
|
|
34
69
|
set(key, val) {
|
|
35
|
-
this.
|
|
70
|
+
const entry = this._entry(key);
|
|
71
|
+
if (entry != null) {
|
|
72
|
+
entry.value = val;
|
|
73
|
+
} else {
|
|
74
|
+
const name = key.name();
|
|
75
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
76
|
+
if (this._map.hasOwnProperty(name) === false) {
|
|
77
|
+
this._map[name] = [];
|
|
78
|
+
}
|
|
79
|
+
this._map[name].push({ key, value: val });
|
|
80
|
+
}
|
|
36
81
|
return this;
|
|
37
82
|
}
|
|
38
83
|
|
|
39
84
|
retainAll(keyList){
|
|
40
|
-
const
|
|
41
|
-
for(let i=0; i<keyList.length; i++){
|
|
42
|
-
|
|
43
|
-
|
|
85
|
+
const retained = new EnumMap();
|
|
86
|
+
for (let i = 0; i < keyList.length; i++) {
|
|
87
|
+
if (this.containsKey(keyList[i])) {
|
|
88
|
+
retained.set(keyList[i], this.get(keyList[i]));
|
|
89
|
+
}
|
|
44
90
|
}
|
|
45
|
-
this._map =
|
|
91
|
+
this._map = retained._map;
|
|
46
92
|
return this;
|
|
47
93
|
}
|
|
48
94
|
|
|
49
|
-
/**
|
|
50
|
-
* due to the bad performance of delete we just set the key entry to undefined.
|
|
51
|
-
*
|
|
52
|
-
* this might lead to issues with "null" entries. Calling clear in the end might solve the issue
|
|
53
|
-
* @param key
|
|
54
|
-
* @returns {*}
|
|
55
|
-
*/
|
|
56
95
|
remove(key){
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
96
|
+
const bucket = this._bucket(key);
|
|
97
|
+
if (bucket != null) {
|
|
98
|
+
for (let i = 0; i < bucket.length; i++) {
|
|
99
|
+
if (bucket[i].key === key) {
|
|
100
|
+
const val = bucket[i].value;
|
|
101
|
+
bucket.splice(i, 1);
|
|
102
|
+
if (bucket.length === 0) {
|
|
103
|
+
delete this._map[key.name()];
|
|
104
|
+
}
|
|
105
|
+
return val;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
61
110
|
}
|
|
62
111
|
|
|
63
|
-
|
|
64
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Returns the fields that have a value, a snapshot that is not changed by later updates.
|
|
114
|
+
*
|
|
115
|
+
* @return {TemporalField[]}
|
|
116
|
+
*/
|
|
117
|
+
keys(){
|
|
118
|
+
const keys = [];
|
|
119
|
+
for (const name in this._map) {
|
|
120
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
121
|
+
if (this._map.hasOwnProperty(name)) {
|
|
122
|
+
const bucket = this._map[name];
|
|
123
|
+
for (let i = 0; i < bucket.length; i++) {
|
|
124
|
+
if (bucket[i].value !== undefined) {
|
|
125
|
+
keys.push(bucket[i].key);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return keys;
|
|
65
131
|
}
|
|
66
132
|
|
|
67
133
|
clear(){
|
|
68
134
|
this._map = {};
|
|
69
135
|
}
|
|
136
|
+
|
|
137
|
+
toString(){
|
|
138
|
+
return `{${this.keys().map((key) => `${key}=${this.get(key)}`).join(', ')}}`;
|
|
139
|
+
}
|
|
70
140
|
}
|
|
@@ -7,6 +7,29 @@ import { ZoneRules } from './ZoneRules';
|
|
|
7
7
|
import { ZoneOffset } from '../ZoneOffset';
|
|
8
8
|
import { DateTimeException } from '../errors';
|
|
9
9
|
|
|
10
|
+
// the range of a Date in epoch millis (ECMA-262), a Date outside of it is invalid
|
|
11
|
+
const MAX_DATE_EPOCH_MILLI = 8640000000000000;
|
|
12
|
+
|
|
13
|
+
// the timezone offset of a Date in minutes, as Date.getTimezoneOffset(); an epoch milli outside
|
|
14
|
+
// the range of a Date is clamped to it, so that far instants get the offset at the end of the range
|
|
15
|
+
function timezoneOffsetInMinutes(epochMilli) {
|
|
16
|
+
const clamped = Math.max(-MAX_DATE_EPOCH_MILLI, Math.min(MAX_DATE_EPOCH_MILLI, epochMilli));
|
|
17
|
+
return new Date(clamped).getTimezoneOffset();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// the epoch milli of the instant, clamped to the range of a Date; Instant.toEpochMilli() would
|
|
21
|
+
// overflow for instants like Instant.MAX
|
|
22
|
+
function clampedEpochMilli(instant) {
|
|
23
|
+
const epochSecond = instant.epochSecond();
|
|
24
|
+
if (epochSecond > MAX_DATE_EPOCH_MILLI / 1000) {
|
|
25
|
+
return MAX_DATE_EPOCH_MILLI;
|
|
26
|
+
}
|
|
27
|
+
if (epochSecond < -MAX_DATE_EPOCH_MILLI / 1000) {
|
|
28
|
+
return -MAX_DATE_EPOCH_MILLI;
|
|
29
|
+
}
|
|
30
|
+
return instant.toEpochMilli();
|
|
31
|
+
}
|
|
32
|
+
|
|
10
33
|
export class SystemDefaultZoneRules extends ZoneRules {
|
|
11
34
|
|
|
12
35
|
isFixedOffset(){
|
|
@@ -19,7 +42,7 @@ export class SystemDefaultZoneRules extends ZoneRules {
|
|
|
19
42
|
* @returns {ZoneOffset}
|
|
20
43
|
*/
|
|
21
44
|
offsetOfInstant(instant){
|
|
22
|
-
const offsetInMinutes =
|
|
45
|
+
const offsetInMinutes = timezoneOffsetInMinutes(clampedEpochMilli(instant));
|
|
23
46
|
return ZoneOffset.ofTotalMinutes(offsetInMinutes * -1);
|
|
24
47
|
}
|
|
25
48
|
|
|
@@ -29,7 +52,7 @@ export class SystemDefaultZoneRules extends ZoneRules {
|
|
|
29
52
|
* @returns {ZoneOffset}
|
|
30
53
|
*/
|
|
31
54
|
offsetOfEpochMilli(epochMilli){
|
|
32
|
-
const offsetInMinutes =
|
|
55
|
+
const offsetInMinutes = timezoneOffsetInMinutes(epochMilli);
|
|
33
56
|
return ZoneOffset.ofTotalMinutes(offsetInMinutes * -1);
|
|
34
57
|
}
|
|
35
58
|
|
|
@@ -48,9 +71,9 @@ export class SystemDefaultZoneRules extends ZoneRules {
|
|
|
48
71
|
*/
|
|
49
72
|
offsetOfLocalDateTime(localDateTime){
|
|
50
73
|
const epochMilli = localDateTime.toEpochSecond(ZoneOffset.UTC) * 1000;
|
|
51
|
-
const offsetInMinutesBeforePossibleTransition =
|
|
74
|
+
const offsetInMinutesBeforePossibleTransition = timezoneOffsetInMinutes(epochMilli);
|
|
52
75
|
const epochMilliSystemZone = epochMilli + offsetInMinutesBeforePossibleTransition * 60000;
|
|
53
|
-
const offsetInMinutesAfterPossibleTransition =
|
|
76
|
+
const offsetInMinutesAfterPossibleTransition = timezoneOffsetInMinutes(epochMilliSystemZone);
|
|
54
77
|
return ZoneOffset.ofTotalMinutes(offsetInMinutesAfterPossibleTransition * -1);
|
|
55
78
|
}
|
|
56
79
|
|