@@ -12,6 +12,7 @@ import type {
12
12
UnixTimestampNumber ,
13
13
} from '../types'
14
14
import { localDate , LocalDate } from './localDate'
15
+ import { WallTime } from './wallTime'
15
16
16
17
export type LocalTimeUnit = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second'
17
18
@@ -28,15 +29,15 @@ export enum ISODayOfWeek {
28
29
export type LocalTimeInput = LocalTime | Date | IsoDateTimeString | UnixTimestampNumber
29
30
export type LocalTimeFormatter = ( ld : LocalTime ) => string
30
31
31
- export type LocalTimeComponents = DateComponents & TimeComponents
32
+ export type DateTimeObject = DateObject & TimeObject
32
33
33
- interface DateComponents {
34
+ export interface DateObject {
34
35
year : number
35
36
month : number
36
37
day : number
37
38
}
38
39
39
- interface TimeComponents {
40
+ export interface TimeObject {
40
41
hour : number
41
42
minute : number
42
43
second : number
@@ -68,6 +69,83 @@ export class LocalTime {
68
69
return new LocalTime ( new Date ( this . $date . getTime ( ) ) )
69
70
}
70
71
72
+ /**
73
+ * Returns [cloned] fake LocalTime that has yyyy-mm-dd hh:mm:ss in the provided timezone.
74
+ * It is a fake LocalTime in a sense that it's timezone is not real.
75
+ * See this ("common errors"): https://stackoverflow.com/a/15171030/4919972
76
+ * Fake also means that unixTimestamp of that new LocalDate is not the same.
77
+ * For that reason we return WallTime, and not a LocalTime.
78
+ * WallTime can be pretty-printed as Date-only, Time-only or DateAndTime.
79
+ *
80
+ * E.g `inTimezone('America/New_York').toISOTime()`
81
+ *
82
+ * https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
83
+ *
84
+ * @experimental
85
+ */
86
+ inTimezone ( tz : string ) : WallTime {
87
+ const d = new Date ( this . $date . toLocaleString ( 'en-US' , { timeZone : tz } ) )
88
+ return new WallTime ( {
89
+ year : d . getFullYear ( ) ,
90
+ month : d . getMonth ( ) + 1 ,
91
+ day : d . getDate ( ) ,
92
+ hour : d . getHours ( ) ,
93
+ minute : d . getMinutes ( ) ,
94
+ second : d . getSeconds ( ) ,
95
+ } )
96
+ }
97
+
98
+ /**
99
+ * UTC offset is the opposite of "timezone offset" - it's the number of minutes to add
100
+ * to the local time to get UTC time.
101
+ *
102
+ * E.g utcOffset for CEST is -120,
103
+ * which means that you need to add -120 minutes to the local time to get UTC time.
104
+ *
105
+ * Instead of -0 it returns 0, for the peace of mind and less weird test/snapshot differences.
106
+ *
107
+ * If timezone (tz) is specified, e.g `America/New_York`,
108
+ * it will return the UTC offset for that timezone.
109
+ *
110
+ * https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
111
+ */
112
+ getUTCOffsetMinutes ( tz ?: string ) : NumberOfMinutes {
113
+ if ( tz ) {
114
+ // based on: https://stackoverflow.com/a/53652131/4919972
115
+ const nowTime = this . $date . getTime ( )
116
+ const tzTime = new Date ( this . $date . toLocaleString ( 'en-US' , { timeZone : tz } ) ) . getTime ( )
117
+ return Math . round ( ( tzTime - nowTime ) / 60000 ) || 0
118
+ }
119
+
120
+ return - this . $date . getTimezoneOffset ( ) || 0
121
+ }
122
+
123
+ /**
124
+ * Same as getUTCOffsetMinutes, but rounded to hours.
125
+ *
126
+ * E.g for CEST it is -2.
127
+ *
128
+ * Instead of -0 it returns 0, for the peace of mind and less weird test/snapshot differences.
129
+ *
130
+ * If timezone (tz) is specified, e.g `America/New_York`,
131
+ * it will return the UTC offset for that timezone.
132
+ */
133
+ getUTCOffsetHours ( tz ?: string ) : NumberOfHours {
134
+ return Math . round ( this . getUTCOffsetMinutes ( tz ) / 60 )
135
+ }
136
+
137
+ /**
138
+ * Returns e.g `-05:00` for New_York winter time.
139
+ */
140
+ getUTCOffsetString ( tz : string ) : string {
141
+ const minutes = this . getUTCOffsetMinutes ( tz )
142
+ const hours = Math . trunc ( minutes / 60 )
143
+ const sign = hours < 0 ? '-' : '+'
144
+ const h = String ( Math . abs ( hours ) ) . padStart ( 2 , '0' )
145
+ const m = String ( minutes % 60 ) . padStart ( 2 , '0' )
146
+ return `${ sign } ${ h } :${ m } `
147
+ }
148
+
71
149
get ( unit : LocalTimeUnit ) : number {
72
150
if ( unit === 'year' ) {
73
151
return this . $date . getFullYear ( )
@@ -166,7 +244,7 @@ export class LocalTime {
166
244
return v === undefined ? this . get ( 'second' ) : this . set ( 'second' , v )
167
245
}
168
246
169
- setComponents ( c : Partial < LocalTimeComponents > , mutate = false ) : LocalTime {
247
+ setComponents ( c : Partial < DateTimeObject > , mutate = false ) : LocalTime {
170
248
const d = mutate ? this . $date : new Date ( this . $date )
171
249
172
250
// Year, month and day set all-at-once, to avoid 30/31 (and 28/29) mishap
@@ -434,22 +512,22 @@ export class LocalTime {
434
512
return t1 < t2 ? - 1 : 1
435
513
}
436
514
437
- components ( ) : LocalTimeComponents {
515
+ getDateTimeObject ( ) : DateTimeObject {
438
516
return {
439
- ...this . dateComponents ( ) ,
440
- ...this . timeComponents ( ) ,
517
+ ...this . getDateObject ( ) ,
518
+ ...this . getTimeObject ( ) ,
441
519
}
442
520
}
443
521
444
- private dateComponents ( ) : DateComponents {
522
+ getDateObject ( ) : DateObject {
445
523
return {
446
524
year : this . $date . getFullYear ( ) ,
447
525
month : this . $date . getMonth ( ) + 1 ,
448
526
day : this . $date . getDate ( ) ,
449
527
}
450
528
}
451
529
452
- private timeComponents ( ) : TimeComponents {
530
+ getTimeObject ( ) : TimeObject {
453
531
return {
454
532
hour : this . $date . getHours ( ) ,
455
533
minute : this . $date . getMinutes ( ) ,
@@ -518,7 +596,7 @@ export class LocalTime {
518
596
* Returns e.g: `1984-06-21`, only the date part of DateTime
519
597
*/
520
598
toISODate ( ) : IsoDateString {
521
- const { year, month, day } = this . dateComponents ( )
599
+ const { year, month, day } = this . getDateObject ( )
522
600
523
601
return [
524
602
String ( year ) . padStart ( 4 , '0' ) ,
@@ -534,7 +612,7 @@ export class LocalTime {
534
612
* Returns e.g: `17:03:15` (or `17:03` with seconds=false)
535
613
*/
536
614
toISOTime ( seconds = true ) : string {
537
- const { hour, minute, second } = this . timeComponents ( )
615
+ const { hour, minute, second } = this . getTimeObject ( )
538
616
539
617
return [
540
618
String ( hour ) . padStart ( 2 , '0' ) ,
@@ -552,7 +630,7 @@ export class LocalTime {
552
630
* Returns e.g: `19840621_1705`
553
631
*/
554
632
toStringCompact ( seconds = false ) : string {
555
- const { year, month, day, hour, minute, second } = this . components ( )
633
+ const { year, month, day, hour, minute, second } = this . getDateTimeObject ( )
556
634
557
635
return [
558
636
String ( year ) . padStart ( 4 , '0' ) ,
@@ -672,6 +750,25 @@ class LocalTimeFactory {
672
750
return this . parseOrNull ( d ) !== null
673
751
}
674
752
753
+ /**
754
+ * Returns the IANA timezone e.g `Europe/Stockholm`.
755
+ * https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
756
+ */
757
+ getTimezone ( ) : string {
758
+ return Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone
759
+ }
760
+
761
+ /**
762
+ * Returns true if passed IANA timezone is valid/supported.
763
+ * E.g `Europe/Stockholm` is valid, but `Europe/Stockholm2` is not.
764
+ *
765
+ * This implementation is not optimized for performance. If you need frequent validation -
766
+ * consider caching the Intl.supportedValuesOf values as Set and reuse that.
767
+ */
768
+ isTimezoneValid ( tz : string ) : boolean {
769
+ return Intl . supportedValuesOf ( 'timeZone' ) . includes ( tz )
770
+ }
771
+
675
772
now ( ) : LocalTime {
676
773
return new LocalTime ( new Date ( ) )
677
774
}
@@ -692,7 +789,7 @@ class LocalTimeFactory {
692
789
return d ? this . of ( d ) : this . now ( )
693
790
}
694
791
695
- fromComponents ( c : { year : number ; month : number } & Partial < LocalTimeComponents > ) : LocalTime {
792
+ fromComponents ( c : { year : number ; month : number } & Partial < DateTimeObject > ) : LocalTime {
696
793
return new LocalTime (
697
794
new Date ( c . year , c . month - 1 , c . day || 1 , c . hour || 0 , c . minute || 0 , c . second || 0 ) ,
698
795
)
@@ -857,27 +954,3 @@ Object.setPrototypeOf(localTime, localTimeFactory)
857
954
export function nowUnix ( ) : UnixTimestampNumber {
858
955
return Math . floor ( Date . now ( ) / 1000 )
859
956
}
860
-
861
- /**
862
- * UTC offset is the opposite of "timezone offset" - it's the number of minutes to add
863
- * to the local time to get UTC time.
864
- *
865
- * E.g utcOffset for CEST is -120,
866
- * which means that you need to add -120 minutes to the local time to get UTC time.
867
- *
868
- * Instead of -0 it returns 0, for the peace of mind and less weird test/snapshot differences.
869
- */
870
- export function getUTCOffsetMinutes ( ) : NumberOfMinutes {
871
- return - new Date ( ) . getTimezoneOffset ( ) || 0
872
- }
873
-
874
- /**
875
- * Same as getUTCOffsetMinutes, but rounded to hours.
876
- *
877
- * E.g for CEST it is -2.
878
- *
879
- * Instead of -0 it returns 0, for the peace of mind and less weird test/snapshot differences.
880
- */
881
- export function getUTCOffsetHours ( ) : NumberOfHours {
882
- return Math . round ( getUTCOffsetMinutes ( ) / 60 )
883
- }
0 commit comments