@fable-org/fable-library-js 2.6.0 → 2.7.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/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- last_commit_released: bf694309ae5dbc6bb546d9acc4d091f5e35fe330
2
+ last_commit_released: c49d1ec5d1a491bfff65fe4bd6c71014cac9b321
3
3
  updaters:
4
4
  - package.json:
5
5
  file: package.json
@@ -15,6 +15,19 @@ All notable changes to this project will be documented in this file.
15
15
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
16
16
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
17
17
 
18
+ ## 2.7.0 - 2026-09-08
19
+
20
+ ### 🚀 Features
21
+
22
+ * Add Temporal dates and times (opt-in) (#4759) ([3e8f56f7](https://github.com/fable-compiler/Fable/commit/3e8f56f78345fbee8a4394c5452eb212264701f9))
23
+
24
+ ### 🐞 Bug Fixes
25
+
26
+ * *(js/ts)* Match .NET for G, E and custom numeric formats ([6fb03090](https://github.com/fable-compiler/Fable/commit/6fb03090ce328b8c5d6a36422f120dd641aefe3a))
27
+ * *(js/ts)* Allow the E format specifier on Int64 and UInt64 ([c49d1ec5](https://github.com/fable-compiler/Fable/commit/c49d1ec5d1a491bfff65fe4bd6c71014cac9b321))
28
+
29
+ <strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/bf694309ae5dbc6bb546d9acc4d091f5e35fe330..c49d1ec5d1a491bfff65fe4bd6c71014cac9b321)</small></strong>
30
+
18
31
  ## 2.6.0 - 2026-09-03
19
32
 
20
33
  ### 🚀 Features
@@ -0,0 +1,146 @@
1
+ import { dateTime as DateTime_stamp } from "./DateTimeTemporal.js";
2
+ import { fromDate as DateTimeOffset_fromDate } from "./DateTimeOffsetTemporal.js";
3
+ import { Exception, DateTimeKind, padWithZeros } from "./Util.js";
4
+ export const PlainDate = Temporal.PlainDate;
5
+ // The generic equality/comparison/hashing helpers in Util.ts dispatch at runtime
6
+ // on .NET-style Equals/CompareTo/GetHashCode methods (with a special case for JS
7
+ // Date, which Temporal.PlainDate cannot benefit from). Attach them so DateOnly
8
+ // also works in generic contexts: records, tuples, erased generics, etc.
9
+ const proto = Temporal.PlainDate.prototype;
10
+ proto.Equals = function (other) { return this.equals(other); };
11
+ proto.CompareTo = function (other) { return Temporal.PlainDate.compare(this, other); };
12
+ proto.GetHashCode = function () { return dayNumber(this); };
13
+ // Lets String.Format format this value without String.ts importing this module (see String.ts)
14
+ proto[Symbol.for("Fable.DateTimeFormattable")] = function (format) { return toString(this, format); };
15
+ export function create(year, month, day) {
16
+ return new Temporal.PlainDate(year, month, day);
17
+ }
18
+ export function maxValue() {
19
+ return new Temporal.PlainDate(9999, 12, 31);
20
+ }
21
+ export function minValue() {
22
+ return new Temporal.PlainDate(1, 1, 1);
23
+ }
24
+ export function dayNumber(d) {
25
+ return minValue().until(d).days;
26
+ }
27
+ export function fromDayNumber(dayNumber) {
28
+ return minValue().add({ days: dayNumber });
29
+ }
30
+ export function fromDateTime(d) {
31
+ // Under the Temporal representation a DateTime is a PlainDateTime (kind-agnostic wall-clock)
32
+ return new Temporal.PlainDate(d.year, d.month, d.day);
33
+ }
34
+ export function dayOfWeek(d) {
35
+ // Temporal: Monday = 1 ... Sunday = 7, .NET: Sunday = 0 ... Saturday = 6
36
+ return d.dayOfWeek % 7;
37
+ }
38
+ export function toDateTime(d, time, kind = DateTimeKind.Unspecified) {
39
+ return DateTime_stamp(d.toPlainDateTime(time), kind);
40
+ }
41
+ export function toDateTimeOffset(d, time, offset) {
42
+ return DateTimeOffset_fromDate(DateTime_stamp(d.toPlainDateTime(time), DateTimeKind.Unspecified), offset);
43
+ }
44
+ export function addDays(d, v) {
45
+ return d.add({ days: v });
46
+ }
47
+ export function addMonths(d, v) {
48
+ return d.add({ months: v });
49
+ }
50
+ export function addYears(d, v) {
51
+ return d.add({ years: v });
52
+ }
53
+ export function equals(x, y) {
54
+ return x.equals(y);
55
+ }
56
+ export function compare(x, y) {
57
+ return Temporal.PlainDate.compare(x, y);
58
+ }
59
+ export function hash(d) {
60
+ return dayNumber(d);
61
+ }
62
+ export function toString(d, format = "d", _provider) {
63
+ switch (format) {
64
+ case "d":
65
+ return `${padWithZeros(d.month, 2)}/${padWithZeros(d.day, 2)}/${padWithZeros(d.year, 4)}`;
66
+ case "o":
67
+ case "O":
68
+ // PlainDate.toString() is the ISO yyyy-MM-dd round-trip format
69
+ return d.toString();
70
+ default:
71
+ throw new Exception("Custom formats are not supported");
72
+ }
73
+ }
74
+ export function parse(str) {
75
+ function fail() {
76
+ throw new Exception(`String '${str}' was not recognized as a valid DateOnly.`);
77
+ }
78
+ // Allowed separators: . , / -
79
+ // TODO whitespace alone as the separator
80
+ //
81
+ // Whitespace around separators
82
+ //
83
+ // Allowed format types:
84
+ // yyyy/mm/dd
85
+ // mm/dd/yyyy
86
+ // mm/dd
87
+ // mm/yyyy
88
+ // yyyy/mm
89
+ const r = /^\s*(\d{1,4})(?:\s*[.,-\/]\s*(\d{1,2}))?\s*[.,-\/]\s*(\d{1,4})\s*$/.exec(str);
90
+ if (r != null) {
91
+ let y = 0;
92
+ let m = 0;
93
+ let d = 1;
94
+ if (r[2] == null) {
95
+ if (r[1].length < 3) {
96
+ if (r[3].length < 3) {
97
+ // 12/30 = December 30, {CurrentYear}
98
+ y = Temporal.Now.plainDateISO().year;
99
+ m = +r[1];
100
+ d = +r[3];
101
+ }
102
+ else {
103
+ // 12/2000 = December 1, 2000
104
+ m = +r[1];
105
+ y = +r[3];
106
+ }
107
+ }
108
+ else {
109
+ if (r[3].length > 2)
110
+ fail();
111
+ // 2000/12 = December 1, 2000
112
+ y = +r[1];
113
+ m = +r[3];
114
+ }
115
+ }
116
+ else {
117
+ // 2000/1/30 or 1/30/2000
118
+ const yearFirst = r[1].length > 2;
119
+ const yTmp = r[yearFirst ? 1 : 3];
120
+ y = +yTmp;
121
+ // year 0-29 is 2000-2029, 30-99 is 1930-1999
122
+ if (yTmp.length < 3)
123
+ y += y >= 30 ? 1900 : 2000;
124
+ m = +r[yearFirst ? 2 : 1];
125
+ d = +r[yearFirst ? 3 : 2];
126
+ }
127
+ if (y > 0) {
128
+ try {
129
+ return new Temporal.PlainDate(y, m, d);
130
+ }
131
+ catch {
132
+ return fail();
133
+ }
134
+ }
135
+ }
136
+ return fail();
137
+ }
138
+ export function tryParse(v, defValue) {
139
+ try {
140
+ defValue.contents = parse(v);
141
+ return true;
142
+ }
143
+ catch {
144
+ return false;
145
+ }
146
+ }
@@ -0,0 +1,523 @@
1
+ /**
2
+ * Field-based .NET date/time formatting and lenient parsing, shared by the
3
+ * Temporal DateTime / DateTimeOffset representations.
4
+ *
5
+ * The formatters operate on a plain `DateInfo` (wall-clock fields), which the
6
+ * Temporal types expose directly (`d.year`, `d.hour`, `d.dayOfWeek % 7`, ...),
7
+ * so no JS-Date intermediate is needed there.
8
+ *
9
+ * Parsing is the exception. .NET accepts far more than ISO 8601 ("12/30/2009",
10
+ * "June 15, 2009 1:45 PM"), and the JS `Date` built-in is the only lenient date
11
+ * parser the platform offers — Temporal's `from` is strict ISO and rejects all of
12
+ * it. So `parseRaw` uses `Date` as a parsing engine and returns the instant it
13
+ * resolved, which callers convert into the Temporal value they need. That instant
14
+ * is only millisecond-precise, hence `subMillisecondTicks`: the finer digits are
15
+ * read straight off the input, because `Date` has already discarded them.
16
+ *
17
+ * This is a port of the equivalent logic in Date.ts / DateOffset.ts, kept
18
+ * separate so the Temporal modules do not depend on the JS-Date representation.
19
+ */
20
+ import { Exception, DateTimeKind, padWithZeros } from "./Util.js";
21
+ // The 7 fractional-second digits .NET works with, most significant first.
22
+ function fractionDigits(info) {
23
+ return padWithZeros(info.tick, 7);
24
+ }
25
+ const shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
26
+ const longDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
27
+ const shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
28
+ const longMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
29
+ function parseRepeatToken(format, pos, patternChar) {
30
+ let tokenLength = 0;
31
+ let internalPos = pos;
32
+ while (internalPos < format.length && format[internalPos] === patternChar) {
33
+ internalPos++;
34
+ tokenLength++;
35
+ }
36
+ return tokenLength;
37
+ }
38
+ function parseNextChar(format, pos) {
39
+ if (pos >= format.length - 1) {
40
+ return -1;
41
+ }
42
+ return format.charCodeAt(pos + 1);
43
+ }
44
+ function parseQuotedString(format, pos) {
45
+ let beginPos = pos;
46
+ // Get the character used to quote the string
47
+ const quoteChar = format[pos];
48
+ let result = "";
49
+ let foundQuote = false;
50
+ while (pos < format.length) {
51
+ pos++;
52
+ const currentChar = format[pos];
53
+ if (currentChar === quoteChar) {
54
+ foundQuote = true;
55
+ break;
56
+ }
57
+ else if (currentChar === "\\") {
58
+ if (pos < format.length) {
59
+ pos++;
60
+ result += format[pos];
61
+ }
62
+ else {
63
+ // This means that '\' is the last character in the string.
64
+ throw new Exception("Invalid string format");
65
+ }
66
+ }
67
+ else {
68
+ result += currentChar;
69
+ }
70
+ }
71
+ if (!foundQuote) {
72
+ // We could not find the matching quote
73
+ throw new Exception(`Invalid string format could not find matching quote for ${quoteChar}`);
74
+ }
75
+ return [result, pos - beginPos + 1];
76
+ }
77
+ // Custom format string. `kind` decides the K/z tokens; `hostOffsetString`
78
+ // (e.g. "+01:00") is the host-zone offset at this wall-clock, used for the
79
+ // Local / Unspecified cases.
80
+ export function dateToStringWithCustomFormat(info, format, kind, hostOffsetString) {
81
+ let cursorPos = 0;
82
+ let tokenLength = 0;
83
+ let result = "";
84
+ while (cursorPos < format.length) {
85
+ const token = format[cursorPos];
86
+ switch (token) {
87
+ case "d":
88
+ tokenLength = parseRepeatToken(format, cursorPos, "d");
89
+ cursorPos += tokenLength;
90
+ switch (tokenLength) {
91
+ case 1:
92
+ result += info.day;
93
+ break;
94
+ case 2:
95
+ result += padWithZeros(info.day, 2);
96
+ break;
97
+ case 3:
98
+ result += shortDays[info.dayOfWeek];
99
+ break;
100
+ case 4:
101
+ default:
102
+ result += longDays[info.dayOfWeek];
103
+ break;
104
+ }
105
+ break;
106
+ case "f":
107
+ // The first `tokenLength` fractional-second digits, always padded.
108
+ tokenLength = parseRepeatToken(format, cursorPos, "f");
109
+ cursorPos += tokenLength;
110
+ if (tokenLength <= 7) {
111
+ result += fractionDigits(info).substring(0, tokenLength);
112
+ }
113
+ else {
114
+ throw "Input string was not in a correct format.";
115
+ }
116
+ break;
117
+ case "F":
118
+ // As "f", but with trailing zeros dropped — and nothing at all when the
119
+ // requested digits are all zero.
120
+ tokenLength = parseRepeatToken(format, cursorPos, "F");
121
+ cursorPos += tokenLength;
122
+ if (tokenLength <= 7) {
123
+ result += fractionDigits(info).substring(0, tokenLength).replace(/0+$/, "");
124
+ }
125
+ else {
126
+ throw "Input string was not in a correct format.";
127
+ }
128
+ break;
129
+ case "g":
130
+ tokenLength = parseRepeatToken(format, cursorPos, "g");
131
+ cursorPos += tokenLength;
132
+ result += "A.D.";
133
+ break;
134
+ case "h":
135
+ tokenLength = parseRepeatToken(format, cursorPos, "h");
136
+ cursorPos += tokenLength;
137
+ switch (tokenLength) {
138
+ case 1:
139
+ const h1Value = info.hour % 12;
140
+ result += h1Value ? h1Value : 12;
141
+ break;
142
+ case 2:
143
+ default:
144
+ const h2Value = info.hour % 12;
145
+ result += padWithZeros(h2Value ? h2Value : 12, 2);
146
+ break;
147
+ }
148
+ break;
149
+ case "H":
150
+ tokenLength = parseRepeatToken(format, cursorPos, "H");
151
+ cursorPos += tokenLength;
152
+ switch (tokenLength) {
153
+ case 1:
154
+ result += info.hour;
155
+ break;
156
+ case 2:
157
+ default:
158
+ result += padWithZeros(info.hour, 2);
159
+ break;
160
+ }
161
+ break;
162
+ case "K":
163
+ tokenLength = parseRepeatToken(format, cursorPos, "K");
164
+ cursorPos += tokenLength;
165
+ switch (tokenLength) {
166
+ case 1:
167
+ switch (kind) {
168
+ case DateTimeKind.Utc:
169
+ result += "Z";
170
+ break;
171
+ case DateTimeKind.Local:
172
+ result += hostOffsetString;
173
+ break;
174
+ case DateTimeKind.Unspecified:
175
+ break;
176
+ }
177
+ break;
178
+ default:
179
+ break;
180
+ }
181
+ break;
182
+ case "m":
183
+ tokenLength = parseRepeatToken(format, cursorPos, "m");
184
+ cursorPos += tokenLength;
185
+ switch (tokenLength) {
186
+ case 1:
187
+ result += info.minute;
188
+ break;
189
+ case 2:
190
+ default:
191
+ result += padWithZeros(info.minute, 2);
192
+ break;
193
+ }
194
+ break;
195
+ case "M":
196
+ tokenLength = parseRepeatToken(format, cursorPos, "M");
197
+ cursorPos += tokenLength;
198
+ switch (tokenLength) {
199
+ case 1:
200
+ result += info.month;
201
+ break;
202
+ case 2:
203
+ result += padWithZeros(info.month, 2);
204
+ break;
205
+ case 3:
206
+ result += shortMonths[info.month - 1];
207
+ break;
208
+ case 4:
209
+ default:
210
+ result += longMonths[info.month - 1];
211
+ break;
212
+ }
213
+ break;
214
+ case "s":
215
+ tokenLength = parseRepeatToken(format, cursorPos, "s");
216
+ cursorPos += tokenLength;
217
+ switch (tokenLength) {
218
+ case 1:
219
+ result += info.second;
220
+ break;
221
+ case 2:
222
+ default:
223
+ result += padWithZeros(info.second, 2);
224
+ break;
225
+ }
226
+ break;
227
+ case "t":
228
+ tokenLength = parseRepeatToken(format, cursorPos, "t");
229
+ cursorPos += tokenLength;
230
+ switch (tokenLength) {
231
+ case 1:
232
+ result += info.hour < 12 ? "A" : "P";
233
+ break;
234
+ case 2:
235
+ default:
236
+ result += info.hour < 12 ? "AM" : "PM";
237
+ break;
238
+ }
239
+ break;
240
+ case "y":
241
+ tokenLength = parseRepeatToken(format, cursorPos, "y");
242
+ cursorPos += tokenLength;
243
+ switch (tokenLength) {
244
+ case 1:
245
+ result += info.year % 100;
246
+ break;
247
+ case 2:
248
+ result += padWithZeros(info.year % 100, 2);
249
+ break;
250
+ default:
251
+ result += padWithZeros(info.year, tokenLength);
252
+ break;
253
+ }
254
+ break;
255
+ case "z":
256
+ tokenLength = parseRepeatToken(format, cursorPos, "z");
257
+ cursorPos += tokenLength;
258
+ const utcOffsetText = kind === DateTimeKind.Utc ? "+00:00" : hostOffsetString;
259
+ const sign = utcOffsetText[0] === "-" ? "-" : "+";
260
+ const hours = parseInt(utcOffsetText.substring(1, 3), 10);
261
+ const minutes = parseInt(utcOffsetText.substring(4, 6), 10);
262
+ switch (tokenLength) {
263
+ case 1:
264
+ result += `${sign}${hours}`;
265
+ break;
266
+ case 2:
267
+ result += `${sign}${padWithZeros(hours, 2)}`;
268
+ break;
269
+ default:
270
+ result += `${sign}${padWithZeros(hours, 2)}:${padWithZeros(minutes, 2)}`;
271
+ break;
272
+ }
273
+ break;
274
+ case ":":
275
+ result += ":";
276
+ cursorPos++;
277
+ break;
278
+ case "/":
279
+ result += "/";
280
+ cursorPos++;
281
+ break;
282
+ case "'":
283
+ case '"':
284
+ const [quotedString, quotedStringLenght] = parseQuotedString(format, cursorPos);
285
+ result += quotedString;
286
+ cursorPos += quotedStringLenght;
287
+ break;
288
+ case "%":
289
+ const nextChar = parseNextChar(format, cursorPos);
290
+ if (nextChar >= 0 && nextChar !== "%".charCodeAt(0)) {
291
+ cursorPos += 2;
292
+ result += dateToStringWithCustomFormat(info, String.fromCharCode(nextChar), kind, hostOffsetString);
293
+ }
294
+ else {
295
+ throw new Exception("Invalid format string");
296
+ }
297
+ break;
298
+ case "\\":
299
+ const nextChar2 = parseNextChar(format, cursorPos);
300
+ if (nextChar2 >= 0) {
301
+ cursorPos += 2;
302
+ result += String.fromCharCode(nextChar2);
303
+ }
304
+ else {
305
+ throw new Exception("Invalid format string");
306
+ }
307
+ break;
308
+ default:
309
+ cursorPos++;
310
+ result += token;
311
+ break;
312
+ }
313
+ }
314
+ return result;
315
+ }
316
+ // --- Standard single-letter format building blocks (InvariantCulture) ---
317
+ export function dateToString_D(info) {
318
+ return longDays[info.dayOfWeek]
319
+ + ", " + padWithZeros(info.day, 2)
320
+ + " " + longMonths[info.month - 1]
321
+ + " " + info.year;
322
+ }
323
+ export function dateToString_d(info) {
324
+ return padWithZeros(info.month, 2)
325
+ + "/" + padWithZeros(info.day, 2)
326
+ + "/" + info.year;
327
+ }
328
+ export function dateToString_T(info) {
329
+ return padWithZeros(info.hour, 2)
330
+ + ":" + padWithZeros(info.minute, 2)
331
+ + ":" + padWithZeros(info.second, 2);
332
+ }
333
+ export function dateToString_t(info) {
334
+ return padWithZeros(info.hour, 2)
335
+ + ":" + padWithZeros(info.minute, 2);
336
+ }
337
+ // RFC 1123: "Thu, 01 Jan 2009 00:00:00 GMT". `info` must already be UTC.
338
+ export function dateToString_R(info) {
339
+ return shortDays[info.dayOfWeek] + ", "
340
+ + padWithZeros(info.day, 2) + " "
341
+ + shortMonths[info.month - 1] + " "
342
+ + info.year + " "
343
+ + padWithZeros(info.hour, 2) + ":"
344
+ + padWithZeros(info.minute, 2) + ":"
345
+ + padWithZeros(info.second, 2) + " GMT";
346
+ }
347
+ // Universal sortable: "2009-06-15 13:45:30Z". `info` must already be UTC.
348
+ export function dateToString_u(info) {
349
+ return padWithZeros(info.year, 4) + "-"
350
+ + padWithZeros(info.month, 2) + "-"
351
+ + padWithZeros(info.day, 2) + " "
352
+ + padWithZeros(info.hour, 2) + ":"
353
+ + padWithZeros(info.minute, 2) + ":"
354
+ + padWithZeros(info.second, 2) + "Z";
355
+ }
356
+ // Month/day (InvariantCulture "MMMM dd"): "June 15"
357
+ export function dateToString_M(info) {
358
+ return longMonths[info.month - 1] + " " + padWithZeros(info.day, 2);
359
+ }
360
+ // Year/month (InvariantCulture "yyyy MMMM"): "2009 June"
361
+ export function dateToString_Y(info) {
362
+ return info.year + " " + longMonths[info.month - 1];
363
+ }
364
+ // .NET's standard date/time format specifiers, InvariantCulture. Anything that is
365
+ // not a single recognised letter is treated as a custom format string.
366
+ export function dateToString(ctx, format) {
367
+ const info = ctx.info;
368
+ if (typeof format !== "string") {
369
+ return dateToString_d(info) + " " + dateToString_T(info) + ctx.defaultSuffix;
370
+ }
371
+ if (format.length === 1) {
372
+ switch (format) {
373
+ case "D": return dateToString_D(info);
374
+ case "d": return dateToString_d(info);
375
+ case "F": return dateToString_D(info) + " " + dateToString_T(info);
376
+ case "f": return dateToString_D(info) + " " + dateToString_t(info);
377
+ case "G": return dateToString_d(info) + " " + dateToString_T(info);
378
+ case "g": return dateToString_d(info) + " " + dateToString_t(info);
379
+ case "M":
380
+ case "m": return dateToString_M(info);
381
+ case "O":
382
+ case "o": return ctx.roundTrip();
383
+ case "R":
384
+ case "r": return dateToString_R(ctx.utcInfo());
385
+ case "s": return ctx.sortable();
386
+ case "T": return dateToString_T(info);
387
+ case "t": return dateToString_t(info);
388
+ case "u": return dateToString_u(ctx.utcInfo());
389
+ case "U": {
390
+ const utc = ctx.utcInfo();
391
+ return dateToString_D(utc) + " " + dateToString_T(utc);
392
+ }
393
+ case "Y":
394
+ case "y": return dateToString_Y(info);
395
+ default: throw new Exception("Unrecognized Date print format");
396
+ }
397
+ }
398
+ return dateToStringWithCustomFormat(info, format, ctx.kind, ctx.hostOffsetString());
399
+ }
400
+ // The only date words .NET's invariant parser recognises: month names, weekday names,
401
+ // meridiem designators and zone markers (plus the ISO "T" separator). Anything else is
402
+ // rejected. Used to reject JS-permissive inputs (see `parseRaw`).
403
+ const recognizedDateWords = new Set([
404
+ "january", "february", "march", "april", "may", "june",
405
+ "july", "august", "september", "october", "november", "december",
406
+ "jan", "feb", "mar", "apr", "jun", "jul", "aug", "sep", "sept", "oct", "nov", "dec",
407
+ "monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday",
408
+ "mon", "tue", "wed", "thu", "fri", "sat", "sun",
409
+ "am", "pm", "gmt", "utc", "ut", "t", "z",
410
+ ]);
411
+ // The ticks below the millisecond (0..9999) written in the seconds fraction of
412
+ // `input`, e.g. "…:30.6175425" -> 5425.
413
+ //
414
+ // `parseRaw` resolves the instant through the JS `Date` built-in, which is
415
+ // millisecond-precise, so those digits would otherwise be lost. Anchoring on
416
+ // ":ss." keeps date separators (as in "15.06.2009") from matching. Digits past
417
+ // the 7th are truncated, matching how TimeOnly/TimeSpan parsing treats them.
418
+ export function subMillisecondTicks(input) {
419
+ const m = /:\d{1,2}\.(\d+)/.exec(input);
420
+ return m == null ? 0 : +m[1].padEnd(7, "0").substring(3, 7);
421
+ }
422
+ // A trailing UTC designator, if the input carries one. The JS `Date` built-in
423
+ // applies the offset but does not report whether there was one, and callers need
424
+ // that: .NET gives a parsed value Kind=Local when an offset is present and
425
+ // Kind=Unspecified when it is not.
426
+ //
427
+ // The offset is only recognised after a time, so that the trailing "-11" of a
428
+ // date like "2014-09-11" is not mistaken for one.
429
+ function detectOffset(input) {
430
+ const m = /\d\d:\d\d(?::\d\d)?(?:\.\d+)?\s*(Z|[+-]\d\d:?(?:\d\d)?)\s*$/i.exec(input);
431
+ if (m == null) {
432
+ return null;
433
+ }
434
+ const designator = m[1];
435
+ if (designator.toUpperCase() === "Z") {
436
+ return "Z";
437
+ }
438
+ const digits = designator.substring(1).replace(":", "");
439
+ const minutes = +digits.substring(0, 2) * 60 + (digits.length > 2 ? +digits.substring(2) : 0);
440
+ return designator[0] === "-" ? -minutes : minutes;
441
+ }
442
+ // Lenient .NET-compatible parse. Returns a JS Date (the parsed instant) and the
443
+ // detected offset, if any. Callers read the wall-clock fields and build the
444
+ // appropriate Temporal value.
445
+ export function parseRaw(input) {
446
+ function fail() {
447
+ throw new Exception(`The string is not a valid Date: ${input}`);
448
+ }
449
+ if (input == null || input.trim() === "") {
450
+ fail();
451
+ }
452
+ if ((input.match(/[a-z]+/gi) ?? []).some(word => !recognizedDateWords.has(word.toLowerCase()))) {
453
+ fail();
454
+ }
455
+ // ISO dates without TZ are parsed as UTC. Adding time without TZ keeps them local.
456
+ if (input.length === 10 && input[4] === "-" && input[7] === "-") {
457
+ input += "T00:00:00";
458
+ }
459
+ let date = new Date(input);
460
+ let offset = detectOffset(input);
461
+ if (isNaN(date.getTime())) {
462
+ // Try to check strings JS Date cannot parse (see #1045, #1422)
463
+ const m = /^\s*(\d+[^\w\s:]\d+[^\w\s:]\d+)?\s*(\d+:\d+(?::\d+(?:\.\d+)?)?)?\s*([AaPp][Mm])?\s*(Z|[+-]([01]?\d):?([0-5]?\d)?)?\s*$/.exec(input);
464
+ if (m != null) {
465
+ let baseDate;
466
+ let timeInSeconds = 0;
467
+ if (m[2] != null) {
468
+ const timeParts = m[2].split(":");
469
+ const hourPart = parseInt(timeParts[0], 10);
470
+ timeInSeconds =
471
+ hourPart * 3600 +
472
+ parseInt(timeParts[1] || "0", 10) * 60 +
473
+ parseFloat(timeParts[2] || "0");
474
+ if (m[3] != null && m[3].toUpperCase() === "PM" && hourPart < 12) {
475
+ timeInSeconds += 12 * 3600;
476
+ }
477
+ else if (m[3] != null && m[3].toUpperCase() === "AM" && hourPart === 12) {
478
+ timeInSeconds -= 12 * 3600;
479
+ }
480
+ }
481
+ if (m[4] != null) { // There's an offset, parse as UTC
482
+ if (m[1] != null) {
483
+ baseDate = new Date(m[1] + " UTC");
484
+ }
485
+ else {
486
+ const d = new Date();
487
+ baseDate = new Date(d.getUTCFullYear() + "/" + (d.getUTCMonth() + 1) + "/" + d.getUTCDate());
488
+ }
489
+ if (m[4] === "Z") {
490
+ offset = "Z";
491
+ }
492
+ else {
493
+ let offsetInMinutes = parseInt(m[5], 10) * 60 + parseInt(m[6] || "0", 10);
494
+ if (m[4][0] === "-") {
495
+ offsetInMinutes *= -1;
496
+ }
497
+ offset = offsetInMinutes;
498
+ timeInSeconds -= offsetInMinutes * 60;
499
+ }
500
+ }
501
+ else {
502
+ if (m[1] != null) {
503
+ baseDate = new Date(m[1]);
504
+ }
505
+ else {
506
+ const d = new Date();
507
+ baseDate = new Date(d.getFullYear() + "/" + (d.getMonth() + 1) + "/" + d.getDate());
508
+ }
509
+ }
510
+ date = new Date(baseDate.getTime() + timeInSeconds * 1000);
511
+ // correct for daylight savings time
512
+ date = new Date(date.getTime() + (date.getTimezoneOffset() - baseDate.getTimezoneOffset()) * 60000);
513
+ }
514
+ else {
515
+ fail();
516
+ }
517
+ // Check again the date is valid after transformations, see #2229
518
+ if (isNaN(date.getTime())) {
519
+ fail();
520
+ }
521
+ }
522
+ return [date, offset];
523
+ }