I have a date as an int like so for example: 201805 I'd like to have a way where I can give the start date and it'll return back the next date, in this example: 201806. Currently, I have this solution:
def incrementDate(startdate):
try:
newdate = dt.datetime.strptime(str(startdate + 1), "%Y%m")
return str(newdate.year) + newdate.strftime('%m')
except:
newdate = dt.datetime.strptime(str(startdate), "%Y%m")
return int(str(newdate.year + 1) + "01")
Is this a good way of going about this or is there a simple better way?
How about something using relativedelta:
Your first step will be to install the package dateutil:
pip install dateutil
Then you will be able to use it:
import datetime
from dateutil.relativedelta import relativedelta
print(datetime.datetime.strptime(str(201805), "%Y%m") + relativedelta(months=1))
Or a string:
print((datetime.datetime.strptime(str(201805), "%Y%m") + relativedelta(months=1)).strftime("%Y%m"))
Note: relativedelta comes from a separate package called 'dateutil' (here for the details). It includes notably a nice way to add month / days / year .. without creating a custom function.
You can use datetime.timedelta:
from datetime import datetime, timedelta
def incrementDate(startdate):
cur_date = datetime.strptime(str(startdate), '%Y%m')
next_date = cur_date + timedelta(days=32)
return datetime.strftime(next_date, '%Y%m')
print(incrementDate(201806)) # 201807
print(incrementDate(201812)) # 201901
Just for fun, if your input is really an integer that represents a 4 digit year folowed by a 2 digit month, then you can increment the month without any string or datetime conversions.
d = 201812
y, m = d // 100, d % 100
if m == 12:
y += 1
m = 1
else:
m +=1
d = y * 100 + m
print(d)
# 201806
The replace() method fron the datetime objects allows you to update a parameter from the datetime object:
import datetime
today = datetime.datetime.now()
current_month = today.month
year = today.year
# Check for december
if current_month < 12:
next_month = current_month + 1
else:
next_month = 1
year += 1
today= today.replace(month = next_month, year = year )
Reference
To put in in your function:
import datetime
def increment_date(startdate):
date = datetime.strptime(str(startdate), '%Y%m')
current_month = date .month
year = date .year
# Check for december
if current_month < 12:
next_month = current_month + 1
else:
next_month = 1
year += 1
return date .replace(month = next_month, year = year )
Related
How can I convert any date to just number of days? This is what I tried:
import datetime
import calendar
def leap_day_counter(yr):
leap_days = 0
# since 1582 11 days are missing
if yr >= 1582:
leap_days += 11
for specific_year in range(1, yr):
if calendar.isleap(specific_year):
leap_days += 1
return leap_days
def month_to_day(yr, mth):
all_days = 0
for specific_month in range(1, mth+1):
days_in_month = calendar.monthrange(yr, specific_month)
all_days += days_in_month[1]
return all_days
date = datetime.datetime.now()
days_passed = ((date.year * 365) + leap_day_counter(date.year)) + month_to_day(date.year, date.month) + date.day
print(days_passed)
I got 737 158 days but according to https://www.timeanddate.com/date/durationresult.html I should have 736 755 days. Do I miss something? Is there easier way to do this?
This helps
from datetime import date
d0 = date(2000, 1, 01)
d1 = date.today()
delta = d1 - d0
print delta.days
Are the amount of days in a year correct for you?
01/01/0001 - 01/01/2018 has 736,696, you say there is 737,060. This is roughly 1 year too many.
(date.year - 1) * 365
After fixing the above, we should check if 01/01/0001 - 01/02/2018 works.
The website says 736,727, where you say 736,754. Which is about the entire month of February too many.
for specific_month in range(1, mth)
You have one too many leap years.
for specific_year in range(1, yr)
You can also simplify this code to:
def leap_day_counter(y):
y -= 1
return y//4 - y//100 + y//400
This is now the same as datetime.datetime.now().toordinal().
The number of days between two dates can be calculated as below: For more see here. Hope this may help
>>>enddate = "2018/03/12" +" 23:59"
>>>enddate = datetime.strptime(enddate, "%Y/%m/%d %H:%M")
>>>(enddate-datetime.now()).days
12
Update:edit
>>>import datetime
>>>checkdate = datetime.datetime.strptime("0001-01-01", "%Y-%m-%d")
>>>days = (datetime.datetime.now()-checkdate).days
>>>days
736757
2 days difference because start days and end date are excluded.
I use the current_month variable to query data but here's the catch- if the day in the month is later then the 15th, I want to set the current month to be the following month. So the current month for 4/16/2016 should be 5/1/2016. I've got code that works but it doesn't feel pythonic. Suggestions would be appreciated.
month = datetime.datetime.now().strftime("%m")
year = datetime.datetime.now().strftime("%Y")
day = datetime.datetime.now().strftime("%d")
#Check if day in the month is past 15th if so set current month
if int(day) > 15:
if int(month) < 9: # check if the month in 1-9 if so pad leading zero
x = int(month)+1
current_month = year+"-0"+str(x)+"-01"
if int(month) == 9: # check if the month in 1-9 if so pad leading zero
x = int(month)+1
current_month = year+"-"+str(x)+"-01"
elif int(month) == 12: # check if the month is Dec if so roll to the next year and set month to Jan
month = "01"
y = int(year)+1
current_month = str(y)+"-"+month+"-01"
else:
x = int(month)+1 # just add one to the month if months are 10 or 11
current_month = year+"-"+str(x)+"-01"
else:
current_month = year+"-"+month+"-01" #prior to the 15'th so just use normal year, month and day
# Get today's date/time
today = datetime.datetime.now()
# add 16 days if after the 15th
if today.day > 15:
today += datetime.timedelta(16)
# Format that date w/ day being 1
current_month = today.strftime("%Y-%m-01")
An alternative to Scott's approach (which works perfectly well and you should probably accept it) is to use a dateutil.relativedelta:
from datetime import datetime
from dateutil.relativedelta import relativedelta
def next_month_after_day(dt, trans_day=15):
rd = relativedelta(months=(dt.day > trans_day), day=1)
return dt + rd
dt1 = datetime(1996, 2, 3)
dt2 = datetime(1996, 2, 15)
dt3 = datetime(1996, 2, 16)
dt4 = datetime(1996, 12, 18)
fmt = "%Y-%m-%d"
for x in (dt1, dt2, dt3, dt4):
dt_before = x
dt_after = next_month_after_day(x)
print('{} -> {}'.format(dt_before.strftime(fmt), dt_after.strftime(fmt)))
The result is:
1996-02-03 -> 1996-02-01
1996-02-15 -> 1996-02-01
1996-02-16 -> 1996-03-01
1996-12-18 -> 1997-01-01
Thanks to Scott for pointing out why my original approach was needlessly complicated (and wrong).
I'm struggling with writing a pythonic, clean generator method that, given a date period, like ['2014-01-15', '2015-02-03], will give me this:
['2014-01-15', '2014-01-31']
['2014-02-01', '2014-02-28']
...
['2015-02-01', '2015-02-03']
This is what I came up with:
from datetime import datetime
import calendar
def genDatePeriods(startDate, endDate, format='%Y-%m-%d'):
dt1 = datetime.strptime(startDate, format)
dt2 = datetime.strptime(endDate, format)
for year in range(dt1.year, dt2.year + 1):
for month in range(1, 13):
day0 = dt1.day if month == dt1.month and year == dt1.year else 1
day1 = dt2.day if month == dt2.month and year == dt2.year else calendar.monthrange(year, month)[1]
if (year == dt1.year and month < dt1.month) or (year == dt2.year and month > dt2.month):
continue
else:
d0 = (year, month, day0)
d1 = (year, month, day1)
yield [datetime(*d).strftime(format) for d in [d0, d1]]
It works, however I feel like there is a more pythonic/tidy/efficient way to do this. Any ideas?
The following is much more concise, using datetime.date() objects to find the first day of the next month each time, until you reach the end date:
from datetime import datetime, timedelta
def genDatePeriods(startDate, endDate, format='%Y-%m-%d'):
curr = datetime.strptime(startDate, format).date()
end = datetime.strptime(endDate, format).date()
while curr <= end:
# first day of the next month, using modular arithmetic
next_month = curr.replace(
month=curr.month % 12 + 1, year=curr.year + curr.month // 12,
day=1)
curr_formatted = curr.strftime(format)
# end date is next month's first day, minus one day,
# or the given endDate, whichever comes first
end_formatted = min(next_month - timedelta(days=1), end).strftime(format)
yield [curr_formatted, end_formatted]
curr = next_month
Demo:
>>> for res in genDatePeriods('2014-01-15', '2015-02-03'):
... print res
...
['2014-01-15', '2014-01-31']
['2014-02-01', '2014-02-28']
['2014-03-01', '2014-03-31']
['2014-04-01', '2014-04-30']
['2014-05-01', '2014-05-31']
['2014-06-01', '2014-06-30']
['2014-07-01', '2014-07-31']
['2014-08-01', '2014-08-31']
['2014-09-01', '2014-09-30']
['2014-10-01', '2014-10-31']
['2014-11-01', '2014-11-30']
['2014-12-01', '2014-12-31']
['2015-01-01', '2015-01-31']
['2015-02-01', '2015-02-03']
How can I get the numberof Sundays of the current month in Python?
Anyone got any idea about this?
This gives you the number of sundays in a current month as you wanted:
import calendar
from datetime import datetime
In [367]: len([1 for i in calendar.monthcalendar(datetime.now().year,
datetime.now().month) if i[6] != 0])
Out[367]: 4
I happened to need a solution for this, but was unsatisfactory with the solutions here, so I came up with my own:
import calendar
year = 2016
month = 3
day_to_count = calendar.SUNDAY
matrix = calendar.monthcalendar(year,month)
num_days = sum(1 for x in matrix if x[day_to_count] != 0)
I'd do it like this:
import datetime
today = datetime.date.today()
day = datetime.date(today.year, today.month, 1)
single_day = datetime.timedelta(days=1)
sundays = 0
while day.month == today.month:
if day.weekday() == 6:
sundays += 1
day += single_day
print 'Sundays:', sundays
My take: (saves having to worry about being in the right month etc...)
from calendar import weekday, monthrange, SUNDAY
y, m = 2012, 10
days = [weekday(y, m, d+1) for d in range(*monthrange(y, m))]
print days.count(SUNDAY)
Or, as #mgilson has pointed out, you can do away with the list-comp, and wrap it all up as a generator:
sum(1 for d in range(*monthrange(y,m)) if weekday(y,m,d+1)==SUNDAY)
And I suppose, you could throw in a:
from collections import Counter
days = Counter(weekday(y, m, d + 1) for d in range(*monthrange(y, m)))
print days[SUNDAY]
Another example using calendar and datetime:
import datetime
import calendar
today = datetime.date.today()
m = today.month
y = today.year
sum(1 for week in calendar.monthcalendar(y,m) if week[-1])
Perhaps a slightly faster way to do it would be:
first_day,month_len = monthrange(y,m)
date_of_first_sun = 1+6-first_day
print sum(1 for x in range(date_of_first_sun,month_len+1,7))
You can do this using ISO week numbers:
from datetime import date
bom = date.today().replace(day=1) # first day of current month
eom = (date(bom.year, 12, 31) if bom.month == 12 else
(bom.replace(month=bom.month + 1) - 1)) # last day of current month
_, b_week, _ = bom.isocalendar()
_, e_week, e_weekday = eom.isocalendar()
num_sundays = (e_week - b_week) + (1 if e_weekday == 7 else 0)
In general for a particular day of the week (1 = Monday, 7 = Sunday) the calculation is:
num_days = ((e_week - b_week) +
(-1 if b_weekday > day else 0) +
( 1 if e_weekday >= day else 0))
import calendar
MONTH = 10
sundays = 0
cal = calendar.Calendar()
for day in cal.itermonthdates(2012, MONTH):
if day.weekday() == 6 and day.month == MONTH:
sundays += 1
PAY ATTENTION:
Here are the Calendar.itermonthdates's docs:
Return an iterator for one month. The iterator will yield datetime.date
values and will always iterate through complete weeks, so it will yield
dates outside the specified month.
That's why day.month == MONTH is needed
If you want the weekdays to be in range 0-6, use day.weekday(),
if you want them to be in range 1-7, use day.isoweekday()
My solution.
The following was inspired by #Lingson's answer, but I think it does lesser loops.
import calendar
def get_number_of_weekdays(year: int, month: int) -> list:
main_calendar = calendar.monthcalendar(year, month)
number_of_weeks = len(main_calendar)
number_of_weekdays = []
for i in range(7):
number_of_weekday = number_of_weeks
if main_calendar[0][i] == 0:
number_of_weekday -= 1
if main_calendar[-1][i] == 0:
number_of_weekday -= 1
number_of_weekdays.append(number_of_weekday)
return sum(number_of_weekdays) # In my application I needed the number of each weekday, so you could return just the list to do that.
I'm trying to add n (integer) working days to a given date, the date addition has to avoid the holidays and weekends (it's not included in the working days)
Skipping weekends would be pretty easy doing something like this:
import datetime
def date_by_adding_business_days(from_date, add_days):
business_days_to_add = add_days
current_date = from_date
while business_days_to_add > 0:
current_date += datetime.timedelta(days=1)
weekday = current_date.weekday()
if weekday >= 5: # sunday = 6
continue
business_days_to_add -= 1
return current_date
#demo:
print '10 business days from today:'
print date_by_adding_business_days(datetime.date.today(), 10)
The problem with holidays is that they vary a lot by country or even by region, religion, etc. You would need a list/set of holidays for your use case and then skip them in a similar way. A starting point may be the calendar feed that Apple publishes for iCal (in the ics format), the one for the US would be http://files.apple.com/calendars/US32Holidays.ics
You could use the icalendar module to parse this.
If you don't mind using a 3rd party library then dateutil is handy
from dateutil.rrule import *
print "In 4 business days, it's", rrule(DAILY, byweekday=(MO,TU,WE,TH,FR))[4]
You can also look at rruleset and using .exdate() to provide the holidays to skip those in the calculation, and optionally there's a cache option to avoid re-calculating that might be worth looking in to.
There is no real shortcut to do this. Try this approach:
Create a class which has a method skip(self, d) which returns True for dates that should be skipped.
Create a dictionary in the class which contains all holidays as date objects. Don't use datetime or similar because the fractions of a day will kill you.
Return True for any date that is in the dictionary or d.weekday() >= 5
To add N days, use this method:
def advance(d, days):
delta = datetime.timedelta(1)
for x in range(days):
d = d + delta
while holidayHelper.skip(d):
d = d + delta
return d
Thanks based on omz code i made some little changes ...it maybe helpful for other users:
import datetime
def date_by_adding_business_days(from_date, add_days,holidays):
business_days_to_add = add_days
current_date = from_date
while business_days_to_add > 0:
current_date += datetime.timedelta(days=1)
weekday = current_date.weekday()
if weekday >= 5: # sunday = 6
continue
if current_date in holidays:
continue
business_days_to_add -= 1
return current_date
#demo:
Holidays =[datetime.datetime(2012,10,3),datetime.datetime(2012,10,4)]
print date_by_adding_business_days(datetime.datetime(2012,10,2), 10,Holidays)
I wanted a solution that wasn't O(N) and it looked like a fun bit of code golf. Here's what I banged out in case anyone's interested. Works for positive and negative numbers. Let me know if I missed anything.
def add_business_days(d, business_days_to_add):
num_whole_weeks = business_days_to_add / 5
extra_days = num_whole_weeks * 2
first_weekday = d.weekday()
remainder_days = business_days_to_add % 5
natural_day = first_weekday + remainder_days
if natural_day > 4:
if first_weekday == 5:
extra_days += 1
elif first_weekday != 6:
extra_days += 2
return d + timedelta(business_days_to_add + extra_days)
I know it does not handle holidays, but I found this solution more helpful because it is constant in time. It consists of counting the number of whole weeks, adding holidays is a little more complex. I hope it can help somebody :)
def add_days(days):
today = datetime.date.today()
weekday = today.weekday() + ceil(days)
complete_weeks = weekday // 7
added_days = weekday + complete_weeks * 2
return today + datetime.timedelta(days=added_days)
This will take some work since there isn't any defined construct for holidays in any library (by my knowledge at least). You will need to create your own enumeration of those.
Checking for weekend days is done easily by calling .weekday() < 6 on your datetime object.
Refactoring omz code, and using holidays package, this is what I use to add business days taking into account the country's holidays
import datetime
import holidays
def today_is_holiday(date):
isHoliday = date.date() in [key for key in holidays.EN(years = date.year).keys()]
isWeekend = date.weekday() >= 5
return isWeekend or isHoliday
def date_by_adding_business_days(from_date, add_days):
business_days_to_add = add_days
current_date = from_date
while business_days_to_add > 0:
current_date += datetime.timedelta(days=1)
if today_is_holiday(current_date):
continue
business_days_to_add -= 1
return current_date
Hope this helps. It's not O(N) but O(holidays). Also, holidays only works when the offset is positive.
def add_working_days(start, working_days, holidays=()):
"""
Add working_days to start start date , skipping weekends and holidays.
:param start: the date to start from
:type start: datetime.datetime|datetime.date
:param working_days: offset in working days you want to add (can be negative)
:type working_days: int
:param holidays: iterator of datetime.datetime of datetime.date instances
:type holidays: iter(datetime.date|datetime.datetime)
:return: the new date wroking_days date from now
:rtype: datetime.datetime
:raise:
ValueError if working_days < 0 and holidays
"""
assert isinstance(start, (datetime.date, datetime.datetime)), 'start should be a datetime instance'
assert isinstance(working_days, int)
if working_days < 0 and holidays:
raise ValueError('Holidays and a negative offset is not implemented. ')
if working_days == 0:
return start
# first just add the days
new_date = start + datetime.timedelta(working_days)
# now compensate for the weekends.
# the days is 2 times plus the amount of weeks are included in the offset added to the day of the week
# from the start. This compensates for adding 1 to a friday because 4+1 // 5 = 1
new_date += datetime.timedelta(2 * ((working_days + start.weekday()) // 5))
# now compensate for the holidays
# process only the relevant dates so order the list and abort the handling when the holiday is no longer
# relevant. Check each holiday not being in a weekend, otherwise we don't mind because we skip them anyway
# next, if a holiday is found, just add 1 to the date, using the add_working_days function to compensate for
# weekends. Don't pass the holiday to avoid recursion more then 1 call deep.
for hday in sorted(holidays):
if hday < start:
# ignore holidays before start, we don't care
continue
if hday.weekday() > 4:
# skip holidays in weekends
continue
if hday <= new_date:
# only work with holidays up to and including the current new_date.
# increment using recursion to compensate for weekends
new_date = add_working_days(new_date, 1)
else:
break
return new_date
If someone needs to add/substract days, extending #omz's answer:
def add_business_days(from_date, ndays):
business_days_to_add = abs(ndays)
current_date = from_date
sign = ndays/abs(ndays)
while business_days_to_add > 0:
current_date += datetime.timedelta(sign * 1)
weekday = current_date.weekday()
if weekday >= 5: # sunday = 6
continue
business_days_to_add -= 1
return current_date
similar to #omz solution but recursively:
def add_days_skipping_weekends(start_date, days):
if not days:
return start_date
start_date += timedelta(days=1)
if start_date.weekday() < 5:
days -= 1
return add_days_skipping_weekends(start_date, days)
If you are interested in using NumPy, then you can follow the solution below:
import numpy as np
from datetime import datetime, timedelta
def get_future_date_excluding_weekends(date,no_of_days):
"""This methods return future date by adding given number of days excluding
weekends"""
future_date = date + timedelta(no_of_days)
no_of_busy_days = int(np.busday_count(date.date(),future_date.date()))
if no_of_busy_days != no_of_days:
extend_future_date_by = no_of_days - no_of_busy_days
future_date = future_date + timedelta(extend_future_date_by)
return future_date
This is the best solution because it has O(1) complexity (no loop) and no 3-rd party, but it does not take into account the holidays:
def add_working_days_to_date(self, start_date, days_to_add):
from datetime import timedelta
start_weekday = start_date.weekday()
# first week
total_days = start_weekday + days_to_add
if total_days < 5:
return start_date + timedelta(days=total_days)
else:
# first week
total_days = 7 - start_weekday
days_to_add -= 5 - start_weekday
# middle whole weeks
whole_weeks = days_to_add // 5
remaining_days = days_to_add % 5
total_days += whole_weeks * 7
days_to_add -= whole_weeks * 5
# last week
total_days += remaining_days
return start_date + timedelta(days=total_days)
Even though this does not fully solves your problem, I wanted to let it here because the solutions found on the internet for adding working days to dates, all of them have O(n) complexity.
Keep in mind that, if you want to add 500 days to a date, you will go through a loop and make the same set of computations 500 times. The above approach operates in the same amount of time, no matter how many days you have.
This was heavily tested.
Use numpy (you can skip holidays too):
np.busday_offset(
np.datetime64('2022-12-08'),
offsets=range(12),
roll='following',
weekmask="1111100",
holidays=[])
Result:
array(['2022-12-08', '2022-12-09', '2022-12-12', '2022-12-13',
'2022-12-14', '2022-12-15', '2022-12-16', '2022-12-19',
'2022-12-20', '2022-12-21', '2022-12-22', '2022-12-23'],
dtype='datetime64[D]')
I am using following code to handle business date delta. For holidays, you need to create your own list to skip.
today = datetime.now()
t_1 = today - BDay(1)
t_5 = today - BDay(5)
t_1_str = datetime.strftime(t_1,"%Y%m%d")