2
2
import * as Temporal from '../../polyfill/lib/temporal.mjs' ;
3
3
import ICAL from 'ical.js' ;
4
4
5
- // The time zone can either be a named IANA time zone (in which case everything
6
- // works just like Temporal.ZonedDateTime) or an iCalendar rule-based time zone
5
+ // Example of a wrapper class for Temporal.ZonedDateTime that implements custom
6
+ // time zones.
7
+ // The use case is based on Thunderbird's use of the ical.js library to parse
8
+ // iCalendar data. iCalendar uses VTIMEZONE components which define UTC offset
9
+ // transitions inside the data format. VTIMEZONE can include a TZID field, which
10
+ // may or may not be an IANA time zone ID. If it's an IANA time zone ID,
11
+ // Thunderbird uses the environment's TZDB definition and ignores the rest of
12
+ // the VTIMEZONE (in which case everything works just like
13
+ // Temporal.ZonedDateTime, as we delegate to the this.#impl object). However,
14
+ // Microsoft Exchange often generates TZID strings that aren't IANA IDs, and
15
+ // then Thunderbird falls back to the iCalendar VTIMEZONE definition (in which
16
+ // case we use ical.js to perform the time zone calculations.)
17
+
7
18
class ZonedDateTime {
19
+ // #impl: The internal Temporal.ZonedDateTime object. If the VTIMEZONE is an
20
+ // IANA time zone, its timeZoneId is the VTIMEZONE's TZID, and we delegate all
21
+ // the operations to it. If not, its timeZoneId is UTC.
8
22
#impl;
9
- #timeZone;
10
- #isIANA;
23
+ #timeZone; // The ICAL.Timezone instance.
24
+ #isIANA; // Convenience flag indicating whether we can delegate to #impl.
11
25
12
26
// These properties allow the object to be used as a PlainDateTime property
13
- // bag if the time zone isn't IANA
27
+ // bag if the time zone isn't IANA. For example, as a relativeTo parameter in
28
+ // Duration methods.
14
29
era ;
15
30
eraYear ;
16
31
year ;
@@ -84,7 +99,7 @@ class ZonedDateTime {
84
99
} ,
85
100
timeZone
86
101
) ;
87
- const epochSeconds = icalTime . toUnixTime ( ) ; // apply disambiguation parameter?
102
+ const epochSeconds = icalTime . toUnixTime ( ) ; // TODO: apply disambiguation parameter?
88
103
const epochNanoseconds =
89
104
BigInt ( epochSeconds ) * 1000000000n + BigInt ( pdt . millisecond * 1e6 + pdt . microsecond * 1e3 + pdt . nanosecond ) ;
90
105
return new ZonedDateTime ( epochNanoseconds , timeZone , pdt . calendarId ) ;
@@ -98,6 +113,8 @@ class ZonedDateTime {
98
113
if ( this . #isIANA) {
99
114
return this . #impl. toPlainDateTime ( ) ;
100
115
}
116
+ // this.#impl with a non-IANA time zone uses UTC internally, so we can just
117
+ // calculate the plain date-time in UTC and add the UTC offset.
101
118
return this . #impl. toPlainDateTime ( ) . add ( { nanoseconds : this . offsetNanoseconds } ) ;
102
119
}
103
120
@@ -167,6 +184,8 @@ class ZonedDateTime {
167
184
this . #isIANA ||
168
185
( duration . years === 0 && duration . months === 0 && duration . weeks === 0 && duration . days === 0 )
169
186
) {
187
+ // Adding non-calendar units is independent of time zone, so in that case
188
+ // we can delegate to this.#impl even in the case of a non-IANA time zone
170
189
const temporalZDT = this . #impl. add ( duration , options ) ;
171
190
return new ZonedDateTime ( temporalZDT . epochNanoseconds , this . #timeZone, this . #impl. calendarId ) ;
172
191
}
@@ -202,6 +221,8 @@ class ZonedDateTime {
202
221
if ( largestUnit === 'year' || largestUnit === 'month' || largestUnit === 'week' || largestUnit === 'day' ) {
203
222
throw new Error ( 'not implemented' ) ;
204
223
}
224
+ // Non-calendar largestUnit is independent of time zone, so we can delegate
225
+ // to this.#impl even in the case of a non-IANA time zone
205
226
return this . #impl. until ( other . #impl, options ) ;
206
227
}
207
228
0 commit comments