diff --git a/docs/changes.txt b/docs/changes.txt index 8fa6d43046..17df9bf973 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -562,6 +562,7 @@ Major new features in this release All: +- Adjust dates invalid due to DST consistently under all platforms in wxDateTime. - Allow using custom HTTP methods with wxHTTP (Kolya Kosenko). - Add wxFileName::SetPermissions() (Catalin Raceanu). - Fix build with wxUSE_FFILE==0 (jroemmler). diff --git a/interface/wx/datetime.h b/interface/wx/datetime.h index c181b9c75e..f7f1906921 100644 --- a/interface/wx/datetime.h +++ b/interface/wx/datetime.h @@ -389,11 +389,25 @@ public: /** Sets the date to be equal to Today() and the time from supplied parameters. + + See the full Set() overload for the remarks about DST. */ wxDateTime& Set(wxDateTime_t hour, wxDateTime_t minute = 0, wxDateTime_t second = 0, wxDateTime_t millisec = 0); /** Sets the date and time from the parameters. + + If the function parameters are invalid, e.g. @a month is February and + @a day is 30, the object is left in an invalid state, i.e. IsValid() + method will return @false. + + If the specified time moment is invalid due to DST, i.e. it falls into + the "missing" hour on the date on which the DST starts, a valid + wxDateTime object is still constructed but its hour component is moved + forward to ensure that it corresponds to a valid moment in the local + time zone. For example, in the CET time zone the DST started on + 2013-03-31T02:00:00 in 2013 and so setting the object to 2:30 at this + date actually sets the hour to 3, and not 2. */ wxDateTime& Set(wxDateTime_t day, Month month, int year = Inv_Year, wxDateTime_t hour = 0, diff --git a/src/common/datetime.cpp b/src/common/datetime.cpp index 48818ea622..669d0a3269 100644 --- a/src/common/datetime.cpp +++ b/src/common/datetime.cpp @@ -1129,10 +1129,37 @@ wxDateTime& wxDateTime::Set(const struct tm& tm) return *this; } - else + + // mktime() only adjusts tm_wday, tm_yday and tm_isdst fields normally, if + // it changed anything else, it must have performed the DST adjustment. But + // the trouble with this is that different implementations do it + // differently, e.g. GNU libc moves the time forward if the specified time + // is invalid in the local time zone, while MSVC CRT moves it backwards + // which is especially pernicious as it can change the date if the DST + // starts at midnight, as it does in some time zones (see #15419), and this + // is completely unexpected for the code working with dates only. + // + // So standardize on moving the time forwards to have consistent behaviour + // under all platforms and to avoid the problem above. + if ( tm2.tm_hour != tm.tm_hour ) { - return Set(timet); + tm2 = tm; + tm2.tm_hour++; + if ( tm2.tm_hour == 24 ) + { + // This shouldn't normally happen as the DST never starts at 23:00 + // but if it does, we have a problem as we need to adjust the day + // as well. However we stop here, i.e. we don't adjust the month + // (or the year) because mktime() is supposed to take care of this + // for us. + tm2.tm_hour = 0; + tm2.tm_mday++; + } + + timet = mktime(&tm2); } + + return Set(timet); } wxDateTime& wxDateTime::Set(wxDateTime_t hour, diff --git a/tests/datetime/datetimetest.cpp b/tests/datetime/datetimetest.cpp index 8eefcb862f..112e3291a6 100644 --- a/tests/datetime/datetimetest.cpp +++ b/tests/datetime/datetimetest.cpp @@ -1335,6 +1335,19 @@ void DateTimeTestCase::TestDSTBug() CPPUNIT_ASSERT_EQUAL(0, (int)dt2.GetSecond()); CPPUNIT_ASSERT_EQUAL(0, (int)dt2.GetMillisecond()); #endif // CHANGE_SYSTEM_DATE + + // Verify that setting the date to the beginning of the DST period moves it + // forward (as this date on its own would be invalid). The problem here is + // that our GetBeginDST() is far from being trustworthy, so just try a + // couple of dates for the common time zones and check that all of them are + // either unchanged or moved forward. + wxDateTime dtDST(10, wxDateTime::Mar, 2013, 2, 0, 0); + if ( dtDST.GetHour() != 2 ) + CPPUNIT_ASSERT_EQUAL( 3, dtDST.GetHour() ); + + dtDST = wxDateTime(31, wxDateTime::Mar, 2013, 2, 0, 0); + if ( dtDST.GetHour() != 2 ) + CPPUNIT_ASSERT_EQUAL( 3, dtDST.GetHour() ); } void DateTimeTestCase::TestDateOnly()