8

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?

rmeador
  • 25,504
  • 18
  • 62
  • 103
  • this is a great example of why we should all write test harness code - comparing expected to actuals – slf Feb 22 '10 at 17:18

2 Answers2

5

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
  • 1,308
  • 13
  • 28
  • 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 Feb 16 '10 at 15:59
  • 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 Feb 16 '10 at 16:32
  • 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 Feb 22 '10 at 17:34
0

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 !

  • 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 Feb 11 '10 at 18:13