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.
This commit is contained in:
2023-12-24 14:55:39 +01:00
parent 0cebb2dc52
commit 09dc6782c8
2 changed files with 150 additions and 63 deletions

View File

@@ -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();
}