22import  *  as  Temporal  from  '../../polyfill/lib/temporal.mjs' ; 
33import  ICAL  from  'ical.js' ; 
44
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+ 
718class  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. 
822  #impl; 
9-   #timeZone; 
10-   #isIANA; 
23+   #timeZone;   // The ICAL.Timezone instance. 
24+   #isIANA;   // Convenience flag indicating whether we can delegate to #impl. 
1125
1226  // 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. 
1429  era ; 
1530  eraYear ; 
1631  year ; 
@@ -84,7 +99,7 @@ class ZonedDateTime {
8499      } , 
85100      timeZone 
86101    ) ; 
87-     const  epochSeconds  =  icalTime . toUnixTime ( ) ;  // apply disambiguation parameter? 
102+     const  epochSeconds  =  icalTime . toUnixTime ( ) ;  // TODO:  apply disambiguation parameter? 
88103    const  epochNanoseconds  = 
89104      BigInt ( epochSeconds )  *  1000000000n  +  BigInt ( pdt . millisecond  *  1e6  +  pdt . microsecond  *  1e3  +  pdt . nanosecond ) ; 
90105    return  new  ZonedDateTime ( epochNanoseconds ,  timeZone ,  pdt . calendarId ) ; 
@@ -98,6 +113,8 @@ class ZonedDateTime {
98113    if  ( this . #isIANA)  { 
99114      return  this . #impl. toPlainDateTime ( ) ; 
100115    } 
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. 
101118    return  this . #impl. toPlainDateTime ( ) . add ( {  nanoseconds : this . offsetNanoseconds  } ) ; 
102119  } 
103120
@@ -167,6 +184,8 @@ class ZonedDateTime {
167184      this . #isIANA || 
168185      ( duration . years  ===  0  &&  duration . months  ===  0  &&  duration . weeks  ===  0  &&  duration . days  ===  0 ) 
169186    )  { 
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 
170189      const  temporalZDT  =  this . #impl. add ( duration ,  options ) ; 
171190      return  new  ZonedDateTime ( temporalZDT . epochNanoseconds ,  this . #timeZone,  this . #impl. calendarId ) ; 
172191    } 
@@ -202,6 +221,8 @@ class ZonedDateTime {
202221    if  ( largestUnit  ===  'year'  ||  largestUnit  ===  'month'  ||  largestUnit  ===  'week'  ||  largestUnit  ===  'day' )  { 
203222      throw  new  Error ( 'not implemented' ) ; 
204223    } 
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 
205226    return  this . #impl. until ( other . #impl,  options ) ; 
206227  } 
207228
0 commit comments