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
}
This commit is contained in:
Tomasz Sowa 2023-12-20 03:58:21 +01:00
parent 0724ca8eb3
commit 0cebb2dc52
Signed by: tomasz.sowa
GPG Key ID: 662CC1438638588B
4 changed files with 188 additions and 117 deletions

View File

@ -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;

View File

@ -74,16 +74,7 @@ public:
// checking whether specified 'date' is in the range of <start, end>
// 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);
};

View File

@ -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

View File

@ -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