@naturalcycles/js-lib 14.230.1 → 14.232.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.
@@ -113,12 +113,21 @@ export declare class LocalDate {
113
113
  * Timezone will match local timezone.
114
114
  */
115
115
  toDate(): Date;
116
+ /**
117
+ * Converts LocalDate to Date in UTC timezone.
118
+ * Unlike normal `.toDate` that uses browser's timezone by default.
119
+ */
120
+ toDateInUTC(): Date;
116
121
  toLocalTime(): LocalTime;
117
122
  toISODate(): IsoDateString;
118
123
  /**
119
124
  * Returns e.g: `1984-06-21T17:56:21`
120
125
  */
121
126
  toISODateTime(): IsoDateTimeString;
127
+ /**
128
+ * Returns e.g: `1984-06-21T17:56:21Z` (notice the Z at the end, which indicates UTC)
129
+ */
130
+ toISODateTimeUTC(): IsoDateTimeString;
122
131
  toString(): IsoDateString;
123
132
  toStringCompact(): string;
124
133
  toMonthId(): MonthId;
@@ -51,7 +51,6 @@ class LocalDate {
51
51
  if (d instanceof Date) {
52
52
  return this.fromDate(d);
53
53
  }
54
- // const [year, month, day] = d.slice(0, 10).split('-').map(Number)
55
54
  const matches = typeof d === 'string' && DATE_REGEX.exec(d.slice(0, 10));
56
55
  if (!matches)
57
56
  return null;
@@ -69,10 +68,6 @@ class LocalDate {
69
68
  }
70
69
  return new LocalDate(year, month, day);
71
70
  }
72
- // Can use just .toString()
73
- // static parseToString(d: LocalDateConfig): IsoDateString {
74
- // return typeof d === 'string' ? d : d.toString()
75
- // }
76
71
  static isValid(iso) {
77
72
  return this.parseOrNull(iso) !== null;
78
73
  }
@@ -391,6 +386,13 @@ class LocalDate {
391
386
  toDate() {
392
387
  return new Date(this.$year, this.$month - 1, this.$day);
393
388
  }
389
+ /**
390
+ * Converts LocalDate to Date in UTC timezone.
391
+ * Unlike normal `.toDate` that uses browser's timezone by default.
392
+ */
393
+ toDateInUTC() {
394
+ return new Date(this.toISODateTimeUTC());
395
+ }
394
396
  toLocalTime() {
395
397
  return localTime_1.LocalTime.of(this.toDate());
396
398
  }
@@ -403,6 +405,12 @@ class LocalDate {
403
405
  toISODateTime() {
404
406
  return this.toString() + 'T00:00:00';
405
407
  }
408
+ /**
409
+ * Returns e.g: `1984-06-21T17:56:21Z` (notice the Z at the end, which indicates UTC)
410
+ */
411
+ toISODateTimeUTC() {
412
+ return this.toISODateTime() + 'Z';
413
+ }
406
414
  toString() {
407
415
  return [
408
416
  String(this.$year).padStart(4, '0'),
@@ -508,6 +516,8 @@ exports.localDateOrToday = localDateOrToday;
508
516
  */
509
517
  function todayString() {
510
518
  // It was benchmarked to be faster than by concatenating individual Date components
511
- return new Date().toISOString().slice(0, 10);
519
+ // return new Date().toISOString().slice(0, 10)
520
+ // But, toISOString always returns the date in UTC, so in the Browser it would give unexpected result!
521
+ return LocalDate.fromDate(new Date()).toString();
512
522
  }
513
523
  exports.todayString = todayString;
@@ -12,10 +12,13 @@ export declare enum ISODayOfWeek {
12
12
  }
13
13
  export type LocalTimeInput = LocalTime | Date | IsoDateTimeString | UnixTimestampNumber;
14
14
  export type LocalTimeFormatter = (ld: LocalTime) => string;
15
- export interface LocalTimeComponents {
15
+ export type LocalTimeComponents = DateComponents & TimeComponents;
16
+ interface DateComponents {
16
17
  year: number;
17
18
  month: number;
18
19
  day: number;
20
+ }
21
+ interface TimeComponents {
19
22
  hour: number;
20
23
  minute: number;
21
24
  second: number;
@@ -47,6 +50,16 @@ export declare class LocalTime {
47
50
  year: number;
48
51
  month: number;
49
52
  } & Partial<LocalTimeComponents>): LocalTime;
53
+ /**
54
+ * Returns LocalTime that is based on the same unixtimestamp, but in UTC timezone.
55
+ * Opposite of `.local()` method.
56
+ */
57
+ utc(): LocalTime;
58
+ /**
59
+ * Returns LocalTime that is based on the same unixtimestamp, but in local timezone.
60
+ * Opposite of `.utc()` method.
61
+ */
62
+ local(): LocalTime;
50
63
  get(unit: LocalTimeUnit): number;
51
64
  set(unit: LocalTimeUnit, v: number, mutate?: boolean): LocalTime;
52
65
  year(): number;
@@ -126,6 +139,8 @@ export declare class LocalTime {
126
139
  */
127
140
  cmp(d: LocalTimeInput): -1 | 0 | 1;
128
141
  components(): LocalTimeComponents;
142
+ private dateComponents;
143
+ private timeComponents;
129
144
  fromNow(now?: LocalTimeInput): string;
130
145
  getDate(): Date;
131
146
  clone(): LocalTime;
@@ -133,6 +148,11 @@ export declare class LocalTime {
133
148
  unixMillis(): UnixTimestampMillisNumber;
134
149
  valueOf(): UnixTimestampNumber;
135
150
  toLocalDate(): LocalDate;
151
+ /**
152
+ * Returns e.g: `1984-06-21 17:56:21`
153
+ * or (if seconds=false):
154
+ * `1984-06-21 17:56`
155
+ */
136
156
  toPretty(seconds?: boolean): IsoDateTimeString;
137
157
  /**
138
158
  * Returns e.g: `1984-06-21T17:56:21`
@@ -196,3 +216,4 @@ export declare function getUTCOffsetMinutes(): NumberOfMinutes;
196
216
  * Instead of -0 it returns 0, for the peace of mind and less weird test/snapshot differences.
197
217
  */
198
218
  export declare function getUTCOffsetHours(): NumberOfHours;
219
+ export {};
@@ -64,16 +64,18 @@ class LocalTime {
64
64
  return null;
65
65
  }
66
66
  else {
67
+ // Slicing removes the "timezone component", and makes the date "local"
68
+ // e.g 2022-04-06T23:15:00+09:00
69
+ // becomes 2022-04-06T23:15:00
67
70
  date = new Date(d.slice(0, 19));
71
+ // We used to slice to remove the timezone information, now we don't
72
+ // date = new Date(d)
68
73
  }
69
74
  // validation
70
75
  if (isNaN(date.getDate())) {
71
76
  // throw new TypeError(`Cannot parse "${d}" into LocalTime`)
72
77
  return null;
73
78
  }
74
- // if (utc) {
75
- // date.setMinutes(date.getMinutes() + date.getTimezoneOffset())
76
- // }
77
79
  return new LocalTime(date);
78
80
  }
79
81
  static parseToDate(d) {
@@ -107,6 +109,20 @@ class LocalTime {
107
109
  static fromComponents(c) {
108
110
  return new LocalTime(new Date(c.year, c.month - 1, c.day || 1, c.hour || 0, c.minute || 0, c.second || 0));
109
111
  }
112
+ /**
113
+ * Returns LocalTime that is based on the same unixtimestamp, but in UTC timezone.
114
+ * Opposite of `.local()` method.
115
+ */
116
+ utc() {
117
+ return new LocalTime(new Date(this.$date.toISOString()));
118
+ }
119
+ /**
120
+ * Returns LocalTime that is based on the same unixtimestamp, but in local timezone.
121
+ * Opposite of `.utc()` method.
122
+ */
123
+ local() {
124
+ return new LocalTime(new Date(this.$date.getTime()));
125
+ }
110
126
  get(unit) {
111
127
  if (unit === 'year') {
112
128
  return this.$date.getFullYear();
@@ -422,6 +438,20 @@ class LocalTime {
422
438
  second: this.$date.getSeconds(),
423
439
  };
424
440
  }
441
+ dateComponents() {
442
+ return {
443
+ year: this.$date.getFullYear(),
444
+ month: this.$date.getMonth() + 1,
445
+ day: this.$date.getDate(),
446
+ };
447
+ }
448
+ timeComponents() {
449
+ return {
450
+ hour: this.$date.getHours(),
451
+ minute: this.$date.getMinutes(),
452
+ second: this.$date.getSeconds(),
453
+ };
454
+ }
425
455
  fromNow(now = new Date()) {
426
456
  const msDiff = LocalTime.parseToDate(now).valueOf() - this.$date.valueOf();
427
457
  if (msDiff === 0)
@@ -449,27 +479,52 @@ class LocalTime {
449
479
  toLocalDate() {
450
480
  return localDate_1.LocalDate.fromDate(this.$date);
451
481
  }
482
+ /**
483
+ * Returns e.g: `1984-06-21 17:56:21`
484
+ * or (if seconds=false):
485
+ * `1984-06-21 17:56`
486
+ */
452
487
  toPretty(seconds = true) {
453
- const s = this.$date.toISOString();
454
- return s.slice(0, 10) + ' ' + s.slice(11, seconds ? 19 : 16);
488
+ return this.toISODate() + ' ' + this.toISOTime(seconds);
489
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
490
+ // const s = this.$date.toISOString()
491
+ // return s.slice(0, 10) + ' ' + s.slice(11, seconds ? 19 : 16)
455
492
  }
456
493
  /**
457
494
  * Returns e.g: `1984-06-21T17:56:21`
458
495
  */
459
496
  toISODateTime() {
460
- return this.$date.toISOString().slice(0, 19);
497
+ return this.toISODate() + 'T' + this.toISOTime();
498
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
499
+ // return this.$date.toISOString().slice(0, 19)
461
500
  }
462
501
  /**
463
502
  * Returns e.g: `1984-06-21`, only the date part of DateTime
464
503
  */
465
504
  toISODate() {
466
- return this.$date.toISOString().slice(0, 10);
505
+ const { year, month, day } = this.dateComponents();
506
+ return [
507
+ String(year).padStart(4, '0'),
508
+ String(month).padStart(2, '0'),
509
+ String(day).padStart(2, '0'),
510
+ ].join('-');
511
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
512
+ // return this.$date.toISOString().slice(0, 10)
467
513
  }
468
514
  /**
469
515
  * Returns e.g: `17:03:15` (or `17:03` with seconds=false)
470
516
  */
471
517
  toISOTime(seconds = true) {
472
- return this.$date.toISOString().slice(11, seconds ? 19 : 16);
518
+ const { hour, minute, second } = this.timeComponents();
519
+ return [
520
+ String(hour).padStart(2, '0'),
521
+ String(minute).padStart(2, '0'),
522
+ seconds && String(second).padStart(2, '0'),
523
+ ]
524
+ .filter(Boolean)
525
+ .join(':');
526
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
527
+ // return this.$date.toISOString().slice(11, seconds ? 19 : 16)
473
528
  }
474
529
  /**
475
530
  * Returns e.g: `19840621_1705`
@@ -493,7 +548,7 @@ class LocalTime {
493
548
  return this.unix();
494
549
  }
495
550
  toMonthId() {
496
- return this.$date.toISOString().slice(0, 7);
551
+ return this.toISODate().slice(0, 7);
497
552
  }
498
553
  format(fmt) {
499
554
  if (fmt instanceof Intl.DateTimeFormat) {
@@ -48,7 +48,6 @@ export class LocalDate {
48
48
  if (d instanceof Date) {
49
49
  return this.fromDate(d);
50
50
  }
51
- // const [year, month, day] = d.slice(0, 10).split('-').map(Number)
52
51
  const matches = typeof d === 'string' && DATE_REGEX.exec(d.slice(0, 10));
53
52
  if (!matches)
54
53
  return null;
@@ -66,10 +65,6 @@ export class LocalDate {
66
65
  }
67
66
  return new LocalDate(year, month, day);
68
67
  }
69
- // Can use just .toString()
70
- // static parseToString(d: LocalDateConfig): IsoDateString {
71
- // return typeof d === 'string' ? d : d.toString()
72
- // }
73
68
  static isValid(iso) {
74
69
  return this.parseOrNull(iso) !== null;
75
70
  }
@@ -388,6 +383,13 @@ export class LocalDate {
388
383
  toDate() {
389
384
  return new Date(this.$year, this.$month - 1, this.$day);
390
385
  }
386
+ /**
387
+ * Converts LocalDate to Date in UTC timezone.
388
+ * Unlike normal `.toDate` that uses browser's timezone by default.
389
+ */
390
+ toDateInUTC() {
391
+ return new Date(this.toISODateTimeUTC());
392
+ }
391
393
  toLocalTime() {
392
394
  return LocalTime.of(this.toDate());
393
395
  }
@@ -400,6 +402,12 @@ export class LocalDate {
400
402
  toISODateTime() {
401
403
  return this.toString() + 'T00:00:00';
402
404
  }
405
+ /**
406
+ * Returns e.g: `1984-06-21T17:56:21Z` (notice the Z at the end, which indicates UTC)
407
+ */
408
+ toISODateTimeUTC() {
409
+ return this.toISODateTime() + 'Z';
410
+ }
403
411
  toString() {
404
412
  return [
405
413
  String(this.$year).padStart(4, '0'),
@@ -498,5 +506,7 @@ export function localDateOrToday(d) {
498
506
  */
499
507
  export function todayString() {
500
508
  // It was benchmarked to be faster than by concatenating individual Date components
501
- return new Date().toISOString().slice(0, 10);
509
+ // return new Date().toISOString().slice(0, 10)
510
+ // But, toISOString always returns the date in UTC, so in the Browser it would give unexpected result!
511
+ return LocalDate.fromDate(new Date()).toString();
502
512
  }
@@ -61,16 +61,18 @@ export class LocalTime {
61
61
  return null;
62
62
  }
63
63
  else {
64
+ // Slicing removes the "timezone component", and makes the date "local"
65
+ // e.g 2022-04-06T23:15:00+09:00
66
+ // becomes 2022-04-06T23:15:00
64
67
  date = new Date(d.slice(0, 19));
68
+ // We used to slice to remove the timezone information, now we don't
69
+ // date = new Date(d)
65
70
  }
66
71
  // validation
67
72
  if (isNaN(date.getDate())) {
68
73
  // throw new TypeError(`Cannot parse "${d}" into LocalTime`)
69
74
  return null;
70
75
  }
71
- // if (utc) {
72
- // date.setMinutes(date.getMinutes() + date.getTimezoneOffset())
73
- // }
74
76
  return new LocalTime(date);
75
77
  }
76
78
  static parseToDate(d) {
@@ -104,6 +106,20 @@ export class LocalTime {
104
106
  static fromComponents(c) {
105
107
  return new LocalTime(new Date(c.year, c.month - 1, c.day || 1, c.hour || 0, c.minute || 0, c.second || 0));
106
108
  }
109
+ /**
110
+ * Returns LocalTime that is based on the same unixtimestamp, but in UTC timezone.
111
+ * Opposite of `.local()` method.
112
+ */
113
+ utc() {
114
+ return new LocalTime(new Date(this.$date.toISOString()));
115
+ }
116
+ /**
117
+ * Returns LocalTime that is based on the same unixtimestamp, but in local timezone.
118
+ * Opposite of `.utc()` method.
119
+ */
120
+ local() {
121
+ return new LocalTime(new Date(this.$date.getTime()));
122
+ }
107
123
  get(unit) {
108
124
  if (unit === 'year') {
109
125
  return this.$date.getFullYear();
@@ -420,6 +436,20 @@ export class LocalTime {
420
436
  second: this.$date.getSeconds(),
421
437
  };
422
438
  }
439
+ dateComponents() {
440
+ return {
441
+ year: this.$date.getFullYear(),
442
+ month: this.$date.getMonth() + 1,
443
+ day: this.$date.getDate(),
444
+ };
445
+ }
446
+ timeComponents() {
447
+ return {
448
+ hour: this.$date.getHours(),
449
+ minute: this.$date.getMinutes(),
450
+ second: this.$date.getSeconds(),
451
+ };
452
+ }
423
453
  fromNow(now = new Date()) {
424
454
  const msDiff = LocalTime.parseToDate(now).valueOf() - this.$date.valueOf();
425
455
  if (msDiff === 0)
@@ -447,27 +477,52 @@ export class LocalTime {
447
477
  toLocalDate() {
448
478
  return LocalDate.fromDate(this.$date);
449
479
  }
480
+ /**
481
+ * Returns e.g: `1984-06-21 17:56:21`
482
+ * or (if seconds=false):
483
+ * `1984-06-21 17:56`
484
+ */
450
485
  toPretty(seconds = true) {
451
- const s = this.$date.toISOString();
452
- return s.slice(0, 10) + ' ' + s.slice(11, seconds ? 19 : 16);
486
+ return this.toISODate() + ' ' + this.toISOTime(seconds);
487
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
488
+ // const s = this.$date.toISOString()
489
+ // return s.slice(0, 10) + ' ' + s.slice(11, seconds ? 19 : 16)
453
490
  }
454
491
  /**
455
492
  * Returns e.g: `1984-06-21T17:56:21`
456
493
  */
457
494
  toISODateTime() {
458
- return this.$date.toISOString().slice(0, 19);
495
+ return this.toISODate() + 'T' + this.toISOTime();
496
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
497
+ // return this.$date.toISOString().slice(0, 19)
459
498
  }
460
499
  /**
461
500
  * Returns e.g: `1984-06-21`, only the date part of DateTime
462
501
  */
463
502
  toISODate() {
464
- return this.$date.toISOString().slice(0, 10);
503
+ const { year, month, day } = this.dateComponents();
504
+ return [
505
+ String(year).padStart(4, '0'),
506
+ String(month).padStart(2, '0'),
507
+ String(day).padStart(2, '0'),
508
+ ].join('-');
509
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
510
+ // return this.$date.toISOString().slice(0, 10)
465
511
  }
466
512
  /**
467
513
  * Returns e.g: `17:03:15` (or `17:03` with seconds=false)
468
514
  */
469
515
  toISOTime(seconds = true) {
470
- return this.$date.toISOString().slice(11, seconds ? 19 : 16);
516
+ const { hour, minute, second } = this.timeComponents();
517
+ return [
518
+ String(hour).padStart(2, '0'),
519
+ String(minute).padStart(2, '0'),
520
+ seconds && String(second).padStart(2, '0'),
521
+ ]
522
+ .filter(Boolean)
523
+ .join(':');
524
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
525
+ // return this.$date.toISOString().slice(11, seconds ? 19 : 16)
471
526
  }
472
527
  /**
473
528
  * Returns e.g: `19840621_1705`
@@ -491,7 +546,7 @@ export class LocalTime {
491
546
  return this.unix();
492
547
  }
493
548
  toMonthId() {
494
- return this.$date.toISOString().slice(0, 7);
549
+ return this.toISODate().slice(0, 7);
495
550
  }
496
551
  format(fmt) {
497
552
  if (fmt instanceof Intl.DateTimeFormat) {
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.230.1",
3
+ "version": "14.232.0",
4
4
  "scripts": {
5
5
  "prepare": "husky",
6
6
  "build-prod": "build-prod-esm-cjs",
7
+ "test-tz1": "TZ=Europe/Stockholm yarn test local",
8
+ "test-tz2": "TZ=JST-9 yarn test local",
7
9
  "docs-dev": "vitepress dev docs --open",
8
10
  "docs-build": "vitepress build docs",
9
11
  "docs-preview": "vitepress preview docs"
@@ -74,7 +74,6 @@ export class LocalDate {
74
74
  return this.fromDate(d)
75
75
  }
76
76
 
77
- // const [year, month, day] = d.slice(0, 10).split('-').map(Number)
78
77
  const matches = typeof (d as any) === 'string' && DATE_REGEX.exec(d.slice(0, 10))
79
78
  if (!matches) return null
80
79
 
@@ -97,11 +96,6 @@ export class LocalDate {
97
96
  return new LocalDate(year, month, day)
98
97
  }
99
98
 
100
- // Can use just .toString()
101
- // static parseToString(d: LocalDateConfig): IsoDateString {
102
- // return typeof d === 'string' ? d : d.toString()
103
- // }
104
-
105
99
  static isValid(iso: string | undefined | null): boolean {
106
100
  return this.parseOrNull(iso) !== null
107
101
  }
@@ -473,6 +467,14 @@ export class LocalDate {
473
467
  return new Date(this.$year, this.$month - 1, this.$day)
474
468
  }
475
469
 
470
+ /**
471
+ * Converts LocalDate to Date in UTC timezone.
472
+ * Unlike normal `.toDate` that uses browser's timezone by default.
473
+ */
474
+ toDateInUTC(): Date {
475
+ return new Date(this.toISODateTimeUTC())
476
+ }
477
+
476
478
  toLocalTime(): LocalTime {
477
479
  return LocalTime.of(this.toDate())
478
480
  }
@@ -488,6 +490,13 @@ export class LocalDate {
488
490
  return this.toString() + 'T00:00:00'
489
491
  }
490
492
 
493
+ /**
494
+ * Returns e.g: `1984-06-21T17:56:21Z` (notice the Z at the end, which indicates UTC)
495
+ */
496
+ toISODateTimeUTC(): IsoDateTimeString {
497
+ return this.toISODateTime() + 'Z'
498
+ }
499
+
491
500
  toString(): IsoDateString {
492
501
  return [
493
502
  String(this.$year).padStart(4, '0'),
@@ -616,5 +625,7 @@ export function localDateOrToday(d?: LocalDateInput | null): LocalDate {
616
625
  */
617
626
  export function todayString(): IsoDateString {
618
627
  // It was benchmarked to be faster than by concatenating individual Date components
619
- return new Date().toISOString().slice(0, 10)
628
+ // return new Date().toISOString().slice(0, 10)
629
+ // But, toISOString always returns the date in UTC, so in the Browser it would give unexpected result!
630
+ return LocalDate.fromDate(new Date()).toString()
620
631
  }
@@ -28,10 +28,15 @@ export enum ISODayOfWeek {
28
28
  export type LocalTimeInput = LocalTime | Date | IsoDateTimeString | UnixTimestampNumber
29
29
  export type LocalTimeFormatter = (ld: LocalTime) => string
30
30
 
31
- export interface LocalTimeComponents {
31
+ export type LocalTimeComponents = DateComponents & TimeComponents
32
+
33
+ interface DateComponents {
32
34
  year: number
33
35
  month: number
34
36
  day: number
37
+ }
38
+
39
+ interface TimeComponents {
35
40
  hour: number
36
41
  minute: number
37
42
  second: number
@@ -88,7 +93,12 @@ export class LocalTime {
88
93
  // unexpected type, e.g Function or something
89
94
  return null
90
95
  } else {
96
+ // Slicing removes the "timezone component", and makes the date "local"
97
+ // e.g 2022-04-06T23:15:00+09:00
98
+ // becomes 2022-04-06T23:15:00
91
99
  date = new Date(d.slice(0, 19))
100
+ // We used to slice to remove the timezone information, now we don't
101
+ // date = new Date(d)
92
102
  }
93
103
 
94
104
  // validation
@@ -97,10 +107,6 @@ export class LocalTime {
97
107
  return null
98
108
  }
99
109
 
100
- // if (utc) {
101
- // date.setMinutes(date.getMinutes() + date.getTimezoneOffset())
102
- // }
103
-
104
110
  return new LocalTime(date)
105
111
  }
106
112
 
@@ -146,6 +152,22 @@ export class LocalTime {
146
152
  )
147
153
  }
148
154
 
155
+ /**
156
+ * Returns LocalTime that is based on the same unixtimestamp, but in UTC timezone.
157
+ * Opposite of `.local()` method.
158
+ */
159
+ utc(): LocalTime {
160
+ return new LocalTime(new Date(this.$date.toISOString()))
161
+ }
162
+
163
+ /**
164
+ * Returns LocalTime that is based on the same unixtimestamp, but in local timezone.
165
+ * Opposite of `.utc()` method.
166
+ */
167
+ local(): LocalTime {
168
+ return new LocalTime(new Date(this.$date.getTime()))
169
+ }
170
+
149
171
  get(unit: LocalTimeUnit): number {
150
172
  if (unit === 'year') {
151
173
  return this.$date.getFullYear()
@@ -514,6 +536,22 @@ export class LocalTime {
514
536
  }
515
537
  }
516
538
 
539
+ private dateComponents(): DateComponents {
540
+ return {
541
+ year: this.$date.getFullYear(),
542
+ month: this.$date.getMonth() + 1,
543
+ day: this.$date.getDate(),
544
+ }
545
+ }
546
+
547
+ private timeComponents(): TimeComponents {
548
+ return {
549
+ hour: this.$date.getHours(),
550
+ minute: this.$date.getMinutes(),
551
+ second: this.$date.getSeconds(),
552
+ }
553
+ }
554
+
517
555
  fromNow(now: LocalTimeInput = new Date()): string {
518
556
  const msDiff = LocalTime.parseToDate(now).valueOf() - this.$date.valueOf()
519
557
 
@@ -550,30 +588,59 @@ export class LocalTime {
550
588
  return LocalDate.fromDate(this.$date)
551
589
  }
552
590
 
591
+ /**
592
+ * Returns e.g: `1984-06-21 17:56:21`
593
+ * or (if seconds=false):
594
+ * `1984-06-21 17:56`
595
+ */
553
596
  toPretty(seconds = true): IsoDateTimeString {
554
- const s = this.$date.toISOString()
555
- return s.slice(0, 10) + ' ' + s.slice(11, seconds ? 19 : 16)
597
+ return this.toISODate() + ' ' + this.toISOTime(seconds)
598
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
599
+ // const s = this.$date.toISOString()
600
+ // return s.slice(0, 10) + ' ' + s.slice(11, seconds ? 19 : 16)
556
601
  }
557
602
 
558
603
  /**
559
604
  * Returns e.g: `1984-06-21T17:56:21`
560
605
  */
561
606
  toISODateTime(): IsoDateTimeString {
562
- return this.$date.toISOString().slice(0, 19)
607
+ return this.toISODate() + 'T' + this.toISOTime()
608
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
609
+ // return this.$date.toISOString().slice(0, 19)
563
610
  }
564
611
 
565
612
  /**
566
613
  * Returns e.g: `1984-06-21`, only the date part of DateTime
567
614
  */
568
615
  toISODate(): IsoDateString {
569
- return this.$date.toISOString().slice(0, 10)
616
+ const { year, month, day } = this.dateComponents()
617
+
618
+ return [
619
+ String(year).padStart(4, '0'),
620
+ String(month).padStart(2, '0'),
621
+ String(day).padStart(2, '0'),
622
+ ].join('-')
623
+
624
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
625
+ // return this.$date.toISOString().slice(0, 10)
570
626
  }
571
627
 
572
628
  /**
573
629
  * Returns e.g: `17:03:15` (or `17:03` with seconds=false)
574
630
  */
575
631
  toISOTime(seconds = true): string {
576
- return this.$date.toISOString().slice(11, seconds ? 19 : 16)
632
+ const { hour, minute, second } = this.timeComponents()
633
+
634
+ return [
635
+ String(hour).padStart(2, '0'),
636
+ String(minute).padStart(2, '0'),
637
+ seconds && String(second).padStart(2, '0'),
638
+ ]
639
+ .filter(Boolean)
640
+ .join(':')
641
+
642
+ // !! Not using toISOString(), as it returns time in UTC, not in local timezone (unexpected!)
643
+ // return this.$date.toISOString().slice(11, seconds ? 19 : 16)
577
644
  }
578
645
 
579
646
  /**
@@ -602,7 +669,7 @@ export class LocalTime {
602
669
  }
603
670
 
604
671
  toMonthId(): MonthId {
605
- return this.$date.toISOString().slice(0, 7)
672
+ return this.toISODate().slice(0, 7)
606
673
  }
607
674
 
608
675
  format(fmt: Intl.DateTimeFormat | LocalTimeFormatter): string {