Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 75 additions & 12 deletions zoneinfo/src/posix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: document return type

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
Copy link
Contributor

Choose a reason for hiding this comment

The 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

https://github.com/eggert/tz/blob/main/zic.c

Copy link
Member Author

Choose a reason for hiding this comment

The 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 }
}
}
Expand Down
15 changes: 15 additions & 0 deletions zoneinfo/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like, even for pub(crate) this should return a Option<Self> where the caller should be responsible for doing the unreachable assertion, which holds in the case of zoneinfo/src/posix.rs:180

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree

Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Member Author

Choose a reason for hiding this comment

The 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> {
Expand Down
6 changes: 6 additions & 0 deletions zoneinfo/tests/posix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,10 @@ fn posix_string_test() {

let moscow_posix = zic.get_posix_time_zone("Europe/Moscow").unwrap();
assert_eq!(moscow_posix.to_string(), Ok("MSK-3".into()));

let santiago_posix = zic.get_posix_time_zone("America/Santiago").unwrap();
assert_eq!(
santiago_posix.to_string(),
Ok("<-04>4<-03>,M9.1.6/24,M4.1.6/24".into())
)
}
57 changes: 57 additions & 0 deletions zoneinfo/tests/zoneinfo
Original file line number Diff line number Diff line change
Expand Up @@ -435,4 +435,61 @@ Zone Europe/Riga 1:36:34 - LMT 1880
2:00 - EET 2001 Jan 2
2:00 EU EE%sT

# America/Santiago test case

# Rule NAME FROM TO - IN ON AT SAVE LETTER/S
Rule Chile 1927 1931 - Sep 1 0:00 1:00 -
Rule Chile 1928 1932 - Apr 1 0:00 0 -
Rule Chile 1968 only - Nov 3 4:00u 1:00 -
Rule Chile 1969 only - Mar 30 3:00u 0 -
Rule Chile 1969 only - Nov 23 4:00u 1:00 -
Rule Chile 1970 only - Mar 29 3:00u 0 -
Rule Chile 1971 only - Mar 14 3:00u 0 -
Rule Chile 1970 1972 - Oct Sun>=9 4:00u 1:00 -
Rule Chile 1972 1986 - Mar Sun>=9 3:00u 0 -
Rule Chile 1973 only - Sep 30 4:00u 1:00 -
Rule Chile 1974 1987 - Oct Sun>=9 4:00u 1:00 -
Rule Chile 1987 only - Apr 12 3:00u 0 -
Rule Chile 1988 1990 - Mar Sun>=9 3:00u 0 -
Rule Chile 1988 1989 - Oct Sun>=9 4:00u 1:00 -
Rule Chile 1990 only - Sep 16 4:00u 1:00 -
Rule Chile 1991 1996 - Mar Sun>=9 3:00u 0 -
Rule Chile 1991 1997 - Oct Sun>=9 4:00u 1:00 -
Rule Chile 1997 only - Mar 30 3:00u 0 -
Rule Chile 1998 only - Mar Sun>=9 3:00u 0 -
Rule Chile 1998 only - Sep 27 4:00u 1:00 -
Rule Chile 1999 only - Apr 4 3:00u 0 -
Rule Chile 1999 2010 - Oct Sun>=9 4:00u 1:00 -
Rule Chile 2000 2007 - Mar Sun>=9 3:00u 0 -
# N.B.: the end of March 29 in Chile is March 30 in Universal time,
# which is used below in specifying the transition.
Rule Chile 2008 only - Mar 30 3:00u 0 -
Rule Chile 2009 only - Mar Sun>=9 3:00u 0 -
Rule Chile 2010 only - Apr Sun>=1 3:00u 0 -
Rule Chile 2011 only - May Sun>=2 3:00u 0 -
Rule Chile 2011 only - Aug Sun>=16 4:00u 1:00 -
Rule Chile 2012 2014 - Apr Sun>=23 3:00u 0 -
Rule Chile 2012 2014 - Sep Sun>=2 4:00u 1:00 -
Rule Chile 2016 2018 - May Sun>=9 3:00u 0 -
Rule Chile 2016 2018 - Aug Sun>=9 4:00u 1:00 -
Rule Chile 2019 max - Apr Sun>=2 3:00u 0 -
Rule Chile 2019 2021 - Sep Sun>=2 4:00u 1:00 -
Rule Chile 2022 only - Sep Sun>=9 4:00u 1:00 -
Rule Chile 2023 max - Sep Sun>=2 4:00u 1:00 -
# IATA SSIM anomalies: (1992-02) says 1992-03-14;
# (1996-09) says 1998-03-08. Ignore these.
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone America/Santiago -4:42:45 - LMT 1890
-4:42:45 - SMT 1910 Jan 10 # Santiago Mean Time
-5:00 - %z 1916 Jul 1
-4:42:45 - SMT 1918 Sep 10
-4:00 - %z 1919 Jul 1
-4:42:45 - SMT 1927 Sep 1
-5:00 Chile %z 1932 Sep 1
-4:00 - %z 1942 Jun 1
-5:00 - %z 1942 Aug 1
-4:00 - %z 1946 Jul 14 24:00
-4:00 1:00 %z 1946 Aug 28 24:00 # central CL
-5:00 1:00 %z 1947 Mar 31 24:00
-5:00 - %z 1947 May 21 23:00
-4:00 Chile %z