@js-joda/core 6.0.1 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@js-joda/core",
3
- "version": "6.0.1",
3
+ "version": "6.1.0",
4
4
  "description": "a date and time library for javascript",
5
5
  "repository": {
6
6
  "type": "git",
@@ -47,5 +47,5 @@
47
47
  "sourceMap": false,
48
48
  "instrument": false
49
49
  },
50
- "gitHead": "a7f1bec40afb1e84ffc0ce2d88d72d690fe72f6a"
50
+ "gitHead": "d82df374b19b312796817f40945c22783536170b"
51
51
  }
package/src/Duration.js CHANGED
@@ -1209,9 +1209,22 @@ export class Duration extends TemporalAmount /*implements TemporalAmount, Compar
1209
1209
  if (this === Duration.ZERO) {
1210
1210
  return 'PT0S';
1211
1211
  }
1212
- const hours = MathUtil.intDiv(this._seconds, LocalTime.SECONDS_PER_HOUR);
1213
- const minutes = MathUtil.intDiv(MathUtil.intMod(this._seconds, LocalTime.SECONDS_PER_HOUR), LocalTime.SECONDS_PER_MINUTE);
1214
- const secs = MathUtil.intMod(this._seconds, LocalTime.SECONDS_PER_MINUTE);
1212
+ // A negative duration is stored as a negative second count plus a
1213
+ // non-negative nano offset, e.g. -0.5s is held as seconds = -1,
1214
+ // nanos = 500000000. To render ISO-8601 components that all carry the
1215
+ // duration's sign, recover the signed whole-second part and the
1216
+ // magnitude of the sub-second fraction separately: when the duration is
1217
+ // negative and has a fractional offset, one second of that offset
1218
+ // belongs to the whole part, and the remaining fraction is its
1219
+ // complement against a full second.
1220
+ const negativeFraction = this._seconds < 0 && this._nanos > 0;
1221
+ const wholeSeconds = negativeFraction ? this._seconds + 1 : this._seconds;
1222
+ const fractionNanos = negativeFraction ? LocalTime.NANOS_PER_SECOND - this._nanos : this._nanos;
1223
+
1224
+ const hours = MathUtil.intDiv(wholeSeconds, LocalTime.SECONDS_PER_HOUR);
1225
+ const minutes = MathUtil.intDiv(MathUtil.intMod(wholeSeconds, LocalTime.SECONDS_PER_HOUR), LocalTime.SECONDS_PER_MINUTE);
1226
+ const secs = MathUtil.intMod(wholeSeconds, LocalTime.SECONDS_PER_MINUTE);
1227
+
1215
1228
  let rval = 'PT';
1216
1229
  if (hours !== 0) {
1217
1230
  rval += `${hours}H`;
@@ -1219,32 +1232,28 @@ export class Duration extends TemporalAmount /*implements TemporalAmount, Compar
1219
1232
  if (minutes !== 0) {
1220
1233
  rval += `${minutes}M`;
1221
1234
  }
1222
- if (secs === 0 && this._nanos === 0 && rval.length > 2) {
1235
+ if (secs === 0 && fractionNanos === 0 && rval.length > 2) {
1223
1236
  return rval;
1224
1237
  }
1225
- if (secs < 0 && this._nanos > 0) {
1226
- if (secs === -1) {
1227
- rval += '-0';
1228
- } else {
1229
- rval += secs + 1;
1230
- }
1238
+ // The seconds field needs an explicit minus sign when the whole-second
1239
+ // part is zero but the duration is a negative fraction (e.g. -0.5s
1240
+ // prints as "-0.5S", not "0.5S").
1241
+ if (secs === 0 && negativeFraction) {
1242
+ rval += '-0';
1231
1243
  } else {
1232
- rval += secs;
1244
+ rval += `${secs}`;
1233
1245
  }
1234
- if (this._nanos > 0) {
1235
- rval += '.';
1236
- let nanoString;
1237
- if (secs < 0) {
1238
- nanoString = `${2 * LocalTime.NANOS_PER_SECOND - this._nanos}`;
1239
- } else {
1240
- nanoString = `${LocalTime.NANOS_PER_SECOND + this._nanos}`;
1246
+ if (fractionNanos > 0) {
1247
+ // fractionNanos is a magnitude in [1, 1e9); print it as a fixed
1248
+ // nine-digit fraction with any trailing zeros removed.
1249
+ let fraction = `${fractionNanos}`;
1250
+ while (fraction.length < 9) {
1251
+ fraction = `0${fraction}`;
1241
1252
  }
1242
- // remove the leading '1'
1243
- nanoString = nanoString.slice(1, nanoString.length);
1244
- rval += nanoString;
1245
- while (rval.charAt(rval.length - 1) === '0') {
1246
- rval = rval.slice(0, rval.length - 1);
1253
+ while (fraction.charAt(fraction.length - 1) === '0') {
1254
+ fraction = fraction.slice(0, fraction.length - 1);
1247
1255
  }
1256
+ rval += `.${fraction}`;
1248
1257
  }
1249
1258
  rval += 'S';
1250
1259
  return rval;
package/src/Instant.js CHANGED
@@ -520,7 +520,7 @@ export class Instant extends Temporal {
520
520
  throw new DateTimeException('Unit must divide into a standard day without remainder');
521
521
  }
522
522
  const nod = MathUtil.intMod(this._seconds, LocalTime.SECONDS_PER_DAY) * LocalTime.NANOS_PER_SECOND + this._nanos;
523
- const result = MathUtil.intDiv(nod, dur) * dur;
523
+ const result = MathUtil.floorDiv(nod, dur) * dur;
524
524
  return this.plusNanos(result - nod);
525
525
  }
526
526
 
@@ -1516,7 +1516,7 @@ class InstantPrinterParser {
1516
1516
  const parser = new DateTimeFormatterBuilder()
1517
1517
  .append(DateTimeFormatter.ISO_LOCAL_DATE).appendLiteral('T')
1518
1518
  .appendValue(ChronoField.HOUR_OF_DAY, 2).appendLiteral(':').appendValue(ChronoField.MINUTE_OF_HOUR, 2).appendLiteral(':')
1519
- .appendValue(ChronoField.SECOND_OF_MINUTE, 2).appendFraction(ChronoField.NANO_OF_SECOND, minDigits, maxDigits, true).appendLiteral('Z')
1519
+ .appendValue(ChronoField.SECOND_OF_MINUTE, 2).appendFraction(ChronoField.NANO_OF_SECOND, minDigits, maxDigits, true).appendOffsetId()
1520
1520
  .toFormatter()._toPrinterParser(false);
1521
1521
  const pos = parser.parse(newContext, text, position);
1522
1522
  if (pos < 0) {
@@ -1531,6 +1531,10 @@ class InstantPrinterParser {
1531
1531
  const min = newContext.getParsed(ChronoField.MINUTE_OF_HOUR);
1532
1532
  const secVal = newContext.getParsed(ChronoField.SECOND_OF_MINUTE);
1533
1533
  const nanoVal = newContext.getParsed(ChronoField.NANO_OF_SECOND);
1534
+ // The instant is parsed with an offset id (which also accepts 'Z' as
1535
+ // zero), so resolve against the parsed offset rather than assuming UTC.
1536
+ const offsetSecs = newContext.getParsed(ChronoField.OFFSET_SECONDS);
1537
+ const offset = (offsetSecs != null ? offsetSecs : 0);
1534
1538
  let sec = (secVal != null ? secVal : 0);
1535
1539
  const nano = (nanoVal != null ? nanoVal : 0);
1536
1540
  const year = MathUtil.intMod(yearParsed, 10000);
@@ -1545,7 +1549,7 @@ class InstantPrinterParser {
1545
1549
  let instantSecs;
1546
1550
  try {
1547
1551
  const ldt = LocalDateTime.of(year, month, day, hour, min, sec, 0).plusDays(days);
1548
- instantSecs = ldt.toEpochSecond(ZoneOffset.UTC);
1552
+ instantSecs = ldt.toEpochSecond(ZoneOffset.ofTotalSeconds(offset));
1549
1553
  instantSecs += MathUtil.safeMultiply(MathUtil.intDiv(yearParsed, 10000), SECONDS_PER_10000_YEARS);
1550
1554
  } catch (ex) {
1551
1555
  return ~position;
@@ -9,6 +9,7 @@ import { assert, requireNonNull } from '../assert';
9
9
  import { DateTimeBuilder } from './DateTimeBuilder';
10
10
  import { EnumMap } from './EnumMap';
11
11
 
12
+ import { Period } from '../Period';
12
13
  import { IsoChronology } from '../chrono/IsoChronology';
13
14
  import { Temporal } from '../temporal/Temporal';
14
15
  import { TemporalQueries } from '../temporal/TemporalQueries';
@@ -244,6 +245,7 @@ class Parsed extends Temporal {
244
245
  this.zone = null;
245
246
  this.fieldValues = new EnumMap();
246
247
  this.leapSecond = false;
248
+ this.excessDays = Period.ZERO;
247
249
  this.dateTimeParseContext = dateTimeParseContext;
248
250
  }
249
251
 
@@ -71,7 +71,67 @@ export class ZoneIdPrinterParser {
71
71
  return ~position;
72
72
  }
73
73
 
74
- // handle fixed time-zone IDs
74
+ // A fixed ID (Z, UT, UTC, GMT, an offset) can also be the start of a
75
+ // longer region id such as GMT0 or Zulu, so parse both and keep the
76
+ // longest match (as the class doc above promises).
77
+ const region = this._parseRegion(text, position);
78
+ const fixedEndPos = this._parseFixedId(context, text, position);
79
+
80
+ const fixedLength = fixedEndPos >= 0 ? fixedEndPos - position : 0;
81
+ if (region != null && region.parseLength > fixedLength) {
82
+ context.setParsedZone(ZoneRegion.ofId(region.parsedZoneId));
83
+ return position + region.parseLength;
84
+ }
85
+ return fixedEndPos;
86
+ }
87
+
88
+ /**
89
+ * Finds the longest available zone region id matching text at position,
90
+ * without touching the parse context.
91
+ *
92
+ * @param {String} text
93
+ * @param {number} position
94
+ * @return {?{parsedZoneId: string, parseLength: number}}
95
+ */
96
+ _parseRegion(text, position) {
97
+ const availableZoneIds = ZoneRulesProvider.getAvailableZoneIds();
98
+ if (availableZoneIds.length === 0) {
99
+ return null;
100
+ }
101
+ if (zoneIdTree.size !== availableZoneIds.length) {
102
+ zoneIdTree = ZoneIdTree.createTreeMap(availableZoneIds);
103
+ }
104
+
105
+ const maxParseLength = text.length - position;
106
+ let treeMap = zoneIdTree.treeMap;
107
+ let parsedZoneId = null;
108
+ let parseLength = 0;
109
+ while(treeMap != null) {
110
+ const parsedSubZoneId = text.substr(position, Math.min(treeMap.length, maxParseLength));
111
+ treeMap = treeMap.get(parsedSubZoneId);
112
+ if (treeMap != null && treeMap.isLeaf) {
113
+ parsedZoneId = parsedSubZoneId;
114
+ parseLength = treeMap.length;
115
+ }
116
+ }
117
+ if (parsedZoneId == null) {
118
+ return null;
119
+ }
120
+ return { parsedZoneId, parseLength };
121
+ }
122
+
123
+ /**
124
+ * Parses a fixed zone id (an offset, a UT/UTC/GMT prefixed offset, SYSTEM
125
+ * or Z) and sets it on the context. Returns the end position, or ~position
126
+ * if no fixed id matches at position.
127
+ *
128
+ * @param {DateTimeParseContext} context
129
+ * @param {String} text
130
+ * @param {number} position
131
+ * @return {number}
132
+ */
133
+ _parseFixedId(context, text, position) {
134
+ const length = text.length;
75
135
  const nextChar = text.charAt(position);
76
136
  if (nextChar === '+' || nextChar === '-') {
77
137
  const newContext = context.copy();
@@ -80,8 +140,7 @@ export class ZoneIdPrinterParser {
80
140
  return endPos;
81
141
  }
82
142
  const offset = newContext.getParsed(ChronoField.OFFSET_SECONDS);
83
- const zone = ZoneOffset.ofTotalSeconds(offset);
84
- context.setParsedZone(zone);
143
+ context.setParsedZone(ZoneOffset.ofTotalSeconds(offset));
85
144
  return endPos;
86
145
  } else if (length >= position + 2) {
87
146
  const nextNextChar = text.charAt(position + 1);
@@ -104,35 +163,10 @@ export class ZoneIdPrinterParser {
104
163
  context.setParsedZone(ZoneId.systemDefault());
105
164
  return position + 6;
106
165
  }
107
-
108
- // ...
109
166
  if (context.charEquals(nextChar, 'Z')) {
110
167
  context.setParsedZone(ZoneOffset.UTC);
111
168
  return position + 1;
112
169
  }
113
-
114
- const availableZoneIds = ZoneRulesProvider.getAvailableZoneIds();
115
- if (zoneIdTree.size !== availableZoneIds.length) {
116
- zoneIdTree = ZoneIdTree.createTreeMap(availableZoneIds);
117
- }
118
-
119
- const maxParseLength = length - position;
120
- let treeMap = zoneIdTree.treeMap;
121
- let parsedZoneId = null;
122
- let parseLength = 0;
123
- while(treeMap != null) {
124
- const parsedSubZoneId = text.substr(position, Math.min(treeMap.length, maxParseLength));
125
- treeMap = treeMap.get(parsedSubZoneId);
126
- if (treeMap != null && treeMap.isLeaf) {
127
- parsedZoneId = parsedSubZoneId;
128
- parseLength = treeMap.length;
129
- }
130
- }
131
- if (parsedZoneId != null) {
132
- context.setParsedZone(ZoneRegion.ofId(parsedZoneId));
133
- return position + parseLength;
134
- }
135
-
136
170
  return ~position;
137
171
  }
138
172
 
@@ -110,7 +110,7 @@ declare module "js-joda" {
110
110
  toNanos(): number;
111
111
  toHoursPart(): number;
112
112
  toMinutesPart(): number;
113
- toSecondspart(): number;
113
+ toSecondsPart(): number;
114
114
  toMillisPart(): number;
115
115
  toString(): string;
116
116
  units(): any;