@naturalcycles/js-lib 14.90.0 → 14.91.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.
@@ -1,5 +1,6 @@
1
1
  import { Sequence } from '../seq/seq';
2
- import { IsoDate } from '../types';
2
+ import { IsoDate, UnixTimestamp } from '../types';
3
+ import { LocalTime } from './localTime';
3
4
  export declare type LocalDateUnit = 'year' | 'month' | 'day';
4
5
  export declare type LocalDateConfig = LocalDate | string;
5
6
  /**
@@ -18,6 +19,11 @@ export declare class LocalDate {
18
19
  static of(d: LocalDateConfig): LocalDate;
19
20
  static parseCompact(d: string): LocalDate;
20
21
  static fromDate(d: Date): LocalDate;
22
+ /**
23
+ * Returns null if invalid.
24
+ */
25
+ static parseOrNull(d: LocalDateConfig): LocalDate | null;
26
+ static isValid(iso: string): boolean;
21
27
  static today(): LocalDate;
22
28
  static sort(items: LocalDate[], mutate?: boolean, descending?: boolean): LocalDate[];
23
29
  static earliestOrUndefined(items: LocalDate[]): LocalDate | undefined;
@@ -54,9 +60,9 @@ export declare class LocalDate {
54
60
  subtract(num: number, unit: LocalDateUnit, mutate?: boolean): LocalDate;
55
61
  startOf(unit: LocalDateUnit): LocalDate;
56
62
  endOf(unit: LocalDateUnit): LocalDate;
57
- private getYearDays;
58
- private getMonthLen;
59
- private isLeapYear;
63
+ static getYearLength(year: number): number;
64
+ static getMonthLength(year: number, month: number): number;
65
+ static isLeapYear(year: number): boolean;
60
66
  clone(): LocalDate;
61
67
  /**
62
68
  * Converts LocalDate into instance of Date.
@@ -65,8 +71,12 @@ export declare class LocalDate {
65
71
  * Timezone will match local timezone.
66
72
  */
67
73
  toDate(): Date;
74
+ toLocalTime(): LocalTime;
75
+ toISODate(): IsoDate;
68
76
  toString(): IsoDate;
69
77
  toStringCompact(): string;
78
+ unix(): UnixTimestamp;
79
+ unixMillis(): number;
70
80
  toJSON(): IsoDate;
71
81
  }
72
82
  /**
@@ -4,6 +4,7 @@ exports.localDate = exports.LocalDate = void 0;
4
4
  const assert_1 = require("../error/assert");
5
5
  const seq_1 = require("../seq/seq");
6
6
  const types_1 = require("../types");
7
+ const localTime_1 = require("./localTime");
7
8
  const m31 = new Set([1, 3, 5, 7, 8, 10, 12]);
8
9
  /**
9
10
  * @experimental
@@ -22,17 +23,15 @@ class LocalDate {
22
23
  * Input can already be a LocalDate - it is returned as-is in that case.
23
24
  */
24
25
  static of(d) {
25
- if (d instanceof LocalDate)
26
- return d;
27
- const [year, month, day] = d.slice(0, 10).split('-').map(Number);
28
- if (!day || !month || (!year && year !== 0)) {
26
+ const t = this.parseOrNull(d);
27
+ if (t === null) {
29
28
  throw new Error(`Cannot parse "${d}" into LocalDate`);
30
29
  }
31
- return new LocalDate(year, month, day);
30
+ return t;
32
31
  }
33
32
  static parseCompact(d) {
34
33
  const [year, month, day] = [d.slice(0, 4), d.slice(4, 2), d.slice(6, 2)].map(Number);
35
- if (!day || !month || (!year && year !== 0)) {
34
+ if (!day || !month || !year) {
36
35
  throw new Error(`Cannot parse "${d}" into LocalDate`);
37
36
  }
38
37
  return new LocalDate(year, month, day);
@@ -40,6 +39,28 @@ class LocalDate {
40
39
  static fromDate(d) {
41
40
  return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate());
42
41
  }
42
+ /**
43
+ * Returns null if invalid.
44
+ */
45
+ static parseOrNull(d) {
46
+ if (d instanceof LocalDate)
47
+ return d;
48
+ // todo: explore more performant options
49
+ const [year, month, day] = d.slice(0, 10).split('-').map(Number);
50
+ if (!year ||
51
+ !month ||
52
+ month < 1 ||
53
+ month > 12 ||
54
+ !day ||
55
+ day < 1 ||
56
+ day > this.getMonthLength(year, month)) {
57
+ return null;
58
+ }
59
+ return new LocalDate(year, month, day);
60
+ }
61
+ static isValid(iso) {
62
+ return this.parseOrNull(iso) !== null;
63
+ }
43
64
  static today() {
44
65
  return this.fromDate(new Date());
45
66
  }
@@ -148,22 +169,22 @@ class LocalDate {
148
169
  let days = this.day - d.day;
149
170
  if (d.year < this.year) {
150
171
  for (let year = d.year; year < this.year; year++) {
151
- days += this.getYearDays(year);
172
+ days += LocalDate.getYearLength(year);
152
173
  }
153
174
  }
154
175
  else if (this.year < d.year) {
155
176
  for (let year = this.year; year < d.year; year++) {
156
- days -= this.getYearDays(year);
177
+ days -= LocalDate.getYearLength(year);
157
178
  }
158
179
  }
159
180
  if (d.month < this.month) {
160
181
  for (let month = d.month; month < this.month; month++) {
161
- days += this.getMonthLen(this.year, month);
182
+ days += LocalDate.getMonthLength(this.year, month);
162
183
  }
163
184
  }
164
185
  else if (this.month < d.month) {
165
186
  for (let month = this.month; month < d.month; month++) {
166
- days -= this.getMonthLen(d.year, month);
187
+ days -= LocalDate.getMonthLength(d.year, month);
167
188
  }
168
189
  }
169
190
  return days;
@@ -180,7 +201,7 @@ class LocalDate {
180
201
  year += num;
181
202
  }
182
203
  // check day overflow
183
- let monLen = this.getMonthLen(year, month);
204
+ let monLen = LocalDate.getMonthLength(year, month);
184
205
  while (day > monLen) {
185
206
  day -= monLen;
186
207
  month += 1;
@@ -188,7 +209,7 @@ class LocalDate {
188
209
  year += 1;
189
210
  month -= 12;
190
211
  }
191
- monLen = this.getMonthLen(year, month);
212
+ monLen = LocalDate.getMonthLength(year, month);
192
213
  }
193
214
  while (day < 1) {
194
215
  day += monLen;
@@ -197,7 +218,7 @@ class LocalDate {
197
218
  year -= 1;
198
219
  month += 12;
199
220
  }
200
- monLen = this.getMonthLen(year, month);
221
+ monLen = LocalDate.getMonthLength(year, month);
201
222
  }
202
223
  // check month overflow
203
224
  while (month > 12) {
@@ -231,19 +252,19 @@ class LocalDate {
231
252
  if (unit === 'day')
232
253
  return this;
233
254
  if (unit === 'month')
234
- return LocalDate.create(this.year, this.month, this.getMonthLen(this.year, this.month));
255
+ return LocalDate.create(this.year, this.month, LocalDate.getMonthLength(this.year, this.month));
235
256
  // year
236
257
  return LocalDate.create(this.year, 12, 31);
237
258
  }
238
- getYearDays(year) {
259
+ static getYearLength(year) {
239
260
  return this.isLeapYear(year) ? 366 : 365;
240
261
  }
241
- getMonthLen(year, month) {
262
+ static getMonthLength(year, month) {
242
263
  if (month === 2)
243
264
  return this.isLeapYear(year) ? 29 : 28;
244
265
  return m31.has(month) ? 31 : 30;
245
266
  }
246
- isLeapYear(year) {
267
+ static isLeapYear(year) {
247
268
  if (year % 4 !== 0)
248
269
  return false;
249
270
  if (year % 100 !== 0)
@@ -262,6 +283,12 @@ class LocalDate {
262
283
  toDate() {
263
284
  return new Date(this.year, this.month - 1, this.day);
264
285
  }
286
+ toLocalTime() {
287
+ return localTime_1.LocalTime.of(this.toDate());
288
+ }
289
+ toISODate() {
290
+ return this.toString();
291
+ }
265
292
  toString() {
266
293
  return [
267
294
  String(this.year).padStart(4, '0'),
@@ -276,6 +303,13 @@ class LocalDate {
276
303
  String(this.day).padStart(2, '0'),
277
304
  ].join('');
278
305
  }
306
+ // May be not optimal, as LocalTime better suits it
307
+ unix() {
308
+ return Math.floor(this.toDate().valueOf() / 1000);
309
+ }
310
+ unixMillis() {
311
+ return this.toDate().valueOf();
312
+ }
279
313
  toJSON() {
280
314
  return this.toString();
281
315
  }
@@ -1,4 +1,5 @@
1
- import { IsoDateTime, UnixTimestamp } from '../types';
1
+ import { IsoDate, IsoDateTime, UnixTimestamp } from '../types';
2
+ import { LocalDate } from './localDate';
2
3
  export declare type LocalTimeUnit = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second';
3
4
  export declare type LocalTimeConfig = LocalTime | Date | IsoDateTime | UnixTimestamp;
4
5
  export interface LocalTimeComponents {
@@ -20,6 +21,11 @@ export declare class LocalTime {
20
21
  * Input can already be a LocalDate - it is returned as-is in that case.
21
22
  */
22
23
  static of(d: LocalTimeConfig): LocalTime;
24
+ /**
25
+ * Returns null if invalid
26
+ */
27
+ static parseOrNull(d: LocalTimeConfig): LocalTime | null;
28
+ static isValid(d: LocalTimeConfig): boolean;
23
29
  static unix(ts: UnixTimestamp): LocalTime;
24
30
  static now(): LocalTime;
25
31
  static fromComponents(c: {
@@ -66,9 +72,18 @@ export declare class LocalTime {
66
72
  getDate(): Date;
67
73
  clone(): LocalTime;
68
74
  unix(): UnixTimestamp;
75
+ unixMillis(): number;
69
76
  valueOf(): UnixTimestamp;
70
- toISO8601(): IsoDateTime;
71
- toPretty(): IsoDateTime;
77
+ toLocalDate(): LocalDate;
78
+ toPretty(seconds?: boolean): IsoDateTime;
79
+ /**
80
+ * Returns e.g: `1984-06-21T17:56:21`, only the date part of DateTime
81
+ */
82
+ toISODateTime(): IsoDateTime;
83
+ /**
84
+ * Returns e.g: `1984-06-21`, only the date part of DateTime
85
+ */
86
+ toISODate(): IsoDate;
72
87
  /**
73
88
  * Returns e.g: `19840621_1705`
74
89
  */
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.localTime = exports.LocalTime = void 0;
4
4
  const assert_1 = require("../error/assert");
5
+ const localDate_1 = require("./localDate");
5
6
  /* eslint-disable no-dupe-class-members */
6
7
  // Design choices:
7
8
  // No milliseconds
@@ -24,21 +25,38 @@ class LocalTime {
24
25
  * Input can already be a LocalDate - it is returned as-is in that case.
25
26
  */
26
27
  static of(d) {
28
+ const t = this.parseOrNull(d);
29
+ if (t === null) {
30
+ throw new TypeError(`Cannot parse "${d}" into LocalTime`);
31
+ }
32
+ return t;
33
+ }
34
+ /**
35
+ * Returns null if invalid
36
+ */
37
+ static parseOrNull(d) {
27
38
  if (d instanceof LocalTime)
28
39
  return d;
29
- if (d instanceof Date)
30
- return new LocalTime(d);
31
- if (typeof d === 'number') {
32
- // unix timestamp
33
- return new LocalTime(new Date(d * 1000));
40
+ let date;
41
+ if (d instanceof Date) {
42
+ date = d;
43
+ }
44
+ else if (typeof d === 'number') {
45
+ date = new Date(d * 1000);
46
+ }
47
+ else {
48
+ date = new Date(d);
34
49
  }
35
- const date = new Date(d);
36
50
  // validation
37
51
  if (isNaN(date.getDate())) {
38
- throw new TypeError(`Cannot parse "${d}" into LocalTime`);
52
+ // throw new TypeError(`Cannot parse "${d}" into LocalTime`)
53
+ return null;
39
54
  }
40
55
  return new LocalTime(date);
41
56
  }
57
+ static isValid(d) {
58
+ return this.parseOrNull(d) !== null;
59
+ }
42
60
  static unix(ts) {
43
61
  return new LocalTime(new Date(ts * 1000));
44
62
  }
@@ -277,14 +295,33 @@ class LocalTime {
277
295
  unix() {
278
296
  return Math.floor(this.$date.valueOf() / 1000);
279
297
  }
298
+ unixMillis() {
299
+ return this.$date.valueOf();
300
+ }
280
301
  valueOf() {
281
302
  return Math.floor(this.$date.valueOf() / 1000);
282
303
  }
283
- toISO8601() {
304
+ toLocalDate() {
305
+ return localDate_1.LocalDate.create(this.$date.getFullYear(), this.$date.getMonth() + 1, this.$date.getDate());
306
+ }
307
+ toPretty(seconds = true) {
308
+ return this.$date
309
+ .toISOString()
310
+ .slice(0, seconds ? 19 : 16)
311
+ .split('T')
312
+ .join(' ');
313
+ }
314
+ /**
315
+ * Returns e.g: `1984-06-21T17:56:21`, only the date part of DateTime
316
+ */
317
+ toISODateTime() {
284
318
  return this.$date.toISOString().slice(0, 19);
285
319
  }
286
- toPretty() {
287
- return this.$date.toISOString().slice(0, 19).split('T').join(' ');
320
+ /**
321
+ * Returns e.g: `1984-06-21`, only the date part of DateTime
322
+ */
323
+ toISODate() {
324
+ return this.$date.toISOString().slice(0, 10);
288
325
  }
289
326
  /**
290
327
  * Returns e.g: `19840621_1705`
@@ -1,6 +1,7 @@
1
1
  import { _assert } from '../error/assert';
2
2
  import { Sequence } from '../seq/seq';
3
3
  import { END } from '../types';
4
+ import { LocalTime } from './localTime';
4
5
  const m31 = new Set([1, 3, 5, 7, 8, 10, 12]);
5
6
  /**
6
7
  * @experimental
@@ -19,17 +20,15 @@ export class LocalDate {
19
20
  * Input can already be a LocalDate - it is returned as-is in that case.
20
21
  */
21
22
  static of(d) {
22
- if (d instanceof LocalDate)
23
- return d;
24
- const [year, month, day] = d.slice(0, 10).split('-').map(Number);
25
- if (!day || !month || (!year && year !== 0)) {
23
+ const t = this.parseOrNull(d);
24
+ if (t === null) {
26
25
  throw new Error(`Cannot parse "${d}" into LocalDate`);
27
26
  }
28
- return new LocalDate(year, month, day);
27
+ return t;
29
28
  }
30
29
  static parseCompact(d) {
31
30
  const [year, month, day] = [d.slice(0, 4), d.slice(4, 2), d.slice(6, 2)].map(Number);
32
- if (!day || !month || (!year && year !== 0)) {
31
+ if (!day || !month || !year) {
33
32
  throw new Error(`Cannot parse "${d}" into LocalDate`);
34
33
  }
35
34
  return new LocalDate(year, month, day);
@@ -37,6 +36,28 @@ export class LocalDate {
37
36
  static fromDate(d) {
38
37
  return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate());
39
38
  }
39
+ /**
40
+ * Returns null if invalid.
41
+ */
42
+ static parseOrNull(d) {
43
+ if (d instanceof LocalDate)
44
+ return d;
45
+ // todo: explore more performant options
46
+ const [year, month, day] = d.slice(0, 10).split('-').map(Number);
47
+ if (!year ||
48
+ !month ||
49
+ month < 1 ||
50
+ month > 12 ||
51
+ !day ||
52
+ day < 1 ||
53
+ day > this.getMonthLength(year, month)) {
54
+ return null;
55
+ }
56
+ return new LocalDate(year, month, day);
57
+ }
58
+ static isValid(iso) {
59
+ return this.parseOrNull(iso) !== null;
60
+ }
40
61
  static today() {
41
62
  return this.fromDate(new Date());
42
63
  }
@@ -145,22 +166,22 @@ export class LocalDate {
145
166
  let days = this.day - d.day;
146
167
  if (d.year < this.year) {
147
168
  for (let year = d.year; year < this.year; year++) {
148
- days += this.getYearDays(year);
169
+ days += LocalDate.getYearLength(year);
149
170
  }
150
171
  }
151
172
  else if (this.year < d.year) {
152
173
  for (let year = this.year; year < d.year; year++) {
153
- days -= this.getYearDays(year);
174
+ days -= LocalDate.getYearLength(year);
154
175
  }
155
176
  }
156
177
  if (d.month < this.month) {
157
178
  for (let month = d.month; month < this.month; month++) {
158
- days += this.getMonthLen(this.year, month);
179
+ days += LocalDate.getMonthLength(this.year, month);
159
180
  }
160
181
  }
161
182
  else if (this.month < d.month) {
162
183
  for (let month = this.month; month < d.month; month++) {
163
- days -= this.getMonthLen(d.year, month);
184
+ days -= LocalDate.getMonthLength(d.year, month);
164
185
  }
165
186
  }
166
187
  return days;
@@ -177,7 +198,7 @@ export class LocalDate {
177
198
  year += num;
178
199
  }
179
200
  // check day overflow
180
- let monLen = this.getMonthLen(year, month);
201
+ let monLen = LocalDate.getMonthLength(year, month);
181
202
  while (day > monLen) {
182
203
  day -= monLen;
183
204
  month += 1;
@@ -185,7 +206,7 @@ export class LocalDate {
185
206
  year += 1;
186
207
  month -= 12;
187
208
  }
188
- monLen = this.getMonthLen(year, month);
209
+ monLen = LocalDate.getMonthLength(year, month);
189
210
  }
190
211
  while (day < 1) {
191
212
  day += monLen;
@@ -194,7 +215,7 @@ export class LocalDate {
194
215
  year -= 1;
195
216
  month += 12;
196
217
  }
197
- monLen = this.getMonthLen(year, month);
218
+ monLen = LocalDate.getMonthLength(year, month);
198
219
  }
199
220
  // check month overflow
200
221
  while (month > 12) {
@@ -228,19 +249,19 @@ export class LocalDate {
228
249
  if (unit === 'day')
229
250
  return this;
230
251
  if (unit === 'month')
231
- return LocalDate.create(this.year, this.month, this.getMonthLen(this.year, this.month));
252
+ return LocalDate.create(this.year, this.month, LocalDate.getMonthLength(this.year, this.month));
232
253
  // year
233
254
  return LocalDate.create(this.year, 12, 31);
234
255
  }
235
- getYearDays(year) {
256
+ static getYearLength(year) {
236
257
  return this.isLeapYear(year) ? 366 : 365;
237
258
  }
238
- getMonthLen(year, month) {
259
+ static getMonthLength(year, month) {
239
260
  if (month === 2)
240
261
  return this.isLeapYear(year) ? 29 : 28;
241
262
  return m31.has(month) ? 31 : 30;
242
263
  }
243
- isLeapYear(year) {
264
+ static isLeapYear(year) {
244
265
  if (year % 4 !== 0)
245
266
  return false;
246
267
  if (year % 100 !== 0)
@@ -259,6 +280,12 @@ export class LocalDate {
259
280
  toDate() {
260
281
  return new Date(this.year, this.month - 1, this.day);
261
282
  }
283
+ toLocalTime() {
284
+ return LocalTime.of(this.toDate());
285
+ }
286
+ toISODate() {
287
+ return this.toString();
288
+ }
262
289
  toString() {
263
290
  return [
264
291
  String(this.year).padStart(4, '0'),
@@ -273,6 +300,13 @@ export class LocalDate {
273
300
  String(this.day).padStart(2, '0'),
274
301
  ].join('');
275
302
  }
303
+ // May be not optimal, as LocalTime better suits it
304
+ unix() {
305
+ return Math.floor(this.toDate().valueOf() / 1000);
306
+ }
307
+ unixMillis() {
308
+ return this.toDate().valueOf();
309
+ }
276
310
  toJSON() {
277
311
  return this.toString();
278
312
  }
@@ -1,4 +1,5 @@
1
1
  import { _assert } from '../error/assert';
2
+ import { LocalDate } from './localDate';
2
3
  /* eslint-disable no-dupe-class-members */
3
4
  // Design choices:
4
5
  // No milliseconds
@@ -21,21 +22,38 @@ export class LocalTime {
21
22
  * Input can already be a LocalDate - it is returned as-is in that case.
22
23
  */
23
24
  static of(d) {
25
+ const t = this.parseOrNull(d);
26
+ if (t === null) {
27
+ throw new TypeError(`Cannot parse "${d}" into LocalTime`);
28
+ }
29
+ return t;
30
+ }
31
+ /**
32
+ * Returns null if invalid
33
+ */
34
+ static parseOrNull(d) {
24
35
  if (d instanceof LocalTime)
25
36
  return d;
26
- if (d instanceof Date)
27
- return new LocalTime(d);
28
- if (typeof d === 'number') {
29
- // unix timestamp
30
- return new LocalTime(new Date(d * 1000));
37
+ let date;
38
+ if (d instanceof Date) {
39
+ date = d;
40
+ }
41
+ else if (typeof d === 'number') {
42
+ date = new Date(d * 1000);
43
+ }
44
+ else {
45
+ date = new Date(d);
31
46
  }
32
- const date = new Date(d);
33
47
  // validation
34
48
  if (isNaN(date.getDate())) {
35
- throw new TypeError(`Cannot parse "${d}" into LocalTime`);
49
+ // throw new TypeError(`Cannot parse "${d}" into LocalTime`)
50
+ return null;
36
51
  }
37
52
  return new LocalTime(date);
38
53
  }
54
+ static isValid(d) {
55
+ return this.parseOrNull(d) !== null;
56
+ }
39
57
  static unix(ts) {
40
58
  return new LocalTime(new Date(ts * 1000));
41
59
  }
@@ -274,14 +292,33 @@ export class LocalTime {
274
292
  unix() {
275
293
  return Math.floor(this.$date.valueOf() / 1000);
276
294
  }
295
+ unixMillis() {
296
+ return this.$date.valueOf();
297
+ }
277
298
  valueOf() {
278
299
  return Math.floor(this.$date.valueOf() / 1000);
279
300
  }
280
- toISO8601() {
301
+ toLocalDate() {
302
+ return LocalDate.create(this.$date.getFullYear(), this.$date.getMonth() + 1, this.$date.getDate());
303
+ }
304
+ toPretty(seconds = true) {
305
+ return this.$date
306
+ .toISOString()
307
+ .slice(0, seconds ? 19 : 16)
308
+ .split('T')
309
+ .join(' ');
310
+ }
311
+ /**
312
+ * Returns e.g: `1984-06-21T17:56:21`, only the date part of DateTime
313
+ */
314
+ toISODateTime() {
281
315
  return this.$date.toISOString().slice(0, 19);
282
316
  }
283
- toPretty() {
284
- return this.$date.toISOString().slice(0, 19).split('T').join(' ');
317
+ /**
318
+ * Returns e.g: `1984-06-21`, only the date part of DateTime
319
+ */
320
+ toISODate() {
321
+ return this.$date.toISOString().slice(0, 10);
285
322
  }
286
323
  /**
287
324
  * Returns e.g: `19840621_1705`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.90.0",
3
+ "version": "14.91.2",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -1,6 +1,7 @@
1
1
  import { _assert } from '../error/assert'
2
2
  import { Sequence } from '../seq/seq'
3
- import { END, IsoDate } from '../types'
3
+ import { END, IsoDate, UnixTimestamp } from '../types'
4
+ import { LocalTime } from './localTime'
4
5
 
5
6
  export type LocalDateUnit = 'year' | 'month' | 'day'
6
7
 
@@ -23,21 +24,19 @@ export class LocalDate {
23
24
  * Input can already be a LocalDate - it is returned as-is in that case.
24
25
  */
25
26
  static of(d: LocalDateConfig): LocalDate {
26
- if (d instanceof LocalDate) return d
27
-
28
- const [year, month, day] = d.slice(0, 10).split('-').map(Number)
27
+ const t = this.parseOrNull(d)
29
28
 
30
- if (!day || !month || (!year && year !== 0)) {
29
+ if (t === null) {
31
30
  throw new Error(`Cannot parse "${d}" into LocalDate`)
32
31
  }
33
32
 
34
- return new LocalDate(year, month, day)
33
+ return t
35
34
  }
36
35
 
37
36
  static parseCompact(d: string): LocalDate {
38
37
  const [year, month, day] = [d.slice(0, 4), d.slice(4, 2), d.slice(6, 2)].map(Number)
39
38
 
40
- if (!day || !month || (!year && year !== 0)) {
39
+ if (!day || !month || !year) {
41
40
  throw new Error(`Cannot parse "${d}" into LocalDate`)
42
41
  }
43
42
 
@@ -48,6 +47,34 @@ export class LocalDate {
48
47
  return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate())
49
48
  }
50
49
 
50
+ /**
51
+ * Returns null if invalid.
52
+ */
53
+ static parseOrNull(d: LocalDateConfig): LocalDate | null {
54
+ if (d instanceof LocalDate) return d
55
+
56
+ // todo: explore more performant options
57
+ const [year, month, day] = d.slice(0, 10).split('-').map(Number)
58
+
59
+ if (
60
+ !year ||
61
+ !month ||
62
+ month < 1 ||
63
+ month > 12 ||
64
+ !day ||
65
+ day < 1 ||
66
+ day > this.getMonthLength(year, month)
67
+ ) {
68
+ return null
69
+ }
70
+
71
+ return new LocalDate(year, month, day)
72
+ }
73
+
74
+ static isValid(iso: string): boolean {
75
+ return this.parseOrNull(iso) !== null
76
+ }
77
+
51
78
  static today(): LocalDate {
52
79
  return this.fromDate(new Date())
53
80
  }
@@ -203,21 +230,21 @@ export class LocalDate {
203
230
 
204
231
  if (d.year < this.year) {
205
232
  for (let year = d.year; year < this.year; year++) {
206
- days += this.getYearDays(year)
233
+ days += LocalDate.getYearLength(year)
207
234
  }
208
235
  } else if (this.year < d.year) {
209
236
  for (let year = this.year; year < d.year; year++) {
210
- days -= this.getYearDays(year)
237
+ days -= LocalDate.getYearLength(year)
211
238
  }
212
239
  }
213
240
 
214
241
  if (d.month < this.month) {
215
242
  for (let month = d.month; month < this.month; month++) {
216
- days += this.getMonthLen(this.year, month)
243
+ days += LocalDate.getMonthLength(this.year, month)
217
244
  }
218
245
  } else if (this.month < d.month) {
219
246
  for (let month = this.month; month < d.month; month++) {
220
- days -= this.getMonthLen(d.year, month)
247
+ days -= LocalDate.getMonthLength(d.year, month)
221
248
  }
222
249
  }
223
250
 
@@ -236,7 +263,7 @@ export class LocalDate {
236
263
  }
237
264
 
238
265
  // check day overflow
239
- let monLen = this.getMonthLen(year, month)
266
+ let monLen = LocalDate.getMonthLength(year, month)
240
267
  while (day > monLen) {
241
268
  day -= monLen
242
269
  month += 1
@@ -245,7 +272,7 @@ export class LocalDate {
245
272
  month -= 12
246
273
  }
247
274
 
248
- monLen = this.getMonthLen(year, month)
275
+ monLen = LocalDate.getMonthLength(year, month)
249
276
  }
250
277
  while (day < 1) {
251
278
  day += monLen
@@ -255,7 +282,7 @@ export class LocalDate {
255
282
  month += 12
256
283
  }
257
284
 
258
- monLen = this.getMonthLen(year, month)
285
+ monLen = LocalDate.getMonthLength(year, month)
259
286
  }
260
287
 
261
288
  // check month overflow
@@ -292,21 +319,25 @@ export class LocalDate {
292
319
  endOf(unit: LocalDateUnit): LocalDate {
293
320
  if (unit === 'day') return this
294
321
  if (unit === 'month')
295
- return LocalDate.create(this.year, this.month, this.getMonthLen(this.year, this.month))
322
+ return LocalDate.create(
323
+ this.year,
324
+ this.month,
325
+ LocalDate.getMonthLength(this.year, this.month),
326
+ )
296
327
  // year
297
328
  return LocalDate.create(this.year, 12, 31)
298
329
  }
299
330
 
300
- private getYearDays(year: number): number {
331
+ static getYearLength(year: number): number {
301
332
  return this.isLeapYear(year) ? 366 : 365
302
333
  }
303
334
 
304
- private getMonthLen(year: number, month: number): number {
335
+ static getMonthLength(year: number, month: number): number {
305
336
  if (month === 2) return this.isLeapYear(year) ? 29 : 28
306
337
  return m31.has(month) ? 31 : 30
307
338
  }
308
339
 
309
- private isLeapYear(year: number): boolean {
340
+ static isLeapYear(year: number): boolean {
310
341
  if (year % 4 !== 0) return false
311
342
  if (year % 100 !== 0) return true
312
343
  return year % 400 === 0
@@ -326,6 +357,14 @@ export class LocalDate {
326
357
  return new Date(this.year, this.month - 1, this.day)
327
358
  }
328
359
 
360
+ toLocalTime(): LocalTime {
361
+ return LocalTime.of(this.toDate())
362
+ }
363
+
364
+ toISODate(): IsoDate {
365
+ return this.toString()
366
+ }
367
+
329
368
  toString(): IsoDate {
330
369
  return [
331
370
  String(this.year).padStart(4, '0'),
@@ -342,6 +381,15 @@ export class LocalDate {
342
381
  ].join('')
343
382
  }
344
383
 
384
+ // May be not optimal, as LocalTime better suits it
385
+ unix(): UnixTimestamp {
386
+ return Math.floor(this.toDate().valueOf() / 1000)
387
+ }
388
+
389
+ unixMillis(): number {
390
+ return this.toDate().valueOf()
391
+ }
392
+
345
393
  toJSON(): IsoDate {
346
394
  return this.toString()
347
395
  }
@@ -1,5 +1,6 @@
1
1
  import { _assert } from '../error/assert'
2
- import { IsoDateTime, UnixTimestamp } from '../types'
2
+ import { IsoDate, IsoDateTime, UnixTimestamp } from '../types'
3
+ import { LocalDate } from './localDate'
3
4
 
4
5
  export type LocalTimeUnit = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'
5
6
 
@@ -36,24 +37,44 @@ export class LocalTime {
36
37
  * Input can already be a LocalDate - it is returned as-is in that case.
37
38
  */
38
39
  static of(d: LocalTimeConfig): LocalTime {
39
- if (d instanceof LocalTime) return d
40
- if (d instanceof Date) return new LocalTime(d)
40
+ const t = this.parseOrNull(d)
41
41
 
42
- if (typeof d === 'number') {
43
- // unix timestamp
44
- return new LocalTime(new Date(d * 1000))
42
+ if (t === null) {
43
+ throw new TypeError(`Cannot parse "${d}" into LocalTime`)
45
44
  }
46
45
 
47
- const date = new Date(d)
46
+ return t
47
+ }
48
+
49
+ /**
50
+ * Returns null if invalid
51
+ */
52
+ static parseOrNull(d: LocalTimeConfig): LocalTime | null {
53
+ if (d instanceof LocalTime) return d
54
+
55
+ let date
56
+
57
+ if (d instanceof Date) {
58
+ date = d
59
+ } else if (typeof d === 'number') {
60
+ date = new Date(d * 1000)
61
+ } else {
62
+ date = new Date(d)
63
+ }
48
64
 
49
65
  // validation
50
66
  if (isNaN(date.getDate())) {
51
- throw new TypeError(`Cannot parse "${d}" into LocalTime`)
67
+ // throw new TypeError(`Cannot parse "${d}" into LocalTime`)
68
+ return null
52
69
  }
53
70
 
54
71
  return new LocalTime(date)
55
72
  }
56
73
 
74
+ static isValid(d: LocalTimeConfig): boolean {
75
+ return this.parseOrNull(d) !== null
76
+ }
77
+
57
78
  static unix(ts: UnixTimestamp): LocalTime {
58
79
  return new LocalTime(new Date(ts * 1000))
59
80
  }
@@ -331,16 +352,42 @@ export class LocalTime {
331
352
  return Math.floor(this.$date.valueOf() / 1000)
332
353
  }
333
354
 
355
+ unixMillis(): number {
356
+ return this.$date.valueOf()
357
+ }
358
+
334
359
  valueOf(): UnixTimestamp {
335
360
  return Math.floor(this.$date.valueOf() / 1000)
336
361
  }
337
362
 
338
- toISO8601(): IsoDateTime {
363
+ toLocalDate(): LocalDate {
364
+ return LocalDate.create(
365
+ this.$date.getFullYear(),
366
+ this.$date.getMonth() + 1,
367
+ this.$date.getDate(),
368
+ )
369
+ }
370
+
371
+ toPretty(seconds = true): IsoDateTime {
372
+ return this.$date
373
+ .toISOString()
374
+ .slice(0, seconds ? 19 : 16)
375
+ .split('T')
376
+ .join(' ')
377
+ }
378
+
379
+ /**
380
+ * Returns e.g: `1984-06-21T17:56:21`, only the date part of DateTime
381
+ */
382
+ toISODateTime(): IsoDateTime {
339
383
  return this.$date.toISOString().slice(0, 19)
340
384
  }
341
385
 
342
- toPretty(): IsoDateTime {
343
- return this.$date.toISOString().slice(0, 19).split('T').join(' ')
386
+ /**
387
+ * Returns e.g: `1984-06-21`, only the date part of DateTime
388
+ */
389
+ toISODate(): IsoDate {
390
+ return this.$date.toISOString().slice(0, 10)
344
391
  }
345
392
 
346
393
  /**