Merge branch 'tz-fixes'

Miscellaneous fixes for time zones and DST handling in wxDateTime.

This still leaves 2 big problems:

1. We have no support for using the correct time zone offset at the
   given date and always use the current time zone offset, which may,
   and often is, wrong.

2. Our code for converting to/from broken down representation doesn't
   handle DST at all, so support for DST is non-existent for the dates
   before 1970-01-01 or after 2038-01-01 (i.e. roughly outside of the
   32 bit time_t range).

See #10445 and the other tickets linked from there.
This commit is contained in:
Vadim Zeitlin
2017-12-02 16:28:05 +01:00
8 changed files with 215 additions and 133 deletions

View File

@@ -306,7 +306,9 @@ public:
return tz; return tz;
} }
long GetOffset() const { return m_offset; } bool IsLocal() const { return m_offset == -1; }
long GetOffset() const;
private: private:
// offset for this timezone from GMT in seconds // offset for this timezone from GMT in seconds

View File

@@ -158,7 +158,11 @@ public:
line = line_; line = line_;
component = component_; component = component_;
timestamp = time(NULL); // don't initialize the timestamp yet, we might not need it at all if
// the message doesn't end up being logged and otherwise we'll fill it
// just before logging it, which won't change it by much and definitely
// less than a second resolution of the timestamp
timestamp = 0;
#if wxUSE_THREADS #if wxUSE_THREADS
threadId = wxThread::GetCurrentId(); threadId = wxThread::GetCurrentId();
@@ -1162,6 +1166,11 @@ private:
void DoCallOnLog(wxLogLevel level, const wxString& format, va_list argptr) void DoCallOnLog(wxLogLevel level, const wxString& format, va_list argptr)
{ {
// As explained in wxLogRecordInfo ctor, we don't initialize its
// timestamp to avoid calling time() unnecessary, but now that we are
// about to log the message, we do need to do it.
m_info.timestamp = time(NULL);
wxLog::OnLog(level, wxString::FormatV(format, argptr), m_info); wxLog::OnLog(level, wxString::FormatV(format, argptr), m_info);
} }

View File

@@ -254,6 +254,17 @@ public:
/// Create a time zone with the given offset in seconds. /// Create a time zone with the given offset in seconds.
static TimeZone Make(long offset); static TimeZone Make(long offset);
/**
Return true if this is the local time zone.
This method can be useful for distinguishing between UTC time zone
and local time zone in Great Britain, which use the same offset as
UTC (i.e. 0), but do use DST.
@since 3.1.1
*/
bool IsLocal() const;
/// Return the offset of this time zone from UTC, in seconds. /// Return the offset of this time zone from UTC, in seconds.
long GetOffset() const; long GetOffset() const;
}; };
@@ -1237,6 +1248,10 @@ public:
for more information about time zones. Normally, these functions should for more information about time zones. Normally, these functions should
be rarely used. be rarely used.
Note that all functions in this section always use the current offset
for the specified time zone and don't take into account its possibly
different historical value at the given date.
Related functions in other groups: GetBeginDST(), GetEndDST() Related functions in other groups: GetBeginDST(), GetEndDST()
*/ */
//@{ //@{
@@ -1246,10 +1261,7 @@ public:
If @a noDST is @true, no DST adjustments will be made. If @a noDST is @true, no DST adjustments will be made.
Notice using wxDateTime::Local for @a tz parameter doesn't really make If @a tz parameter is wxDateTime::Local, no adjustment is performed.
sense and may result in unexpected results as it will return a
different object when DST is in use and @a noDST has its default value
of @false.
@return The date adjusted by the different between the given and the @return The date adjusted by the different between the given and the
local time zones. local time zones.
@@ -1286,9 +1298,7 @@ public:
If @a noDST is @true, no DST adjustments will be made. If @a noDST is @true, no DST adjustments will be made.
Notice that, as with FromTimezone(), using wxDateTime::Local as @a tz If @a tz parameter is wxDateTime::Local, no adjustment is performed.
doesn't really make sense and may return a different object when DST is
in effect and @a noDST is @false.
@return The date adjusted by the different between the local and the @return The date adjusted by the different between the local and the
given time zones. given time zones.

View File

@@ -341,6 +341,33 @@ void wxInitTm(struct tm& tm)
tm.tm_isdst = -1; // auto determine tm.tm_isdst = -1; // auto determine
} }
// Internal helper function called only for times outside of standard time_t
// range.
//
// It is just a hack to work around the fact that we can't call IsDST() and
// related methods from GetTm() for the reasons explained there.
static int GetDSTOffset(wxLongLong t)
{
bool isDST = false;
switch ( wxDateTime::GetCountry() )
{
case wxDateTime::UK:
// We don't need to check for the end value in 1971 as this is
// inside the standard range, so check just for beginning of the
// permanent BST period in UK, see IsDST().
if ( t < 0 &&
t >= wxDateTime(27, wxDateTime::Oct, 1968).GetValue() )
isDST = true;
break;
default:
break;
}
return isDST ? wxDateTime::DST_OFFSET : 0;
}
// ============================================================================ // ============================================================================
// implementation of wxDateTime // implementation of wxDateTime
// ============================================================================ // ============================================================================
@@ -456,9 +483,8 @@ wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz)
switch ( tz ) switch ( tz )
{ {
case wxDateTime::Local: case wxDateTime::Local:
// get the offset from C RTL: it returns the difference GMT-local // Use a special value for local time zone.
// while we want to have the offset _from_ GMT, hence the '-' m_offset = -1;
m_offset = -wxGetTimeZone();
break; break;
case wxDateTime::GMT_12: case wxDateTime::GMT_12:
@@ -503,6 +529,13 @@ wxDateTime::TimeZone::TimeZone(wxDateTime::TZ tz)
} }
} }
long wxDateTime::TimeZone::GetOffset() const
{
// get the offset from C RTL: it returns the difference GMT-local
// while we want to have the offset _from_ GMT, hence the '-'
return m_offset == -1 ? -wxGetTimeZone() : m_offset;
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// static functions // static functions
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@@ -855,7 +888,8 @@ wxDateTime::Country wxDateTime::GetCountry()
struct tm *tm = wxLocaltime_r(&t, &tmstruct); struct tm *tm = wxLocaltime_r(&t, &tmstruct);
wxString tz = wxCallStrftime(wxS("%Z"), tm); wxString tz = wxCallStrftime(wxS("%Z"), tm);
if ( tz == wxT("WET") || tz == wxT("WEST") ) if ( tz == wxT("WET") || tz == wxT("WEST") ||
tz == wxT("BST") || tz == wxT("GMT") )
{ {
ms_country = UK; ms_country = UK;
} }
@@ -1430,6 +1464,24 @@ unsigned long wxDateTime::GetAsDOS() const
// time_t <-> broken down time conversions // time_t <-> broken down time conversions
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
const tm* wxTryGetTm(tm& tmstruct, time_t t, const wxDateTime::TimeZone& tz)
{
if ( tz.IsLocal() )
{
// we are working with local time
return wxLocaltime_r(&t, &tmstruct);
}
else
{
t += (time_t)tz.GetOffset();
#if !defined(__VMS__) // time is unsigned so avoid warning
if ( t < 0 )
return NULL;
#endif
return wxGmtime_r(&t, &tmstruct);
}
}
wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const
{ {
wxASSERT_MSG( IsValid(), wxT("invalid wxDateTime") ); wxASSERT_MSG( IsValid(), wxT("invalid wxDateTime") );
@@ -1437,39 +1489,9 @@ wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const
time_t time = GetTicks(); time_t time = GetTicks();
if ( time != (time_t)-1 ) if ( time != (time_t)-1 )
{ {
// use C RTL functions // Try to use the RTL.
struct tm tmstruct; struct tm tmstruct;
tm *tm; if ( const tm* tm = wxTryGetTm(tmstruct, time, tz) )
if ( tz.GetOffset() == -wxGetTimeZone() )
{
// we are working with local time
tm = wxLocaltime_r(&time, &tmstruct);
// should never happen
wxCHECK_MSG( tm, Tm(), wxT("wxLocaltime_r() failed") );
}
else
{
time += (time_t)tz.GetOffset();
#if defined(__VMS__) // time is unsigned so avoid warning
int time2 = (int) time;
if ( time2 >= 0 )
#else
if ( time >= 0 )
#endif
{
tm = wxGmtime_r(&time, &tmstruct);
// should never happen
wxCHECK_MSG( tm, Tm(), wxT("wxGmtime_r() failed") );
}
else
{
tm = (struct tm *)NULL;
}
}
if ( tm )
{ {
// adjust the milliseconds // adjust the milliseconds
Tm tm2(*tm, tz); Tm tm2(*tm, tz);
@@ -1480,11 +1502,21 @@ wxDateTime::Tm wxDateTime::GetTm(const TimeZone& tz) const
//else: use generic code below //else: use generic code below
} }
long secDiff = tz.GetOffset();
// We need to account for DST as always when converting to broken down time
// components, but we can't call IsDST() from here because this would
// result in infinite recursion as IsDST() starts by calling GetYear()
// which just calls back to this function. So call a special function which
// is used just here to determine the DST offset to add.
if ( tz.IsLocal() )
secDiff += GetDSTOffset(m_time);
wxLongLong timeMidnight = m_time + secDiff * 1000;
// remember the time and do the calculations with the date only - this // remember the time and do the calculations with the date only - this
// eliminates rounding errors of the floating point arithmetics // eliminates rounding errors of the floating point arithmetics
wxLongLong timeMidnight = m_time + tz.GetOffset() * 1000;
long timeOnly = (timeMidnight % MILLISECONDS_PER_DAY).ToLong(); long timeOnly = (timeMidnight % MILLISECONDS_PER_DAY).ToLong();
// we want to always have positive time and timeMidnight to be really // we want to always have positive time and timeMidnight to be really
@@ -2086,10 +2118,28 @@ int wxDateTime::IsDST(wxDateTime::Country country) const
{ {
int year = GetYear(); int year = GetYear();
if ( !IsDSTApplicable(year, country) ) country = GetCountry();
switch ( country )
{ {
// no DST time in this year in this country case UK:
return -1; // There is a special, but important, case of UK which was
// permanently on BST, i.e. using DST, during this period. It
// is important because it covers Unix epoch and without
// accounting for the DST during it, various tests done around
// the epoch time would fail in BST time zone (only!).
if ( IsEarlierThan(wxDateTime(31, Oct, 1971)) &&
IsLaterThan(wxDateTime(27, Oct, 1968)) )
{
return true;
}
wxFALLTHROUGH;
default:
if ( !IsDSTApplicable(year, country) )
{
// no DST time in this year in this country
return -1;
}
} }
return IsBetween(GetBeginDST(year, country), GetEndDST(year, country)); return IsBetween(GetBeginDST(year, country), GetEndDST(year, country));
@@ -2100,11 +2150,11 @@ wxDateTime& wxDateTime::MakeTimezone(const TimeZone& tz, bool noDST)
{ {
long secDiff = wxGetTimeZone() + tz.GetOffset(); long secDiff = wxGetTimeZone() + tz.GetOffset();
// We are converting from the local time, but local time zone does not // We are converting from the local time to some other time zone, but local
// include the DST offset (as it varies depending on the date), so we have // time zone does not include the DST offset (as it varies depending on the
// to handle DST manually, unless a special flag inhibiting this was // date), so we have to handle DST manually, unless a special flag
// specified. // inhibiting this was specified.
if ( !noDST && (IsDST() == 1) ) if ( !noDST && (IsDST() == 1) && !tz.IsLocal() )
{ {
secDiff -= DST_OFFSET; secDiff -= DST_OFFSET;
} }
@@ -2117,7 +2167,7 @@ wxDateTime& wxDateTime::MakeFromTimezone(const TimeZone& tz, bool noDST)
long secDiff = wxGetTimeZone() + tz.GetOffset(); long secDiff = wxGetTimeZone() + tz.GetOffset();
// See comment in MakeTimezone() above, the logic here is exactly the same. // See comment in MakeTimezone() above, the logic here is exactly the same.
if ( !noDST && (IsDST() == 1) ) if ( !noDST && (IsDST() == 1) && !tz.IsLocal() )
{ {
secDiff -= DST_OFFSET; secDiff -= DST_OFFSET;
} }

View File

@@ -65,6 +65,7 @@
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
extern void wxInitTm(struct tm& tm); extern void wxInitTm(struct tm& tm);
extern const tm* wxTryGetTm(tm& tmstruct, time_t t, const wxDateTime::TimeZone& tz);
extern wxString wxCallStrftime(const wxString& format, const tm* tm); extern wxString wxCallStrftime(const wxString& format, const tm* tm);
@@ -367,40 +368,9 @@ wxString wxDateTime::Format(const wxString& formatp, const TimeZone& tz) const
if ( canUseStrftime ) if ( canUseStrftime )
{ {
// use strftime() // Try using strftime()
struct tm tmstruct; struct tm tmstruct;
struct tm *tm; if ( const tm* tm = wxTryGetTm(tmstruct, time, tz) )
if ( tz.GetOffset() == -wxGetTimeZone() )
{
// we are working with local time
tm = wxLocaltime_r(&time, &tmstruct);
// should never happen
wxCHECK_MSG( tm, wxEmptyString, wxT("wxLocaltime_r() failed") );
}
else
{
time += (int)tz.GetOffset();
#if defined(__VMS__) // time is unsigned so avoid warning
int time2 = (int) time;
if ( time2 >= 0 )
#else
if ( time >= 0 )
#endif
{
tm = wxGmtime_r(&time, &tmstruct);
// should never happen
wxCHECK_MSG( tm, wxEmptyString, wxT("wxGmtime_r() failed") );
}
else
{
tm = (struct tm *)NULL;
}
}
if ( tm )
{ {
return wxCallStrftime(format, tm); return wxCallStrftime(format, tm);
} }

View File

@@ -24,8 +24,6 @@
#include "wx/wxcrt.h" // for wxStrstr() #include "wx/wxcrt.h" // for wxStrstr()
#include "testdate.h"
// to test Today() meaningfully we must be able to change the system date which // to test Today() meaningfully we must be able to change the system date which
// is not usually the case, but if we're under Win32 we can try it -- define // is not usually the case, but if we're under Win32 we can try it -- define
// the macro below to do it // the macro below to do it
@@ -301,15 +299,13 @@ void DateTimeTestCase::TestTimeSet()
for ( size_t n = 0; n < WXSIZEOF(testDates); n++ ) for ( size_t n = 0; n < WXSIZEOF(testDates); n++ )
{ {
const Date& d1 = testDates[n]; const Date& d1 = testDates[n];
wxDateTime dt = d1.DT(); const wxDateTime dt = d1.DT();
Date d2; Date d2;
d2.Init(dt.GetTm()); d2.Init(dt.GetTm());
wxString s1 = d1.Format(), INFO("n=" << n);
s2 = d2.Format(); CHECK( d1.Format() == d2.Format() );
CPPUNIT_ASSERT_EQUAL( s1, s2 );
} }
} }
@@ -324,10 +320,11 @@ void DateTimeTestCase::TestTimeJDN()
// JDNs must be computed for UTC times // JDNs must be computed for UTC times
double jdn = dt.FromUTC().GetJulianDayNumber(); double jdn = dt.FromUTC().GetJulianDayNumber();
CPPUNIT_ASSERT_EQUAL( d.jdn, jdn ); INFO("n=" << n);
CHECK( d.jdn == jdn );
dt.Set(jdn); dt.Set(jdn);
CPPUNIT_ASSERT_EQUAL( jdn, dt.GetJulianDayNumber() ); CHECK( jdn == dt.GetJulianDayNumber() );
} }
} }
@@ -341,8 +338,10 @@ void DateTimeTestCase::TestTimeWDays()
const Date& d = testDates[n]; const Date& d = testDates[n];
wxDateTime dt(d.day, d.month, d.year, d.hour, d.min, d.sec); wxDateTime dt(d.day, d.month, d.year, d.hour, d.min, d.sec);
INFO("n=" << n);
wxDateTime::WeekDay wday = dt.GetWeekDay(); wxDateTime::WeekDay wday = dt.GetWeekDay();
CPPUNIT_ASSERT_EQUAL( d.wday, wday ); CHECK( d.wday == wday );
} }
// test SetToWeekDay() // test SetToWeekDay()
@@ -665,6 +664,15 @@ void DateTimeTestCase::TestTimeFormat()
CompareTime // time only CompareTime // time only
}; };
const char* const compareKindStrings[] =
{
"nothing",
"both date and time",
"both date and time but without century",
"only dates",
"only times",
};
static const struct static const struct
{ {
CompareKind compareKind; CompareKind compareKind;
@@ -683,7 +691,7 @@ void DateTimeTestCase::TestTimeFormat()
const long timeZonesOffsets[] = const long timeZonesOffsets[] =
{ {
wxDateTime::TimeZone(wxDateTime::Local).GetOffset(), -1, // This is pseudo-offset used for local time zone
// Fictitious TimeZone offsets to ensure time zone formating and // Fictitious TimeZone offsets to ensure time zone formating and
// interpretation works // interpretation works
@@ -712,7 +720,7 @@ void DateTimeTestCase::TestTimeFormat()
for ( unsigned idxtz = 0; idxtz < WXSIZEOF(timeZonesOffsets); ++idxtz ) for ( unsigned idxtz = 0; idxtz < WXSIZEOF(timeZonesOffsets); ++idxtz )
{ {
wxDateTime::TimeZone tz(timeZonesOffsets[idxtz]); wxDateTime::TimeZone tz(timeZonesOffsets[idxtz]);
const bool isLocalTz = tz.GetOffset() == -wxGetTimeZone(); const bool isLocalTz = tz.IsLocal();
for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ ) for ( size_t d = 0; d < WXSIZEOF(formatTestDates); d++ )
{ {
@@ -753,6 +761,21 @@ void DateTimeTestCase::TestTimeFormat()
// do convert date to string // do convert date to string
wxString s = dt.Format(fmt, tz); wxString s = dt.Format(fmt, tz);
// Normally, passing time zone to Format() should have exactly
// the same effect as converting to this time zone before
// calling it, however the former may use standard library date
// handling in strftime() implementation while the latter
// always uses our own code and they may disagree if the offset
// for this time zone has changed since the given date, as the
// standard library handles it correctly (at least under Unix),
// while our code doesn't handle time zone changes at all.
//
// Short of implementing full support for time zone database,
// we can't really do anything about this other than skipping
// the test in this case.
if ( s != dt.ToTimezone(tz).Format(fmt) )
continue;
// convert back // convert back
wxDateTime dt2; wxDateTime dt2;
const char *result = dt2.ParseFormat(s, fmt); const char *result = dt2.ParseFormat(s, fmt);
@@ -785,13 +808,16 @@ void DateTimeTestCase::TestTimeFormat()
if ( !strstr(fmt, "%z") && !isLocalTz ) if ( !strstr(fmt, "%z") && !isLocalTz )
dt2.MakeFromTimezone(tz); dt2.MakeFromTimezone(tz);
INFO("Comparing " << compareKindStrings[kind] << " for "
<< dt << " with " << dt2
<< " (format result=\"" << s << "\")");
switch ( kind ) switch ( kind )
{ {
case CompareYear: case CompareYear:
if ( dt2.GetCentury() != dt.GetCentury() ) if ( dt2.GetCentury() != dt.GetCentury() )
{ {
CPPUNIT_ASSERT_EQUAL(dt.GetYear() % 100, CHECK( dt.GetYear() % 100 == dt2.GetYear() % 100);
dt2.GetYear() % 100);
dt2.SetYear(dt.GetYear()); dt2.SetYear(dt.GetYear());
} }
@@ -799,15 +825,15 @@ void DateTimeTestCase::TestTimeFormat()
wxFALLTHROUGH; wxFALLTHROUGH;
case CompareBoth: case CompareBoth:
CPPUNIT_ASSERT_EQUAL( dt, dt2 ); CHECK( dt == dt2 );
break; break;
case CompareDate: case CompareDate:
CPPUNIT_ASSERT( dt.IsSameDate(dt2) ); CHECK( dt.IsSameDate(dt2) );
break; break;
case CompareTime: case CompareTime:
CPPUNIT_ASSERT( dt.IsSameTime(dt2) ); CHECK( dt.IsSameTime(dt2) );
break; break;
case CompareNone: case CompareNone:
@@ -993,31 +1019,18 @@ void DateTimeTestCase::TestTimeSpanFormat()
void DateTimeTestCase::TestTimeTicks() void DateTimeTestCase::TestTimeTicks()
{ {
static const wxDateTime::TimeZone TZ_LOCAL(wxDateTime::Local);
static const wxDateTime::TimeZone TZ_TEST(wxDateTime::NZST);
// this offset is needed to make the test work in any time zone when we
// only have expected test results in UTC in testDates
static const long tzOffset = TZ_LOCAL.GetOffset() - TZ_TEST.GetOffset();
for ( size_t n = 0; n < WXSIZEOF(testDates); n++ ) for ( size_t n = 0; n < WXSIZEOF(testDates); n++ )
{ {
const Date& d = testDates[n]; const Date& d = testDates[n];
if ( d.gmticks == -1 ) if ( d.gmticks == -1 )
continue; continue;
wxDateTime dt = d.DT().MakeTimezone(TZ_TEST, true /* no DST */); const wxDateTime dt = d.DT().FromTimezone(wxDateTime::UTC);
// GetValue() returns internal UTC-based representation, we need to INFO("n=" << n);
// convert it to local TZ before comparing
time_t ticks = (dt.GetValue() / 1000).ToLong() + TZ_LOCAL.GetOffset();
if ( dt.IsDST() )
ticks += 3600;
CPPUNIT_ASSERT_EQUAL( d.gmticks, ticks + tzOffset );
dt = d.DT().FromTimezone(wxDateTime::UTC); time_t ticks = (dt.GetValue() / 1000).ToLong();
ticks = (dt.GetValue() / 1000).ToLong(); CHECK( d.gmticks == ticks );
CPPUNIT_ASSERT_EQUAL( d.gmticks, ticks );
} }
} }
@@ -1565,21 +1578,20 @@ void DateTimeTestCase::TestTranslateFromUnicodeFormat()
void DateTimeTestCase::TestConvToFromLocalTZ() void DateTimeTestCase::TestConvToFromLocalTZ()
{ {
// Choose a date when the DST is on in many time zones: in this case, // Choose a date when the DST is on in many time zones and verify that
// converting to/from local TZ does modify the object because it // converting from/to local time zone still doesn't modify time in this
// adds/subtracts DST to/from it, so to get the expected results we need to // case as this used to be broken.
// explicitly disable DST support in these functions.
wxDateTime dt(18, wxDateTime::Apr, 2017, 19); wxDateTime dt(18, wxDateTime::Apr, 2017, 19);
CPPUNIT_ASSERT_EQUAL( dt.FromTimezone(wxDateTime::Local, true), dt ); CHECK( dt.FromTimezone(wxDateTime::Local) == dt );
CPPUNIT_ASSERT_EQUAL( dt.ToTimezone(wxDateTime::Local, true), dt ); CHECK( dt.ToTimezone(wxDateTime::Local) == dt );
// And another one when it is off: in this case, there is no need to pass // For a date when the DST is not used, this always worked, but still
// "true" as "noDST" argument to these functions. // verify that it continues to.
dt = wxDateTime(18, wxDateTime::Jan, 2018, 19); dt = wxDateTime(18, wxDateTime::Jan, 2018, 19);
CPPUNIT_ASSERT_EQUAL( dt.FromTimezone(wxDateTime::Local), dt ); CHECK( dt.FromTimezone(wxDateTime::Local) == dt );
CPPUNIT_ASSERT_EQUAL( dt.ToTimezone(wxDateTime::Local), dt ); CHECK( dt.ToTimezone(wxDateTime::Local) == dt );
} }
static void DoTestSetFunctionsOnDST(const wxDateTime &orig) static void DoTestSetFunctionsOnDST(const wxDateTime &orig)
@@ -1638,4 +1650,27 @@ TEST_CASE("wxDateTime::SetOnDST", "[datetime][dst]")
} }
} }
// Tests random problems that used to appear in BST time zone during DST.
// This test is disabled by default as it only passes in BST time zone, due to
// the times hard-coded in it.
TEST_CASE("wxDateTime-BST-bugs", "[datetime][dst][BST][.]")
{
SECTION("bug-17220")
{
wxDateTime dt;
dt.Set(22, wxDateTime::Oct, 2015, 10, 10, 10, 10);
REQUIRE( dt.IsDST() );
CHECK( dt.GetTm().hour == 10 );
CHECK( dt.GetTm(wxDateTime::UTC).hour == 9 );
CHECK( dt.Format("%Y-%m-%d %H:%M:%S", wxDateTime::Local ) == "2015-10-22 10:10:10" );
CHECK( dt.Format("%Y-%m-%d %H:%M:%S", wxDateTime::UTC ) == "2015-10-22 09:10:10" );
dt.MakeFromUTC();
CHECK( dt.Format("%Y-%m-%d %H:%M:%S", wxDateTime::Local ) == "2015-10-22 11:10:10" );
CHECK( dt.Format("%Y-%m-%d %H:%M:%S", wxDateTime::UTC ) == "2015-10-22 10:10:10" );
}
}
#endif // wxUSE_DATETIME #endif // wxUSE_DATETIME

View File

@@ -11,6 +11,8 @@
#include "wx/datetime.h" #include "wx/datetime.h"
#include <ostream>
// need this to be able to use CPPUNIT_ASSERT_EQUAL with wxDateTime objects // need this to be able to use CPPUNIT_ASSERT_EQUAL with wxDateTime objects
inline std::ostream& operator<<(std::ostream& ostr, const wxDateTime& dt) inline std::ostream& operator<<(std::ostream& ostr, const wxDateTime& dt)
{ {

View File

@@ -4,6 +4,10 @@
#include "wx/wxprec.h" #include "wx/wxprec.h"
#include "wx/stopwatch.h" #include "wx/stopwatch.h"
#include "wx/evtloop.h" #include "wx/evtloop.h"
// This needs to be included before catch.hpp to be taken into account.
#include "testdate.h"
#include "wx/catch_cppunit.h" #include "wx/catch_cppunit.h"
// Custom test macro that is only defined when wxUIActionSimulator is available // Custom test macro that is only defined when wxUIActionSimulator is available