@fable-org/fable-library-ts 1.1.0 → 1.3.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/Boolean.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import { FSharpRef } from "./Types.js"
2
2
 
3
3
  export function tryParse(str: string, defValue: FSharpRef<boolean>): boolean {
4
- if (str.match(/^\s*true\s*$/i)) {
4
+ if (str != null && str.match(/^\s*true\s*$/i)) {
5
5
  defValue.contents = true;
6
6
  return true;
7
- } else if (str.match(/^\s*false\s*$/i)) {
7
+ } else if (str != null && str.match(/^\s*false\s*$/i)) {
8
8
  defValue.contents = false;
9
9
  return true;
10
10
  }
package/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 1.3.0 - 2024-03-18
11
+
12
+ * [JS/TS] `Boolean.tryParse` should not crash on `null` string (@goswinr)
13
+
14
+ ## 1.2.0 - 2024-03-01
15
+
16
+ ### Fixed
17
+
18
+ * [GH-3772](https://github.com/fable-compiler/Fable/pull/3772) [JS/TS] Re-implement `DateTime.ToString` custom format handling (by @MangelMaxime)
19
+
20
+ It now supports all custom format specifiers, and behave as if `CultureInfo.InvariantCulture` was used (Fable does not support Globalization).
21
+
10
22
  ## 1.1.0 - 2024-02-20
11
23
 
12
24
  ### Added
package/Date.ts CHANGED
@@ -15,6 +15,360 @@ import { compareDates, DateKind, dateOffset, IDateTime, IDateTimeOffset, padWith
15
15
  export type OffsetInMinutes = number;
16
16
  export type Offset = "Z" | OffsetInMinutes | null;
17
17
 
18
+ const shortDays =
19
+ [
20
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
21
+ ]
22
+
23
+ const longDays =
24
+ [
25
+ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
26
+ ]
27
+
28
+ const shortMonths =
29
+ [
30
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
31
+ ]
32
+
33
+ const longMonths =
34
+ [
35
+ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
36
+ ]
37
+
38
+
39
+ function parseRepeatToken(format: string, pos: number, patternChar: string) {
40
+ let tokenLength = 0;
41
+ let internalPos = pos;
42
+ while (internalPos < format.length && format[internalPos] === patternChar) {
43
+ internalPos++;
44
+ tokenLength++;
45
+ }
46
+
47
+ return tokenLength;
48
+ }
49
+
50
+ function parseNextChar(format: string, pos: number) {
51
+ if (pos >= format.length - 1) {
52
+ return -1
53
+ }
54
+
55
+ return format.charCodeAt(pos + 1);
56
+ }
57
+
58
+ function parseQuotedString(format: string, pos: number) : [string, number] {
59
+ let beginPos = pos;
60
+ // Get the character used to quote the string
61
+ const quoteChar = format[pos];
62
+
63
+ let result = "";
64
+ let foundQuote = false;
65
+
66
+ while (pos < format.length) {
67
+ pos++;
68
+ const currentChar = format[pos];
69
+ if (currentChar === quoteChar) {
70
+ foundQuote = true;
71
+ break;
72
+ } else if (currentChar === "\\") {
73
+ if (pos < format.length) {
74
+ pos++;
75
+ result += format[pos];
76
+ } else {
77
+ // This means that '\' is the last character in the string.
78
+ throw new Error("Invalid string format");
79
+ }
80
+ } else {
81
+ result += currentChar;
82
+ }
83
+ }
84
+
85
+ if (!foundQuote) {
86
+ // We could not find the matching quote
87
+ throw new Error(`Invalid string format could not find matching quote for ${quoteChar}`);
88
+ }
89
+
90
+ return [result, pos - beginPos + 1];
91
+ }
92
+
93
+ function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean) {
94
+ let cursorPos = 0;
95
+ let tokenLength = 0;
96
+ let result = "";
97
+ const localizedDate = utc ? DateTime(date.getTime(), DateKind.UTC) : date;
98
+
99
+ while (cursorPos < format.length) {
100
+ const token = format[cursorPos];
101
+
102
+ switch (token) {
103
+ case "d":
104
+ tokenLength = parseRepeatToken(format, cursorPos, "d");
105
+ cursorPos += tokenLength;
106
+ switch (tokenLength) {
107
+ case 1:
108
+ result += localizedDate.getDate();
109
+ break;
110
+ case 2:
111
+ result += padWithZeros(localizedDate.getDate(), 2);
112
+ break;
113
+ case 3:
114
+ result += shortDays[dayOfWeek(localizedDate)];
115
+ break;
116
+ case 4:
117
+ result += longDays[dayOfWeek(localizedDate)];
118
+ break;
119
+ default:
120
+ break;
121
+ }
122
+ break;
123
+ case "f":
124
+ tokenLength = parseRepeatToken(format, cursorPos, "f");
125
+ cursorPos += tokenLength;
126
+ if (tokenLength <= 3) {
127
+ const precision = 10 ** (3 - tokenLength);
128
+ result += padWithZeros(Math.floor(millisecond(localizedDate) / precision), tokenLength);
129
+ } else if (tokenLength <= 7) {
130
+ // JavaScript Date only support precision to the millisecond
131
+ // so we fill the rest of the precision with 0 as if the date didn't have
132
+ // milliseconds provided to it.
133
+ // This is to have the same behavior as .NET when doing:
134
+ // DateTime(1, 2, 3, 4, 5, 6, DateTimeKind.Utc).ToString("fffff") => 00000
135
+ result += (""+millisecond(localizedDate)).padEnd(tokenLength, "0");
136
+ }
137
+ break;
138
+ case "F":
139
+ tokenLength = parseRepeatToken(format, cursorPos, "F");
140
+ cursorPos += tokenLength;
141
+ if (tokenLength <= 3) {
142
+ const precision = 10 ** (3 - tokenLength);
143
+ const value = Math.floor(millisecond(localizedDate) / precision)
144
+ if (value != 0) {
145
+ result += padWithZeros(value, tokenLength);
146
+ }
147
+ } else if (tokenLength <= 7) {
148
+ // JavaScript Date only support precision to the millisecond
149
+ // so we can't go beyond that.
150
+ // We also need to pad start with 0 if the value is not 0
151
+ const value = millisecond(localizedDate);
152
+ if (value != 0) {
153
+ result += padWithZeros(value, 3);
154
+ }
155
+ }
156
+ break;
157
+ case "g":
158
+ tokenLength = parseRepeatToken(format, cursorPos, "g");
159
+ cursorPos += tokenLength;
160
+ if (tokenLength <= 2) {
161
+ result += "A.D.";
162
+ }
163
+ break;
164
+ case "h":
165
+ tokenLength = parseRepeatToken(format, cursorPos, "h");
166
+ cursorPos += tokenLength;
167
+ switch (tokenLength) {
168
+ case 1:
169
+ result += hour(localizedDate) % 12
170
+ break;
171
+ case 2:
172
+ result += padWithZeros(hour(localizedDate) % 12, 2);
173
+ break;
174
+ default:
175
+ break;
176
+ }
177
+ break;
178
+ case "H":
179
+ tokenLength = parseRepeatToken(format, cursorPos, "H");
180
+ cursorPos += tokenLength;
181
+ switch (tokenLength) {
182
+ case 1:
183
+ result += hour(localizedDate);
184
+ break;
185
+ case 2:
186
+ result += padWithZeros(hour(localizedDate), 2);
187
+ break;
188
+ default:
189
+ break;
190
+ }
191
+ break;
192
+ case "K":
193
+ tokenLength = parseRepeatToken(format, cursorPos, "K");
194
+ cursorPos += tokenLength;
195
+ switch (tokenLength) {
196
+ case 1:
197
+ switch (kind(localizedDate)) {
198
+ case DateKind.UTC:
199
+ result += "Z";
200
+ break;
201
+ case DateKind.Local:
202
+ result += dateOffsetToString(localizedDate.getTimezoneOffset() * -60000);
203
+ break;
204
+ case DateKind.Unspecified:
205
+ break;
206
+ }
207
+ break;
208
+ default:
209
+ break;
210
+ }
211
+ break;
212
+ case "m":
213
+ tokenLength = parseRepeatToken(format, cursorPos, "m");
214
+ cursorPos += tokenLength;
215
+ switch (tokenLength) {
216
+ case 1:
217
+ result += minute(localizedDate);
218
+ break;
219
+ case 2:
220
+ result += padWithZeros(minute(localizedDate), 2);
221
+ break;
222
+ default:
223
+ break;
224
+ }
225
+ break;
226
+ case "M":
227
+ tokenLength = parseRepeatToken(format, cursorPos, "M");
228
+ cursorPos += tokenLength;
229
+ switch (tokenLength) {
230
+ case 1:
231
+ result += month(localizedDate);
232
+ break;
233
+ case 2:
234
+ result += padWithZeros(month(localizedDate), 2);
235
+ break;
236
+ case 3:
237
+ result += shortMonths[month(localizedDate) - 1];
238
+ break;
239
+ case 4:
240
+ result += longMonths[month(localizedDate) - 1];
241
+ break;
242
+ default:
243
+ break;
244
+ }
245
+ break;
246
+ case "s":
247
+ tokenLength = parseRepeatToken(format, cursorPos, "s");
248
+ cursorPos += tokenLength;
249
+ switch (tokenLength) {
250
+ case 1:
251
+ result += second(localizedDate);
252
+ break;
253
+ case 2:
254
+ result += padWithZeros(second(localizedDate), 2);
255
+ break;
256
+ default:
257
+ break;
258
+ }
259
+ break;
260
+ case "t":
261
+ tokenLength = parseRepeatToken(format, cursorPos, "t");
262
+ cursorPos += tokenLength;
263
+ switch (tokenLength) {
264
+ case 1:
265
+ result += localizedDate.getHours() < 12 ? "A" : "P";
266
+ break;
267
+ case 2:
268
+ result += localizedDate.getHours() < 12 ? "AM" : "PM";
269
+ break;
270
+ default:
271
+ break;
272
+ }
273
+ break;
274
+ case "y":
275
+ tokenLength = parseRepeatToken(format, cursorPos, "y");
276
+ cursorPos += tokenLength;
277
+ switch (tokenLength) {
278
+ case 1:
279
+ result += localizedDate.getFullYear() % 100;
280
+ break;
281
+ case 2:
282
+ result += padWithZeros(localizedDate.getFullYear() % 100, 2);
283
+ break;
284
+ case 3:
285
+ result += padWithZeros(localizedDate.getFullYear(), 3);
286
+ break;
287
+ case 4:
288
+ result += padWithZeros(localizedDate.getFullYear(), 4);
289
+ break;
290
+ case 5:
291
+ result += padWithZeros(localizedDate.getFullYear(), 5);
292
+ break;
293
+ default:
294
+ break;
295
+ }
296
+ break;
297
+ case "z":
298
+ tokenLength = parseRepeatToken(format, cursorPos, "z");
299
+ cursorPos += tokenLength;
300
+ let utcOffsetText = "";
301
+
302
+ switch (kind(localizedDate)) {
303
+ case DateKind.UTC:
304
+ utcOffsetText = "+00:00";
305
+ break;
306
+ case DateKind.Local:
307
+ utcOffsetText = dateOffsetToString(localizedDate.getTimezoneOffset() * -60000);
308
+ break;
309
+ case DateKind.Unspecified:
310
+ utcOffsetText = dateOffsetToString(toLocalTime(localizedDate).getTimezoneOffset() * -60000);
311
+ break;
312
+ }
313
+
314
+ const sign = utcOffsetText[0] === "-" ? "-" : "+";
315
+ const hours = parseInt(utcOffsetText.substring(1, 3), 10);
316
+ const minutes = parseInt(utcOffsetText.substring(4, 6), 10);
317
+
318
+ switch (tokenLength) {
319
+ case 1:
320
+ result += `${sign}${hours}`;
321
+ break;
322
+ case 2:
323
+ result += `${sign}${padWithZeros(hours, 2)}`;
324
+ break;
325
+ default:
326
+ result += `${sign}${padWithZeros(hours, 2)}:${padWithZeros(minutes, 2)}`;
327
+ break;
328
+ }
329
+ break;
330
+ case ":":
331
+ result += ":";
332
+ cursorPos++;
333
+ break;
334
+ case "/":
335
+ result += "/";
336
+ cursorPos++;
337
+ break;
338
+ case "'":
339
+ case '"':
340
+ const [quotedString, quotedStringLenght] = parseQuotedString(format, cursorPos);
341
+ result += quotedString;
342
+ cursorPos += quotedStringLenght;
343
+ break;
344
+ case "%":
345
+ const nextChar = parseNextChar(format, cursorPos);
346
+ if (nextChar >= 0 && nextChar !== "%".charCodeAt(0)) {
347
+ cursorPos += 2;
348
+ result += dateToStringWithCustomFormat(localizedDate, String.fromCharCode(nextChar), utc);
349
+ } else {
350
+ throw new Error("Invalid format string");
351
+ }
352
+ break;
353
+ case "\\":
354
+ const nextChar2 = parseNextChar(format, cursorPos);
355
+ if (nextChar2 >= 0) {
356
+ cursorPos += 2;
357
+ result += String.fromCharCode(nextChar2);
358
+ } else {
359
+ throw new Error("Invalid format string");
360
+ }
361
+ break;
362
+ default:
363
+ cursorPos++;
364
+ result += token;
365
+ break;
366
+ }
367
+ }
368
+
369
+ return result;
370
+ }
371
+
18
372
  export function kind(value: IDateTime): number {
19
373
  return value.kind || 0;
20
374
  }
@@ -66,31 +420,6 @@ function dateToISOStringWithOffset(dateWithOffset: Date, offset: number) {
66
420
  return str.substring(0, str.length - 1) + dateOffsetToString(offset);
67
421
  }
68
422
 
69
- function dateToStringWithCustomFormat(date: Date, format: string, utc: boolean) {
70
- return format.replace(/(\w)\1*/g, (match: string) => {
71
- let rep = Number.NaN;
72
- switch (match.substring(0, 1)) {
73
- case "y":
74
- const y = utc ? date.getUTCFullYear() : date.getFullYear();
75
- rep = match.length < 4 ? y % 100 : y; break;
76
- case "M": rep = (utc ? date.getUTCMonth() : date.getMonth()) + 1; break;
77
- case "d": rep = utc ? date.getUTCDate() : date.getDate(); break;
78
- case "H": rep = utc ? date.getUTCHours() : date.getHours(); break;
79
- case "h":
80
- const h = utc ? date.getUTCHours() : date.getHours();
81
- rep = h > 12 ? h % 12 : h; break;
82
- case "m": rep = utc ? date.getUTCMinutes() : date.getMinutes(); break;
83
- case "s": rep = utc ? date.getUTCSeconds() : date.getSeconds(); break;
84
- case "f": rep = utc ? date.getUTCMilliseconds() : date.getMilliseconds(); break;
85
- }
86
- if (Number.isNaN(rep)) {
87
- return match;
88
- } else {
89
- return padWithZeros(rep, match.length);
90
- }
91
- });
92
- }
93
-
94
423
  function dateToStringWithOffset(date: IDateTimeOffset, format?: string) {
95
424
  const d = new Date(date.getTime() + (date.offset ?? 0));
96
425
  if (typeof format !== "string") {
@@ -381,7 +710,7 @@ export function dayOfYear(d: IDateTime) {
381
710
 
382
711
  export function add(d: IDateTime, ts: number) {
383
712
  const newDate = DateTime(d.getTime() + ts, d.kind);
384
- if (d.kind === DateKind.Local) {
713
+ if (d.kind !== DateKind.UTC) {
385
714
  const oldTzOffset = d.getTimezoneOffset();
386
715
  const newTzOffset = newDate.getTimezoneOffset();
387
716
  return oldTzOffset !== newTzOffset
package/Util.ts CHANGED
@@ -270,22 +270,11 @@ export function lazyFromValue<T>(v: T) {
270
270
  }
271
271
 
272
272
  export function padWithZeros(i: number, length: number) {
273
- let str = i.toString(10);
274
- while (str.length < length) {
275
- str = "0" + str;
276
- }
277
- return str;
273
+ return i.toString(10).padStart(length, "0");
278
274
  }
279
275
 
280
276
  export function padLeftAndRightWithZeros(i: number, lengthLeft: number, lengthRight: number) {
281
- let str = i.toString(10);
282
- while (str.length < lengthLeft) {
283
- str = "0" + str;
284
- }
285
- while (str.length < lengthRight) {
286
- str = str + "0";
287
- }
288
- return str;
277
+ return i.toString(10).padStart(lengthLeft, "0").padEnd(lengthRight, "0");
289
278
  }
290
279
 
291
280
  export function dateOffset(date: IDateTime | IDateTimeOffset): number {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "type": "module",
5
5
  "name": "@fable-org/fable-library-ts",
6
- "version": "1.1.0",
6
+ "version": "1.3.0",
7
7
  "description": "Core library used by F# projects compiled with fable.io",
8
8
  "author": "Fable Contributors",
9
9
  "license": "MIT",