From 09dc6782c8f6ef12f23fbad443aded381c19493e Mon Sep 17 00:00:00 2001 From: Tomasz Sowa Date: Sun, 24 Dec 2023 14:55:39 +0100 Subject: [PATCH] fix: remember the first/last weekday flags from a timezone DST If a date is given in a relative form with first/last parameters, we must remember them in order to select the appropriate day of the week from the appropriate year. --- winixd/core/timezone.cpp | 144 ++++++++++++++++++++++++++------------- winixd/core/timezone.h | 69 ++++++++++++++----- 2 files changed, 150 insertions(+), 63 deletions(-) diff --git a/winixd/core/timezone.cpp b/winixd/core/timezone.cpp index 0cb346c..0ca2626 100644 --- a/winixd/core/timezone.cpp +++ b/winixd/core/timezone.cpp @@ -40,6 +40,85 @@ namespace Winix { +TimeZone::DstDate::DstDate() +{ + Clear(); +} + + +void TimeZone::DstDate::Clear() +{ + date_type = ordinary_date; + weekday = 0; + date.Clear(); +} + + + +int TimeZone::DstDate::Compare(const pt::Date & utc_date) const +{ + int res = 0; + + if( date_type == ordinary_date ) + { + res = date.Compare(utc_date, true); + } + else + if( date_type == first_weekday ) + { + pt::Date tmp_date = CalculateRelativeDate(utc_date.year, date.month, true, weekday); + res = tmp_date.Compare(utc_date, true); + } + else + if( date_type == last_weekday ) + { + pt::Date tmp_date = CalculateRelativeDate(utc_date.year, date.month, false, weekday); + res = tmp_date.Compare(utc_date, true); + } + + return res; +} + + +pt::Date TimeZone::DstDate::CalculateRelativeDate(int year, int month, bool is_first, int weekday) +{ + pt::Date new_date; + new_date.year = year; + new_date.month = month; + + if( is_first ) + { + new_date.day = 1; + int w = new_date.WeekDay(); + + if( w < weekday ) + { + new_date.day += (weekday - w); + } + else + if( w > weekday ) + { + new_date.day += (weekday - w) + 7; + } + } + else + { + new_date.day = pt::Date::MonthLen(new_date.year, new_date.month); + int w = new_date.WeekDay(); + + if( w > weekday ) + { + new_date.day -= (w - weekday); + } + else + if( w < weekday ) + { + new_date.day -= (w - weekday) + 7; + } + } + + return new_date; +} TimeZone::Dst::Dst() @@ -57,9 +136,12 @@ void TimeZone::Dst::Clear() } + + + bool TimeZone::Dst::IsDstUsed(const pt::Date & utc_date) const { - return has_dst && start.Compare(utc_date, true) <= 0 && utc_date.Compare(end) < 0; + return has_dst && start.Compare(utc_date) <= 0 && end.Compare(utc_date) > 0; } @@ -309,42 +391,17 @@ int TimeZone::ParseDstDateWeekday(const wchar_t * date_str, const wchar_t ** aft } -void TimeZone::ParseDstDateDay(bool is_first, int weekday, pt::Date & date) -{ - int len = 1; - int adder = 1; - - if( is_first ) - { - len = pt::Date::MonthLen(date.year, date.month); - date.day = 1; - adder = 1; - } - else - { - len = pt::Date::MonthLen(date.year, date.month); - date.day = len; - adder = -1; - } - - for(int i=0 ; i < len && date.WeekDay() != weekday ; ++i) - { - date.day += adder; - } -} - - -bool TimeZone::ParseDstDate(int year, const wchar_t * date_str, pt::Date & date) +bool TimeZone::ParseDstDate(int year, const wchar_t * date_str, DstDate & dst_date) { bool was_overflow = false; bool is_ok = false; const wchar_t * after_date_str; - date.Clear(); - date.year = year; + dst_date.Clear(); + dst_date.date.year = year; date_str = pt::skip_white(date_str, false, false); - date.month = pt::to_i(date_str, 10, &after_date_str, &was_overflow, true); + dst_date.date.month = pt::to_i(date_str, 10, &after_date_str, &was_overflow, true); if( !was_overflow && after_date_str != date_str ) { @@ -353,35 +410,30 @@ bool TimeZone::ParseDstDate(int year, const wchar_t * date_str, pt::Date & date) if( pt::is_substr(L"first", date_str) ) { date_str += 5; // length of "first" - int weekday = ParseDstDateWeekday(date_str, &date_str); - - if( weekday >= 0 ) - { - ParseDstDateDay(true, weekday, date); - is_ok = true; - } + dst_date.date_type = DstDate::first_weekday; + dst_date.weekday = ParseDstDateWeekday(date_str, &date_str); + dst_date.date.day = 1; // to return true from IsCorrectDate() at the end of this method, will not be used in the future + is_ok = (dst_date.weekday >= 0); } else if( pt::is_substr(L"last", date_str) ) { date_str += 4; // length of "last" - int weekday = ParseDstDateWeekday(date_str, &date_str); - - if( weekday >= 0 ) - { - ParseDstDateDay(false, weekday, date); - is_ok = true; - } + dst_date.date_type = DstDate::last_weekday; + dst_date.weekday = ParseDstDateWeekday(date_str, &date_str); + dst_date.date.day = 1; + is_ok = (dst_date.weekday >= 0); } else { - date.day = pt::to_i(date_str, 10, &after_date_str, &was_overflow, true); + dst_date.date_type = DstDate::ordinary_date; + dst_date.date.day = pt::to_i(date_str, 10, &after_date_str, &was_overflow, true); is_ok = !was_overflow && after_date_str != date_str; date_str = after_date_str; } } - return is_ok && date.ParseTime(date_str) && date.IsCorrectDate(); + return is_ok && dst_date.date.ParseTime(date_str) && dst_date.date.IsCorrectDate(); } diff --git a/winixd/core/timezone.h b/winixd/core/timezone.h index 8132035..54a7489 100644 --- a/winixd/core/timezone.h +++ b/winixd/core/timezone.h @@ -53,37 +53,73 @@ class TimeZone public: + struct DstDate + { + enum DateType { + ordinary_date = 0, + first_weekday, + last_weekday, + }; + + /* + * for ordinary_date + * + */ + DateType date_type; + + /* + * a week day (0-sunday, 1-monday, ..., 6-saturday) + * used when date_type is first_weekday or last_weekday + */ + int weekday; + + /* + * if date_type=ordinary_date the date is used as normal date represented in UTC time + * if date_type=first_weekday we are looking for a first weekday in the date.month (year will be changed) + * if date_type=last_weekday we are looking for the last weekady in the date.month (year will be changed) + */ + pt::Date date; + + + DstDate(); + void Clear(); + int Compare(const pt::Date & utc_date) const; + static pt::Date CalculateRelativeDate(int year, int month, bool is_first, int weekday); + }; + + struct Dst { - // true if a time zone has daylight saving time + /* + * true if a time zone has daylight saving time + */ bool has_dst; - // time zone daylight saving time (used if has_dst is true) - // the 'year' field is the same in 'start' and 'end' - // start and end are represented in UTC time - pt::Date start, end; + /* + * time zone daylight saving time (used if has_dst is true) + */ + DstDate start, end; - // time zone daylight saving time offset - // used when has_dst is true and the date is whithin start and end - // this offset should be added to time zone offset + /* + * time zone daylight saving time offset + * used when has_dst is true and the date is whithin start and end + * this offset should be added to time zone offset + */ time_t offset; Dst(); void Clear(); - // checking whether specified 'date' is in the range of - // the year field in date, start and end is ignored - // has_dst must be true + /* + * checking whether specified 'date' is in the range of Dst + * has_dst must be true + */ bool IsDstUsed(const pt::Date & utc_date) const; - }; TimeZone(); - - /* - */ void Clear(); @@ -148,9 +184,8 @@ private: time_t ParseStrOffset(const wchar_t * str); time_t GetOffset(pt::Space & space); int ParseDstDateWeekday(const wchar_t * date_str, const wchar_t ** after_date_str); - void ParseDstDateDay(bool is_first, int weekday, pt::Date & date); const wchar_t * SkipDstDateSeparator(const wchar_t * date_str); - bool ParseDstDate(int year, const wchar_t * date_str, pt::Date & date); + bool ParseDstDate(int year, const wchar_t * date_str, DstDate & dst_date); bool SetTzDst(pt::Space & year); static void PrintOffsetPart(long val, pt::Stream & str);