From 0cebb2dc52070fc05b76215aedaa2eceb3cbbf7a Mon Sep 17 00:00:00 2001 From: Tomasz Sowa Date: Wed, 20 Dec 2023 03:58:21 +0100 Subject: [PATCH] allow to use a first|last weekday name when defining a timezone dst the format of a 'start' or 'end' field in a dst is: MM-[first|last]:[monday|tuesday|wednesday|thursday|friday|saturday|sunday] HH:MM:SS or just as beforehand: MM-DD HH:MM:SS a sample timezone: { name = "tz_+01:00d" id = "40" offset_str = "+01:00" dst = ( { year = "2001" has_dst = true offset_str = "+01:00" start = "03-last-sunday 01:00" end = "10-last-sunday 01:00" } ) # end of dst } --- winixd/core/timezone.cpp | 185 +++++++++++++++++++++++++++---------- winixd/core/timezone.h | 17 ++-- winixd/etc/time_zones.conf | 101 +++++++++----------- winixd/locale/pl | 2 +- 4 files changed, 188 insertions(+), 117 deletions(-) diff --git a/winixd/core/timezone.cpp b/winixd/core/timezone.cpp index 73ba18c..0cb346c 100644 --- a/winixd/core/timezone.cpp +++ b/winixd/core/timezone.cpp @@ -57,40 +57,9 @@ void TimeZone::Dst::Clear() } -bool TimeZone::Dst::IsDstUsed(const pt::Date & date) const +bool TimeZone::Dst::IsDstUsed(const pt::Date & utc_date) const { - if( !has_dst ) - return false; - - if( Compare(start, date) <= 0 ) // !! CHECK ME <= or < ? (what about the one second?) - if( Compare(date, end) < 0 ) - return true; - -return false; -} - - -int TimeZone::Dst::Compare(const pt::Date & date1, const pt::Date & date2) const -{ - // year is ignored - - if( date1.month != date2.month ) - return date1.month - date2.month; - - if( date1.day != date2.day ) - return date1.day - date2.day; - - if( date1.hour != date2.hour ) - return date1.hour - date2.hour; - - if( date1.min != date2.min ) - return date1.min - date2.min; - - if( date1.sec != date2.sec ) - return date1.sec - date2.sec; - - // dates are equal - return 0; + return has_dst && start.Compare(utc_date, true) <= 0 && utc_date.Compare(end) < 0; } @@ -290,9 +259,135 @@ time_t TimeZone::GetOffset(pt::Space & space) } +const wchar_t * TimeZone::SkipDstDateSeparator(const wchar_t * date_str) +{ + date_str = pt::skip_white(date_str, false, false); + + // the same separators as in the pikotools library + if( *date_str == '-' || *date_str == '/' || *date_str == '.' ) + { + ++date_str; + } + + return pt::skip_white(date_str, false, false); +} + + + +int TimeZone::ParseDstDateWeekday(const wchar_t * date_str, const wchar_t ** after_date_str) +{ + date_str = SkipDstDateSeparator(date_str); + int weekday_int = -1; + + struct WeekDay { + const wchar_t * name; + int weekday; + }; + + static const WeekDay week_days[] = { + {L"sunday", 0}, + {L"monday", 1}, + {L"tuesday", 2}, + {L"wednesday", 3}, + {L"thursday", 4}, + {L"friday", 5}, + {L"saturday", 6}, + }; + + for(const WeekDay & week_day : week_days) + { + if( pt::is_substr(week_day.name, date_str) ) + { + date_str += wcslen(week_day.name); + weekday_int = week_day.weekday; + break; + } + } + + *after_date_str = date_str; + return weekday_int; +} + + +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 was_overflow = false; + bool is_ok = false; + const wchar_t * after_date_str; + + date.Clear(); + 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); + + if( !was_overflow && after_date_str != date_str ) + { + date_str = SkipDstDateSeparator(after_date_str); + + 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; + } + } + 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; + } + } + else + { + 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(); +} + + bool TimeZone::SetTzDst(pt::Space & year) { - time_t h24 = 60 * 60 * 24; // 24 hours + time_t h24 = pt::Date::ONE_DAY; bool result = true; Dst dst; @@ -305,19 +400,15 @@ bool TimeZone::SetTzDst(pt::Space & year) if( year_int < 1970 || year_int > 10000 ) return false; - dst.has_dst = year.to_bool(L"has_dst", false); + bool has_dst = year.to_bool(L"has_dst", false); + std::wstring * dst_start = year.get_wstr(L"start"); + std::wstring * dst_end = year.get_wstr(L"end"); - if( dst.has_dst ) + if( has_dst && dst_start && dst_end ) { - dst.start.year = year_int; - dst.end.year = year_int; - - if( !dst.start.ParseMonthDayTime(year.to_wstr(L"start")) ) - result = false; - - if( !dst.end.ParseMonthDayTime(year.to_wstr(L"end")) ) - result = false; - + dst.has_dst = true; + result = result && ParseDstDate(year_int, dst_start->c_str(), dst.start); + result = result && ParseDstDate(year_int, dst_end->c_str(), dst.end); dst.offset = GetOffset(year); if( dst.offset < -h24 || dst.offset > h24 ) @@ -327,7 +418,7 @@ bool TimeZone::SetTzDst(pt::Space & year) if( result ) dst_map[year_int] = dst; -return result; + return result; } @@ -343,7 +434,7 @@ bool TimeZone::SetTz(pt::Space & space) id = space.to_int(L"id", -1); offset = GetOffset(space); - time_t h24 = 60 * 60 * 24; // 24 hours + time_t h24 = pt::Date::ONE_DAY; if( offset < -h24 || offset > h24 ) result = false; diff --git a/winixd/core/timezone.h b/winixd/core/timezone.h index 47c7580..8132035 100644 --- a/winixd/core/timezone.h +++ b/winixd/core/timezone.h @@ -74,16 +74,7 @@ public: // checking whether specified 'date' is in the range of // the year field in date, start and end is ignored // has_dst must be true - bool IsDstUsed(const pt::Date & date) const; - - - private: - - // Compare returns zero if date1 and date2 are equal - // return value less than zero if date1 is lower than date2 - // and a value greater than zero if date1 is greater than date2 - // the year field is ignored - int Compare(const pt::Date & date1, const pt::Date & date2) const; + bool IsDstUsed(const pt::Date & utc_date) const; }; @@ -156,7 +147,11 @@ private: time_t ParseStrOffset(const wchar_t * str); time_t GetOffset(pt::Space & space); - bool SetTzDst(pt::Space & year); + 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 SetTzDst(pt::Space & year); static void PrintOffsetPart(long val, pt::Stream & str); }; diff --git a/winixd/etc/time_zones.conf b/winixd/etc/time_zones.conf index 22d680a..4040fda 100644 --- a/winixd/etc/time_zones.conf +++ b/winixd/etc/time_zones.conf @@ -15,8 +15,8 @@ dst = ( year = "2012" # daylight saving time in a specified year has_dst = false # whether the time zone has daylight saving time {bool} in this year offset_str = "00:00" # offset of the daylight saving time -start = "" # when the daylight saving time starts, format: MM:DD HH:MM:SS -end = "" # when the daylight saving time ends, format: MM:DD HH:MM:SS +start = "" # when the daylight saving time starts treated as utc date, format: MM-DD HH:MM:SS or MM-[first|last]:[monday|tuesday|wednesday|thursday|friday|saturday|sunday] HH:MM:SS +end = "" # when the daylight saving time ends treated as utc date, format the same as in the start } ) # end of dst @@ -711,11 +711,12 @@ offset_str = "00:00" dst = ( { -year = "2012" +year = "2001" has_dst = true offset_str = "+01:00" -start = "03-25 01:00" -end = "10-28 01:00"} +start = "03-last-sunday 01:00" +end = "10-last-sunday 01:00" +} ) # end of dst } @@ -730,11 +731,12 @@ offset_str = "00:00" dst = ( { -year = "2012" -has_dst = true -offset_str = "+01:00" -start = "03-25 01:00" -end = "10-28 01:00"} +year = "1970" +has_dst = false +offset_str = "00:00" +start = "" +end = "" +} ) # end of dst } @@ -749,11 +751,12 @@ offset_str = "+01:00" dst = ( { -year = "2012" +year = "2001" has_dst = true offset_str = "+01:00" -start = "03-25 01:00" -end = "10-28 01:00"} +start = "03-last-sunday 01:00" +end = "10-last-sunday 01:00" +} ) # end of dst } @@ -768,11 +771,12 @@ offset_str = "+01:00" dst = ( { -year = "2012" +year = "2001" has_dst = true offset_str = "+01:00" -start = "03-25 01:00" -end = "10-28 01:00"} +start = "03-last-sunday 01:00" +end = "10-last-sunday 01:00" +} ) # end of dst } @@ -787,11 +791,12 @@ offset_str = "+01:00" dst = ( { -year = "2012" +year = "2001" has_dst = true offset_str = "+01:00" -start = "03-25 01:00" -end = "10-28 01:00"} +start = "03-last-sunday 01:00" +end = "10-last-sunday 01:00" +} ) # end of dst } @@ -806,32 +811,12 @@ offset_str = "+01:00" dst = ( { -year = "2012" +year = "2001" has_dst = true offset_str = "+01:00" -start = "03-25 01:00" -end = "10-28 01:00"} - -{ -year = "2018" -has_dst = true -offset_str = "+01:00" -start = "03-25 01:00" -end = "10-28 01:00"} - -{ -year = "2022" -has_dst = true -offset_str = "+01:00" -start = "03-27 01:00" -end = "10-30 01:00"} - -{ -year = "2023" -has_dst = true -offset_str = "+01:00" -start = "03-26 01:00" -end = "10-29 01:00"} +start = "03-last-sunday 01:00" +end = "10-last-sunday 01:00" +} ) # end of dst @@ -908,11 +893,11 @@ offset_str = "+02:00" dst = ( { -year = "2012" -has_dst = false -offset_str = "00:00" -start = "" -end = "" +year = "2001" +has_dst = true +offset_str = "+01:00" +start = "03-last-sunday 01:00" +end = "10-last-sunday 01:00" } ) # end of dst @@ -1008,11 +993,11 @@ offset_str = "+02:00" dst = ( { -year = "2012" -has_dst = false -offset_str = "00:00" -start = "" -end = "" +year = "2001" +has_dst = true +offset_str = "+01:00" +start = "03-last-sunday 01:00" +end = "10-last-sunday 01:00" } ) # end of dst @@ -1068,11 +1053,11 @@ offset_str = "+02:00" dst = ( { -year = "2012" -has_dst = false -offset_str = "00:00" -start = "" -end = "" +year = "2001" +has_dst = true +offset_str = "+01:00" +start = "03-last-sunday 01:00" +end = "10-last-sunday 01:00" } ) # end of dst diff --git a/winixd/locale/pl b/winixd/locale/pl index 07eaded..d0485fe 100644 --- a/winixd/locale/pl +++ b/winixd/locale/pl @@ -643,7 +643,7 @@ tz_+02:00c = Bejrut tz_+02:00d = Kair tz_+02:00e = Damaszek tz_+02:00f = Harare, Pretoria -tz_+02:00g = Helsinki, Kijów, Ryga, Sofia, Talin, Wilno +tz_+02:00g = Helsinki, Kijów, Ryga, Sofia, Tallin, Wilno tz_+02:00h = Stambuł tz_+02:00i = Jerozolima tz_+02:00j = Nikozja