* Foundation/include/Poco/LocalDateTime.h

* Foundation/src/LocalDateTime.cpp
  - Fixed Sourceforge tracker 1800031.
    The time zone differential was not being maintained in many places.
    There are some minor functionality changes with this changeset however.
    * Assignment methods which do not take the full time information or a
      tzd as parameters will adjust the time stamp for DST according to
      the time specified. The only methods that do not fall under this
      constraint are:
      - constructors or assigns which accept year, month, day, etc.
      - constructors or assigns which accept a tzd
    * operators += and -= correctly adjust for DST boundary crossings.
    * operators + and - will return a LocalDateTime instance that has
      been adjust for DST boundary crossings.
* Foundation/testsuite/src/LocalDateTimeTest.h
* Foundation/testsuite/src/LocalDateTimeTest.cpp
  - Added testTimezone method.
  - Removed an assertion in testGregorian1() that would fail when the
    current DST offset differed from that of 1970-1-1.
This commit is contained in:
David Shawley 2007-10-01 03:09:05 +00:00
parent d9364e8f51
commit 01d998dcc2
4 changed files with 184 additions and 26 deletions

View File

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

View File

@ -38,22 +38,22 @@
#include "Poco/Timezone.h"
#include "Poco/Timespan.h"
#include <algorithm>
#include <ctime>
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

View File

@ -29,8 +29,11 @@
// DEALINGS IN THE SOFTWARE.
//
#include "LocalDateTimeTest.h"
#include <ctime>
#include <iostream>
#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;
}

View File

@ -56,6 +56,7 @@ public:
void testArithmetics1();
void testArithmetics2();
void testSwap();
void testTimezone();
void setUp();
void tearDown();