Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python datetime to decimal year: one day off, where is the bug?

I implemented a datetime.datetime to decimal year (year fraction) converter based on a previous answer. I am not getting the expected results but I can't find the bug.

from datetime import datetime, timedelta

def decimal_year_to_datetime(decimal_year_float):
    year = int(decimal_year_float)
    remain = decimal_year_float - year
    base = datetime(year, 1, 1)
    whole_year_time_delta = (base.replace(year=base.year + 1) - base)
    fractional_seconds = whole_year_time_delta.total_seconds() * remain
    our_time_delta = timedelta(seconds=fractional_seconds)
    result = base + our_time_delta
    return result

def test_conversion():
    year = 2013
    month = 1
    day = 1
    hour = 2
    minute = 16
    second = 48
    date = datetime(year=year, month=month, day=day)
    fraction_of_the_day = (hour + (minute + second / 60.0) / 60.0) / 24.
    days_in_year = (date.replace(year=date.year + 1) - date).days
    dec_yr = year + (date.timetuple().tm_yday + 
                     fraction_of_the_day) / float(days_in_year)

    expect_date = datetime(year=year, month=month, day=day, 
                              hour=hour, minute=minute, second=second)

    got_date = decimal_year_to_datetime(dec_yr)

    assert(got_date == expect_date)

if __name__ == '__main__':
    test_conversion()

I seem to be a day (an and a fraction of a second) off with my conversion. But I cannot see the bug.

Have I missed something obvious?

like image 739
Laurence Billingham Avatar asked Dec 01 '25 06:12

Laurence Billingham


1 Answers

You're out by one day (a classic fencepost error!) because, although we consider 1st January to to be day one of the year, in computing terms that's the zeroth day. The simplest fix is to add in a - 1, but your approach seems generally quite complex; I would do it as follows:

def dt_to_dec(dt):
    """Convert a datetime to decimal year."""
    year_start = datetime(dt.year, 1, 1)
    year_end = year_start.replace(year=dt.year+1)
    return dt.year + ((dt - year_start).total_seconds() /  # seconds so far
        float((year_end - year_start).total_seconds()))  # seconds in year

In use:

>>> dec = dt_to_dec(datetime(2013, 1, 1, 2, 16, 48))
>>> dec
2013.0002602739726
>>> decimal_year_to_datetime(dec)
datetime.datetime(2013, 1, 1, 2, 16, 47, 999999)

(given floating point accuracy, that's as close as you're likely to get...)

like image 54
jonrsharpe Avatar answered Dec 03 '25 19:12

jonrsharpe



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!