@@ -136,41 +136,104 @@ pub enum PosixDate {
136136}
137137
138138impl PosixDate {
139- pub ( crate ) fn from_rule ( rule : & Rule ) -> Self {
139+ /// Creates a [`PosixDate`] from a provided rule. This method returns both a posix date and an
140+ /// integer, representing the days off the target weekday in seconds.
141+ pub ( crate ) fn from_rule ( rule : & Rule ) -> ( Self , i64 ) {
140142 match rule. on_date {
141- DayOfMonth :: Day ( day) if rule. in_month == Month :: Jan || rule. in_month == Month :: Feb => {
142- PosixDate :: JulianNoLeap ( month_to_day ( rule. in_month as u8 , 1 ) as u16 + day as u16 )
143- }
144- DayOfMonth :: Day ( day) => {
145- PosixDate :: JulianLeap ( month_to_day ( rule. in_month as u8 , 1 ) as u16 + day as u16 )
146- }
147- DayOfMonth :: Last ( wd) => PosixDate :: MonthWeekDay ( MonthWeekDay ( rule. in_month , 5 , wd) ) ,
143+ DayOfMonth :: Day ( day) if rule. in_month == Month :: Jan || rule. in_month == Month :: Feb => (
144+ PosixDate :: JulianNoLeap ( month_to_day ( rule. in_month as u8 , 1 ) as u16 + day as u16 ) ,
145+ 0 ,
146+ ) ,
147+ DayOfMonth :: Day ( day) => (
148+ PosixDate :: JulianLeap ( month_to_day ( rule. in_month as u8 , 1 ) as u16 + day as u16 ) ,
149+ 0 ,
150+ ) ,
151+ DayOfMonth :: Last ( wd) => (
152+ PosixDate :: MonthWeekDay ( MonthWeekDay ( rule. in_month , 5 , wd) ) ,
153+ 0 ,
154+ ) ,
148155 DayOfMonth :: WeekDayGEThanMonthDay ( week_day, day_of_month) => {
149- let week = 1 + ( day_of_month - 1 ) / 7 ;
150- PosixDate :: MonthWeekDay ( MonthWeekDay ( rule. in_month , week, week_day) )
156+ // Handle week day offset correctly (See America/Santiago; i.e. Sun>=2)
157+ //
158+ // To do this for the GE case, we work with a zero based day of month,
159+ // This ensures that day_of_month being 1 aligns with Sun = 0, for
160+ // Sun>=1 purposes.
161+ //
162+ // The primary purpose for this approach as noted in zic.c is to support
163+ // America/Santiago timestamps beyond 2038.
164+ //
165+ // See the below link for more info.
166+ //
167+ // https://github.com/eggert/tz/commit/07351e0248b5a42151e49e4506bca0363c846f8c
168+
169+ // Calculate the difference between the day of month and the week day.
170+ let zero_based_day_of_month = day_of_month - 1 ;
171+ let week_day_from_dom = zero_based_day_of_month % 7 ;
172+ // N.B., this could be a negative. If we look at Sun>=2, then this becomes
173+ // 0 - 1.
174+ let mut adjusted_week_day = week_day as i8 - week_day_from_dom as i8 ;
175+
176+ // Calculate what week we are in.
177+ //
178+ // Since we are operating with a zero based day of month, we add
179+ let week = 1 + zero_based_day_of_month / 7 ;
180+
181+ // If we have shifted beyond the month, add 7 to shift back into the first
182+ // week.
183+ if adjusted_week_day < 0 {
184+ adjusted_week_day += 7 ;
185+ }
186+ let week_day = WeekDay :: from_u8 ( adjusted_week_day as u8 ) ;
187+ // N.B. The left of time the target weekday becomes a time overflow added
188+ // to the minutes.
189+ (
190+ PosixDate :: MonthWeekDay ( MonthWeekDay ( rule. in_month , week, week_day) ) ,
191+ week_day_from_dom as i64 * 86_400 ,
192+ )
151193 }
152194 DayOfMonth :: WeekDayLEThanMonthDay ( week_day, day_of_month) => {
195+ // Handle week day offset correctly
196+ //
197+ // We don't worry about the last day of the month in this scenario, which
198+ // is the upper bound as that is handled by DayOfMonth::Last
199+ let week_day_from_dom = day_of_month as i8 % 7 ;
200+ let mut adjusted_week_day = week_day as i8 - week_day_from_dom;
153201 let week = day_of_month / 7 ;
154- PosixDate :: MonthWeekDay ( MonthWeekDay ( rule. in_month , week, week_day) )
202+ if adjusted_week_day < 0 {
203+ adjusted_week_day += 7 ;
204+ }
205+ (
206+ PosixDate :: MonthWeekDay ( MonthWeekDay (
207+ rule. in_month ,
208+ week,
209+ WeekDay :: from_u8 ( adjusted_week_day as u8 ) ,
210+ ) ) ,
211+ week_day_from_dom as i64 * 86_400 ,
212+ )
155213 }
156214 }
157215 }
158216}
159217
160218#[ derive( Debug , PartialEq , Clone , Copy ) ]
161219pub struct PosixDateTime {
220+ /// The designated [`PosixDate`]
162221 pub date : PosixDate ,
222+ /// The local time for a [`PosixDateTime`] at which a transition occurs.
223+ ///
224+ /// N.B., this can be in the range of -167..=167
163225 pub time : Time ,
164226}
165227
166228impl PosixDateTime {
167229 pub ( crate ) fn from_rule_and_transition_info ( rule : & Rule , offset : Time , savings : Time ) -> Self {
168- let date = PosixDate :: from_rule ( rule) ;
230+ let ( date, time_overflow ) = PosixDate :: from_rule ( rule) ;
169231 let time = match rule. at {
170232 QualifiedTime :: Local ( time) => time,
171233 QualifiedTime :: Standard ( standard_time) => standard_time. add ( rule. save ) ,
172234 QualifiedTime :: Universal ( universal_time) => universal_time. add ( offset) . add ( savings) ,
173235 } ;
236+ let time = time. add ( Time :: from_seconds ( time_overflow) ) ;
174237 Self { date, time }
175238 }
176239}
0 commit comments