Pythonic difference between two dates in years? - python

Is there a more efficient way of doing this below? I want to have the difference in years between two dates as a single scalar. Any suggestions are welcome.
from datetime import datetime
start_date = datetime(2010,4,28,12,33)
end_date = datetime(2010,5,5,23,14)
difference = end_date - start_date
difference_in_years = (difference.days + difference.seconds/86400)/365.2425

If you want precise results, I recommend using the dateutil library.
from dateutil.relativedelta import relativedelta
difference_in_years = relativedelta(end_date, start_date).years
This is for complete years (e.g. a person's age). If you want fractional years, then add months, days, hours, ... up to the desired precision.

I use one of these to calculate person's age:
import datetime
dob = datetime.date(1980, 10, 10)
def age():
today = datetime.date.today()
years = today.year - dob.year
if today.month < dob.month or (today.month == dob.month and today.day < dob.day):
years -= 1
return years
def age2():
today = datetime.date.today()
this_year_birthday = datetime.date(today.year, dob.month, dob.day)
if this_year_birthday < today:
years = today.year - dob.year
else:
years = today.year - dob.year - 1
return years

Just do this:
from dateutil.relativedelta import relativedelta
myBirthday = datetime.datetime(1983,5,20,0,0,0,0)
now = datetime.datetime.now()
difference = relativedelta(now, myBirthday)
print("My years: "+str(difference.years))

More efficient? No, but more correct, probably. But it depends on how correct you want to be. Dates are not trivial things.
Years do not have a constant length. Do you want the difference in leap years or normal years? :-) As you calculate you are always going to get a slightly incorrect answer. And how long is a day in years? You say 1/365.2425. Well, yeah, averaged over a thousand years, yeah. But otherwise not.
So the question doesn't really make much sense.
To be correct you have to do this:
from datetime import datetime
from calendar import isleap
start_date = datetime(2005,4,28,12,33)
end_date = datetime(2010,5,5,23,14)
diffyears = end_date.year - start_date.year
difference = end_date - start_date.replace(end_date.year)
days_in_year = isleap(end_date.year) and 366 or 365
difference_in_years = diffyears + (difference.days + difference.seconds/86400.0)/days_in_year
In this case that's a difference of 0.0012322917425568528 years, or 0.662 days, considering that this is not a leap year.
(and then we are ignoring microseconds. Heh.)

To make sense of leap years, you are almost forced to break this into two parts: an integral number of years, and a fractional part. Both need to deal with leap years, but in different ways - the integral needs to deal with a starting date of February 29, and the fractional must deal with the differing number of days in a year. You want the fractional part to increment in equal amounts until it equals 1.0 at the next anniversary date, so it should be based on the number of days in the year after the end date.
Do you want your date range to include 1900 or 2100? Things get a little easier if you don't.
Edit: It has taken me a long time to reason this through. The basic problem is that calendar years are not a constant size, but you're coercing them to be constant by setting them to 1.0. Any solution you come up with is going to have anomalies because of this, and you're going to have to choose which anomalies you can live with. John Machin was right.
What's the difference between 2008-02-28 and 2009-02-28? Most people would agree that it should be exactly 1.0 years. How about the difference between 2008-03-01 and 2009-03-01? Again, most people would agree that it should be exactly 1.0 years. If you choose to represent a date as a year plus a fraction of a year based on the day, it is impossible to make both of these statements true. This is the case for your original code which assumed a day was 1/365.2425 of a year, or indeed for any code which assumes a constant fraction of a year per day, even if the size of a day accounts for the years which are leap years.
My assertion that you needed to break this down into integral years and fractional years was an attempt to get around this problem. If you treat each of the previous conditions as an integral year, all you have to do is decide on which fraction to assign to any number of days left over. The problem with this scheme is that you still can't make sense of (date2-date1)+date3, because the fraction can't be resolved back to a day with any consistency.
Thus I am proposing yet another encoding, based on each year containing 366 days whether it is a leap year or not. The anomalies will firstly be that there can't be a date which is exactly a year (or 2 or 3) from Feb. 29 - "Sorry Johnny, you don't get a birthday this year, there's no Feb. 29" isn't always acceptable. Second is that if you try to coerce such a number back to a date, you'll have to account for non-leap years and check for the special case of Feb. 29 and convert it, probably to Mar. 1.
from datetime import datetime
from datetime import timedelta
from calendar import isleap
size_of_day = 1. / 366.
size_of_second = size_of_day / (24. * 60. * 60.)
def date_as_float(dt):
days_from_jan1 = dt - datetime(dt.year, 1, 1)
if not isleap(dt.year) and days_from_jan1.days >= 31+28:
days_from_jan1 += timedelta(1)
return dt.year + days_from_jan1.days * size_of_day + days_from_jan1.seconds * size_of_second
start_date = datetime(2010,4,28,12,33)
end_date = datetime(2010,5,5,23,14)
difference_in_years = date_as_float(end_time) - date_as_float(start_time)
I'm not suggesting that this is the solution, because I don't think a perfect solution is possible. But it has some desirable properties:
The difference between any dates with the same month and day and time will be an exact number of years.
Adding a difference to another date will result in a value that can be converted back into a useful date.

I think what you're looking for is:
difference_in_years = difference.dt.days / 365.25

Here's a spin off of what Kostyantyn posted in his "age2" function. It's slightly shorter/cleaner and uses the traditional/colloquial meaning of an "age" or difference in years as well:
def ageInYears( d ):
today = datetime.date.today()
currentYrAnniversary = datetime.date( today.year, d.month, d.day )
return (today.year - d.year) - (1 if today < currentYrAnniversary else 0)

Since we're coming to the end of 2018...
from dateutil import parser
from dateutil.relativedelta import relativedelta
rip = [
["Tim Bergling\t\t", " 8 Sep 1989", "20 Apr 2018"], # Avicii Swedish musician
["Stephen Hillenburg\t", "21 Aug 1961", "26 Nov 2018"], # Creator of Spongebob
["Stephen Hawking\t\t", " 8 Jan 1942", "14 Mar 2018"], # Theoretical physicist
["Stan Lee\t\t", "28 Dec 1922", "12 Nov 2018"], # American comic book writer
["Stefán Karl Stefánsson\t", "10 Jul 1975", "21 Aug 2018"] # Robbie Rotten from LazyTown
]
for name,born,died in rip:
print("%s %s\t %s\t died at %i"%(name,born,died,relativedelta(parser.parse(died),parser.parse(born)).years))
output
Tim Bergling 8 Sep 1989 20 Apr 2018 died at 28
Stephen Hillenburg 21 Aug 1961 26 Nov 2018 died at 57
Stephen Hawking 8 Jan 1942 14 Mar 2018 died at 76
Stan Lee 28 Dec 1922 12 Nov 2018 died at 95
Stefán Karl Stefánsson 10 Jul 1975 21 Aug 2018 died at 43

If you mean efficient in terms of code space then no, that's about the most efficient way to do that.

Here's what I came up with, without using an external dependency:
def year_diff(d1, d2):
"""Returns the number of years between the dates as a positive integer."""
later = max(d1, d2)
earlier = min(d1, d2)
result = later.year - earlier.year
if later.month < earlier.month or (later.month == earlier.month and later.day < earlier.day):
result -= 1
return result

More robust function - calculates difference in years (age) and days:
def get_diff_in_years_and_days(from_date, to_date):
try:
from_in_this_year = date(to_date.year, from_date.month, from_date.day)
except:
from_in_this_year = date(to_date.year, from_date.month, from_date.day-1) # today is feb in leap year
if from_in_this_year <= to_date:
years = to_date.year - from_date.year
days = (to_date - from_in_this_year).days
else:
years = to_date.year - from_date.year - 1
try:
from_in_prev_year = date(to_date.year-1, from_date.month, from_date.day)
except:
from_in_prev_year = date(to_date.year-1, from_date.month, from_date.day-1) # today is feb in leap year
days = (to_date - from_in_prev_year).days
assert days>=0 and days<=365, days
assert years>=0, years
return years, days
some unit-tests:
self.assertEqual((0, 0), get_diff_in_years_and_days(date(2018,1, 1), date(2018,1, 1)))
self.assertEqual((1, 0), get_diff_in_years_and_days(date(2017,1, 1), date(2018,1, 1)))
self.assertEqual((1, 1), get_diff_in_years_and_days(date(2017,1, 1), date(2018,1, 2)))
self.assertEqual((2, 0), get_diff_in_years_and_days(date(2016,2,29), date(2018,2,28)))
self.assertEqual((2, 1), get_diff_in_years_and_days(date(2014,2,28), date(2016,2,29)))
self.assertEqual((1,364), get_diff_in_years_and_days(date(2014,2,28), date(2016, 2,27)))
self.assertEqual((3,30) , get_diff_in_years_and_days(date(2015,10,1), date(2018,10,31)))
self.assertEqual((10,30), get_diff_in_years_and_days(date(2010,10,1), date(2020,10,31)))
self.assertEqual((3,31) , get_diff_in_years_and_days(date(2015,10,1), date(2018,11, 1)))
self.assertEqual((2,364), get_diff_in_years_and_days(date(2015,10,1), date(2018, 9,30)))

Before install library :
choco upgrade Python -y
python pip install python-dateutil
In One line in Python in Cmder (windows) :
python -c "import datetime; from dateutil.relativedelta import relativedelta; myBirthday = datetime.datetime(2019,2,6,11,0,0,0); now = datetime.datetime.utcnow(); diff = relativedelta(now, myBirthday); print ("'My'+'" "'+'year'+'" "'+':'+'" "'+'%d''" "''and''" "''%d''" "''microseconds'" % (diff.years, diff.microseconds))"
In Batch escape percent with %% :
python -c "import datetime; from dateutil.relativedelta import relativedelta; myBirthday = datetime.datetime(2019,2,6,11,0,0,0); now = datetime.datetime.utcnow(); diff = relativedelta(now, myBirthday); print ("'My'+'" "'+'year'+'" "'+':'+'" "'+'%%d''" "''and''" "''%%d''" "''microseconds'" %% (diff.years, diff.microseconds))"

An update to BuvinJ solution which was an update to the Kostyantyn solution, age2().
Those functions fail if the date of birth is on leap day and the current year is not a leap year. The datetime library will throw an exception when trying to create 'currentYrAnniversary'.
Instead try,
from datetime import date
def ageInYears( d ):
today = datetime.date.today()
notQuiteYourBirthday = (today.month, today.day) < (born.month, born.day)
return (today.year - d.year) - notQuiteYourBirthday

If you have the DOB as a string already you can do this:
from datetime import datetime as dt
def get_age(dob_str):
now_str = dt.strftime(dt.utcnow(), '%Y-%m-%d')
return int(now_str[:4]) - int(dob_str[:4]) - int(dob_str[5:] > now_str[5:])
Or if you want to write some unit tests set now_str as a named parameter:
from datetime import datetime as dt
def get_age(dob_str, now_str=dt.strftime(dt.utcnow(), '%Y-%m-%d')):
return int(now_str[:4]) - int(dob_str[:4]) - int(dob_str[5:] > now_str[5:])

The simplest solution
from datetime import datetime
my_birthday = datetime(2000, 1, 1, 0, 0, 0, 0 )
now = datetime.now()
age = now.year - my_birthday.year
print(age)

Related

I need to subtract a base date from the current date plus 7 days; but not sure how to account for change in year?

PaymentDate = datetime.now().timetuple().tm_yday + 7
NS_Date_Text = driver.find_element(By.ID, "custrecord_ps_inv_date_val").text
basedate = datetime.strptime(NS_Date_Text, '%m/%d/%Y')
basedate1 = datetime.timetuple(basedate).tm_yday
DaysUntilPayment = PaymentDate - basedate1
This code works so far for the year of 2019. But, I am not sure how to account for 2020 or 2018
So, I am converting the current date to the number of day in the year (January 1 would be 1 and December 31 would be 365/366) then adding 7 to that number. This is the Payment Date.
Then, I am finding a date on the webpage(BaseDate) and converting that number to the day of the year.
Then subtracting those two numbers.
I am not sure how well it's going to work if the current date is: January 10, 2020 (10th day) + 7 = day 17. But the base date is 12/28/2019 (362nd day).
The number I will get would be 345 and that's way too far ahead, while I need the number(DaysUntilPayment) to be 20.
I hope I was able to explain this well. Please lmk if you have any questions!
You are making this way more complicated than it needs to be. Python's datetime.date() objects know how to handle deltas themselves; if you subtract two date() objects you get a timedelta() instance, which has a .days attribute.
Next, you can create your own timedelta() object to add 7 days to 'today':
from datetime import date, timedelta
# 7 days from today
payment_date = date.today() + timedelta(days=7)
# find base date on the webpage
ns_date_text = driver.find_element(By.ID, "custrecord_ps_inv_date_val").text
basedate = datetime.strptime(ns_date_text, '%m/%d/%Y').date()
# calculate the difference in days between these two dates
# date - date = timedelta, so take the .days attribute from that result
days_until_payment = (payment_date - basedate).days
Note that I used only the date component of the datetime.strptime() result. You can do all this with datetime objects too, but then you may have to worry about timezones and such, and it's just easier not to have to do that.
These operations take care of details such as handling years, and more importantly, handling leap years:
>>> from datetime import date, datetime, timedelta
>>> payment_date = date(2020, 2, 22) + timedelta(days=7)
>>> payment_date # this is February 29th, a leap day!
datetime.date(2020, 2, 29)
>>> basedate = datetime.strptime("12/31/2019", '%m/%d/%Y').date # last day of 2019
>>> payment_date - basedate
datetime.timedelta(days=60)
>>> (payment_date - basedate).days # February 29th is 60 days later
60
For further details, see the datetime.date documentation, which has a Supported operations section, with:
date2 = date1 + timedelta
date2 is timedelta.days days removed from date1
timedelta = date1 - date2
This is exact, and cannot overflow. timedelta.seconds and timedelta.microseconds are 0, and date2 + timedelta == date1 after.

Get number of days in a specific month that are in a date range

Haven't been able to find an answer to this problem. Basically what I'm trying to do is this:
Take a daterange, for example October 10th to November 25th. What is the best algorithm for determining how many of the days in the daterange are in October and how many are in November.
Something like this:
def daysInMonthFromDaterange(daterange, month):
# do stuff
return days
I know that this is pretty easy to implement, I'm just wondering if there's a very good or efficient algorithm.
Thanks
Borrowing the algorithm from this answer How do I divide a date range into months in Python?
, this might work. The inputs are in date format, but can be changed to date strings if preferred:
import datetime
begin = '2018-10-10'
end = '2018-11-25'
dt_start = datetime.datetime.strptime(begin, '%Y-%m-%d')
dt_end = datetime.datetime.strptime(end, '%Y-%m-%d')
one_day = datetime.timedelta(1)
start_dates = [dt_start]
end_dates = []
today = dt_start
while today <= dt_end:
#print(today)
tomorrow = today + one_day
if tomorrow.month != today.month:
start_dates.append(tomorrow)
end_dates.append(today)
today = tomorrow
end_dates.append(dt_end)
out_fmt = '%d %B %Y'
for start, end in zip(start_dates,end_dates):
diff = (end - start).days
print('{} to {}: {} days'.format(start.strftime(out_fmt), end.strftime(out_fmt), diff))
result:
10 October 2018 to 31 October 2018: 21 days
01 November 2018 to 25 November 2018: 24 days
The problem as stated may not have a unique answer. For example what should you get from daysInMonthFromDaterange('Feb 15 - Mar 15', 'February')? That will depend on the year!
But if you substitute actual days, I would suggest converting from dates to integer days, using the first of the month to the first of the next month as your definition of a month. This is now reduced to intersecting intervals of integers, which is much easier.
The assumption that the first of the month always happened deals with months of different lengths, variable length months, and even correctly handles the traditional placement of the switch from the Julian calendar to the Gregorian. See cal 1752 for that. (It will not handle that switch for all locations though. Should you be dealing with a library that does Romanian dates in 1919, you could have a problem...)
You can use the datetime module:
from datetime import datetime
start = datetime(2018,10,10)
end = datetime(2018,11,25)
print((end - start).days)
Something like this would work:
def daysInMonthFromDaterange(date1, date2, month):
return [x for x in range(date1.toordinal(), date2.toordinal()) if datetime.date.fromordinal(x).year == month.year and datetime.date.fromordinal(x).month == month.month]
print(len(days_in_month(date(2018,10,10), date(2018,11,25), date(2018,10,01))))
This just loops through all the days between date1 and date2, and returns it as part of a list if it matches the year and month of the third argument.

Convert two timestamp floats to a readable number of years, months, and days

I have two timestamps which are stored in float format:
tms1 = 1479081600.0
tms2 = 1482105600.0
Upon calculating the difference I get
tms2 - tms1
3024000.0
How do I go about displaying this time difference of 3024000 into a readable format in days, months or years? (The answer is 35 days between 14 Nov 2016 to 19 Dec 2016 using an online unix time difference calculator)
You can use (after importing datetime)
datetime.timedelta(seconds=3024000).days
which is
35
You should use timedelta as this is a time delta - a difference in time, not an absolute time. A full representation can also be obtained by coercing a timedelta to a string:
print(datetime.timedelta(seconds=3024000))
Gives the output:
35 days, 0:00:00
Note that you don't need an online calculator for anything - datetime comes with batteries included. You could do:
import datetime
date_format = "%d %b %Y"
start_date = datetime.datetime.strptime("14 Nov 2016", date_format)
end_date = datetime.datetime.strptime("19 Dec 2016", date_format)
print(start_date == datetime.datetime.fromtimestamp(1479081600))
print(start_date)
print(end_date.strftime("%d/%m/%Y"))
diff = end_date - start_date
print(diff)
print(diff.days)
which outputs:
True
2016-11-14 00:00:00
19/12/2016
35 days, 0:00:00
35
Note that diff here is identical to the original timedelta object, but is dynamically created from datetimes rather than statically constructed. I've also demonstrated the fact that you can build a datetime from a timestamp, should you wish, and I've also taken the liberty of demonstrating strftime and the like to illustrate the power of datetime. I highly recommend the datetime approach over an arithmetic approach as it's a lot more readable and extensible.
This answer is pretty lightweight, which isn't necessarily bad, as often you might not need any more functionality than it provides, but if the timedelta between two days is less than 24 hours, it will round down to 0 days, for example. It also can't handle timezones. If you need either of those, see the legendary Raymond's awesome answer
Just subtracting seconds doesn't help you know whether a day boundary has been crossed, so it is necessary to convert the timestamps to datetime objects before computing the days.
Add since the timezone can affect what the calendar day is for a UTC timestamp, you may need a tzinfo object as well.
Once the calendar dates are known, a little calendar math is needed to compute the difference in years, months, and days:
from datetime import timedelta, datetime
def time_diff(start_timestamp, end_timestamp, tz=None):
""" Return time difference in years, months, and days.
If *tz* is None, the timestamp is converted to the platform’s local date
and time. Otherwise, *tz* should be an instance of a *tzinfo* subclass.
"""
# Determine whether we're going forward or backward in time
ago = ''
if end_timestamp < start_timestamp:
ago = 'ago'
start_timestamp, end_timestamp = end_timestamp, start_timestamp
# Compute the calendar dates from the timestamps
d1 = datetime.fromtimestamp(start_timestamp, tz)
d2 = datetime.fromtimestamp(end_timestamp, tz)
# Advance d1 day-by-day until the day is at or above d2.day
days = 0
while d2.day < d1.day:
days += 1
d1 += timedelta(days=1)
# Now compute the day difference
days += d2.day - d1.day
# Compute the totals months difference and express in years and months
total_months = (d2.year * 12 + d2.month) - (d1.year * 12 + d1.month)
years, months = divmod(total_months, 12)
# format the output
plural = lambda n: '' if n == 1 else 's'
return '%d year%s, %d month%s, and %d day%s %s' % (
years, plural(years), months, plural(months), days, plural(days), ago)
Here is an example of how to use the function:
from datetime import tzinfo
class GMT1(tzinfo):
# Example tzinfo subclass taken from the Python docs
def utcoffset(self, dt):
return timedelta(hours=1)
def dst(self, dt):
return timedelta(0)
def tzname(self,dt):
return "Europe/Prague"
print(time_diff(1479081600.0, 1482105600.0, tz=GMT1()))
This outputs:
0 years, 1 month, and 5 days

Calculate Elapsed time in python in specific format

I am trying to write a program to determine the time and date corresponding to a elapsed number of seconds since 00: 00: 00 on 1 January 2016.
But i wanted my result in specific format.
i.e should be the corresponding hour (in military time), minute, second, day of the month, month name, year, and day of the week name (Sunday – Saturday).
for example,output should look like the following
23:59:32 2 January 2018 Tuesday
i tried to code this
import time
start = time.time()
#do something
end = time.time()
temp = end-start
print(temp)
hours = temp//3600
temp = temp - 3600*hours
minutes = temp//60
seconds = temp - 60*minutes
print('%d:%d:%d' %(hours,minutes,seconds))
But i could only get the hours, minutes and seconds part,
Is there any way i can get the month,year,and week name too ?
Also i'm trying to handle leap years too.since a leap year has 366 days with 29 days in February. Leap years are years that are evenly divisible by 4, with the exception of those evenly divisible by 100 but not 400.
To format a datetime you can use strftime().
import datetime
my_time = datetime.datetime(year=2018, month=1, day=2, hour=23, minute=59,second=32)
print (my_time.strftime("%X %-d %B %Y %A"))
If you want to change the format use this table for reference
You want to use the Datetime Module. Handling dates and times is trickier than it appears! That's why it's good that Python has datetime handling built-in. Datetime and timedelta objects account for leap years and leap seconds, and they handle operations like addition and subtraction 'intuitively'.
import datetime
def convert_seconds_since_jan_1(seconds):
JAN_1_2001 = datetime.datetime(year = 2001, month = 1, day = 1)
added_time = datetime.timedelta(seconds = seconds)
return (JAN_1_2001+added_time)
Getting the weekday is simply a matter of string formatting. Remember that weekdays are modulo 7!

How can I subtract two dates in Python?

So basically what I want to do is to subtract the date of birth from todays date in order to get a persons age, I've successfully done this, but I can only get it to show the persons age in days.
dateofbirth = 19981128
dateofbirth = list(str(dateofbirth))
now = datetime.date.today()
yr = dateofbirth[:4]
yr = ''.join(map(str, yr))
month = dateofbirth[4:6]
month = ''.join(map(str, month))
day = dateofbirth[6:8]
day = ''.join(map(str, day))
birth = datetime.date(int(yr), int(month), int(day))
age = now - birth
print(age)
In this case, age comes out as days, is there any way to get it as xx years xx months and xx days?
You can use strptime:
>>> import datetime
>>> datetime.datetime.strptime('19981128', '%Y%m%d')
datetime.datetime(1998, 11, 28, 0, 0)
>>> datetime.datetime.now() - datetime.datetime.strptime('19981128', '%Y%m%d')
datetime.timedelta(5823, 81486, 986088)
>>> print (datetime.datetime.now() - datetime.datetime.strptime('19981128', '%Y%m%d'))
5823 days, 22:38:18.039365
The result of subtracting two dates in Python is a timedelta object, which just represents a duration. It doesn't "remember" when it starts, and so it can't tell you how many months have elapsed.
Consider that the period from 1st January to 1st March is "two months", and the period from 1st March to 28th April is "1 month and 28 days", but in a non-leap year they're both the same duration, 59 days. Actually, daylight savings, but let's not make this any more complicated than it needs to be to make the point ;-)
There may be a third-party library that helps you, but as far as standard Python libraries are concerned, AFAIK you'll have to roll your sleeves up and do it yourself by finding the differences of the day/month/year components of the two dates in turn. Of course, the month and day differences might be negative numbers so you'll have to deal with those cases. Recall how you were taught to do subtraction in school, and be very careful when carrying numbers from the month column to the days column, to use the correct number of days for the relevant month.

Categories