diff --git a/Foundation/include/Poco/LocalDateTime.h b/Foundation/include/Poco/LocalDateTime.h index 7a1f6799c..90d063bc0 100644 --- a/Foundation/include/Poco/LocalDateTime.h +++ b/Foundation/include/Poco/LocalDateTime.h @@ -64,6 +64,14 @@ class Foundation_API LocalDateTime /// class for better performance. The relational operators /// normalize the dates/times involved to UTC before carrying out /// the comparison. + /// + /// The time zone differential is based on the input date and + /// time and current time zone. A number of constructors accept + /// an explicit time zone differential parameter. These should + /// not be used since daylight savings time processing is impossible + /// since the time zone is unknown. Each of the constructors + /// accepting a tzd parameter have been marked as deprecated and + /// may be removed in a future revision. { public: LocalDateTime(); @@ -81,6 +89,7 @@ public: /// * millisecond is from 0 to 999. /// * microsecond is from 0 to 999. + //@ deprecated LocalDateTime(int tzd, int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond); /// Creates a DateTime for the given Gregorian date and time in the /// time zone denoted by the time zone differential in tzd. @@ -98,11 +107,13 @@ public: /// Creates a LocalDateTime from the UTC time given in dateTime, /// using the time zone differential of the current time zone. + //@ deprecated LocalDateTime(int tzd, const DateTime& dateTime); /// Creates a LocalDateTime from the UTC time given in dateTime, /// using the given time zone differential. Adjusts dateTime /// for the given time zone differential. + //@ deprecated LocalDateTime(int tzd, const DateTime& dateTime, bool adjust); /// Creates a LocalDateTime from the UTC time given in dateTime, /// using the given time zone differential. If adjust is true, @@ -111,6 +122,7 @@ public: LocalDateTime(double julianDay); /// Creates a LocalDateTime for the given Julian day in the local time zone. + //@ deprecated LocalDateTime(int tzd, double julianDay); /// Creates a LocalDateTime for the given Julian day in the time zone /// denoted by the time zone differential in tzd. @@ -141,6 +153,7 @@ public: /// * millisecond is from 0 to 999. /// * microsecond is from 0 to 999. + //@ deprecated LocalDateTime& assign(int tzd, int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds); /// Assigns a Gregorian local date and time in the time zone denoted by /// the time zone differential in tzd. @@ -154,6 +167,7 @@ public: /// * millisecond is from 0 to 999. /// * microsecond is from 0 to 999. + //@ deprecated LocalDateTime& assign(int tzd, double julianDay); /// Assigns a Julian day in the time zone denoted by the /// time zone differential in tzd. @@ -245,6 +259,13 @@ public: protected: LocalDateTime(Timestamp::UtcTimeVal utcTime, Timestamp::TimeDiff diff, int tzd); + void determineTzd (bool adjust = false); + /// Recalculate the tzd based on the _dateTime member based + /// on the current timezone using the Standard C runtime functions. + /// If adjust is true, then adjustForTzd() is called after the + /// differential is calculated. + void adjustForTzd(); + /// Adjust the _dateTime member based on the _tzd member. private: DateTime _dateTime; @@ -364,6 +385,12 @@ inline Timestamp::UtcTimeVal LocalDateTime::utcTime() const } +inline void LocalDateTime::adjustForTzd() +{ + _dateTime += Timespan(((Timestamp::TimeDiff) _tzd)*Timespan::SECONDS); +} + + inline void swap(LocalDateTime& d1, LocalDateTime& d2) { d1.swap(d2); diff --git a/Foundation/src/LocalDateTime.cpp b/Foundation/src/LocalDateTime.cpp index db10df842..ebaf1f176 100644 --- a/Foundation/src/LocalDateTime.cpp +++ b/Foundation/src/LocalDateTime.cpp @@ -38,22 +38,22 @@ #include "Poco/Timezone.h" #include "Poco/Timespan.h" #include +#include namespace Poco { -LocalDateTime::LocalDateTime(): - _tzd(Timezone::tzd()) +LocalDateTime::LocalDateTime() { - _dateTime += Timespan(((Timestamp::TimeDiff) _tzd)*Timespan::SECONDS); + determineTzd(true); } LocalDateTime::LocalDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond): - _dateTime(year, month, day, hour, minute, second, millisecond, microsecond), - _tzd(Timezone::tzd()) + _dateTime(year, month, day, hour, minute, second, millisecond, microsecond) { + determineTzd(); } @@ -65,10 +65,9 @@ LocalDateTime::LocalDateTime(int tzd, int year, int month, int day, int hour, in LocalDateTime::LocalDateTime(double julianDay): - _dateTime(julianDay), - _tzd(Timezone::tzd()) + _dateTime(julianDay) { - _dateTime += Timespan(((Timestamp::TimeDiff) _tzd)*Timespan::SECONDS); + determineTzd(true); } @@ -76,15 +75,14 @@ LocalDateTime::LocalDateTime(int tzd, double julianDay): _dateTime(julianDay), _tzd(tzd) { - _dateTime += Timespan(((Timestamp::TimeDiff) _tzd)*Timespan::SECONDS); + adjustForTzd(); } LocalDateTime::LocalDateTime(const DateTime& dateTime): - _dateTime(dateTime), - _tzd(Timezone::tzd()) + _dateTime(dateTime) { - _dateTime += Timespan(((Timestamp::TimeDiff) _tzd)*Timespan::SECONDS); + determineTzd(true); } @@ -92,7 +90,7 @@ LocalDateTime::LocalDateTime(int tzd, const DateTime& dateTime): _dateTime(dateTime), _tzd(tzd) { - _dateTime += Timespan(((Timestamp::TimeDiff) _tzd)*Timespan::SECONDS); + adjustForTzd(); } @@ -101,7 +99,7 @@ LocalDateTime::LocalDateTime(int tzd, const DateTime& dateTime, bool adjust): _tzd(tzd) { if (adjust) - _dateTime += Timespan(((Timestamp::TimeDiff) _tzd)*Timespan::SECONDS); + adjustForTzd(); } @@ -116,6 +114,7 @@ LocalDateTime::LocalDateTime(Timestamp::UtcTimeVal utcTime, Timestamp::TimeDiff _dateTime(utcTime, diff), _tzd(tzd) { + adjustForTzd(); } @@ -123,7 +122,7 @@ LocalDateTime::~LocalDateTime() { } - + LocalDateTime& LocalDateTime::operator = (const LocalDateTime& dateTime) { if (&dateTime != this) @@ -138,7 +137,10 @@ LocalDateTime& LocalDateTime::operator = (const LocalDateTime& dateTime) LocalDateTime& LocalDateTime::operator = (const Timestamp& timestamp) { if (timestamp != this->timestamp()) + { _dateTime = timestamp; + determineTzd(true); + } return *this; } @@ -146,17 +148,16 @@ LocalDateTime& LocalDateTime::operator = (const Timestamp& timestamp) LocalDateTime& LocalDateTime::operator = (double julianDay) { - _tzd = Timezone::tzd(); _dateTime = julianDay; - _dateTime += Timespan(((Timestamp::TimeDiff) _tzd)*Timespan::SECONDS); + determineTzd(true); return *this; } - + LocalDateTime& LocalDateTime::assign(int year, int month, int day, int hour, int minute, int second, int millisecond, int microseconds) { _dateTime.assign(year, month, day, hour, minute, second, millisecond, microseconds); - _tzd = Timezone::tzd(); + determineTzd(false); return *this; } @@ -173,7 +174,7 @@ LocalDateTime& LocalDateTime::assign(int tzd, double julianDay) { _tzd = tzd; _dateTime = julianDay; - _dateTime += Timespan(((Timestamp::TimeDiff) _tzd)*Timespan::SECONDS); + adjustForTzd(); return *this; } @@ -229,13 +230,19 @@ bool LocalDateTime::operator >= (const LocalDateTime& dateTime) const LocalDateTime LocalDateTime::operator + (const Timespan& span) const { - return LocalDateTime(_dateTime.utcTime(), span.totalMicroseconds(), _tzd); + // First calculate the adjusted UTC time, then calculate the + // locally adjusted time by constructing a new LocalDateTime. + DateTime tmp(utcTime(), span.totalMicroseconds()); + return LocalDateTime(tmp); } LocalDateTime LocalDateTime::operator - (const Timespan& span) const { - return LocalDateTime(_dateTime.utcTime(), -span.totalMicroseconds(), _tzd); + // First calculate the adjusted UTC time, then calculate the + // locally adjusted time by constructing a new LocalDateTime. + DateTime tmp(utcTime(), -span.totalMicroseconds()); + return LocalDateTime(tmp); } @@ -247,16 +254,44 @@ Timespan LocalDateTime::operator - (const LocalDateTime& dateTime) const LocalDateTime& LocalDateTime::operator += (const Timespan& span) { - _dateTime += span; + // Use the same trick as in operator+. Create a UTC time, adjust + // it for the span, and convert back to LocalDateTime. This will + // recalculate the tzd correctly in the case where the addition + // crosses a DST boundary. + *this = DateTime(utcTime(), span.totalMicroseconds()); return *this; } LocalDateTime& LocalDateTime::operator -= (const Timespan& span) { - _dateTime -= span; + // Use the same trick as in operator-. Create a UTC time, adjust + // it for the span, and convert back to LocalDateTime. This will + // recalculate the tzd correctly in the case where the subtraction + // crosses a DST boundary. + *this = DateTime(utcTime(), -span.totalMicroseconds()); return *this; } +void LocalDateTime::determineTzd (bool adjust) +{ + std::time_t local; + std::tm broken; + + broken.tm_year = (_dateTime.year() - 1900); + broken.tm_mon = (_dateTime.month() - 1); + broken.tm_mday = _dateTime.day(); + broken.tm_hour = _dateTime.hour(); + broken.tm_min = _dateTime.minute(); + broken.tm_sec = _dateTime.second(); + broken.tm_isdst = -1; + local = std::mktime(&broken); + + _tzd = (Timezone::utcOffset() + ((broken.tm_isdst == 1) ? 3600 : 0)); + if (adjust) + adjustForTzd(); +} + } // namespace Poco + diff --git a/Foundation/testsuite/src/LocalDateTimeTest.cpp b/Foundation/testsuite/src/LocalDateTimeTest.cpp index edd22432f..f5de40429 100644 --- a/Foundation/testsuite/src/LocalDateTimeTest.cpp +++ b/Foundation/testsuite/src/LocalDateTimeTest.cpp @@ -29,8 +29,11 @@ // DEALINGS IN THE SOFTWARE. // - #include "LocalDateTimeTest.h" + +#include +#include + #include "CppUnit/TestCaller.h" #include "CppUnit/TestSuite.h" #include "Poco/LocalDateTime.h" @@ -39,6 +42,8 @@ #include "Poco/Timespan.h" #include "Poco/Timezone.h" +#include "Poco/DateTimeFormat.h" +#include "Poco/DateTimeFormatter.h" using Poco::LocalDateTime; using Poco::DateTime; @@ -68,7 +73,9 @@ void LocalDateTimeTest::testGregorian1() assert (dt.second() == 0); assert (dt.millisecond() == 0); assert (dt.dayOfWeek() == 4); - assert (dt.tzd() == Timezone::tzd()); + // REMOVED: this fails when the current DST offset differs from + // the one on 1970-1-1 + //assert (dt.tzd() == Timezone::tzd()); assert (dt.julianDay() == 2440587.5); dt.assign(2001, 9, 9, 1, 46, 40); @@ -371,6 +378,93 @@ void LocalDateTimeTest::testSwap() } +void LocalDateTimeTest::testTimezone() +{ + std::time_t tINCREMENT = (30 * 24 * 60 * 60); // 30 days + Timespan tsINCREMENT(30*Timespan::DAYS); + LocalDateTime now; + std::time_t t = std::time(NULL); + std::tm then; + bool foundDST = false; + + then = *std::localtime(&t); + if (then.tm_isdst >= 0) + { + std::string tzNow, tzThen; + char tzBuf[12]; + int iterations = 0; + std::strftime(&tzBuf[0], sizeof(tzBuf), "%z", &then); + tzNow = tzThen = tzBuf; + while (iterations < 14) + { + // Add one month until the timezone changes or we roll + // over 13 months. + t += tINCREMENT; + then = *std::localtime(&t); + std::strftime(&tzBuf[0], sizeof(tzBuf), "%z", &then); + tzThen = tzBuf; + foundDST = (tzNow == tzThen); + if (foundDST) + { + break; + } + ++iterations; + } + if (foundDST) + { + // We found a timezone change that was induced by changing + // the month, so we crossed a DST boundary. Now we can + // actually do the test... + // + // Start with the current time and add 30 days for 13 + // iterations. Do this with both a LocalDateTime object and + // a ANSI C time_t. Then create a LocalDateTime based on the + // time_t and verify that the time_t calculated value is equal + // to the LocalDateTime value. The comparision operator + // verifies the _dateTime and _tzd members. + LocalDateTime dt2; + t = std::time(NULL); + for (iterations = 0; iterations < 14; ++iterations) + { + t += tINCREMENT; + dt2 += tsINCREMENT; + then = *std::localtime(&t); + + // This is the tricky part. We have to use the constructor + // from a UTC DateTime object that is constructed from the + // time_t. The LocalDateTime constructor with integer + // arguments, LocalDateTime(yr, mon, day, ...), assumes that + // the time is already adjusted with respect to the time + // zone. The DateTime conversion constructor, however, does + // not. So we want to construct from the UTC time. + // + // The second tricky part is that we want to use the + // sub-second information from the LocalDateTime object + // since ANSI C time routines are not sub-second accurate. + then = *std::gmtime(&t); + LocalDateTime calcd(DateTime((then.tm_year + 1900), + (then.tm_mon + 1), + then.tm_mday, + then.tm_hour, + then.tm_min, + then.tm_sec, + dt2.millisecond(), + dt2.microsecond())); + assert (dt2 == calcd); + } + } + } + + if (!foundDST) + { + std::cerr + << __FILE__ << ":" << __LINE__ + << " - failed to locate DST boundary, timezone test skipped." + << std::endl; + } +} + + void LocalDateTimeTest::setUp() { } @@ -395,6 +489,7 @@ CppUnit::Test* LocalDateTimeTest::suite() CppUnit_addTest(pSuite, LocalDateTimeTest, testArithmetics1); CppUnit_addTest(pSuite, LocalDateTimeTest, testArithmetics2); CppUnit_addTest(pSuite, LocalDateTimeTest, testSwap); + CppUnit_addTest(pSuite, LocalDateTimeTest, testTimezone); return pSuite; } diff --git a/Foundation/testsuite/src/LocalDateTimeTest.h b/Foundation/testsuite/src/LocalDateTimeTest.h index 687699fa2..f6827974c 100644 --- a/Foundation/testsuite/src/LocalDateTimeTest.h +++ b/Foundation/testsuite/src/LocalDateTimeTest.h @@ -56,6 +56,7 @@ public: void testArithmetics1(); void testArithmetics2(); void testSwap(); + void testTimezone(); void setUp(); void tearDown();