@naturalcycles/js-lib 14.88.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.
@@ -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
+ }
@@ -0,0 +1,362 @@
1
+ import { _assert } from '../error/assert'
2
+ import { IsoDateTime, UnixTimestamp } from '../types'
3
+
4
+ export type LocalTimeUnit = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'
5
+
6
+ export type LocalTimeConfig = LocalTime | Date | IsoDateTime | UnixTimestamp
7
+
8
+ export interface LocalTimeComponents {
9
+ year: number
10
+ month: number
11
+ day: number
12
+ hour: number
13
+ minute: number
14
+ second: number
15
+ }
16
+
17
+ /* eslint-disable no-dupe-class-members */
18
+
19
+ // Design choices:
20
+ // No milliseconds
21
+ // No timezone support, ISO8601 is parsed as LocalDateTime, discarding Timezone information
22
+ // Formats as unix timestamp, ISO8601 or "pretty string"
23
+ // toString and .toJSON formats as unix timestamp
24
+ // No "unixMillis", just pure unixtimestamp
25
+ // .valueOf returns unix timestamp (no millis)
26
+ // Prevents dayjs(undefined) being dayjs.now()
27
+ // Validates on parse, throws if invalid. Doesn't allow invalid objects
28
+ /**
29
+ * @experimental
30
+ */
31
+ export class LocalTime {
32
+ private constructor(private $date: Date) {}
33
+
34
+ /**
35
+ * Parses input String into LocalDate.
36
+ * Input can already be a LocalDate - it is returned as-is in that case.
37
+ */
38
+ static of(d: LocalTimeConfig): LocalTime {
39
+ if (d instanceof LocalTime) return d
40
+ if (d instanceof Date) return new LocalTime(d)
41
+
42
+ if (typeof d === 'number') {
43
+ // unix timestamp
44
+ return new LocalTime(new Date(d * 1000))
45
+ }
46
+
47
+ const date = new Date(d)
48
+
49
+ // validation
50
+ if (isNaN(date.getDate())) {
51
+ throw new TypeError(`Cannot parse "${d}" into LocalTime`)
52
+ }
53
+
54
+ return new LocalTime(date)
55
+ }
56
+
57
+ static unix(ts: UnixTimestamp): LocalTime {
58
+ return new LocalTime(new Date(ts * 1000))
59
+ }
60
+
61
+ static now(): LocalTime {
62
+ return this.of(new Date())
63
+ }
64
+
65
+ static fromComponents(
66
+ c: { year: number; month: number } & Partial<LocalTimeComponents>,
67
+ ): LocalTime {
68
+ return new LocalTime(new Date(c.year, c.month - 1, c.day, c.hour, c.minute, c.second))
69
+ }
70
+
71
+ get(unit: LocalTimeUnit): number {
72
+ if (unit === 'year') {
73
+ return this.$date.getFullYear()
74
+ }
75
+ if (unit === 'month') {
76
+ return this.$date.getMonth() + 1
77
+ }
78
+ if (unit === 'day') {
79
+ return this.$date.getDate()
80
+ }
81
+ if (unit === 'hour') {
82
+ return this.$date.getHours()
83
+ }
84
+ if (unit === 'minute') {
85
+ return this.$date.getMinutes()
86
+ }
87
+ // second
88
+ return this.$date.getSeconds()
89
+ }
90
+
91
+ set(unit: LocalTimeUnit, v: number, mutate = false): LocalTime {
92
+ const t = mutate ? this : this.clone()
93
+
94
+ if (unit === 'year') {
95
+ t.$date.setFullYear(v)
96
+ } else if (unit === 'month') {
97
+ t.$date.setMonth(v - 1)
98
+ } else if (unit === 'day') {
99
+ t.$date.setDate(v)
100
+ } else if (unit === 'hour') {
101
+ t.$date.setHours(v)
102
+ } else if (unit === 'minute') {
103
+ t.$date.setMinutes(v)
104
+ } else if (unit === 'second') {
105
+ t.$date.setSeconds(v)
106
+ }
107
+
108
+ return t
109
+ }
110
+
111
+ year(): number
112
+ year(v: number): LocalTime
113
+ year(v?: number): number | LocalTime {
114
+ return v === undefined ? this.$date.getFullYear() : this.set('year', v)
115
+ }
116
+ month(): number
117
+ month(v: number): LocalTime
118
+ month(v?: number): number | LocalTime {
119
+ return v === undefined ? this.$date.getMonth() + 1 : this.set('month', v)
120
+ }
121
+ date(): number
122
+ date(v: number): LocalTime
123
+ date(v?: number): number | LocalTime {
124
+ return v === undefined ? this.$date.getDate() : this.set('day', v)
125
+ }
126
+ hour(): number
127
+ hour(v: number): LocalTime
128
+ hour(v?: number): number | LocalTime {
129
+ return v === undefined ? this.$date.getHours() : this.set('hour', v)
130
+ }
131
+ minute(): number
132
+ minute(v: number): LocalTime
133
+ minute(v?: number): number | LocalTime {
134
+ return v === undefined ? this.$date.getMinutes() : this.set('minute', v)
135
+ }
136
+ second(): number
137
+ second(v: number): LocalTime
138
+ second(v?: number): number | LocalTime {
139
+ return v === undefined ? this.$date.getSeconds() : this.set('second', v)
140
+ }
141
+
142
+ setComponents(c: Partial<LocalTimeComponents>, mutate = false): LocalTime {
143
+ const d = mutate ? this.$date : new Date(this.$date)
144
+
145
+ if (c.year) {
146
+ d.setFullYear(c.year)
147
+ }
148
+ if (c.month) {
149
+ d.setMonth(c.month - 1)
150
+ }
151
+ if (c.day) {
152
+ d.setDate(c.day)
153
+ }
154
+ if (c.hour !== undefined) {
155
+ d.setHours(c.hour)
156
+ }
157
+ if (c.minute !== undefined) {
158
+ d.setMinutes(c.minute)
159
+ }
160
+ if (c.second !== undefined) {
161
+ d.setSeconds(c.second)
162
+ }
163
+
164
+ return mutate ? this : new LocalTime(d)
165
+ }
166
+
167
+ add(num: number, unit: LocalTimeUnit, mutate = false): LocalTime {
168
+ return this.set(unit, this.get(unit) + num, mutate)
169
+ }
170
+
171
+ subtract(num: number, unit: LocalTimeUnit, mutate = false): LocalTime {
172
+ return this.add(-num, unit, mutate)
173
+ }
174
+
175
+ absDiff(other: LocalTimeConfig, unit: LocalTimeUnit): number {
176
+ return Math.abs(this.diff(other, unit))
177
+ }
178
+
179
+ diff(other: LocalTimeConfig, unit: LocalTimeUnit): number {
180
+ const date2 = LocalTime.of(other).$date
181
+
182
+ if (unit === 'year') {
183
+ return this.$date.getFullYear() - date2.getFullYear()
184
+ }
185
+ if (unit === 'month') {
186
+ return (
187
+ (this.$date.getFullYear() - date2.getFullYear()) * 12 +
188
+ this.$date.getMonth() -
189
+ date2.getMonth()
190
+ )
191
+ }
192
+
193
+ const secDiff = (this.$date.valueOf() - date2.valueOf()) / 1000
194
+ let r
195
+
196
+ if (unit === 'day') {
197
+ r = secDiff / (24 * 60 * 60)
198
+ } else if (unit === 'hour') {
199
+ r = secDiff / (60 * 60)
200
+ } else if (unit === 'minute') {
201
+ r = secDiff / 60
202
+ } else {
203
+ // unit === 'second'
204
+ r = secDiff
205
+ }
206
+
207
+ r = r < 0 ? -Math.floor(-r) : Math.floor(r)
208
+ if (Object.is(r, -0)) return 0
209
+ return r
210
+ }
211
+
212
+ startOf(unit: LocalTimeUnit, mutate = false): LocalTime {
213
+ if (unit === 'second') return this
214
+
215
+ if (mutate) {
216
+ const d = this.$date
217
+ d.setSeconds(0)
218
+ if (unit === 'minute') return this
219
+ d.setMinutes(0)
220
+ if (unit === 'hour') return this
221
+ d.setHours(0)
222
+ if (unit === 'day') return this
223
+ d.setDate(0)
224
+ if (unit === 'month') return this
225
+ d.setMonth(0)
226
+ return this
227
+ }
228
+
229
+ const c = this.components()
230
+
231
+ c.second = 0
232
+ if (unit === 'year') {
233
+ c.month = c.day = 1
234
+ c.hour = c.minute = 0
235
+ } else if (unit === 'month') {
236
+ c.day = 1
237
+ c.hour = c.minute = 0
238
+ } else if (unit === 'day') {
239
+ c.hour = c.minute = 0
240
+ } else if (unit === 'hour') {
241
+ c.minute = 0
242
+ }
243
+
244
+ return LocalTime.fromComponents(c)
245
+ }
246
+
247
+ static sort(items: LocalTime[], mutate = false, descending = false): LocalTime[] {
248
+ const mod = descending ? -1 : 1
249
+ return (mutate ? items : [...items]).sort((a, b) => {
250
+ const v1 = a.$date.valueOf()
251
+ const v2 = b.$date.valueOf()
252
+ if (v1 === v2) return 0
253
+ return (v1 < v2 ? -1 : 1) * mod
254
+ })
255
+ }
256
+
257
+ static earliestOrUndefined(items: LocalTime[]): LocalTime | undefined {
258
+ return items.length ? LocalTime.earliest(items) : undefined
259
+ }
260
+
261
+ static earliest(items: LocalTime[]): LocalTime {
262
+ _assert(items.length, 'LocalTime.earliest called on empty array')
263
+
264
+ return items.reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
265
+ }
266
+
267
+ static latestOrUndefined(items: LocalTime[]): LocalTime | undefined {
268
+ return items.length ? LocalTime.latest(items) : undefined
269
+ }
270
+
271
+ static latest(items: LocalTime[]): LocalTime {
272
+ _assert(items.length, 'LocalTime.latest called on empty array')
273
+
274
+ return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
275
+ }
276
+
277
+ isSame(d: LocalTimeConfig): boolean {
278
+ return this.cmp(d) === 0
279
+ }
280
+
281
+ isBefore(d: LocalTimeConfig): boolean {
282
+ return this.cmp(d) === -1
283
+ }
284
+
285
+ isSameOrBefore(d: LocalTimeConfig): boolean {
286
+ return this.cmp(d) <= 0
287
+ }
288
+
289
+ isAfter(d: LocalTimeConfig): boolean {
290
+ return this.cmp(d) === 1
291
+ }
292
+
293
+ isSameOrAfter(d: LocalTimeConfig): boolean {
294
+ return this.cmp(d) >= 0
295
+ }
296
+
297
+ /**
298
+ * Returns 1 if this > d
299
+ * returns 0 if they are equal
300
+ * returns -1 if this < d
301
+ */
302
+ cmp(d: LocalTimeConfig): -1 | 0 | 1 {
303
+ const t1 = this.$date.valueOf()
304
+ const t2 = LocalTime.of(d).$date.valueOf()
305
+ if (t1 === t2) return 0
306
+ return t1 < t2 ? -1 : 1
307
+ }
308
+
309
+ // todo: endOf
310
+
311
+ components(): LocalTimeComponents {
312
+ return {
313
+ year: this.$date.getFullYear(),
314
+ month: this.$date.getMonth() + 1,
315
+ day: this.$date.getDate(),
316
+ hour: this.$date.getHours(),
317
+ minute: this.$date.getMinutes(),
318
+ second: this.$date.getSeconds(),
319
+ }
320
+ }
321
+
322
+ getDate(): Date {
323
+ return this.$date
324
+ }
325
+
326
+ clone(): LocalTime {
327
+ return new LocalTime(new Date(this.$date))
328
+ }
329
+
330
+ unix(): UnixTimestamp {
331
+ return Math.floor(this.$date.valueOf() / 1000)
332
+ }
333
+
334
+ valueOf(): UnixTimestamp {
335
+ return Math.floor(this.$date.valueOf() / 1000)
336
+ }
337
+
338
+ toISO8601(): IsoDateTime {
339
+ return this.$date.toISOString().slice(0, 19)
340
+ }
341
+
342
+ toPretty(): IsoDateTime {
343
+ return this.$date.toISOString().slice(0, 19).split('T').join(' ')
344
+ }
345
+
346
+ toString(): string {
347
+ return String(this.unix())
348
+ }
349
+
350
+ toJSON(): UnixTimestamp {
351
+ return this.unix()
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Shortcut wrapper around `LocalDate.parse` / `LocalDate.today`
357
+ */
358
+ export function localTime(d?: LocalTimeConfig): LocalTime {
359
+ return d ? LocalTime.of(d) : LocalTime.now()
360
+ }
361
+
362
+ // todo: range