Converting time_duration to DATE

889 Views Asked by At

I want to convert a time_duration to a DATE format, which is the number of days since 1899, 12, 30.

DATE date_from_duration(time_duration td)
{
   double days = td.hours()/24.+td.minutes()/(24.*60.)+td.seconds()/(24.*60.*60.);
   return days;
}

This code almost works but gives sometimes rounding errors, f.i the time_duration(1007645, 15, 0) should result in 2014-12-12 00:15:00, but is actually 2014-12-12 00:14:59.

The check of DATE is done with this method, stolen from here:

ptime pTime_from_DATE(double date)
{
    using boost::math::modf;

    static const ptime::date_type base_date(1899, Dec, 30);
    static const ptime base_time(base_date, ptime::time_duration_type(0,0,0));

    int dayOffset, hourOffset, minuteOffset, secondOffset;
    double fraction = fabs(modf(date, &dayOffset)) * 24; // fraction = hours
    fraction = modf(fraction, &hourOffset) * 60; // fraction = minutes
    fraction = modf(fraction, &minuteOffset) * 60; // fraction = seconds
    modf(fraction, &secondOffset);
    ptime t(base_time);
    t += ptime::time_duration_type(hourOffset, minuteOffset, secondOffset);
    t += ptime::date_duration_type(dayOffset);
    return t;
}

Any ideas how to correct this rounding issue efficiently?

2

There are 2 best solutions below

1
On BEST ANSWER

That kind of depends on your circumstances.

The general problem is that 1 / (24. * 60. * 60.) is not exactly representable as a binary float (because 86400 is not a power of two). The DATE you get is very nearly exact, but there will be a rounding error. Sometimes this means that it is very little more, sometimes very little less, but there's really not a lot you can do to make it more precise; it is as perfectly alright as you can get. That you see a discrepancy of a second is arguably a problem with your check, in that you stop looking at seconds -- if you check the milliseconds, you're likely to get 999, making the rounding error look a lot less extreme. This will continue for microseconds and possibly nanoseconds, depending on the resolution of time_duration.

So quite possibly there's nothing to do because the data is alright. If, however, you don't care about milliseconds and beyond and only want the seconds value to be stable in conversions back and forth, the simplest way to achieve that is to add an epsilon value:

DATE date_from_duration(time_duration td)
{
   double days =
       td.hours  () /  24.
     + td.minutes() / (24. * 60.)
     + td.seconds() / (24. * 60. * 60.)
     + 1e-8; // add roughly a millisecond
   return days;
}

This increases the overall rounding error but ensures that the error is in a "safe" direction, i.e., that converting it back to time_duration will give the same seconds() value and the visible changes will be at the milliseconds() level.

0
On

I might be missing some of the complexity, but it seems really simple to me:

Live On Coliru

#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
using DATE = double;

boost::posix_time::ptime pTime_from_DATE(double date)
{
    static const boost::posix_time::ptime::date_type base_date(1899, boost::gregorian::Dec, 30);
    return boost::posix_time::ptime(
            base_date, 
            boost::posix_time::milliseconds(date * 1000 * 60 * 60 * 24));
}

int main() {
    boost::posix_time::time_duration duration(1007645, 15, 0);

    DATE date = duration.total_milliseconds() / 1000.0 / 60 / 60 / 24;

    std::cout << date << ": " << pTime_from_DATE(date);
}

Prints

41985.2: 2014-Dec-12 05:15:00

See it Live On Coliru