@naturalcycles/js-lib 14.89.0 → 14.91.1
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/datetime/localDate.d.ts +10 -3
- package/dist/datetime/localDate.js +41 -17
- package/dist/datetime/localTime.d.ts +21 -3
- package/dist/datetime/localTime.js +58 -10
- package/dist-esm/datetime/localDate.js +41 -17
- package/dist-esm/datetime/localTime.js +58 -10
- package/package.json +1 -1
- package/src/datetime/localDate.ts +52 -17
- package/src/datetime/localTime.ts +69 -11
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Sequence } from '../seq/seq';
|
|
2
2
|
import { IsoDate } 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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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,6 +71,7 @@ export declare class LocalDate {
|
|
|
65
71
|
* Timezone will match local timezone.
|
|
66
72
|
*/
|
|
67
73
|
toDate(): Date;
|
|
74
|
+
toLocalTime(): LocalTime;
|
|
68
75
|
toString(): IsoDate;
|
|
69
76
|
toStringCompact(): string;
|
|
70
77
|
toJSON(): IsoDate;
|
|
@@ -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
|
-
|
|
26
|
-
|
|
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
|
|
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 ||
|
|
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 +=
|
|
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 -=
|
|
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 +=
|
|
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 -=
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
259
|
+
static getYearLength(year) {
|
|
239
260
|
return this.isLeapYear(year) ? 366 : 365;
|
|
240
261
|
}
|
|
241
|
-
|
|
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,9 @@ 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
|
+
}
|
|
265
289
|
toString() {
|
|
266
290
|
return [
|
|
267
291
|
String(this.year).padStart(4, '0'),
|
|
@@ -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: {
|
|
@@ -67,8 +73,20 @@ export declare class LocalTime {
|
|
|
67
73
|
clone(): LocalTime;
|
|
68
74
|
unix(): UnixTimestamp;
|
|
69
75
|
valueOf(): UnixTimestamp;
|
|
70
|
-
|
|
71
|
-
toPretty(): IsoDateTime;
|
|
76
|
+
toLocalDate(): LocalDate;
|
|
77
|
+
toPretty(seconds?: boolean): IsoDateTime;
|
|
78
|
+
/**
|
|
79
|
+
* Returns e.g: `1984-06-21T17:56:21`, only the date part of DateTime
|
|
80
|
+
*/
|
|
81
|
+
toISODateTime(): IsoDateTime;
|
|
82
|
+
/**
|
|
83
|
+
* Returns e.g: `1984-06-21`, only the date part of DateTime
|
|
84
|
+
*/
|
|
85
|
+
toISODate(): IsoDate;
|
|
86
|
+
/**
|
|
87
|
+
* Returns e.g: `19840621_1705`
|
|
88
|
+
*/
|
|
89
|
+
toStringCompact(seconds?: boolean): string;
|
|
72
90
|
toString(): string;
|
|
73
91
|
toJSON(): UnixTimestamp;
|
|
74
92
|
}
|
|
@@ -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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
}
|
|
@@ -280,11 +298,41 @@ class LocalTime {
|
|
|
280
298
|
valueOf() {
|
|
281
299
|
return Math.floor(this.$date.valueOf() / 1000);
|
|
282
300
|
}
|
|
283
|
-
|
|
301
|
+
toLocalDate() {
|
|
302
|
+
return localDate_1.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() {
|
|
284
315
|
return this.$date.toISOString().slice(0, 19);
|
|
285
316
|
}
|
|
286
|
-
|
|
287
|
-
|
|
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);
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Returns e.g: `19840621_1705`
|
|
325
|
+
*/
|
|
326
|
+
toStringCompact(seconds = false) {
|
|
327
|
+
return [
|
|
328
|
+
String(this.$date.getFullYear()).padStart(4, '0'),
|
|
329
|
+
String(this.$date.getMonth() + 1).padStart(2, '0'),
|
|
330
|
+
String(this.$date.getDate()).padStart(2, '0'),
|
|
331
|
+
'_',
|
|
332
|
+
String(this.$date.getHours()).padStart(2, '0'),
|
|
333
|
+
String(this.$date.getMinutes()).padStart(2, '0'),
|
|
334
|
+
seconds ? String(this.$date.getSeconds()).padStart(2, '0') : '',
|
|
335
|
+
].join('');
|
|
288
336
|
}
|
|
289
337
|
toString() {
|
|
290
338
|
return String(this.unix());
|
|
@@ -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
|
-
|
|
23
|
-
|
|
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
|
|
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 ||
|
|
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 +=
|
|
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 -=
|
|
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 +=
|
|
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 -=
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
256
|
+
static getYearLength(year) {
|
|
236
257
|
return this.isLeapYear(year) ? 366 : 365;
|
|
237
258
|
}
|
|
238
|
-
|
|
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,9 @@ 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
|
+
}
|
|
262
286
|
toString() {
|
|
263
287
|
return [
|
|
264
288
|
String(this.year).padStart(4, '0'),
|
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
}
|
|
@@ -277,11 +295,41 @@ export class LocalTime {
|
|
|
277
295
|
valueOf() {
|
|
278
296
|
return Math.floor(this.$date.valueOf() / 1000);
|
|
279
297
|
}
|
|
280
|
-
|
|
298
|
+
toLocalDate() {
|
|
299
|
+
return LocalDate.create(this.$date.getFullYear(), this.$date.getMonth() + 1, this.$date.getDate());
|
|
300
|
+
}
|
|
301
|
+
toPretty(seconds = true) {
|
|
302
|
+
return this.$date
|
|
303
|
+
.toISOString()
|
|
304
|
+
.slice(0, seconds ? 19 : 16)
|
|
305
|
+
.split('T')
|
|
306
|
+
.join(' ');
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Returns e.g: `1984-06-21T17:56:21`, only the date part of DateTime
|
|
310
|
+
*/
|
|
311
|
+
toISODateTime() {
|
|
281
312
|
return this.$date.toISOString().slice(0, 19);
|
|
282
313
|
}
|
|
283
|
-
|
|
284
|
-
|
|
314
|
+
/**
|
|
315
|
+
* Returns e.g: `1984-06-21`, only the date part of DateTime
|
|
316
|
+
*/
|
|
317
|
+
toISODate() {
|
|
318
|
+
return this.$date.toISOString().slice(0, 10);
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Returns e.g: `19840621_1705`
|
|
322
|
+
*/
|
|
323
|
+
toStringCompact(seconds = false) {
|
|
324
|
+
return [
|
|
325
|
+
String(this.$date.getFullYear()).padStart(4, '0'),
|
|
326
|
+
String(this.$date.getMonth() + 1).padStart(2, '0'),
|
|
327
|
+
String(this.$date.getDate()).padStart(2, '0'),
|
|
328
|
+
'_',
|
|
329
|
+
String(this.$date.getHours()).padStart(2, '0'),
|
|
330
|
+
String(this.$date.getMinutes()).padStart(2, '0'),
|
|
331
|
+
seconds ? String(this.$date.getSeconds()).padStart(2, '0') : '',
|
|
332
|
+
].join('');
|
|
285
333
|
}
|
|
286
334
|
toString() {
|
|
287
335
|
return String(this.unix());
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { _assert } from '../error/assert'
|
|
2
2
|
import { Sequence } from '../seq/seq'
|
|
3
3
|
import { END, IsoDate } 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
|
-
|
|
27
|
-
|
|
28
|
-
const [year, month, day] = d.slice(0, 10).split('-').map(Number)
|
|
27
|
+
const t = this.parseOrNull(d)
|
|
29
28
|
|
|
30
|
-
if (
|
|
29
|
+
if (t === null) {
|
|
31
30
|
throw new Error(`Cannot parse "${d}" into LocalDate`)
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
return
|
|
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 ||
|
|
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 +=
|
|
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 -=
|
|
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 +=
|
|
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 -=
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
331
|
+
static getYearLength(year: number): number {
|
|
301
332
|
return this.isLeapYear(year) ? 366 : 365
|
|
302
333
|
}
|
|
303
334
|
|
|
304
|
-
|
|
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
|
-
|
|
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,10 @@ 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
|
+
|
|
329
364
|
toString(): IsoDate {
|
|
330
365
|
return [
|
|
331
366
|
String(this.year).padStart(4, '0'),
|
|
@@ -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
|
-
|
|
40
|
-
if (d instanceof Date) return new LocalTime(d)
|
|
40
|
+
const t = this.parseOrNull(d)
|
|
41
41
|
|
|
42
|
-
if (
|
|
43
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -335,12 +356,49 @@ export class LocalTime {
|
|
|
335
356
|
return Math.floor(this.$date.valueOf() / 1000)
|
|
336
357
|
}
|
|
337
358
|
|
|
338
|
-
|
|
359
|
+
toLocalDate(): LocalDate {
|
|
360
|
+
return LocalDate.create(
|
|
361
|
+
this.$date.getFullYear(),
|
|
362
|
+
this.$date.getMonth() + 1,
|
|
363
|
+
this.$date.getDate(),
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
toPretty(seconds = true): IsoDateTime {
|
|
368
|
+
return this.$date
|
|
369
|
+
.toISOString()
|
|
370
|
+
.slice(0, seconds ? 19 : 16)
|
|
371
|
+
.split('T')
|
|
372
|
+
.join(' ')
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Returns e.g: `1984-06-21T17:56:21`, only the date part of DateTime
|
|
377
|
+
*/
|
|
378
|
+
toISODateTime(): IsoDateTime {
|
|
339
379
|
return this.$date.toISOString().slice(0, 19)
|
|
340
380
|
}
|
|
341
381
|
|
|
342
|
-
|
|
343
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Returns e.g: `1984-06-21`, only the date part of DateTime
|
|
384
|
+
*/
|
|
385
|
+
toISODate(): IsoDate {
|
|
386
|
+
return this.$date.toISOString().slice(0, 10)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Returns e.g: `19840621_1705`
|
|
391
|
+
*/
|
|
392
|
+
toStringCompact(seconds = false): string {
|
|
393
|
+
return [
|
|
394
|
+
String(this.$date.getFullYear()).padStart(4, '0'),
|
|
395
|
+
String(this.$date.getMonth() + 1).padStart(2, '0'),
|
|
396
|
+
String(this.$date.getDate()).padStart(2, '0'),
|
|
397
|
+
'_',
|
|
398
|
+
String(this.$date.getHours()).padStart(2, '0'),
|
|
399
|
+
String(this.$date.getMinutes()).padStart(2, '0'),
|
|
400
|
+
seconds ? String(this.$date.getSeconds()).padStart(2, '0') : '',
|
|
401
|
+
].join('')
|
|
344
402
|
}
|
|
345
403
|
|
|
346
404
|
toString(): string {
|