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

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?

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...)

Related

Is there a python DateDelta? [duplicate]

I am using the datetime Python module. I am looking to calculate the date 6 months from the current date. Could someone give me a little help doing this?
The reason I want to generate a date 6 months from the current date is to produce a review date. If the user enters data into the system it will have a review date of 6 months from the date they entered the data.
I found this solution to be good. (This uses the python-dateutil extension)
from datetime import date
from dateutil.relativedelta import relativedelta
six_months = date.today() + relativedelta(months=+6)
The advantage of this approach is that it takes care of issues with 28, 30, 31 days etc. This becomes very useful in handling business rules and scenarios (say invoice generation etc.)
$ date(2010,12,31)+relativedelta(months=+1)
datetime.date(2011, 1, 31)
$ date(2010,12,31)+relativedelta(months=+2)
datetime.date(2011, 2, 28)
Well, that depends what you mean by 6 months from the current date.
Using natural months:
inc = 6
year = year + (month + inc - 1) // 12
month = (month + inc - 1) % 12 + 1
Using a banker's definition, 6*30:
date += datetime.timedelta(6 * 30)
With Python 3.x you can do it like this:
from datetime import datetime, timedelta
from dateutil.relativedelta import *
date = datetime.now()
print(date)
# 2018-09-24 13:24:04.007620
date = date + relativedelta(months=+6)
print(date)
# 2019-03-24 13:24:04.007620
but you will need to install python-dateutil module:
pip install python-dateutil
So, here is an example of the dateutil.relativedelta which I found useful for iterating through the past year, skipping a month each time to the present date:
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> today = datetime.datetime.today()
>>> month_count = 0
>>> while month_count < 12:
... day = today - relativedelta(months=month_count)
... print day
... month_count += 1
...
2010-07-07 10:51:45.187968
2010-06-07 10:51:45.187968
2010-05-07 10:51:45.187968
2010-04-07 10:51:45.187968
2010-03-07 10:51:45.187968
2010-02-07 10:51:45.187968
2010-01-07 10:51:45.187968
2009-12-07 10:51:45.187968
2009-11-07 10:51:45.187968
2009-10-07 10:51:45.187968
2009-09-07 10:51:45.187968
2009-08-07 10:51:45.187968
As with the other answers, you have to figure out what you actually mean by "6 months from now." If you mean "today's day of the month in the month six years in the future" then this would do:
datetime.datetime.now() + relativedelta(months=6)
For beginning of month to month calculation:
from datetime import timedelta
from dateutil.relativedelta import relativedelta
end_date = start_date + relativedelta(months=delta_period) + timedelta(days=-delta_period)
Python can use datautil package for that, Please see the example below
It's not Just limited to that, you can pass combination of days, Months and Years at the same time also.
import datetime
from dateutil.relativedelta import relativedelta
# subtract months
proc_dt = datetime.date(2021,8,31)
proc_dt_minus_3_months = proc_dt + relativedelta(months=-3)
print(proc_dt_minus_3_months)
# add months
proc_dt = datetime.date(2021,8,31)
proc_dt_plus_3_months = proc_dt + relativedelta(months=+3)
print(proc_dt_plus_3_months)
# subtract days:
proc_dt = datetime.date(2021,8,31)
proc_dt_minus_3_days = proc_dt + relativedelta(days=-3)
print(proc_dt_minus_3_days)
# add days days:
proc_dt = datetime.date(2021,8,31)
proc_dt_plus_3_days = proc_dt + relativedelta(days=+3)
print(proc_dt_plus_3_days)
# subtract years:
proc_dt = datetime.date(2021,8,31)
proc_dt_minus_3_years = proc_dt + relativedelta(years=-3)
print(proc_dt_minus_3_years)
# add years:
proc_dt = datetime.date(2021,8,31)
proc_dt_plus_3_years = proc_dt + relativedelta(years=+3)
print(proc_dt_plus_3_years)
Results:
2021-05-31
2021-11-30
2021-08-28
2021-09-03
2018-08-31
2024-08-31
This solution works correctly for December, which most of the answers on this page do not.
You need to first shift the months from a 1-based index (ie Jan = 1) to a 0-based index (ie Jan = 0) before using modulus ( % ) or integer division ( // ), otherwise November (11) plus 1 month gives you 12, which when finding the remainder ( 12 % 12 ) gives 0.
(And dont suggest "(month % 12) + 1" or Oct + 1 = december!)
def AddMonths(d,x):
newmonth = ((( d.month - 1) + x ) % 12 ) + 1
newyear = int(d.year + ((( d.month - 1) + x ) / 12 ))
return datetime.date( newyear, newmonth, d.day)
However ... This doesnt account for problem like Jan 31 + one month. So we go back to the OP - what do you mean by adding a month? One solution is to backtrack until you get to a valid day, given that most people would presume the last day of jan, plus one month, equals the last day of Feb.
This will work on negative numbers of months too.
Proof:
>>> import datetime
>>> AddMonths(datetime.datetime(2010,8,25),1)
datetime.date(2010, 9, 25)
>>> AddMonths(datetime.datetime(2010,8,25),4)
datetime.date(2010, 12, 25)
>>> AddMonths(datetime.datetime(2010,8,25),5)
datetime.date(2011, 1, 25)
>>> AddMonths(datetime.datetime(2010,8,25),13)
datetime.date(2011, 9, 25)
>>> AddMonths(datetime.datetime(2010,8,25),24)
datetime.date(2012, 8, 25)
>>> AddMonths(datetime.datetime(2010,8,25),-1)
datetime.date(2010, 7, 25)
>>> AddMonths(datetime.datetime(2010,8,25),0)
datetime.date(2010, 8, 25)
>>> AddMonths(datetime.datetime(2010,8,25),-12)
datetime.date(2009, 8, 25)
>>> AddMonths(datetime.datetime(2010,8,25),-8)
datetime.date(2009, 12, 25)
>>> AddMonths(datetime.datetime(2010,8,25),-7)
datetime.date(2010, 1, 25)>>>
What do you mean by "6 months"?
Is 2009-02-13 + 6 months == 2009-08-13? Or is it 2009-02-13 + 6*30 days?
import mx.DateTime as dt
#6 Months
dt.now()+dt.RelativeDateTime(months=6)
#result is '2009-08-13 16:28:00.84'
#6*30 days
dt.now()+dt.RelativeDateTime(days=30*6)
#result is '2009-08-12 16:30:03.35'
More info about mx.DateTime
This doesn't answer the specific question (using datetime only) but, given that others suggested the use of different modules, here there is a solution using pandas.
import datetime as dt
import pandas as pd
date = dt.date.today() - \
pd.offsets.DateOffset(months=6)
print(date)
2019-05-04 00:00:00
Which works as expected in leap years
date = dt.datetime(2019,8,29) - \
pd.offsets.DateOffset(months=6)
print(date)
2019-02-28 00:00:00
There's no direct way to do it with Python's datetime.
Check out the relativedelta type at python-dateutil. It allows you to specify a time delta in months.
I know this was for 6 months, however the answer shows in google for "adding months in python" if you are adding one month:
import calendar
date = datetime.date.today() //Or your date
datetime.timedelta(days=calendar.monthrange(date.year,date.month)[1])
this would count the days in the current month and add them to the current date, using 365/12 would ad 1/12 of a year can causes issues for short / long months if your iterating over the date.
Just use the timetuple method to extract the months, add your months and build a new dateobject. If there is a already existing method for this I do not know it.
import datetime
def in_the_future(months=1):
year, month, day = datetime.date.today().timetuple()[:3]
new_month = month + months
return datetime.date(year + (new_month / 12), (new_month % 12) or 12, day)
The API is a bit clumsy, but works as an example. Will also obviously not work on corner-cases like 2008-01-31 + 1 month. :)
Using Python standard libraries, i.e. without dateutil or others, and solving the 'February 31st' problem:
import datetime
import calendar
def add_months(date, months):
months_count = date.month + months
# Calculate the year
year = date.year + int(months_count / 12)
# Calculate the month
month = (months_count % 12)
if month == 0:
month = 12
# Calculate the day
day = date.day
last_day_of_month = calendar.monthrange(year, month)[1]
if day > last_day_of_month:
day = last_day_of_month
new_date = datetime.date(year, month, day)
return new_date
Testing:
>>>date = datetime.date(2018, 11, 30)
>>>print(date, add_months(date, 3))
(datetime.date(2018, 11, 30), datetime.date(2019, 2, 28))
>>>print(date, add_months(date, 14))
(datetime.date(2018, 12, 31), datetime.date(2020, 2, 29))
Dateutil package has implementation of such functionality. But be aware, that this will be naive, as others pointed already.
I have a better way to solve the 'February 31st' problem:
def add_months(start_date, months):
import calendar
year = start_date.year + (months / 12)
month = start_date.month + (months % 12)
day = start_date.day
if month > 12:
month = month % 12
year = year + 1
days_next = calendar.monthrange(year, month)[1]
if day > days_next:
day = days_next
return start_date.replace(year, month, day)
I think that it also works with negative numbers (to subtract months), but I haven't tested this very much.
A quick suggestion is Arrow
pip install arrow
>>> import arrow
>>> arrow.now().date()
datetime.date(2019, 6, 28)
>>> arrow.now().shift(months=6).date()
datetime.date(2019, 12, 28)
The QDate class of PyQt4 has an addmonths function.
>>>from PyQt4.QtCore import QDate
>>>dt = QDate(2009,12,31)
>>>required = dt.addMonths(6)
>>>required
PyQt4.QtCore.QDate(2010, 6, 30)
>>>required.toPyDate()
datetime.date(2010, 6, 30)
Modified the AddMonths() for use in Zope and handling invalid day numbers:
def AddMonths(d,x):
days_of_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
newmonth = ((( d.month() - 1) + x ) % 12 ) + 1
newyear = d.year() + ((( d.month() - 1) + x ) // 12 )
if d.day() > days_of_month[newmonth-1]:
newday = days_of_month[newmonth-1]
else:
newday = d.day()
return DateTime( newyear, newmonth, newday)
import time
def add_month(start_time, months):
ret = time.strptime(start_time, '%Y-%m-%d')
t = list(ret)
t[1] += months
if t[1] > 12:
t[0] += 1 + int(months / 12)
t[1] %= 12
return int(time.mktime(tuple(t)))
Modified Johannes Wei's answer in the case 1new_month = 121. This works perfectly for me. The months could be positive or negative.
def addMonth(d,months=1):
year, month, day = d.timetuple()[:3]
new_month = month + months
return datetime.date(year + ((new_month-1) / 12), (new_month-1) % 12 +1, day)
How about this? Not using another library (dateutil) or timedelta?
building on vartec's answer I did this and I believe it works:
import datetime
today = datetime.date.today()
six_months_from_today = datetime.date(today.year + (today.month + 6)/12, (today.month + 6) % 12, today.day)
I tried using timedelta, but because it is counting the days, 365/2 or 6*356/12 does not always translate to 6 months, but rather 182 days. e.g.
day = datetime.date(2015, 3, 10)
print day
>>> 2015-03-10
print (day + datetime.timedelta(6*365/12))
>>> 2015-09-08
I believe that we usually assume that 6 month's from a certain day will land on the same day of the month but 6 months later (i.e. 2015-03-10 --> 2015-09-10, Not 2015-09-08)
I hope you find this helpful.
import datetime
'''
Created on 2011-03-09
#author: tonydiep
'''
def add_business_months(start_date, months_to_add):
"""
Add months in the way business people think of months.
Jan 31, 2011 + 1 month = Feb 28, 2011 to business people
Method: Add the number of months, roll back the date until it becomes a valid date
"""
# determine year
years_change = months_to_add / 12
# determine if there is carryover from adding months
if (start_date.month + (months_to_add % 12) > 12 ):
years_change = years_change + 1
new_year = start_date.year + years_change
# determine month
work = months_to_add % 12
if 0 == work:
new_month = start_date.month
else:
new_month = (start_date.month + (work % 12)) % 12
if 0 == new_month:
new_month = 12
# determine day of the month
new_day = start_date.day
if(new_day in [31, 30, 29, 28]):
#user means end of the month
new_day = 31
new_date = None
while (None == new_date and 27 < new_day):
try:
new_date = start_date.replace(year=new_year, month=new_month, day=new_day)
except:
new_day = new_day - 1 #wind down until we get to a valid date
return new_date
if __name__ == '__main__':
#tests
dates = [datetime.date(2011, 1, 31),
datetime.date(2011, 2, 28),
datetime.date(2011, 3, 28),
datetime.date(2011, 4, 28),
datetime.date(2011, 5, 28),
datetime.date(2011, 6, 28),
datetime.date(2011, 7, 28),
datetime.date(2011, 8, 28),
datetime.date(2011, 9, 28),
datetime.date(2011, 10, 28),
datetime.date(2011, 11, 28),
datetime.date(2011, 12, 28),
]
months = range(1, 24)
for start_date in dates:
for m in months:
end_date = add_business_months(start_date, m)
print("%s\t%s\t%s" %(start_date, end_date, m))
Rework of an earlier answer by user417751. Maybe not so pythonic way, but it takes care of different month lengths and leap years. In this case 31 January 2012 + 1 month = 29 February 2012.
import datetime
import calendar
def add_mths(d, x):
newday = d.day
newmonth = (((d.month - 1) + x) % 12) + 1
newyear = d.year + (((d.month - 1) + x) // 12)
if newday > calendar.mdays[newmonth]:
newday = calendar.mdays[newmonth]
if newyear % 4 == 0 and newmonth == 2:
newday += 1
return datetime.date(newyear, newmonth, newday)
Yet another solution - hope someone will like it:
def add_months(d, months):
return d.replace(year=d.year+months//12).replace(month=(d.month+months)%12)
This solution doesn't work for days 29,30,31 for all cases, so more robust solution is needed (which is not so nice anymore :) ):
def add_months(d, months):
for i in range(4):
day = d.day - i
try:
return d.replace(day=day).replace(year=d.year+int(months)//12).replace(month=(d.month+int(months))%12)
except:
pass
raise Exception("should not happen")
From this answer, see parsedatetime. Code example follows. More details: unit test with many natural-language -> YYYY-MM-DD conversion examples, and apparent parsedatetime conversion challenges/bugs.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time, calendar
from datetime import date
# from https://github.com/bear/parsedatetime
import parsedatetime as pdt
def print_todays_date():
todays_day_of_week = calendar.day_name[date.today().weekday()]
print "today's date = " + todays_day_of_week + ', ' + \
time.strftime('%Y-%m-%d')
def convert_date(natural_language_date):
cal = pdt.Calendar()
(struct_time_date, success) = cal.parse(natural_language_date)
if success:
formal_date = time.strftime('%Y-%m-%d', struct_time_date)
else:
formal_date = '(conversion failed)'
print '{0:12s} -> {1:10s}'.format(natural_language_date, formal_date)
print_todays_date()
convert_date('6 months')
The above code generates the following from a MacOSX machine:
$ ./parsedatetime_simple.py
today's date = Wednesday, 2015-05-13
6 months -> 2015-11-13
$
Here's a example which allows the user to decide how to return a date where the day is greater than the number of days in the month.
def add_months(date, months, endOfMonthBehaviour='RoundUp'):
assert endOfMonthBehaviour in ['RoundDown', 'RoundIn', 'RoundOut', 'RoundUp'], \
'Unknown end of month behaviour'
year = date.year + (date.month + months - 1) / 12
month = (date.month + months - 1) % 12 + 1
day = date.day
last = monthrange(year, month)[1]
if day > last:
if endOfMonthBehaviour == 'RoundDown' or \
endOfMonthBehaviour == 'RoundOut' and months < 0 or \
endOfMonthBehaviour == 'RoundIn' and months > 0:
day = last
elif endOfMonthBehaviour == 'RoundUp' or \
endOfMonthBehaviour == 'RoundOut' and months > 0 or \
endOfMonthBehaviour == 'RoundIn' and months < 0:
# we don't need to worry about incrementing the year
# because there will never be a day in December > 31
month += 1
day = 1
return datetime.date(year, month, day)
>>> from calendar import monthrange
>>> import datetime
>>> add_months(datetime.datetime(2016, 1, 31), 1)
datetime.date(2016, 3, 1)
>>> add_months(datetime.datetime(2016, 1, 31), -2)
datetime.date(2015, 12, 1)
>>> add_months(datetime.datetime(2016, 1, 31), -2, 'RoundDown')
datetime.date(2015, 11, 30)
given that your datetime variable is called date:
date=datetime.datetime(year=date.year+int((date.month+6)/12),
month=(date.month+6)%13 + (1 if (date.month +
months>12) else 0), day=date.day)
General function to get next date after/before x months.
from datetime import date
def after_month(given_date, month):
yyyy = int(((given_date.year * 12 + given_date.month) + month)/12)
mm = int(((given_date.year * 12 + given_date.month) + month)%12)
if mm == 0:
yyyy -= 1
mm = 12
return given_date.replace(year=yyyy, month=mm)
if __name__ == "__main__":
today = date.today()
print(today)
for mm in [-12, -1, 0, 1, 2, 12, 20 ]:
next_date = after_month(today, mm)
print(next_date)
Im chiming in late, but
check out Ken Reitz Maya module,
https://github.com/kennethreitz/maya
something like this may help you, just change hours=1 to days=1 or years=1
>>> from maya import MayaInterval
# Create an event that is one hour long, starting now.
>>> event_start = maya.now()
>>> event_end = event_start.add(hours=1)
>>> event = MayaInterval(start=event_start, end=event_end)
The "python-dateutil" (external extension) is a good solution, but you can do it with build-in Python modules (datetime and datetime)
I made a short and simple code, to solve it (dealing with year, month and day)
(running: Python 3.8.2)
from datetime import datetime
from calendar import monthrange
# Time to increase (in months)
inc = 12
# Returns mod of the division for 12 (months)
month = ((datetime.now().month + inc) % 12) or 1
# Increase the division by 12 (months), if necessary (+ 12 months increase)
year = datetime.now().year + int((month + inc) / 12)
# (IF YOU DON'T NEED DAYS,CAN REMOVE THE BELOW CODE)
# Returns the same day in new month, or the maximum day of new month
day = min(datetime.now().day,monthrange(year, month)[1])
print("Year: {}, Month: {}, Day: {}".format(year, month, day))

Python filter on datetime range?

I have some data with start and end times in it (datetime format), and I want to calculate duration in hours based on a more restricted date range. But so far, I've been unsuccessful. (Still very much an amateur with python.)
Example: John Doe lived at 123 Main St from 1990-01-01T00:00:00.0 to 2016-12-31T23:59:59.0, but I want to know how many hours he lived there between October and December 2015.
The code below will successfully calculates hours, but I haven't been able to successfully filter the datetimes, so that I only get hours between the dates of Oct 1, 2015 and Dec 31, 2015.
from datetime import datetime
# The getValue function retrieves the datetime values from the table
time1str = getValue("START_DT_TM")
time2str = getValue("STOP_DT_TM")
# Intended date range
# periodstart = datetime.strptime("2015-10-01T00:00:00.0", '%Y-%m-%dT%H:%M:%S.%f')
# periodend = datetime.strptime("2015-12-31T23:59:59.0", '%Y-%m-%dT%H:%M:%S.%f')
time1 = datetime.strptime(time1str, '%Y-%m-%dT%H:%M:%S.%f')
time2 = datetime.strptime(time2str, '%Y-%m-%dT%H:%M:%S.%f')
timen = datetime.strptime(nowstr, '%Y-%m-%d %H:%M:%S.%f')
timef = (time2-timen).days*24
if timef > 0:
delta = timen - time1
seconds = delta.seconds/1440
days = delta.days*24
return str(days+seconds)
else:
delta = time2 - time1
seconds = delta.seconds/1440
days = delta.days*24
return str(days+seconds)
You need to do two things:
Determine if your range needs adjusting; if someone didn't move to the place until after October 1st, then you need to pick the later date that they did actually start living at the house. The same applies for the end date.
Then calculate the number of hours between the start and end date (adjusted as needed). Take into account that this may be 0!
I'm leaving out converting values to datetime objects here; you have that down correctly already. So given time1 and time2 being the start and end times a person lives at an address, and periodstart and periodend are the boundaries for which you want to know the number of hours:
# Adjust the start, pick the later value
periodstart = max(periodstart, time1)
# Adjust the end, pick the earlier value
periodend = min(periodend, time2)
duration = periodend - periodstart
hours = duration.total_seconds() // 3600
I took the timedelta.total_seconds() method rather than the .days attribute here to make sure you incorporate durations fractions of a day in your hour count.
Demo:
>>> from datetime import datetime, timedelta
>>> time1, time2 = datetime(1990, 1, 1, 0, 0), datetime(2016, 12, 31, 23, 59, 59)
>>> periodstart, periodend = datetime(2015, 10, 1, 0, 0), datetime(2015, 12, 31, 23, 59, 59)
>>> periodstart = max(periodstart, time1)
>>> periodend = min(periodend, time2)
>>> duration = periodend - periodstart
>>> duration
datetime.timedelta(91, 86399)
>>> duration.total_seconds() // 3600
2207.0
This still holds true when time1 or time2 fall within the period:
>>> from datetime import timedelta
>>> time1 = periodstart + timedelta(days=25) # moved in after the periodstart date
>>> periodstart = max(periodstart, time1)
>>> periodend = min(periodend, time2)
>>> duration = periodend - periodstart
>>> duration.total_seconds() // 3600
1607.0
>>> time1, time2 = datetime(1990, 1, 1, 0, 0), datetime(2016, 12, 31, 23, 59, 59)
>>> periodstart, periodend = datetime(2015, 10, 1, 0, 0), datetime(2015, 12, 31, 23, 59, 59)
>>> time2 = periodend - timedelta(days=42) # moved out before periodend
>>> periodstart = max(periodstart, time1)
>>> periodend = min(periodend, time2)
>>> duration = periodend - periodstart
>>> duration.total_seconds() // 3600
1199.0
I have this method that I have used for computing the working days (from Monday to Friday) in a period.
from datetime import date, timedelta
def working_days(fromdate, todate):
daygenerator = (fromdate + timedelta(x + 1) for x in xrange((todate - fromdate).days))
return sum(1 for day in daygenerator if day.weekday() < 5)
print working_days(date(2018,01,10), date(2018,11,09))
Maybe changing the method in this way could work and you have just to multiply *24 the result obtaining the hours:
def working_days(fromdate, todate):
daygenerator = (fromdate + timedelta(x + 1) for x in xrange((todate - fromdate).days))
return sum(1 for day in daygenerator)
I'm still a beginner, so probably someone will help you in a better way.

How to get total hours and minutes for timedelta in Python

How do I return or turn a timedelta, which is bigger than 24 hours, into an object containing the total hours and minutes (for example, 26:30) instead of "1 day, 2:30"?
You can use total_seconds() to compute the number of seconds. This can then be turned into minutes or hours:
>>> datetime.timedelta(days=3).total_seconds()
259200.0
Completing the answer of Visser using timedelta.total_seconds() :
import datetime
duration = datetime.timedelta(days = 2, hours = 4, minutes = 15)
Once we got a timedelta object:
totsec = duration.total_seconds()
h = totsec//3600
m = (totsec%3600) // 60
sec =(totsec%3600)%60 #just for reference
print "%d:%d" %(h,m)
Out: 52:15
offset_seconds = timedelta.total_seconds()
if offset_seconds < 0:
sign = "-"
else:
sign = "+"
# we will prepend the sign while formatting
if offset_seconds < 0:
offset_seconds *= -1
offset_hours = offset_seconds / 3600.0
offset_minutes = (offset_hours % 1) * 60
offset = "{:02d}:{:02d}".format(int(offset_hours), int(offset_minutes))
offset = sign + offset

Find day difference between two datetimes (excluding weekend days) in Python? [duplicate]

This question already has answers here:
Number of days between 2 dates, excluding weekends
(22 answers)
Closed 7 years ago.
The same problem to Find day difference between two dates (excluding weekend days) but it is for javascript. How to do that in Python?
Try it with scikits.timeseries:
import scikits.timeseries as ts
import datetime
a = datetime.datetime(2011,8,1)
b = datetime.datetime(2011,8,29)
diff_business_days = ts.Date('B', b) - ts.Date('B', a)
# returns 20
or with dateutil:
import datetime
from dateutil import rrule
a = datetime.datetime(2011,8,1)
b = datetime.datetime(2011,8,29)
diff_business_days = len(list(rrule.rrule(rrule.DAILY,
dtstart=a,
until=b - datetime.timedelta(days=1),
byweekday=(rrule.MO, rrule.TU, rrule.WE, rrule.TH, rrule.FR))))
scikits.timeseries look depricated : http://pytseries.sourceforge.net/
With pandas instead someone can do :
import pandas as pd
a = datetime.datetime(2015, 10, 1)
b = datetime.datetime(2015, 10, 29)
diff_calendar_days = pd.date_range(a, b).size
diff_business_days = pd.bdate_range(a, b).size
Not sure that this is the best one solution but it works for me:
from datetime import datetime, timedelta
startDate = datetime(2011, 7, 7)
endDate = datetime(2011, 10, 7)
dayDelta = timedelta(days=1)
diff = 0
while startDate != endDate:
if startDate.weekday() not in [5,6]:
diff += 1
startDate += dayDelta
Here's a O(1) complexity class solution which uses only built-in Python libraries.
It has constant performance regardless of time interval length and doesn't care about argument order.
#
# by default, the last date is not inclusive
#
def workdaycount(first, second, inc = 0):
if first == second:
return 0
import math
if first > second:
first, second = second, first
if inc:
from datetime import timedelta
second += timedelta(days=1)
interval = (second - first).days
weekspan = int(math.ceil(interval / 7.0))
if interval % 7 == 0:
return interval - weekspan * 2
else:
wdf = first.weekday()
if (wdf < 6) and ((interval + wdf) // 7 == weekspan):
modifier = 0
elif (wdf == 6) or ((interval + wdf + 1) // 7 == weekspan):
modifier = 1
else:
modifier = 2
return interval - (2 * weekspan - modifier)
#
# sample usage
#
print workdaycount(date(2011, 8, 15), date(2011, 8, 22)) # returns 5
print workdaycount(date(2011, 8, 15), date(2011, 8, 22), 1) # last date inclusive, returns 6

How do I calculate the date six months from the current date using the datetime Python module?

I am using the datetime Python module. I am looking to calculate the date 6 months from the current date. Could someone give me a little help doing this?
The reason I want to generate a date 6 months from the current date is to produce a review date. If the user enters data into the system it will have a review date of 6 months from the date they entered the data.
I found this solution to be good. (This uses the python-dateutil extension)
from datetime import date
from dateutil.relativedelta import relativedelta
six_months = date.today() + relativedelta(months=+6)
The advantage of this approach is that it takes care of issues with 28, 30, 31 days etc. This becomes very useful in handling business rules and scenarios (say invoice generation etc.)
$ date(2010,12,31)+relativedelta(months=+1)
datetime.date(2011, 1, 31)
$ date(2010,12,31)+relativedelta(months=+2)
datetime.date(2011, 2, 28)
Well, that depends what you mean by 6 months from the current date.
Using natural months:
inc = 6
year = year + (month + inc - 1) // 12
month = (month + inc - 1) % 12 + 1
Using a banker's definition, 6*30:
date += datetime.timedelta(6 * 30)
With Python 3.x you can do it like this:
from datetime import datetime, timedelta
from dateutil.relativedelta import *
date = datetime.now()
print(date)
# 2018-09-24 13:24:04.007620
date = date + relativedelta(months=+6)
print(date)
# 2019-03-24 13:24:04.007620
but you will need to install python-dateutil module:
pip install python-dateutil
So, here is an example of the dateutil.relativedelta which I found useful for iterating through the past year, skipping a month each time to the present date:
>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> today = datetime.datetime.today()
>>> month_count = 0
>>> while month_count < 12:
... day = today - relativedelta(months=month_count)
... print day
... month_count += 1
...
2010-07-07 10:51:45.187968
2010-06-07 10:51:45.187968
2010-05-07 10:51:45.187968
2010-04-07 10:51:45.187968
2010-03-07 10:51:45.187968
2010-02-07 10:51:45.187968
2010-01-07 10:51:45.187968
2009-12-07 10:51:45.187968
2009-11-07 10:51:45.187968
2009-10-07 10:51:45.187968
2009-09-07 10:51:45.187968
2009-08-07 10:51:45.187968
As with the other answers, you have to figure out what you actually mean by "6 months from now." If you mean "today's day of the month in the month six years in the future" then this would do:
datetime.datetime.now() + relativedelta(months=6)
For beginning of month to month calculation:
from datetime import timedelta
from dateutil.relativedelta import relativedelta
end_date = start_date + relativedelta(months=delta_period) + timedelta(days=-delta_period)
Python can use datautil package for that, Please see the example below
It's not Just limited to that, you can pass combination of days, Months and Years at the same time also.
import datetime
from dateutil.relativedelta import relativedelta
# subtract months
proc_dt = datetime.date(2021,8,31)
proc_dt_minus_3_months = proc_dt + relativedelta(months=-3)
print(proc_dt_minus_3_months)
# add months
proc_dt = datetime.date(2021,8,31)
proc_dt_plus_3_months = proc_dt + relativedelta(months=+3)
print(proc_dt_plus_3_months)
# subtract days:
proc_dt = datetime.date(2021,8,31)
proc_dt_minus_3_days = proc_dt + relativedelta(days=-3)
print(proc_dt_minus_3_days)
# add days days:
proc_dt = datetime.date(2021,8,31)
proc_dt_plus_3_days = proc_dt + relativedelta(days=+3)
print(proc_dt_plus_3_days)
# subtract years:
proc_dt = datetime.date(2021,8,31)
proc_dt_minus_3_years = proc_dt + relativedelta(years=-3)
print(proc_dt_minus_3_years)
# add years:
proc_dt = datetime.date(2021,8,31)
proc_dt_plus_3_years = proc_dt + relativedelta(years=+3)
print(proc_dt_plus_3_years)
Results:
2021-05-31
2021-11-30
2021-08-28
2021-09-03
2018-08-31
2024-08-31
This solution works correctly for December, which most of the answers on this page do not.
You need to first shift the months from a 1-based index (ie Jan = 1) to a 0-based index (ie Jan = 0) before using modulus ( % ) or integer division ( // ), otherwise November (11) plus 1 month gives you 12, which when finding the remainder ( 12 % 12 ) gives 0.
(And dont suggest "(month % 12) + 1" or Oct + 1 = december!)
def AddMonths(d,x):
newmonth = ((( d.month - 1) + x ) % 12 ) + 1
newyear = int(d.year + ((( d.month - 1) + x ) / 12 ))
return datetime.date( newyear, newmonth, d.day)
However ... This doesnt account for problem like Jan 31 + one month. So we go back to the OP - what do you mean by adding a month? One solution is to backtrack until you get to a valid day, given that most people would presume the last day of jan, plus one month, equals the last day of Feb.
This will work on negative numbers of months too.
Proof:
>>> import datetime
>>> AddMonths(datetime.datetime(2010,8,25),1)
datetime.date(2010, 9, 25)
>>> AddMonths(datetime.datetime(2010,8,25),4)
datetime.date(2010, 12, 25)
>>> AddMonths(datetime.datetime(2010,8,25),5)
datetime.date(2011, 1, 25)
>>> AddMonths(datetime.datetime(2010,8,25),13)
datetime.date(2011, 9, 25)
>>> AddMonths(datetime.datetime(2010,8,25),24)
datetime.date(2012, 8, 25)
>>> AddMonths(datetime.datetime(2010,8,25),-1)
datetime.date(2010, 7, 25)
>>> AddMonths(datetime.datetime(2010,8,25),0)
datetime.date(2010, 8, 25)
>>> AddMonths(datetime.datetime(2010,8,25),-12)
datetime.date(2009, 8, 25)
>>> AddMonths(datetime.datetime(2010,8,25),-8)
datetime.date(2009, 12, 25)
>>> AddMonths(datetime.datetime(2010,8,25),-7)
datetime.date(2010, 1, 25)>>>
What do you mean by "6 months"?
Is 2009-02-13 + 6 months == 2009-08-13? Or is it 2009-02-13 + 6*30 days?
import mx.DateTime as dt
#6 Months
dt.now()+dt.RelativeDateTime(months=6)
#result is '2009-08-13 16:28:00.84'
#6*30 days
dt.now()+dt.RelativeDateTime(days=30*6)
#result is '2009-08-12 16:30:03.35'
More info about mx.DateTime
This doesn't answer the specific question (using datetime only) but, given that others suggested the use of different modules, here there is a solution using pandas.
import datetime as dt
import pandas as pd
date = dt.date.today() - \
pd.offsets.DateOffset(months=6)
print(date)
2019-05-04 00:00:00
Which works as expected in leap years
date = dt.datetime(2019,8,29) - \
pd.offsets.DateOffset(months=6)
print(date)
2019-02-28 00:00:00
There's no direct way to do it with Python's datetime.
Check out the relativedelta type at python-dateutil. It allows you to specify a time delta in months.
I know this was for 6 months, however the answer shows in google for "adding months in python" if you are adding one month:
import calendar
date = datetime.date.today() //Or your date
datetime.timedelta(days=calendar.monthrange(date.year,date.month)[1])
this would count the days in the current month and add them to the current date, using 365/12 would ad 1/12 of a year can causes issues for short / long months if your iterating over the date.
Just use the timetuple method to extract the months, add your months and build a new dateobject. If there is a already existing method for this I do not know it.
import datetime
def in_the_future(months=1):
year, month, day = datetime.date.today().timetuple()[:3]
new_month = month + months
return datetime.date(year + (new_month / 12), (new_month % 12) or 12, day)
The API is a bit clumsy, but works as an example. Will also obviously not work on corner-cases like 2008-01-31 + 1 month. :)
Using Python standard libraries, i.e. without dateutil or others, and solving the 'February 31st' problem:
import datetime
import calendar
def add_months(date, months):
months_count = date.month + months
# Calculate the year
year = date.year + int(months_count / 12)
# Calculate the month
month = (months_count % 12)
if month == 0:
month = 12
# Calculate the day
day = date.day
last_day_of_month = calendar.monthrange(year, month)[1]
if day > last_day_of_month:
day = last_day_of_month
new_date = datetime.date(year, month, day)
return new_date
Testing:
>>>date = datetime.date(2018, 11, 30)
>>>print(date, add_months(date, 3))
(datetime.date(2018, 11, 30), datetime.date(2019, 2, 28))
>>>print(date, add_months(date, 14))
(datetime.date(2018, 12, 31), datetime.date(2020, 2, 29))
Dateutil package has implementation of such functionality. But be aware, that this will be naive, as others pointed already.
I have a better way to solve the 'February 31st' problem:
def add_months(start_date, months):
import calendar
year = start_date.year + (months / 12)
month = start_date.month + (months % 12)
day = start_date.day
if month > 12:
month = month % 12
year = year + 1
days_next = calendar.monthrange(year, month)[1]
if day > days_next:
day = days_next
return start_date.replace(year, month, day)
I think that it also works with negative numbers (to subtract months), but I haven't tested this very much.
A quick suggestion is Arrow
pip install arrow
>>> import arrow
>>> arrow.now().date()
datetime.date(2019, 6, 28)
>>> arrow.now().shift(months=6).date()
datetime.date(2019, 12, 28)
The QDate class of PyQt4 has an addmonths function.
>>>from PyQt4.QtCore import QDate
>>>dt = QDate(2009,12,31)
>>>required = dt.addMonths(6)
>>>required
PyQt4.QtCore.QDate(2010, 6, 30)
>>>required.toPyDate()
datetime.date(2010, 6, 30)
Modified the AddMonths() for use in Zope and handling invalid day numbers:
def AddMonths(d,x):
days_of_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
newmonth = ((( d.month() - 1) + x ) % 12 ) + 1
newyear = d.year() + ((( d.month() - 1) + x ) // 12 )
if d.day() > days_of_month[newmonth-1]:
newday = days_of_month[newmonth-1]
else:
newday = d.day()
return DateTime( newyear, newmonth, newday)
import time
def add_month(start_time, months):
ret = time.strptime(start_time, '%Y-%m-%d')
t = list(ret)
t[1] += months
if t[1] > 12:
t[0] += 1 + int(months / 12)
t[1] %= 12
return int(time.mktime(tuple(t)))
Modified Johannes Wei's answer in the case 1new_month = 121. This works perfectly for me. The months could be positive or negative.
def addMonth(d,months=1):
year, month, day = d.timetuple()[:3]
new_month = month + months
return datetime.date(year + ((new_month-1) / 12), (new_month-1) % 12 +1, day)
How about this? Not using another library (dateutil) or timedelta?
building on vartec's answer I did this and I believe it works:
import datetime
today = datetime.date.today()
six_months_from_today = datetime.date(today.year + (today.month + 6)/12, (today.month + 6) % 12, today.day)
I tried using timedelta, but because it is counting the days, 365/2 or 6*356/12 does not always translate to 6 months, but rather 182 days. e.g.
day = datetime.date(2015, 3, 10)
print day
>>> 2015-03-10
print (day + datetime.timedelta(6*365/12))
>>> 2015-09-08
I believe that we usually assume that 6 month's from a certain day will land on the same day of the month but 6 months later (i.e. 2015-03-10 --> 2015-09-10, Not 2015-09-08)
I hope you find this helpful.
import datetime
'''
Created on 2011-03-09
#author: tonydiep
'''
def add_business_months(start_date, months_to_add):
"""
Add months in the way business people think of months.
Jan 31, 2011 + 1 month = Feb 28, 2011 to business people
Method: Add the number of months, roll back the date until it becomes a valid date
"""
# determine year
years_change = months_to_add / 12
# determine if there is carryover from adding months
if (start_date.month + (months_to_add % 12) > 12 ):
years_change = years_change + 1
new_year = start_date.year + years_change
# determine month
work = months_to_add % 12
if 0 == work:
new_month = start_date.month
else:
new_month = (start_date.month + (work % 12)) % 12
if 0 == new_month:
new_month = 12
# determine day of the month
new_day = start_date.day
if(new_day in [31, 30, 29, 28]):
#user means end of the month
new_day = 31
new_date = None
while (None == new_date and 27 < new_day):
try:
new_date = start_date.replace(year=new_year, month=new_month, day=new_day)
except:
new_day = new_day - 1 #wind down until we get to a valid date
return new_date
if __name__ == '__main__':
#tests
dates = [datetime.date(2011, 1, 31),
datetime.date(2011, 2, 28),
datetime.date(2011, 3, 28),
datetime.date(2011, 4, 28),
datetime.date(2011, 5, 28),
datetime.date(2011, 6, 28),
datetime.date(2011, 7, 28),
datetime.date(2011, 8, 28),
datetime.date(2011, 9, 28),
datetime.date(2011, 10, 28),
datetime.date(2011, 11, 28),
datetime.date(2011, 12, 28),
]
months = range(1, 24)
for start_date in dates:
for m in months:
end_date = add_business_months(start_date, m)
print("%s\t%s\t%s" %(start_date, end_date, m))
Rework of an earlier answer by user417751. Maybe not so pythonic way, but it takes care of different month lengths and leap years. In this case 31 January 2012 + 1 month = 29 February 2012.
import datetime
import calendar
def add_mths(d, x):
newday = d.day
newmonth = (((d.month - 1) + x) % 12) + 1
newyear = d.year + (((d.month - 1) + x) // 12)
if newday > calendar.mdays[newmonth]:
newday = calendar.mdays[newmonth]
if newyear % 4 == 0 and newmonth == 2:
newday += 1
return datetime.date(newyear, newmonth, newday)
Yet another solution - hope someone will like it:
def add_months(d, months):
return d.replace(year=d.year+months//12).replace(month=(d.month+months)%12)
This solution doesn't work for days 29,30,31 for all cases, so more robust solution is needed (which is not so nice anymore :) ):
def add_months(d, months):
for i in range(4):
day = d.day - i
try:
return d.replace(day=day).replace(year=d.year+int(months)//12).replace(month=(d.month+int(months))%12)
except:
pass
raise Exception("should not happen")
From this answer, see parsedatetime. Code example follows. More details: unit test with many natural-language -> YYYY-MM-DD conversion examples, and apparent parsedatetime conversion challenges/bugs.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time, calendar
from datetime import date
# from https://github.com/bear/parsedatetime
import parsedatetime as pdt
def print_todays_date():
todays_day_of_week = calendar.day_name[date.today().weekday()]
print "today's date = " + todays_day_of_week + ', ' + \
time.strftime('%Y-%m-%d')
def convert_date(natural_language_date):
cal = pdt.Calendar()
(struct_time_date, success) = cal.parse(natural_language_date)
if success:
formal_date = time.strftime('%Y-%m-%d', struct_time_date)
else:
formal_date = '(conversion failed)'
print '{0:12s} -> {1:10s}'.format(natural_language_date, formal_date)
print_todays_date()
convert_date('6 months')
The above code generates the following from a MacOSX machine:
$ ./parsedatetime_simple.py
today's date = Wednesday, 2015-05-13
6 months -> 2015-11-13
$
Here's a example which allows the user to decide how to return a date where the day is greater than the number of days in the month.
def add_months(date, months, endOfMonthBehaviour='RoundUp'):
assert endOfMonthBehaviour in ['RoundDown', 'RoundIn', 'RoundOut', 'RoundUp'], \
'Unknown end of month behaviour'
year = date.year + (date.month + months - 1) / 12
month = (date.month + months - 1) % 12 + 1
day = date.day
last = monthrange(year, month)[1]
if day > last:
if endOfMonthBehaviour == 'RoundDown' or \
endOfMonthBehaviour == 'RoundOut' and months < 0 or \
endOfMonthBehaviour == 'RoundIn' and months > 0:
day = last
elif endOfMonthBehaviour == 'RoundUp' or \
endOfMonthBehaviour == 'RoundOut' and months > 0 or \
endOfMonthBehaviour == 'RoundIn' and months < 0:
# we don't need to worry about incrementing the year
# because there will never be a day in December > 31
month += 1
day = 1
return datetime.date(year, month, day)
>>> from calendar import monthrange
>>> import datetime
>>> add_months(datetime.datetime(2016, 1, 31), 1)
datetime.date(2016, 3, 1)
>>> add_months(datetime.datetime(2016, 1, 31), -2)
datetime.date(2015, 12, 1)
>>> add_months(datetime.datetime(2016, 1, 31), -2, 'RoundDown')
datetime.date(2015, 11, 30)
given that your datetime variable is called date:
date=datetime.datetime(year=date.year+int((date.month+6)/12),
month=(date.month+6)%13 + (1 if (date.month +
months>12) else 0), day=date.day)
General function to get next date after/before x months.
from datetime import date
def after_month(given_date, month):
yyyy = int(((given_date.year * 12 + given_date.month) + month)/12)
mm = int(((given_date.year * 12 + given_date.month) + month)%12)
if mm == 0:
yyyy -= 1
mm = 12
return given_date.replace(year=yyyy, month=mm)
if __name__ == "__main__":
today = date.today()
print(today)
for mm in [-12, -1, 0, 1, 2, 12, 20 ]:
next_date = after_month(today, mm)
print(next_date)
Im chiming in late, but
check out Ken Reitz Maya module,
https://github.com/kennethreitz/maya
something like this may help you, just change hours=1 to days=1 or years=1
>>> from maya import MayaInterval
# Create an event that is one hour long, starting now.
>>> event_start = maya.now()
>>> event_end = event_start.add(hours=1)
>>> event = MayaInterval(start=event_start, end=event_end)
The "python-dateutil" (external extension) is a good solution, but you can do it with build-in Python modules (datetime and datetime)
I made a short and simple code, to solve it (dealing with year, month and day)
(running: Python 3.8.2)
from datetime import datetime
from calendar import monthrange
# Time to increase (in months)
inc = 12
# Returns mod of the division for 12 (months)
month = ((datetime.now().month + inc) % 12) or 1
# Increase the division by 12 (months), if necessary (+ 12 months increase)
year = datetime.now().year + int((month + inc) / 12)
# (IF YOU DON'T NEED DAYS,CAN REMOVE THE BELOW CODE)
# Returns the same day in new month, or the maximum day of new month
day = min(datetime.now().day,monthrange(year, month)[1])
print("Year: {}, Month: {}, Day: {}".format(year, month, day))

Categories

Resources