How do I calculate year fraction from datetime objects in Python? - python

I have two dates expressed as datetime objects and trying to calculate the time between the two dates in fractions of years (equivalent to the Excel yearfrac function).
Using relativedelta I can get the number of years, number of months and number of days between the dates, but not the fraction of years, and I can also subtract the dates to get the number of days, but dividing by 365.25 doesn't seem to get me the answer I would expect.
start_date = dt.datetime(2010, 12, 31)
end_date = dt.datetime(2019, 5, 16);
delta = relativedelta(end_date, start_date);
print(delta)
This is the output I get:
relativedelta(years=+8, months=+4, days=+16)
What I am looking for is: 8.38
If I use the following code:
delta = (end_date - start_date)/365.25
print(delta)
I get output of:
8 days, 8:56:10.841889

I just did the math of 8 + ((months * 30) + days)/365 = 8.3726. This is assuming 30 days in all months and 365 days in a year. Less precise but can fit on one line. What do you get when you divide by the number 365.25 that makes it wrong? How precise does it have to be?
If you need absolute precision, I would simply do:
from datetime import date
d0 = date(2010, 12, 31)
d1 = date(2019, 5, 16)
delta = d1 - d0
delta_fraction = delta.days/365.25
print(delta_fraction)
# Output: 8.72348
EDIT
This is a simplified solution assuming 365.25 days in a year (you can use 365.2425 days to be accurate up to the 400 year exception) to account for leap years. If you require it to match exactly excel's output, your probably better off writing a vba macro for excel

One thing to remember is that datetime.datetime objects have the subtraction operator defined, returning a datetime.timedelta object. And datetime.timedelta objects have the division operator defined, so you can get a ratio between a timedelta and either a common year (365 days) or the average length of all common and leap years (365 days, 5 hours, 49 minutes and 12 seconds).
import datetime as dt
start_date = dt.datetime(2010, 12, 31)
end_date = dt.datetime(2019, 5, 16)
print(round((end_date-start_date)/dt.timedelta(365,0,0,0),2)) #8.38
print(round((end_date-start_date)/dt.timedelta(365,5,49,12),2)) #8.38

Related

Python datetime countdown miscalculates remaining time

delta = datetime.now() - datetime(2020, 3, 24)
yeardif = round(delta.days/365)
yearRem = round(delta.days%365)
mondif = round(yearRem/30)
daydif = round(delta.days%365%30)
The code is for a countdown timer. Variable "delta" should output the time difference in days using
.days
but something about the equation is wrong because the output is incorrect.
If you are looking at time differences in terms of time remaining you wouldn‘t want to round your values but rather use floor() instead.
The code is for a countdown timer. Variable "delta" should output the time difference in days using .days
If you just want the time difference in days this is all you need:
delta = datetime.now() - datetime(2020, 3, 24)
days = delta.days + delta.seconds / 86400
This will give you 13.35866 days (13 whole days, plus 0.35866 days). This is preferable to dividing by 30 and 365 since this method accounts for differing days per month and leap years.

Date differences in Python in Years and Days (No Months)

I have two dates: 2005/04/10 and 2018/02/11.
The following code calculates the difference in terms of years, months and days:
from datetime import datetime
from dateutil.relativedelta import relativedelta
start_date = datetime(2005,4,10)
end_date = datetime(2018,2,11)
difference = relativedelta(end_date, start_date)
print(difference.years)
print(difference.months)
print(difference.days)
The output is:
12
10
1
12 years, 10 months and 1 day. The problem is that I am not interested in months I only want it in years and days. In my example, it should be 12 years and 306 days. I know that a good approximation is 10 months*30=300 days but the result is 301, not 306. I want to calculate precisely the days taking into account leap months and the difference in a number of days for each month. Is there any built-in method in Python to do that?
Look I already did my research on StackOverflow to find an answer but all the one related to my question do not answer to my problem.
After the code you already wrote, do this:
mid_date = datetime(start_date.year + difference.years, start_date.month, start_date.day)
print((end_date - mid_date).days)
That gives 307 for your example input.
The idea is to offset the original start_date by difference.years to avoid double-counting that portion of the difference.
Thanks to John's comment I wrote this code that I think satisfies my request:
from datetime import datetime
from dateutil.relativedelta import relativedelta
start_date = datetime(2005,4,10)
end_date = datetime(2018,2,11)
difference = relativedelta(end_date, start_date)
remaining_days = 0
if start_date != datetime(start_date.year, 1, 1):
end_first_year = datetime(start_date.year, 12, 31)
remaining_days += (end_first_year - start_date).days
if end_date != datetime(start_date.year, 1, 1):
begin_last_year = datetime(end_date.year, 1, 1)
remaining_days += (end_date - begin_last_year).days
print(difference.years)
print(remaining_days)
This gives exactly 306 remaining days.
Can anyone suggest a less verbose snippet of code?

Python timezone delta gives different dates when subtracted from date and datetime

I wrote a python function that gives me datetime or date in the past, with reference to current date.
def get_past_date(no_of_days, date_only=False):
"""Returns timezone aware Datetime object in past based on no_of_days provided"""
if date_only:
return timezone.datetime.today().date() - timezone.timedelta(no_of_days)
past = timezone.datetime.today() - timezone.timedelta(no_of_days)
return timezone.make_aware(past, timezone=pytz.timezone(settings.TIME_ZONE))
The problem is:
timezone.datetime.today().date() - timezone.timedelta(no_of_days)
and
timezone.datetime.today() - timezone.timedelta(no_of_days)
return different date for same input (no_of_days)
timezone.datetime.today() returns a date that is 1 day earlier than timezone.datetime.today().date()
timezone.datetime.today() - timezone.timedelta(6 * 365 / 12)
= datetime.datetime(2018, 1, 1, 21, 12, 43, 741750)
timezone.datetime.today().date() - timezone.timedelta(6 * 365 / 12)
= datetime.date(2018, 1, 2)
Am I missing something here?
The issue here is that you're subtracting an non-integral number of days (6 * 365 / 12 = 182.5). But the smallest unit of a date is a single day, and as described in the documentation, timedelta units smaller than the day are ignored when operating on dates.
So the date operation is equivalent to subtracting 182 days, while the datetime operation is subtracting 182.5 days.
An analogy would be:
184.0 - 182.5 = 1.5
int(184.0) - int(182.5) = 2

Calculating the date a fixed number of days from given date [duplicate]

This question already has answers here:
Days between two dates? [duplicate]
(4 answers)
Closed 5 years ago.
I need to design a code that will automatically generate the date X number of days from current date.
For that, I currently have a function that returns an epoch timestamp, which I then add the fixed number of days in seconds. However, I am currently stuck there and do not know how to convert the epoch timestamp into a Gregorian calendar date format (DD/MM/YYYY HH:MM). Displaying time is optional, can be rounded to the nearest day.
A way to do this without using an epoch timestamp and directly getting the current date in a readable format, printing it, and adding X days to it before generating the second date, is also fine, but I have no idea how that would work.
Any help/input would be much appreciated. Thanks in advance.
You can use timedelta.
import datetime as dt
x = 5 # Days offset.
now = dt.datetime.now()
>>> now + dt.timedelta(days=x)
datetime.datetime(2017, 12, 2, 21, 10, 19, 290884)
Or just using days:
today = dt.date.today()
>>> today + dt.timedelta(days=x)
datetime.date(2017, 12, 2)
Easy enough to convert back to a string using strftime:
>>> (today + dt.timedelta(days=x)).strftime('%Y-%m-%d')
'2017-12-02'
import datetime
x = 5
'''
Date_Time = datetime.datetime.now()#It wil give Date & time both
Time = datetime.datetime.now().time()#It will give Time Only
'''
Date = datetime.datetime.now().date()#It will give date only
print(Date + datetime.timedelta(days=x))#It will add days to current date
Output:
2017-12-03

dateutil.relativedelta - How to get duration in days?

I wish to get the total duration of a relativedelta in terms of days.
Expected:
dateutil.timedelta(1 month, 24 days) -> dateutil.timedelta(55 days)
What I tried:
dateutil.timedelta(1 month, 24 days).days -> 24 (WRONG)
Is there a simple way to do this? Thanks!
This one bothered me as well. There isn't a very clean way to get the span of time in a particular unit. This is partly because of the date-range dependency on units.
relativedelta() takes an argument for months. But when you think about how long a month is, the answer is "it depends". With that said, it's technically impossible to convert a relativedelta() directly to days, without knowing which days the delta lands on.
Here is what I ended up doing.
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
rd = relativedelta(years=3, months=7, days=19)
# I use 'now', but you may want to adjust your start and end range to a specific set of dates.
now = datetime.now()
# calculate the date difference from the relativedelta span
then = now - rd
# unlike normal timedelta 'then' is returned as a datetime
# subtracting two dates will give you a timedelta which contains the value you're looking for
diff = now - then
print diff.days
Simple date diff does it actually.
>>> from datetime import datetime
>>> (datetime(2017, 12, 1) - datetime(2018, 1, 1)).days
-31
To get positive number You can swap dates or use abs:
>>> abs((datetime(2017, 12, 1) - datetime(2018, 1, 1)).days)
31
In many situations you have a much restricted relativedelta, in my case, my relativedelta had only relative fields set (years, months, weeks, and days) and no other field. You may be able to get away with the simple method.
This is definitely off by few days, but it may be all you need
(365 * duration.years) + (30 * duration.months) + (duration.days)

Categories

Resources