@naturalcycles/js-lib 14.86.0 → 14.89.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/dist/datetime/localDate.d.ts +75 -0
- package/dist/datetime/localDate.js +290 -0
- package/dist/datetime/localTime.d.ts +78 -0
- package/dist/datetime/localTime.js +304 -0
- package/dist/decorators/createPromiseDecorator.d.ts +2 -2
- package/dist/decorators/createPromiseDecorator.js +6 -12
- package/dist/index.d.ts +6 -1
- package/dist/index.js +47 -44
- package/dist/string/leven.d.ts +6 -0
- package/dist/string/leven.js +75 -0
- package/dist-esm/datetime/localDate.js +285 -0
- package/dist-esm/datetime/localTime.js +299 -0
- package/dist-esm/decorators/createPromiseDecorator.js +8 -15
- package/dist-esm/index.js +3 -0
- package/dist-esm/string/leven.js +71 -0
- package/dist-esm/vendor/is.js +4 -4
- package/package.json +2 -1
- package/src/__exclude/lazyLocalDate.ts +73 -0
- package/src/datetime/localDate.ts +355 -0
- package/src/datetime/localTime.ts +362 -0
- package/src/decorators/createPromiseDecorator.ts +64 -70
- package/src/index.ts +10 -0
- package/src/string/leven.ts +86 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const array = [];
|
|
2
|
+
const characterCodeCache = [];
|
|
3
|
+
/* eslint-disable unicorn/prefer-code-point, no-bitwise */
|
|
4
|
+
/**
|
|
5
|
+
* Modified version of: https://github.com/sindresorhus/leven/
|
|
6
|
+
*
|
|
7
|
+
* Returns a Levenshtein distance between first and second word.
|
|
8
|
+
*/
|
|
9
|
+
export function _leven(first, second) {
|
|
10
|
+
if (first === second) {
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
const swap = first;
|
|
14
|
+
// Swapping the strings if `a` is longer than `b` so we know which one is the
|
|
15
|
+
// shortest & which one is the longest
|
|
16
|
+
if (first.length > second.length) {
|
|
17
|
+
first = second;
|
|
18
|
+
second = swap;
|
|
19
|
+
}
|
|
20
|
+
let firstLength = first.length;
|
|
21
|
+
let secondLength = second.length;
|
|
22
|
+
// Performing suffix trimming:
|
|
23
|
+
// We can linearly drop suffix common to both strings since they
|
|
24
|
+
// don't increase distance at all
|
|
25
|
+
// Note: `~-` is the bitwise way to perform a `- 1` operation
|
|
26
|
+
while (firstLength > 0 && first.charCodeAt(~-firstLength) === second.charCodeAt(~-secondLength)) {
|
|
27
|
+
firstLength--;
|
|
28
|
+
secondLength--;
|
|
29
|
+
}
|
|
30
|
+
// Performing prefix trimming
|
|
31
|
+
// We can linearly drop prefix common to both strings since they
|
|
32
|
+
// don't increase distance at all
|
|
33
|
+
let start = 0;
|
|
34
|
+
while (start < firstLength && first.charCodeAt(start) === second.charCodeAt(start)) {
|
|
35
|
+
start++;
|
|
36
|
+
}
|
|
37
|
+
firstLength -= start;
|
|
38
|
+
secondLength -= start;
|
|
39
|
+
if (firstLength === 0) {
|
|
40
|
+
return secondLength;
|
|
41
|
+
}
|
|
42
|
+
let bCharacterCode;
|
|
43
|
+
let result;
|
|
44
|
+
let temporary;
|
|
45
|
+
let temporary2;
|
|
46
|
+
let index = 0;
|
|
47
|
+
let index2 = 0;
|
|
48
|
+
while (index < firstLength) {
|
|
49
|
+
characterCodeCache[index] = first.charCodeAt(start + index);
|
|
50
|
+
array[index] = ++index;
|
|
51
|
+
}
|
|
52
|
+
while (index2 < secondLength) {
|
|
53
|
+
bCharacterCode = second.charCodeAt(start + index2);
|
|
54
|
+
temporary = index2++;
|
|
55
|
+
result = index2;
|
|
56
|
+
for (index = 0; index < firstLength; index++) {
|
|
57
|
+
temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1;
|
|
58
|
+
temporary = array[index];
|
|
59
|
+
// eslint-disable-next-line no-multi-assign
|
|
60
|
+
result = array[index] =
|
|
61
|
+
temporary > result
|
|
62
|
+
? temporary2 > result
|
|
63
|
+
? result + 1
|
|
64
|
+
: temporary2
|
|
65
|
+
: temporary2 > temporary
|
|
66
|
+
? temporary + 1
|
|
67
|
+
: temporary2;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
package/dist-esm/vendor/is.js
CHANGED
|
@@ -138,15 +138,15 @@ is.array = (value, assertion) => {
|
|
|
138
138
|
}
|
|
139
139
|
return value.every(assertion);
|
|
140
140
|
};
|
|
141
|
-
is.buffer = (value) => { var _a, _b, _c
|
|
141
|
+
is.buffer = (value) => { var _a, _b, _c; return (_c = (_b = (_a = value === null || value === void 0 ? void 0 : value.constructor) === null || _a === void 0 ? void 0 : _a.isBuffer) === null || _b === void 0 ? void 0 : _b.call(_a, value)) !== null && _c !== void 0 ? _c : false; };
|
|
142
142
|
is.nullOrUndefined = (value) => is.null_(value) || is.undefined(value);
|
|
143
143
|
is.object = (value) => !is.null_(value) && (typeof value === 'object' || is.function_(value));
|
|
144
|
-
is.iterable = (value) =>
|
|
145
|
-
is.asyncIterable = (value) =>
|
|
144
|
+
is.iterable = (value) => is.function_(value === null || value === void 0 ? void 0 : value[Symbol.iterator]);
|
|
145
|
+
is.asyncIterable = (value) => is.function_(value === null || value === void 0 ? void 0 : value[Symbol.asyncIterator]);
|
|
146
146
|
is.generator = (value) => is.iterable(value) && is.function_(value.next) && is.function_(value.throw);
|
|
147
147
|
is.asyncGenerator = (value) => is.asyncIterable(value) && is.function_(value.next) && is.function_(value.throw);
|
|
148
148
|
is.nativePromise = (value) => isObjectOfType('Promise')(value);
|
|
149
|
-
const hasPromiseAPI = (value) =>
|
|
149
|
+
const hasPromiseAPI = (value) => is.function_(value === null || value === void 0 ? void 0 : value.then) && is.function_(value === null || value === void 0 ? void 0 : value.catch);
|
|
150
150
|
is.promise = (value) => is.nativePromise(value) || hasPromiseAPI(value);
|
|
151
151
|
is.generatorFunction = isObjectOfType('GeneratorFunction');
|
|
152
152
|
is.asyncGeneratorFunction = (value) => getObjectType(value) === 'AsyncGeneratorFunction';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/js-lib",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.89.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"prepare": "husky install",
|
|
6
6
|
"build-prod": "build-prod-esm-cjs",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"@naturalcycles/bench-lib": "^1.5.0",
|
|
15
15
|
"@naturalcycles/dev-lib": "^12.0.0",
|
|
16
16
|
"@naturalcycles/nodejs-lib": "^12.33.4",
|
|
17
|
+
"@naturalcycles/time-lib": "^3.5.1",
|
|
17
18
|
"@types/node": "^17.0.4",
|
|
18
19
|
"jest": "^27.0.1",
|
|
19
20
|
"patch-package": "^6.2.1",
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { LocalDateUnit, LocalDate, LocalDateConfig } from '../datetime/localDate'
|
|
2
|
+
|
|
3
|
+
export class LazyLocalDate {
|
|
4
|
+
constructor(private str: string) {}
|
|
5
|
+
|
|
6
|
+
private ld?: LocalDate
|
|
7
|
+
|
|
8
|
+
eq(d: LocalDateConfig): boolean {
|
|
9
|
+
if (typeof d === 'string') return d === this.str
|
|
10
|
+
this.ld ||= LocalDate.of(this.str)
|
|
11
|
+
return this.ld.isSame(d)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
lt(d: LocalDateConfig): boolean {
|
|
15
|
+
return this.cmp(d) === -1
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
lte(d: LocalDateConfig): boolean {
|
|
19
|
+
return this.cmp(d) <= 0
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
gt(d: LocalDateConfig): boolean {
|
|
23
|
+
return this.cmp(d) === 1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
gte(d: LocalDateConfig): boolean {
|
|
27
|
+
return this.cmp(d) >= 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
cmp(d: LocalDateConfig): -1 | 0 | 1 {
|
|
31
|
+
if (typeof d === 'string') {
|
|
32
|
+
return this.str < d ? -1 : this.str > d ? 1 : 0
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.ld ||= LocalDate.of(this.str)
|
|
36
|
+
return this.ld.cmp(d)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
absDiff(d: LocalDateConfig, unit: LocalDateUnit): number {
|
|
40
|
+
return Math.abs(this.diff(d, unit))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
diff(d: LocalDateConfig, unit: LocalDateUnit): number {
|
|
44
|
+
this.ld ||= LocalDate.of(this.str)
|
|
45
|
+
return this.ld.diff(d, unit)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
add(num: number, unit: LocalDateUnit): LocalDate {
|
|
49
|
+
this.ld ||= LocalDate.of(this.str)
|
|
50
|
+
return this.ld.add(num, unit)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
subtract(num: number, unit: LocalDateUnit): LocalDate {
|
|
54
|
+
return this.add(-num, unit)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
clone(): LazyLocalDate {
|
|
58
|
+
return new LazyLocalDate(this.str)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
toDate(): Date {
|
|
62
|
+
this.ld ||= LocalDate.of(this.str)
|
|
63
|
+
return this.ld.toDate()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
toString(): string {
|
|
67
|
+
return this.str
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
toJSON(): string {
|
|
71
|
+
return this.str
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { _assert } from '../error/assert'
|
|
2
|
+
import { Sequence } from '../seq/seq'
|
|
3
|
+
import { END, IsoDate } from '../types'
|
|
4
|
+
|
|
5
|
+
export type LocalDateUnit = 'year' | 'month' | 'day'
|
|
6
|
+
|
|
7
|
+
const m31 = new Set<number>([1, 3, 5, 7, 8, 10, 12])
|
|
8
|
+
|
|
9
|
+
export type LocalDateConfig = LocalDate | string
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @experimental
|
|
13
|
+
*/
|
|
14
|
+
export class LocalDate {
|
|
15
|
+
private constructor(public year: number, public month: number, public day: number) {}
|
|
16
|
+
|
|
17
|
+
static create(year: number, month: number, day: number): LocalDate {
|
|
18
|
+
return new LocalDate(year, month, day)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parses input String into LocalDate.
|
|
23
|
+
* Input can already be a LocalDate - it is returned as-is in that case.
|
|
24
|
+
*/
|
|
25
|
+
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)
|
|
29
|
+
|
|
30
|
+
if (!day || !month || (!year && year !== 0)) {
|
|
31
|
+
throw new Error(`Cannot parse "${d}" into LocalDate`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return new LocalDate(year, month, day)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static parseCompact(d: string): LocalDate {
|
|
38
|
+
const [year, month, day] = [d.slice(0, 4), d.slice(4, 2), d.slice(6, 2)].map(Number)
|
|
39
|
+
|
|
40
|
+
if (!day || !month || (!year && year !== 0)) {
|
|
41
|
+
throw new Error(`Cannot parse "${d}" into LocalDate`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return new LocalDate(year, month, day)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static fromDate(d: Date): LocalDate {
|
|
48
|
+
return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static today(): LocalDate {
|
|
52
|
+
return this.fromDate(new Date())
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static sort(items: LocalDate[], mutate = false, descending = false): LocalDate[] {
|
|
56
|
+
const mod = descending ? -1 : 1
|
|
57
|
+
return (mutate ? items : [...items]).sort((a, b) => a.cmp(b) * mod)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static earliestOrUndefined(items: LocalDate[]): LocalDate | undefined {
|
|
61
|
+
return items.length ? LocalDate.earliest(items) : undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
static earliest(items: LocalDate[]): LocalDate {
|
|
65
|
+
_assert(items.length, 'LocalDate.earliest called on empty array')
|
|
66
|
+
|
|
67
|
+
return items.reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static latestOrUndefined(items: LocalDate[]): LocalDate | undefined {
|
|
71
|
+
return items.length ? LocalDate.latest(items) : undefined
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static latest(items: LocalDate[]): LocalDate {
|
|
75
|
+
_assert(items.length, 'LocalDate.latest called on empty array')
|
|
76
|
+
|
|
77
|
+
return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static range(
|
|
81
|
+
minIncl: LocalDateConfig,
|
|
82
|
+
maxExcl: LocalDateConfig,
|
|
83
|
+
step = 1,
|
|
84
|
+
stepUnit: LocalDateUnit = 'day',
|
|
85
|
+
): LocalDate[] {
|
|
86
|
+
const days: LocalDate[] = []
|
|
87
|
+
let current = LocalDate.of(minIncl).startOf(stepUnit)
|
|
88
|
+
const max = LocalDate.of(maxExcl).startOf(stepUnit)
|
|
89
|
+
|
|
90
|
+
do {
|
|
91
|
+
days.push(current)
|
|
92
|
+
current = current.add(step, stepUnit)
|
|
93
|
+
} while (current.isBefore(max))
|
|
94
|
+
|
|
95
|
+
return days
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static rangeSeq(
|
|
99
|
+
minIncl: LocalDateConfig,
|
|
100
|
+
maxExcl: LocalDateConfig,
|
|
101
|
+
step = 1,
|
|
102
|
+
stepUnit: LocalDateUnit = 'day',
|
|
103
|
+
): Sequence<LocalDate> {
|
|
104
|
+
const min = LocalDate.of(minIncl).startOf(stepUnit)
|
|
105
|
+
const max = LocalDate.of(maxExcl).startOf(stepUnit)
|
|
106
|
+
return Sequence.create(min, d => {
|
|
107
|
+
const next = d.add(step, stepUnit)
|
|
108
|
+
return next.isAfter(max) ? END : next
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
static rangeString(
|
|
113
|
+
minIncl: LocalDateConfig,
|
|
114
|
+
maxExcl: LocalDateConfig,
|
|
115
|
+
step = 1,
|
|
116
|
+
stepUnit: LocalDateUnit = 'day',
|
|
117
|
+
): IsoDate[] {
|
|
118
|
+
return LocalDate.range(minIncl, maxExcl, step, stepUnit).map(ld => ld.toString())
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
static rangeIncl(
|
|
122
|
+
minIncl: LocalDateConfig,
|
|
123
|
+
maxIncl: LocalDateConfig,
|
|
124
|
+
step = 1,
|
|
125
|
+
stepUnit: LocalDateUnit = 'day',
|
|
126
|
+
): LocalDate[] {
|
|
127
|
+
return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static rangeInclString(
|
|
131
|
+
minIncl: LocalDateConfig,
|
|
132
|
+
maxIncl: LocalDateConfig,
|
|
133
|
+
step = 1,
|
|
134
|
+
stepUnit: LocalDateUnit = 'day',
|
|
135
|
+
): IsoDate[] {
|
|
136
|
+
return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit).map(
|
|
137
|
+
ld => ld.toString(),
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
isSame(d: LocalDateConfig): boolean {
|
|
142
|
+
d = LocalDate.of(d)
|
|
143
|
+
return this.day === d.day && this.month === d.month && this.year === d.year
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
isBefore(d: LocalDateConfig): boolean {
|
|
147
|
+
return this.cmp(d) === -1
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
isSameOrBefore(d: LocalDateConfig): boolean {
|
|
151
|
+
return this.cmp(d) <= 0
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
isAfter(d: LocalDateConfig): boolean {
|
|
155
|
+
return this.cmp(d) === 1
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
isSameOrAfter(d: LocalDateConfig): boolean {
|
|
159
|
+
return this.cmp(d) >= 0
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Returns 1 if this > d
|
|
164
|
+
* returns 0 if they are equal
|
|
165
|
+
* returns -1 if this < d
|
|
166
|
+
*/
|
|
167
|
+
cmp(d: LocalDateConfig): -1 | 0 | 1 {
|
|
168
|
+
d = LocalDate.of(d)
|
|
169
|
+
if (this.year < d.year) return -1
|
|
170
|
+
if (this.year > d.year) return 1
|
|
171
|
+
if (this.month < d.month) return -1
|
|
172
|
+
if (this.month > d.month) return 1
|
|
173
|
+
if (this.day < d.day) return -1
|
|
174
|
+
if (this.day > d.day) return 1
|
|
175
|
+
return 0
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Same as Math.abs( diff )
|
|
180
|
+
*/
|
|
181
|
+
absDiff(d: LocalDateConfig, unit: LocalDateUnit): number {
|
|
182
|
+
return Math.abs(this.diff(d, unit))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Returns the number of **full** units difference (aka `Math.ceil`).
|
|
187
|
+
*
|
|
188
|
+
* a.diff(b) means "a minus b"
|
|
189
|
+
*/
|
|
190
|
+
diff(d: LocalDateConfig, unit: LocalDateUnit): number {
|
|
191
|
+
d = LocalDate.of(d)
|
|
192
|
+
|
|
193
|
+
if (unit === 'year') {
|
|
194
|
+
return this.year - d.year
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (unit === 'month') {
|
|
198
|
+
return (this.year - d.year) * 12 + (this.month - d.month)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// unit is 'day'
|
|
202
|
+
let days = this.day - d.day
|
|
203
|
+
|
|
204
|
+
if (d.year < this.year) {
|
|
205
|
+
for (let year = d.year; year < this.year; year++) {
|
|
206
|
+
days += this.getYearDays(year)
|
|
207
|
+
}
|
|
208
|
+
} else if (this.year < d.year) {
|
|
209
|
+
for (let year = this.year; year < d.year; year++) {
|
|
210
|
+
days -= this.getYearDays(year)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (d.month < this.month) {
|
|
215
|
+
for (let month = d.month; month < this.month; month++) {
|
|
216
|
+
days += this.getMonthLen(this.year, month)
|
|
217
|
+
}
|
|
218
|
+
} else if (this.month < d.month) {
|
|
219
|
+
for (let month = this.month; month < d.month; month++) {
|
|
220
|
+
days -= this.getMonthLen(d.year, month)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return days
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
add(num: number, unit: LocalDateUnit, mutate = false): LocalDate {
|
|
228
|
+
let { day, month, year } = this
|
|
229
|
+
|
|
230
|
+
if (unit === 'day') {
|
|
231
|
+
day += num
|
|
232
|
+
} else if (unit === 'month') {
|
|
233
|
+
month += num
|
|
234
|
+
} else if (unit === 'year') {
|
|
235
|
+
year += num
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// check day overflow
|
|
239
|
+
let monLen = this.getMonthLen(year, month)
|
|
240
|
+
while (day > monLen) {
|
|
241
|
+
day -= monLen
|
|
242
|
+
month += 1
|
|
243
|
+
if (month > 12) {
|
|
244
|
+
year += 1
|
|
245
|
+
month -= 12
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
monLen = this.getMonthLen(year, month)
|
|
249
|
+
}
|
|
250
|
+
while (day < 1) {
|
|
251
|
+
day += monLen
|
|
252
|
+
month -= 1
|
|
253
|
+
if (month < 1) {
|
|
254
|
+
year -= 1
|
|
255
|
+
month += 12
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
monLen = this.getMonthLen(year, month)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// check month overflow
|
|
262
|
+
while (month > 12) {
|
|
263
|
+
year += 1
|
|
264
|
+
month -= 12
|
|
265
|
+
}
|
|
266
|
+
while (month < 1) {
|
|
267
|
+
year -= 1
|
|
268
|
+
month += 12
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (mutate) {
|
|
272
|
+
this.year = year
|
|
273
|
+
this.month = month
|
|
274
|
+
this.day = day
|
|
275
|
+
return this
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return new LocalDate(year, month, day)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
subtract(num: number, unit: LocalDateUnit, mutate = false): LocalDate {
|
|
282
|
+
return this.add(-num, unit, mutate)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
startOf(unit: LocalDateUnit): LocalDate {
|
|
286
|
+
if (unit === 'day') return this
|
|
287
|
+
if (unit === 'month') return LocalDate.create(this.year, this.month, 1)
|
|
288
|
+
// year
|
|
289
|
+
return LocalDate.create(this.year, 1, 1)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
endOf(unit: LocalDateUnit): LocalDate {
|
|
293
|
+
if (unit === 'day') return this
|
|
294
|
+
if (unit === 'month')
|
|
295
|
+
return LocalDate.create(this.year, this.month, this.getMonthLen(this.year, this.month))
|
|
296
|
+
// year
|
|
297
|
+
return LocalDate.create(this.year, 12, 31)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private getYearDays(year: number): number {
|
|
301
|
+
return this.isLeapYear(year) ? 366 : 365
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
private getMonthLen(year: number, month: number): number {
|
|
305
|
+
if (month === 2) return this.isLeapYear(year) ? 29 : 28
|
|
306
|
+
return m31.has(month) ? 31 : 30
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private isLeapYear(year: number): boolean {
|
|
310
|
+
if (year % 4 !== 0) return false
|
|
311
|
+
if (year % 100 !== 0) return true
|
|
312
|
+
return year % 400 === 0
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
clone(): LocalDate {
|
|
316
|
+
return new LocalDate(this.year, this.month, this.day)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Converts LocalDate into instance of Date.
|
|
321
|
+
* Year, month and day will match.
|
|
322
|
+
* Hour, minute, second, ms will be 0.
|
|
323
|
+
* Timezone will match local timezone.
|
|
324
|
+
*/
|
|
325
|
+
toDate(): Date {
|
|
326
|
+
return new Date(this.year, this.month - 1, this.day)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
toString(): IsoDate {
|
|
330
|
+
return [
|
|
331
|
+
String(this.year).padStart(4, '0'),
|
|
332
|
+
String(this.month).padStart(2, '0'),
|
|
333
|
+
String(this.day).padStart(2, '0'),
|
|
334
|
+
].join('-')
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
toStringCompact(): string {
|
|
338
|
+
return [
|
|
339
|
+
String(this.year).padStart(4, '0'),
|
|
340
|
+
String(this.month).padStart(2, '0'),
|
|
341
|
+
String(this.day).padStart(2, '0'),
|
|
342
|
+
].join('')
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
toJSON(): IsoDate {
|
|
346
|
+
return this.toString()
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Shortcut wrapper around `LocalDate.parse` / `LocalDate.today`
|
|
352
|
+
*/
|
|
353
|
+
export function localDate(d?: LocalDateConfig): LocalDate {
|
|
354
|
+
return d ? LocalDate.of(d) : LocalDate.today()
|
|
355
|
+
}
|