views:

306

answers:

2

I'm using Boost's datetime library in my project. I was very happy when I discovered that it has time duration types for hours, days, months, years, etc, and they change their value based on what you're adding them to (i.e. adding 1 month advances the month part of the date, it doesn't just add 30 days or somesuch). I thought this property held for the days type, but I decided to test it before I put it into production...

local_date_time t1(date(2010, 3, 14), hours(1), easternTime, false); // 1am on DST transition date

{
    CPPUNIT_ASSERT_EQUAL(greg_year(2010), t1.local_time().date().year());
    CPPUNIT_ASSERT_EQUAL(greg_month(3), t1.local_time().date().month());
    CPPUNIT_ASSERT_EQUAL(greg_day(14), t1.local_time().date().day());
    CPPUNIT_ASSERT_EQUAL(1L, t1.local_time().time_of_day().hours());
    CPPUNIT_ASSERT_EQUAL(0L, t1.local_time().time_of_day().minutes());
    CPPUNIT_ASSERT_EQUAL(0L, t1.local_time().time_of_day().seconds());
}

t1 += days(1); // the time in EST should now be 1am on the 15th
{
    CPPUNIT_ASSERT_EQUAL(greg_year(2010), t1.local_time().date().year());
    CPPUNIT_ASSERT_EQUAL(greg_month(3), t1.local_time().date().month());
    CPPUNIT_ASSERT_EQUAL(greg_day(15), t1.local_time().date().day());
    CPPUNIT_ASSERT_EQUAL(1L, t1.local_time().time_of_day().hours()); // fails, returns 2
    CPPUNIT_ASSERT_EQUAL(0L, t1.local_time().time_of_day().minutes());
    CPPUNIT_ASSERT_EQUAL(0L, t1.local_time().time_of_day().seconds());
}

Above you'll see my CPPUNIT unit test. It fails at the indicated line with 2, which is what I would expect if days() merely added 24 hours, instead of 1 logical day (since the DST transition causes 2010-03-14 to be 23 hours long in EST).

Am I doing something wrong? Is this a bug? Did I just completely misunderstand the design goal of the library with respect to this sort of math?

A: 

Instead of adding the *date_duration* object days , you should create a boost::posix_time::time_duration object and add this to your local time , like so: boost::posix_time::time_duration td ( 24 , 0 , 0 , 0 ) ; //24 hours, 0 minutes, seconds, nano boost::local_time::local_date_time later = now + td ; //assuming that now is your starting //local_date_time , 2010-3-14 //later will now be a local date_time object that takes full account of DST !

programmingDoc
This doesn't solve the OP's problem. Replacing `t1 += days(1);` with `t1 += time_duration(24, 0, 0, 0);` gives the same result.
P-Nuts
+5  A: 

I think the problem is in the asker's conception of what a day is. He wants it to be a 'date' day here, rather than 24 hours, but that is not a reasonable thing to ask for.

If working in local time, one is bound to encounter peculiar effects. For example, what do you expect to happen if, in a timezone that puts clocks forward from 1am to 2am, if your local time 'add date day' calculation should set the (non existent) 1.30am on the relevant Sunday morning?

A time calculation has got to move forward 24 hours - it must operate on the underlying UTC time.

To make the 'jump one day' calculation as described, work with Boost's date type, and only add in the time-of-day as the final action.

The business of being able to advance a month is quite different, because, unlike a day, a calendar month has no specific meaning as a duration. And it causes troubles too: if you advance one calendar month from 31st January, and then go back one calendar month, what date do you end up with?

willw
Upvoted this. Core problem of OP is that he is expecting stable math on Local times, when no such thing exists. And the OP comment about 30-day months is funny, given that there are various financial domains that explicitly use 30/360 or variations to make certain things simpler.
sdg
Boost's Month and Year types behave as I described, and solve the problem willw and sdg are talking about: http://www.boost.org/doc/libs/1_40_0/doc/html/date_time/gregorian.html#additional_duration_types. I merely expected the same from the day type. Time calculations do not have to operate on the UTC time... I though this is why it provided both ptime (for UTC) and local_date_time (for local time) calculations. This doesn't mean you're wrong, but I'm not going to accept your answer until you square your own misconceptions with the Boost docs.
rmeador
Boost's Month and Year types are, to quote the docs, logical representations of spans of days - they are associated with the gregorian::date type. In your example, your type is local_date_time. Semantically, you are declaring an interest in the continuous flow of time, rather than one-day-at-a-time granularity of dates. So when you add one day to your date time variable, it gets converted from a date duration to a time duration - from 1 day into 24 hours. Everything else follows from this.I'm sorry, but I think the misconception is yours, and the classes WAD.
willw