mirror of
https://github.com/HowardHinnant/date.git
synced 2025-08-03 04:34:26 +02:00
Fix Posix::time_zone for southern hemisphere
* The get_info functions were not prepared for the case where the start and end times of a rule are chronologically reversed for a year. I.e. the daylight saving start comes later in the year than the daylight saving end. When save is positive, this happens in the southern hemisphere (e.g. "Australia/Sydney" -> "AEST-10AEDT,M10.1.0,M4.1.0/3"). It also happens in the nothern hemisphere when the save is negative (e.g. "Europe/Dublin" -> "IST-1GMT0,M10.5.0,M3.5.0/1"). * Added tests for Posix::time_zone.
This commit is contained in:
@@ -269,8 +269,76 @@ public:
|
||||
std::string name() const;
|
||||
|
||||
friend bool operator==(const time_zone& x, const time_zone& y);
|
||||
|
||||
private:
|
||||
date::sys_seconds get_start(date::year y) const;
|
||||
date::sys_seconds get_prev_start(date::year y) const;
|
||||
date::sys_seconds get_next_start(date::year y) const;
|
||||
date::sys_seconds get_end(date::year y) const;
|
||||
date::sys_seconds get_prev_end(date::year y) const;
|
||||
date::sys_seconds get_next_end(date::year y) const;
|
||||
date::sys_info contant_offset() const;
|
||||
};
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_start(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(start_rule_(y) - offset_).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_prev_start(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(start_rule_(--y) - offset_).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_next_start(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(start_rule_(++y) - offset_).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_end(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(end_rule_(y) - (offset_ + save_)).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_prev_end(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(end_rule_(--y) - (offset_ + save_)).time_since_epoch()};
|
||||
}
|
||||
|
||||
inline
|
||||
date::sys_seconds
|
||||
time_zone::get_next_end(date::year y) const
|
||||
{
|
||||
return date::sys_seconds{(end_rule_(++y) - (offset_ + save_)).time_since_epoch()};
|
||||
}
|
||||
|
||||
date::sys_info
|
||||
time_zone::contant_offset() const
|
||||
{
|
||||
using date::year;
|
||||
using date::sys_info;
|
||||
using date::sys_days;
|
||||
using date::January;
|
||||
using date::December;
|
||||
using date::last;
|
||||
sys_info r;
|
||||
r.begin = sys_days{year::min()/January/1};
|
||||
r.end = sys_days{year::max()/December/last};
|
||||
r.abbrev = std_abbrev_;
|
||||
r.offset = offset_;
|
||||
return r;
|
||||
}
|
||||
|
||||
inline
|
||||
time_zone::time_zone(const detail::string_t& s)
|
||||
{
|
||||
@@ -313,12 +381,10 @@ time_zone::get_info(date::sys_time<Duration> st) const
|
||||
{
|
||||
using date::sys_info;
|
||||
using date::year_month_day;
|
||||
using date::sys_seconds;
|
||||
using date::sys_days;
|
||||
using date::floor;
|
||||
using date::ceil;
|
||||
using date::days;
|
||||
using date::years;
|
||||
using date::year;
|
||||
using date::January;
|
||||
using date::December;
|
||||
@@ -329,36 +395,59 @@ time_zone::get_info(date::sys_time<Duration> st) const
|
||||
if (start_rule_.ok())
|
||||
{
|
||||
auto y = year_month_day{floor<days>(st)}.year();
|
||||
auto start = sys_seconds{(start_rule_(y) - offset_).time_since_epoch()};
|
||||
auto end = sys_seconds{(end_rule_(y) - (offset_ + save_)).time_since_epoch()};
|
||||
if (start <= st && st < end)
|
||||
auto start = get_start(y);
|
||||
auto end = get_end(y);
|
||||
if (start <= end) // (northern hemisphere)
|
||||
{
|
||||
r.begin = start;
|
||||
r.end = end;
|
||||
r.offset += save_;
|
||||
r.save = ceil<minutes>(save_);
|
||||
r.abbrev = dst_abbrev_;
|
||||
if (start <= st && st < end)
|
||||
{
|
||||
r.begin = start;
|
||||
r.end = end;
|
||||
r.offset += save_;
|
||||
r.save = ceil<minutes>(save_);
|
||||
r.abbrev = dst_abbrev_;
|
||||
}
|
||||
else if (st < start)
|
||||
{
|
||||
r.begin = get_prev_end(y);
|
||||
r.end = start;
|
||||
r.abbrev = std_abbrev_;
|
||||
}
|
||||
else // st >= end
|
||||
{
|
||||
r.begin = end;
|
||||
r.end = get_next_start(y);
|
||||
r.abbrev = std_abbrev_;
|
||||
}
|
||||
}
|
||||
else if (st < start)
|
||||
else // end < start (southern hemisphere)
|
||||
{
|
||||
r.begin = sys_seconds{(end_rule_(y-years{1}) -
|
||||
(offset_ + save_)).time_since_epoch()};
|
||||
r.end = start;
|
||||
r.abbrev = std_abbrev_;
|
||||
}
|
||||
else // st >= end
|
||||
{
|
||||
r.begin = end;
|
||||
r.end = sys_seconds{(start_rule_(y+years{1}) - offset_).time_since_epoch()};
|
||||
r.abbrev = std_abbrev_;
|
||||
if (end <= st && st < start)
|
||||
{
|
||||
r.begin = end;
|
||||
r.end = start;
|
||||
r.abbrev = std_abbrev_;
|
||||
}
|
||||
else if (st < end)
|
||||
{
|
||||
r.begin = get_prev_start(y);
|
||||
r.end = end;
|
||||
r.offset += save_;
|
||||
r.save = ceil<minutes>(save_);
|
||||
r.abbrev = dst_abbrev_;
|
||||
}
|
||||
else // st >= start
|
||||
{
|
||||
r.begin = start;
|
||||
r.end = get_next_end(y);
|
||||
r.offset += save_;
|
||||
r.save = ceil<minutes>(save_);
|
||||
r.abbrev = dst_abbrev_;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // constant offset
|
||||
{
|
||||
r.begin = sys_days{year::min()/January/1};
|
||||
r.end = sys_days{year::max()/December/last};
|
||||
r.abbrev = std_abbrev_;
|
||||
}
|
||||
else
|
||||
r = contant_offset();
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -371,7 +460,6 @@ time_zone::get_info(date::local_time<Duration> tp) const
|
||||
using date::days;
|
||||
using date::sys_days;
|
||||
using date::sys_seconds;
|
||||
using date::years;
|
||||
using date::year;
|
||||
using date::ceil;
|
||||
using date::January;
|
||||
@@ -384,19 +472,25 @@ time_zone::get_info(date::local_time<Duration> tp) const
|
||||
if (start_rule_.ok())
|
||||
{
|
||||
auto y = year_month_day{floor<days>(tp)}.year();
|
||||
auto start = sys_seconds{(start_rule_(y) - offset_).time_since_epoch()};
|
||||
auto end = sys_seconds{(end_rule_(y) - (offset_ + save_)).time_since_epoch()};
|
||||
auto start = get_start(y);
|
||||
auto end = get_end(y);
|
||||
auto utcs = sys_seconds{floor<seconds>(tp - offset_).time_since_epoch()};
|
||||
auto utcd = sys_seconds{floor<seconds>(tp - (offset_ + save_)).time_since_epoch()};
|
||||
auto northern = start <= end;
|
||||
if ((utcs < start) != (utcd < start))
|
||||
{
|
||||
r.first.begin = sys_seconds{(end_rule_(y-years{1}) -
|
||||
(offset_ + save_)).time_since_epoch()};
|
||||
if (northern)
|
||||
r.first.begin = get_prev_end(y);
|
||||
else
|
||||
r.first.begin = end;
|
||||
r.first.end = start;
|
||||
r.first.offset = offset_;
|
||||
r.first.abbrev = std_abbrev_;
|
||||
r.second.begin = start;
|
||||
r.second.end = end;
|
||||
if (northern)
|
||||
r.second.end = end;
|
||||
else
|
||||
r.second.end = get_next_end(y);
|
||||
r.second.abbrev = dst_abbrev_;
|
||||
r.second.offset = offset_ + save_;
|
||||
r.second.save = ceil<minutes>(save_);
|
||||
@@ -405,51 +499,29 @@ time_zone::get_info(date::local_time<Duration> tp) const
|
||||
}
|
||||
else if ((utcs < end) != (utcd < end))
|
||||
{
|
||||
r.first.begin = start;
|
||||
if (northern)
|
||||
r.first.begin = start;
|
||||
else
|
||||
r.first.begin = get_prev_start(y);
|
||||
r.first.end = end;
|
||||
r.first.offset = offset_ + save_;
|
||||
r.first.save = ceil<minutes>(save_);
|
||||
r.first.abbrev = dst_abbrev_;
|
||||
r.second.begin = end;
|
||||
r.second.end = sys_seconds{(start_rule_(y+years{1}) -
|
||||
offset_).time_since_epoch()};
|
||||
if (northern)
|
||||
r.second.end = get_next_start(y);
|
||||
else
|
||||
r.second.end = start;
|
||||
r.second.abbrev = std_abbrev_;
|
||||
r.second.offset = offset_;
|
||||
r.result = save_ > seconds{0} ? local_info::ambiguous
|
||||
: local_info::nonexistent;
|
||||
}
|
||||
else if (utcs < start)
|
||||
{
|
||||
r.first.begin = sys_seconds{(end_rule_(y-years{1}) -
|
||||
(offset_ + save_)).time_since_epoch()};
|
||||
r.first.end = start;
|
||||
r.first.offset = offset_;
|
||||
r.first.abbrev = std_abbrev_;
|
||||
}
|
||||
else if (utcs < end)
|
||||
{
|
||||
r.first.begin = start;
|
||||
r.first.end = end;
|
||||
r.first.offset = offset_ + save_;
|
||||
r.first.save = ceil<minutes>(save_);
|
||||
r.first.abbrev = dst_abbrev_;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.first.begin = end;
|
||||
r.first.end = sys_seconds{(start_rule_(y+years{1}) -
|
||||
offset_).time_since_epoch()};
|
||||
r.first.abbrev = std_abbrev_;
|
||||
r.first.offset = offset_;
|
||||
}
|
||||
}
|
||||
else // constant offset
|
||||
{
|
||||
r.first.begin = sys_days{year::min()/January/1};
|
||||
r.first.end = sys_days{year::max()/December/last};
|
||||
r.first.abbrev = std_abbrev_;
|
||||
r.first.offset = offset_;
|
||||
r.first = get_info(utcs);
|
||||
}
|
||||
else
|
||||
r.first = contant_offset();
|
||||
return r;
|
||||
}
|
||||
|
||||
|
108
test/posix/ptz.pass.cpp
Normal file
108
test/posix/ptz.pass.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
// The MIT License (MIT)
|
||||
//
|
||||
// Copyright (c) 2021 Howard Hinnant
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
// Test Posix::time_zone
|
||||
|
||||
#include "tz.h"
|
||||
#include "ptz.h"
|
||||
#include <cassert>
|
||||
|
||||
bool
|
||||
is_equal(date::sys_info const& x, date::sys_info const& y)
|
||||
{
|
||||
return x.begin == y.begin &&
|
||||
x.end == y.end &&
|
||||
x.offset == y.offset &&
|
||||
x.save == y.save &&
|
||||
x.abbrev == y.abbrev;
|
||||
}
|
||||
|
||||
bool
|
||||
is_equal(date::local_info const& x, date::local_info const& y)
|
||||
{
|
||||
return x.result == y.result && is_equal(x.first, y.first)
|
||||
&& is_equal(x.second, y.second);
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
using namespace date;
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
|
||||
auto tzi = locate_zone("Australia/Sydney");
|
||||
Posix::time_zone tzp{"AEST-10AEDT,M10.1.0,M4.1.0/3"};
|
||||
auto tp = local_days{2021_y/1/1} + 0s;
|
||||
assert(tzp.get_info(tp).result == local_info::unique);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
tp = local_days{2021_y/10/Sunday[1]} + 2h + 30min;
|
||||
assert(tzp.get_info(tp).result == local_info::nonexistent);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
tp = local_days{2021_y/4/Sunday[1]} + 2h + 30min;
|
||||
assert(tzp.get_info(tp).result == local_info::ambiguous);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
tp = local_days{2021_y/7/1};
|
||||
assert(tzp.get_info(tp).result == local_info::unique);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
|
||||
tzi = locate_zone("America/New_York");
|
||||
tzp = Posix::time_zone{"EST5EDT,M3.2.0,M11.1.0"};
|
||||
tp = local_days{2021_y/1/1};
|
||||
assert(tzp.get_info(tp).result == local_info::unique);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
tp = local_days{2021_y/3/Sunday[2]} + 2h + 30min;
|
||||
assert(tzp.get_info(tp).result == local_info::nonexistent);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
tp = local_days{2021_y/11/Sunday[1]} + 1h + 30min;
|
||||
assert(tzp.get_info(tp).result == local_info::ambiguous);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
tp = local_days{2021_y/7/1};
|
||||
assert(tzp.get_info(tp).result == local_info::unique);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
|
||||
tzi = locate_zone("Europe/Dublin");
|
||||
tzp = Posix::time_zone{"IST-1GMT0,M10.5.0,M3.5.0/1"};
|
||||
tp = local_days{2021_y/1/1};
|
||||
assert(tzp.get_info(tp).result == local_info::unique);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
tp = local_days{2021_y/3/Sunday[last]} + 1h + 30min;
|
||||
assert(tzp.get_info(tp).result == local_info::nonexistent);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
tp = local_days{2021_y/10/Sunday[last]} + 1h + 30min;
|
||||
assert(tzp.get_info(tp).result == local_info::ambiguous);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
|
||||
tp = local_days{2021_y/7/1};
|
||||
assert(tzp.get_info(tp).result == local_info::unique);
|
||||
assert(is_equal(tzi->get_info(tp), tzp.get_info(tp)));
|
||||
}
|
Reference in New Issue
Block a user