-
Notifications
You must be signed in to change notification settings - Fork 31
Fix bug in POSIX time zone calculations #658
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
300d817
fe0523d
1be8af8
ae948a8
f639c42
92f8c5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -136,41 +136,104 @@ pub enum PosixDate { | |
| } | ||
|
|
||
| impl PosixDate { | ||
| pub(crate) fn from_rule(rule: &Rule) -> Self { | ||
| /// Creates a [`PosixDate`] from a provided rule. This method returns both a posix date and an | ||
| /// integer, representing the days off the target weekday in seconds. | ||
| pub(crate) fn from_rule(rule: &Rule) -> (Self, i64) { | ||
| match rule.on_date { | ||
| DayOfMonth::Day(day) if rule.in_month == Month::Jan || rule.in_month == Month::Feb => { | ||
| PosixDate::JulianNoLeap(month_to_day(rule.in_month as u8, 1) as u16 + day as u16) | ||
| } | ||
| DayOfMonth::Day(day) => { | ||
| PosixDate::JulianLeap(month_to_day(rule.in_month as u8, 1) as u16 + day as u16) | ||
| } | ||
| DayOfMonth::Last(wd) => PosixDate::MonthWeekDay(MonthWeekDay(rule.in_month, 5, wd)), | ||
| DayOfMonth::Day(day) if rule.in_month == Month::Jan || rule.in_month == Month::Feb => ( | ||
| PosixDate::JulianNoLeap(month_to_day(rule.in_month as u8, 1) as u16 + day as u16), | ||
| 0, | ||
| ), | ||
| DayOfMonth::Day(day) => ( | ||
| PosixDate::JulianLeap(month_to_day(rule.in_month as u8, 1) as u16 + day as u16), | ||
| 0, | ||
| ), | ||
| DayOfMonth::Last(wd) => ( | ||
| PosixDate::MonthWeekDay(MonthWeekDay(rule.in_month, 5, wd)), | ||
| 0, | ||
| ), | ||
| DayOfMonth::WeekDayGEThanMonthDay(week_day, day_of_month) => { | ||
| let week = 1 + (day_of_month - 1) / 7; | ||
| PosixDate::MonthWeekDay(MonthWeekDay(rule.in_month, week, week_day)) | ||
| // Handle week day offset correctly (See America/Santiago; i.e. Sun>=2) | ||
| // | ||
| // To do this for the GE case, we work with a zero based day of month, | ||
| // This ensures that day_of_month being 1 aligns with Sun = 0, for | ||
| // Sun>=1 purposes. | ||
| // | ||
| // The primary purpose for this approach as noted in zic.c is to support | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. link? I don't find any such comment but I might not be looking hard enough
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment is from the commit history. I can link to it. |
||
| // America/Santiago timestamps beyond 2038. | ||
| // | ||
| // See the below link for more info. | ||
| // | ||
| // https://github.com/eggert/tz/commit/07351e0248b5a42151e49e4506bca0363c846f8c | ||
|
|
||
| // Calculate the difference between the day of month and the week day. | ||
| let zero_based_day_of_month = day_of_month - 1; | ||
| let week_day_from_dom = zero_based_day_of_month % 7; | ||
| // N.B., this could be a negative. If we look at Sun>=2, then this becomes | ||
| // 0 - 1. | ||
| let mut adjusted_week_day = week_day as i8 - week_day_from_dom as i8; | ||
|
|
||
| // Calculate what week we are in. | ||
| // | ||
| // Since we are operating with a zero based day of month, we add | ||
| let week = 1 + zero_based_day_of_month / 7; | ||
|
|
||
| // If we have shifted beyond the month, add 7 to shift back into the first | ||
| // week. | ||
| if adjusted_week_day < 0 { | ||
| adjusted_week_day += 7; | ||
| } | ||
| let week_day = WeekDay::from_u8(adjusted_week_day as u8); | ||
| // N.B. The left of time the target weekday becomes a time overflow added | ||
| // to the minutes. | ||
| ( | ||
| PosixDate::MonthWeekDay(MonthWeekDay(rule.in_month, week, week_day)), | ||
| week_day_from_dom as i64 * 86_400, | ||
| ) | ||
| } | ||
| DayOfMonth::WeekDayLEThanMonthDay(week_day, day_of_month) => { | ||
| // Handle week day offset correctly | ||
| // | ||
| // We don't worry about the last day of the month in this scenario, which | ||
| // is the upper bound as that is handled by DayOfMonth::Last | ||
| let week_day_from_dom = day_of_month as i8 % 7; | ||
| let mut adjusted_week_day = week_day as i8 - week_day_from_dom; | ||
| let week = day_of_month / 7; | ||
| PosixDate::MonthWeekDay(MonthWeekDay(rule.in_month, week, week_day)) | ||
| if adjusted_week_day < 0 { | ||
| adjusted_week_day += 7; | ||
| } | ||
| ( | ||
| PosixDate::MonthWeekDay(MonthWeekDay( | ||
| rule.in_month, | ||
| week, | ||
| WeekDay::from_u8(adjusted_week_day as u8), | ||
| )), | ||
| week_day_from_dom as i64 * 86_400, | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug, PartialEq, Clone, Copy)] | ||
| pub struct PosixDateTime { | ||
| /// The designated [`PosixDate`] | ||
| pub date: PosixDate, | ||
| /// The local time for a [`PosixDateTime`] at which a transition occurs. | ||
| /// | ||
| /// N.B., this can be in the range of -167..=167 | ||
| pub time: Time, | ||
| } | ||
|
|
||
| impl PosixDateTime { | ||
| pub(crate) fn from_rule_and_transition_info(rule: &Rule, offset: Time, savings: Time) -> Self { | ||
| let date = PosixDate::from_rule(rule); | ||
| let (date, time_overflow) = PosixDate::from_rule(rule); | ||
| let time = match rule.at { | ||
| QualifiedTime::Local(time) => time, | ||
| QualifiedTime::Standard(standard_time) => standard_time.add(rule.save), | ||
| QualifiedTime::Universal(universal_time) => universal_time.add(offset).add(savings), | ||
| }; | ||
| let time = time.add(Time::from_seconds(time_overflow)); | ||
| Self { date, time } | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -444,6 +444,21 @@ pub enum WeekDay { | |
| Sat, | ||
| } | ||
|
|
||
| impl WeekDay { | ||
| pub(crate) fn from_u8(value: u8) -> Self { | ||
| match value { | ||
| 0 => Self::Sun, | ||
| 1 => Self::Mon, | ||
| 2 => Self::Tues, | ||
| 3 => Self::Wed, | ||
| 4 => Self::Thurs, | ||
| 5 => Self::Fri, | ||
| 6 => Self::Sat, | ||
| _ => unreachable!("invalid week day value"), | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+447
to
+460
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like, even for
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably this is something that needs fixing in more than one area. Maybe I can look into that
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's probably fair. I debated it myself, but decided against it because it was an internal operation that is ultimately unreachable. But that being asserted by the caller is definitely a better argument. |
||
|
|
||
| impl TryFromStr<LineParseContext> for WeekDay { | ||
| type Error = ZoneInfoParseError; | ||
| fn try_from_str(s: &str, ctx: &mut LineParseContext) -> Result<Self, Self::Error> { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: document return type