@naturalcycles/js-lib 14.97.0 → 14.97.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.
@@ -1,8 +1,7 @@
1
1
  import { _assert } from '../error/assert';
2
- import { Sequence } from '../seq/seq';
3
- import { END } from '../types';
4
2
  import { LocalTime } from './localTime';
5
- const m31 = new Set([1, 3, 5, 7, 8, 10, 12]);
3
+ const MDAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4
+ const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
6
5
  /* eslint-disable no-dupe-class-members */
7
6
  /**
8
7
  * @experimental
@@ -48,8 +47,13 @@ export class LocalDate {
48
47
  return null;
49
48
  if (d instanceof LocalDate)
50
49
  return d;
51
- // todo: explore more performant options
52
- const [year, month, day] = d.slice(0, 10).split('-').map(Number);
50
+ // const [year, month, day] = d.slice(0, 10).split('-').map(Number)
51
+ const matches = DATE_REGEX.exec(d.slice(0, 10));
52
+ if (!matches)
53
+ return null;
54
+ const year = Number(matches[1]);
55
+ const month = Number(matches[2]);
56
+ const day = Number(matches[3]);
53
57
  if (!year ||
54
58
  !month ||
55
59
  month < 1 ||
@@ -61,6 +65,10 @@ export class LocalDate {
61
65
  }
62
66
  return new LocalDate(year, month, day);
63
67
  }
68
+ // Can use just .toString()
69
+ // static parseToString(d: LocalDateConfig): IsoDateString {
70
+ // return typeof d === 'string' ? d : d.toString()
71
+ // }
64
72
  static isValid(iso) {
65
73
  return this.parseOrNull(iso) !== null;
66
74
  }
@@ -88,32 +96,23 @@ export class LocalDate {
88
96
  _assert(items.length, 'LocalDate.latest called on empty array');
89
97
  return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item));
90
98
  }
91
- static range(minIncl, maxExcl, step = 1, stepUnit = 'day') {
92
- const days = [];
93
- let current = LocalDate.of(minIncl).startOf(stepUnit);
94
- const max = LocalDate.of(maxExcl).startOf(stepUnit);
95
- do {
96
- days.push(current);
99
+ static range(min, max, incl = '[)', step = 1, stepUnit = 'day') {
100
+ const dates = [];
101
+ const $min = LocalDate.of(min);
102
+ const $max = LocalDate.of(max).startOf(stepUnit);
103
+ let current = $min.startOf(stepUnit);
104
+ if (current.isAfter($min, incl[0] === '[')) {
105
+ // ok
106
+ }
107
+ else {
108
+ current.add(1, stepUnit, true);
109
+ }
110
+ const incl2 = incl[1] === ']';
111
+ while (current.isBefore($max, incl2)) {
112
+ dates.push(current);
97
113
  current = current.add(step, stepUnit);
98
- } while (current.isBefore(max));
99
- return days;
100
- }
101
- static rangeSeq(minIncl, maxExcl, step = 1, stepUnit = 'day') {
102
- const min = LocalDate.of(minIncl).startOf(stepUnit);
103
- const max = LocalDate.of(maxExcl).startOf(stepUnit);
104
- return Sequence.create(min, d => {
105
- const next = d.add(step, stepUnit);
106
- return next.isAfter(max) ? END : next;
107
- });
108
- }
109
- static rangeString(minIncl, maxExcl, step = 1, stepUnit = 'day') {
110
- return LocalDate.range(minIncl, maxExcl, step, stepUnit).map(ld => ld.toString());
111
- }
112
- static rangeIncl(minIncl, maxIncl, step = 1, stepUnit = 'day') {
113
- return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit);
114
- }
115
- static rangeInclString(minIncl, maxIncl, step = 1, stepUnit = 'day') {
116
- return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit).map(ld => ld.toString());
114
+ }
115
+ return dates;
117
116
  }
118
117
  get(unit) {
119
118
  return unit === 'year' ? this.$year : unit === 'month' ? this.$month : this.$day;
@@ -144,14 +143,16 @@ export class LocalDate {
144
143
  d = LocalDate.of(d);
145
144
  return this.$day === d.$day && this.$month === d.$month && this.$year === d.$year;
146
145
  }
147
- isBefore(d) {
148
- return this.cmp(d) === -1;
146
+ isBefore(d, inclusive = false) {
147
+ const r = this.cmp(d);
148
+ return r === -1 || (r === 0 && inclusive);
149
149
  }
150
150
  isSameOrBefore(d) {
151
151
  return this.cmp(d) <= 0;
152
152
  }
153
- isAfter(d) {
154
- return this.cmp(d) === 1;
153
+ isAfter(d, inclusive = false) {
154
+ const r = this.cmp(d);
155
+ return r === 1 || (r === 0 && inclusive);
155
156
  }
156
157
  isSameOrAfter(d) {
157
158
  return this.cmp(d) >= 0;
@@ -241,24 +242,29 @@ export class LocalDate {
241
242
  $year += num;
242
243
  }
243
244
  // check day overflow
244
- let monLen = LocalDate.getMonthLength($year, $month);
245
- while ($day > monLen) {
246
- $day -= monLen;
247
- $month += 1;
248
- if ($month > 12) {
249
- $year += 1;
250
- $month -= 12;
245
+ if (unit === 'day') {
246
+ if ($day < 1) {
247
+ while ($day < 1) {
248
+ $month -= 1;
249
+ if ($month < 1) {
250
+ $year -= 1;
251
+ $month += 12;
252
+ }
253
+ $day += LocalDate.getMonthLength($year, $month);
254
+ }
251
255
  }
252
- monLen = LocalDate.getMonthLength($year, $month);
253
- }
254
- while ($day < 1) {
255
- $day += monLen;
256
- $month -= 1;
257
- if ($month < 1) {
258
- $year -= 1;
259
- $month += 12;
256
+ else {
257
+ let monLen = LocalDate.getMonthLength($year, $month);
258
+ while ($day > monLen) {
259
+ $day -= monLen;
260
+ $month += 1;
261
+ if ($month > 12) {
262
+ $year += 1;
263
+ $month -= 12;
264
+ }
265
+ monLen = LocalDate.getMonthLength($year, $month);
266
+ }
260
267
  }
261
- monLen = LocalDate.getMonthLength($year, $month);
262
268
  }
263
269
  // check month overflow
264
270
  while ($month > 12) {
@@ -302,14 +308,10 @@ export class LocalDate {
302
308
  static getMonthLength(year, month) {
303
309
  if (month === 2)
304
310
  return this.isLeapYear(year) ? 29 : 28;
305
- return m31.has(month) ? 31 : 30;
311
+ return MDAYS[month];
306
312
  }
307
313
  static isLeapYear(year) {
308
- if (year % 4 !== 0)
309
- return false;
310
- if (year % 100 !== 0)
311
- return true;
312
- return year % 400 === 0;
314
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
313
315
  }
314
316
  clone() {
315
317
  return new LocalDate(this.$year, this.$month, this.$day);
@@ -45,7 +45,7 @@ export class LocalTime {
45
45
  date = new Date(d * 1000);
46
46
  }
47
47
  else {
48
- date = new Date(d);
48
+ date = new Date(d.slice(0, 19));
49
49
  }
50
50
  // validation
51
51
  if (isNaN(date.getDate())) {
@@ -57,6 +57,28 @@ export class LocalTime {
57
57
  // }
58
58
  return new LocalTime(date, false);
59
59
  }
60
+ static parseToDate(d) {
61
+ if (d instanceof LocalTime)
62
+ return d.$date;
63
+ if (d instanceof Date)
64
+ return d;
65
+ const date = typeof d === 'number' ? new Date(d * 1000) : new Date(d);
66
+ if (isNaN(date.getDate())) {
67
+ throw new TypeError(`Cannot parse "${d}" to Date`);
68
+ }
69
+ return date;
70
+ }
71
+ static parseToUnixTimestamp(d) {
72
+ if (typeof d === 'number')
73
+ return d;
74
+ if (d instanceof LocalTime)
75
+ return d.unix();
76
+ const date = d instanceof Date ? d : new Date(d);
77
+ if (isNaN(date.getDate())) {
78
+ throw new TypeError(`Cannot parse "${d}" to UnixTimestamp`);
79
+ }
80
+ return date.valueOf() / 1000;
81
+ }
60
82
  static isValid(d) {
61
83
  return this.parseOrNull(d) !== null;
62
84
  }
@@ -161,7 +183,7 @@ export class LocalTime {
161
183
  return Math.abs(this.diff(other, unit));
162
184
  }
163
185
  diff(other, unit) {
164
- const date2 = LocalTime.of(other).$date;
186
+ const date2 = LocalTime.parseToDate(other);
165
187
  if (unit === 'year') {
166
188
  return this.$date.getFullYear() - date2.getFullYear();
167
189
  }
@@ -238,14 +260,16 @@ export class LocalTime {
238
260
  isSame(d) {
239
261
  return this.cmp(d) === 0;
240
262
  }
241
- isBefore(d) {
242
- return this.cmp(d) === -1;
263
+ isBefore(d, inclusive = false) {
264
+ const r = this.cmp(d);
265
+ return r === -1 || (r === 0 && inclusive);
243
266
  }
244
267
  isSameOrBefore(d) {
245
268
  return this.cmp(d) <= 0;
246
269
  }
247
- isAfter(d) {
248
- return this.cmp(d) === 1;
270
+ isAfter(d, inclusive = false) {
271
+ const r = this.cmp(d);
272
+ return r === 1 || (r === 0 && inclusive);
249
273
  }
250
274
  isSameOrAfter(d) {
251
275
  return this.cmp(d) >= 0;
@@ -266,7 +290,7 @@ export class LocalTime {
266
290
  */
267
291
  cmp(d) {
268
292
  const t1 = this.$date.valueOf();
269
- const t2 = LocalTime.of(d).$date.valueOf();
293
+ const t2 = LocalTime.parseToDate(d).valueOf();
270
294
  if (t1 === t2)
271
295
  return 0;
272
296
  return t1 < t2 ? -1 : 1;
@@ -292,8 +316,8 @@ export class LocalTime {
292
316
  second: this.$date.getSeconds(),
293
317
  };
294
318
  }
295
- fromNow(now = LocalTime.now()) {
296
- const msDiff = LocalTime.of(now).unixMillis() - this.unixMillis();
319
+ fromNow(now = new Date()) {
320
+ const msDiff = LocalTime.parseToDate(now).valueOf() - this.$date.valueOf();
297
321
  if (msDiff === 0)
298
322
  return 'now';
299
323
  if (msDiff >= 0) {
@@ -403,4 +427,3 @@ export class LocalTime {
403
427
  export function localTime(d) {
404
428
  return d ? LocalTime.of(d) : LocalTime.now();
405
429
  }
406
- // todo: range
@@ -0,0 +1,87 @@
1
+ import { LocalTime } from './localTime';
2
+ /**
3
+ * Class that supports an "interval of time" between 2 timestamps - start and end.
4
+ * Example: `1649267185/1649267187`.
5
+ *
6
+ * @experimental
7
+ */
8
+ export class TimeInterval {
9
+ constructor($start, $end) {
10
+ this.$start = $start;
11
+ this.$end = $end;
12
+ }
13
+ static of(start, end) {
14
+ return new TimeInterval(LocalTime.parseToUnixTimestamp(start), LocalTime.parseToUnixTimestamp(end));
15
+ }
16
+ get start() {
17
+ return this.$start;
18
+ }
19
+ get end() {
20
+ return this.$end;
21
+ }
22
+ get startTime() {
23
+ return LocalTime.of(this.$start);
24
+ }
25
+ get endTime() {
26
+ return LocalTime.of(this.$end);
27
+ }
28
+ /**
29
+ * Parses string like `1649267185/1649267187` into a TimeInterval.
30
+ */
31
+ static parse(d) {
32
+ if (d instanceof TimeInterval)
33
+ return d;
34
+ const [start, end] = d.split('/').map(Number);
35
+ if (!end || !start) {
36
+ throw new Error(`Cannot parse "${d}" into TimeInterval`);
37
+ }
38
+ return new TimeInterval(start, end);
39
+ }
40
+ isSame(d) {
41
+ return this.cmp(d) === 0;
42
+ }
43
+ isBefore(d, inclusive = false) {
44
+ const r = this.cmp(d);
45
+ return r === -1 || (r === 0 && inclusive);
46
+ }
47
+ isSameOrBefore(d) {
48
+ return this.cmp(d) <= 0;
49
+ }
50
+ isAfter(d, inclusive = false) {
51
+ const r = this.cmp(d);
52
+ return r === 1 || (r === 0 && inclusive);
53
+ }
54
+ isSameOrAfter(d) {
55
+ return this.cmp(d) >= 0;
56
+ }
57
+ includes(d, incl = '[)') {
58
+ d = LocalTime.parseToUnixTimestamp(d);
59
+ if (d < this.$start || (d === this.$start && incl[0] === '('))
60
+ return false;
61
+ if (d > this.$end || (d === this.$end && incl[1] === ')'))
62
+ return false;
63
+ return true;
64
+ }
65
+ /**
66
+ * TimeIntervals compare by start date.
67
+ * If it's the same - then by end date.
68
+ */
69
+ cmp(d) {
70
+ d = TimeInterval.parse(d);
71
+ if (this.$start > d.$start)
72
+ return 1;
73
+ if (this.$start < d.$start)
74
+ return -1;
75
+ if (this.$end > d.$end)
76
+ return 1;
77
+ if (this.$end < d.$end)
78
+ return -1;
79
+ return 0;
80
+ }
81
+ toString() {
82
+ return [this.$start, this.$end].join('/');
83
+ }
84
+ toJSON() {
85
+ return this.toString();
86
+ }
87
+ }
package/dist-esm/index.js CHANGED
@@ -59,4 +59,5 @@ export * from './string/leven';
59
59
  export * from './datetime/localDate';
60
60
  export * from './datetime/localTime';
61
61
  export * from './datetime/dateInterval';
62
+ export * from './datetime/timeInterval';
62
63
  export { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pRetryFn, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.97.0",
3
+ "version": "14.97.1",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -1,6 +1,6 @@
1
- import { LocalDate, LocalDateConfig } from './localDate'
1
+ import { Inclusiveness, LocalDate, LocalDateConfig, LocalDateUnit } from './localDate'
2
2
 
3
- export type DateIntervalConfig = DateInterval | string
3
+ export type DateIntervalConfig = DateInterval | DateIntervalString
4
4
  export type DateIntervalString = string
5
5
 
6
6
  /**
@@ -34,16 +34,18 @@ export class DateInterval {
34
34
  return this.cmp(d) === 0
35
35
  }
36
36
 
37
- isBefore(d: DateIntervalConfig): boolean {
38
- return this.cmp(d) === -1
37
+ isBefore(d: DateIntervalConfig, inclusive = false): boolean {
38
+ const r = this.cmp(d)
39
+ return r === -1 || (r === 0 && inclusive)
39
40
  }
40
41
 
41
42
  isSameOrBefore(d: DateIntervalConfig): boolean {
42
43
  return this.cmp(d) <= 0
43
44
  }
44
45
 
45
- isAfter(d: DateIntervalConfig): boolean {
46
- return this.cmp(d) === 1
46
+ isAfter(d: DateIntervalConfig, inclusive = false): boolean {
47
+ const r = this.cmp(d)
48
+ return r === 1 || (r === 0 && inclusive)
47
49
  }
48
50
 
49
51
  isSameOrAfter(d: DateIntervalConfig): boolean {
@@ -53,9 +55,15 @@ export class DateInterval {
53
55
  /**
54
56
  * Ranges of DateInterval (start, end) are INCLUSIVE.
55
57
  */
56
- includes(d: LocalDateConfig): boolean {
58
+ includes(d: LocalDateConfig, incl: Inclusiveness = '[]'): boolean {
57
59
  d = LocalDate.of(d)
58
- return d.isSameOrAfter(this.start) && d.isSameOrBefore(this.end)
60
+ return d.isAfter(this.start, incl[0] === '[') && d.isBefore(this.end, incl[1] === ']')
61
+ }
62
+
63
+ intersects(int: DateIntervalConfig, inclusive = true): boolean {
64
+ const $int = DateInterval.parse(int)
65
+ const incl = inclusive ? '[]' : '()'
66
+ return this.includes($int.start, incl) || this.includes($int.end, incl)
59
67
  }
60
68
 
61
69
  /**
@@ -67,19 +75,15 @@ export class DateInterval {
67
75
  return this.start.cmp(d.start) || this.end.cmp(d.end)
68
76
  }
69
77
 
78
+ getDays(incl: Inclusiveness = '[]'): LocalDate[] {
79
+ return LocalDate.range(this.start, this.end, incl, 1, 'day')
80
+ }
81
+
70
82
  /**
71
83
  * Returns an array of LocalDates that are included in the interval.
72
- * Ranges are INCLUSIVE.
73
84
  */
74
- getDays(): LocalDate[] {
75
- const days: LocalDate[] = []
76
- let current = this.start
77
- do {
78
- days.push(current)
79
- current = current.add(1, 'day')
80
- } while (current.isSameOrBefore(this.end))
81
-
82
- return days
85
+ range(incl: Inclusiveness = '[]', step = 1, stepUnit: LocalDateUnit = 'day'): LocalDate[] {
86
+ return LocalDate.range(this.start, this.end, incl, step, stepUnit)
83
87
  }
84
88
 
85
89
  toString(): DateIntervalString {
@@ -1,14 +1,14 @@
1
1
  import { _assert } from '../error/assert'
2
- import { Sequence } from '../seq/seq'
3
- import { END, IsoDateString, UnixTimestampNumber } from '../types'
2
+ import { IsoDateString, UnixTimestampNumber } from '../types'
4
3
  import { LocalTime } from './localTime'
5
4
 
6
5
  export type LocalDateUnit = 'year' | 'month' | 'day'
7
6
  export type Inclusiveness = '()' | '[]' | '[)' | '(]'
8
7
 
9
- const m31 = new Set<number>([1, 3, 5, 7, 8, 10, 12])
8
+ const MDAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
9
+ const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
10
10
 
11
- export type LocalDateConfig = LocalDate | string
11
+ export type LocalDateConfig = LocalDate | IsoDateString
12
12
 
13
13
  /* eslint-disable no-dupe-class-members */
14
14
 
@@ -61,8 +61,13 @@ export class LocalDate {
61
61
  if (!d) return null
62
62
  if (d instanceof LocalDate) return d
63
63
 
64
- // todo: explore more performant options
65
- const [year, month, day] = d.slice(0, 10).split('-').map(Number)
64
+ // const [year, month, day] = d.slice(0, 10).split('-').map(Number)
65
+ const matches = DATE_REGEX.exec(d.slice(0, 10))
66
+ if (!matches) return null
67
+
68
+ const year = Number(matches[1])
69
+ const month = Number(matches[2])
70
+ const day = Number(matches[3])
66
71
 
67
72
  if (
68
73
  !year ||
@@ -79,6 +84,11 @@ export class LocalDate {
79
84
  return new LocalDate(year, month, day)
80
85
  }
81
86
 
87
+ // Can use just .toString()
88
+ // static parseToString(d: LocalDateConfig): IsoDateString {
89
+ // return typeof d === 'string' ? d : d.toString()
90
+ // }
91
+
82
92
  static isValid(iso: string | undefined | null): boolean {
83
93
  return this.parseOrNull(iso) !== null
84
94
  }
@@ -117,64 +127,30 @@ export class LocalDate {
117
127
  }
118
128
 
119
129
  static range(
120
- minIncl: LocalDateConfig,
121
- maxExcl: LocalDateConfig,
130
+ min: LocalDateConfig,
131
+ max: LocalDateConfig,
132
+ incl: Inclusiveness = '[)',
122
133
  step = 1,
123
134
  stepUnit: LocalDateUnit = 'day',
124
135
  ): LocalDate[] {
125
- const days: LocalDate[] = []
126
- let current = LocalDate.of(minIncl).startOf(stepUnit)
127
- const max = LocalDate.of(maxExcl).startOf(stepUnit)
128
-
129
- do {
130
- days.push(current)
131
- current = current.add(step, stepUnit)
132
- } while (current.isBefore(max))
133
-
134
- return days
135
- }
136
+ const dates: LocalDate[] = []
137
+ const $min = LocalDate.of(min)
138
+ const $max = LocalDate.of(max).startOf(stepUnit)
136
139
 
137
- static rangeSeq(
138
- minIncl: LocalDateConfig,
139
- maxExcl: LocalDateConfig,
140
- step = 1,
141
- stepUnit: LocalDateUnit = 'day',
142
- ): Sequence<LocalDate> {
143
- const min = LocalDate.of(minIncl).startOf(stepUnit)
144
- const max = LocalDate.of(maxExcl).startOf(stepUnit)
145
- return Sequence.create(min, d => {
146
- const next = d.add(step, stepUnit)
147
- return next.isAfter(max) ? END : next
148
- })
149
- }
150
-
151
- static rangeString(
152
- minIncl: LocalDateConfig,
153
- maxExcl: LocalDateConfig,
154
- step = 1,
155
- stepUnit: LocalDateUnit = 'day',
156
- ): IsoDateString[] {
157
- return LocalDate.range(minIncl, maxExcl, step, stepUnit).map(ld => ld.toString())
158
- }
140
+ let current = $min.startOf(stepUnit)
141
+ if (current.isAfter($min, incl[0] === '[')) {
142
+ // ok
143
+ } else {
144
+ current.add(1, stepUnit, true)
145
+ }
159
146
 
160
- static rangeIncl(
161
- minIncl: LocalDateConfig,
162
- maxIncl: LocalDateConfig,
163
- step = 1,
164
- stepUnit: LocalDateUnit = 'day',
165
- ): LocalDate[] {
166
- return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit)
167
- }
147
+ const incl2 = incl[1] === ']'
148
+ while (current.isBefore($max, incl2)) {
149
+ dates.push(current)
150
+ current = current.add(step, stepUnit)
151
+ }
168
152
 
169
- static rangeInclString(
170
- minIncl: LocalDateConfig,
171
- maxIncl: LocalDateConfig,
172
- step = 1,
173
- stepUnit: LocalDateUnit = 'day',
174
- ): IsoDateString[] {
175
- return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit).map(
176
- ld => ld.toString(),
177
- )
153
+ return dates
178
154
  }
179
155
 
180
156
  get(unit: LocalDateUnit): number {
@@ -216,16 +192,18 @@ export class LocalDate {
216
192
  return this.$day === d.$day && this.$month === d.$month && this.$year === d.$year
217
193
  }
218
194
 
219
- isBefore(d: LocalDateConfig): boolean {
220
- return this.cmp(d) === -1
195
+ isBefore(d: LocalDateConfig, inclusive = false): boolean {
196
+ const r = this.cmp(d)
197
+ return r === -1 || (r === 0 && inclusive)
221
198
  }
222
199
 
223
200
  isSameOrBefore(d: LocalDateConfig): boolean {
224
201
  return this.cmp(d) <= 0
225
202
  }
226
203
 
227
- isAfter(d: LocalDateConfig): boolean {
228
- return this.cmp(d) === 1
204
+ isAfter(d: LocalDateConfig, inclusive = false): boolean {
205
+ const r = this.cmp(d)
206
+ return r === 1 || (r === 0 && inclusive)
229
207
  }
230
208
 
231
209
  isSameOrAfter(d: LocalDateConfig): boolean {
@@ -317,26 +295,31 @@ export class LocalDate {
317
295
  }
318
296
 
319
297
  // check day overflow
320
- let monLen = LocalDate.getMonthLength($year, $month)
321
- while ($day > monLen) {
322
- $day -= monLen
323
- $month += 1
324
- if ($month > 12) {
325
- $year += 1
326
- $month -= 12
327
- }
328
-
329
- monLen = LocalDate.getMonthLength($year, $month)
330
- }
331
- while ($day < 1) {
332
- $day += monLen
333
- $month -= 1
334
- if ($month < 1) {
335
- $year -= 1
336
- $month += 12
298
+ if (unit === 'day') {
299
+ if ($day < 1) {
300
+ while ($day < 1) {
301
+ $month -= 1
302
+ if ($month < 1) {
303
+ $year -= 1
304
+ $month += 12
305
+ }
306
+
307
+ $day += LocalDate.getMonthLength($year, $month)
308
+ }
309
+ } else {
310
+ let monLen = LocalDate.getMonthLength($year, $month)
311
+
312
+ while ($day > monLen) {
313
+ $day -= monLen
314
+ $month += 1
315
+ if ($month > 12) {
316
+ $year += 1
317
+ $month -= 12
318
+ }
319
+
320
+ monLen = LocalDate.getMonthLength($year, $month)
321
+ }
337
322
  }
338
-
339
- monLen = LocalDate.getMonthLength($year, $month)
340
323
  }
341
324
 
342
325
  // check month overflow
@@ -388,13 +371,11 @@ export class LocalDate {
388
371
 
389
372
  static getMonthLength(year: number, month: number): number {
390
373
  if (month === 2) return this.isLeapYear(year) ? 29 : 28
391
- return m31.has(month) ? 31 : 30
374
+ return MDAYS[month]!
392
375
  }
393
376
 
394
377
  static isLeapYear(year: number): boolean {
395
- if (year % 4 !== 0) return false
396
- if (year % 100 !== 0) return true
397
- return year % 400 === 0
378
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
398
379
  }
399
380
 
400
381
  clone(): LocalDate {