Add One Month to a Given Date (Rounded Day After) With Python

Add one month to a given date (rounded day after) with Python

You can use dateutil.relativedelta.relativedelta and manually check the datetime.day attribute, if the original day is greater than the new day, then add a day.

The function below accepts a datetime object and relativedelta object. Note that the code below only works for years and months, I don't believe it'll work if you use anything below that (days, hours, etc). You could easily modify this function to take years and months as arguments and then construct the relativedelta inside the function itself.

from datetime import datetime
from dateutil.relativedelta import relativedelta

def add_time(d, rd):
day = relativedelta(days=+1)

out = d + rd
if d.day > out.day:
out = out + day

return out

# Check that it "rolls over"
print(add_time(datetime(year=2015, month=1, day=29), relativedelta(years=+4, months=+1))) # 2019-03-01 00:00:00
print(add_time(datetime(year=2015, month=3, day=31), relativedelta(years=+0, months=+2))) # 2015-05-01 00:00:00

# Check that it handles "normal" scenarios
print(add_time(datetime(year=2015, month=6, day=19), relativedelta(months=+1))) # 2015-07-19 00:00:00
print(add_time(datetime(year=2015, month=6, day=30), relativedelta(years=+2, months=+1))) # 2017-07-30 00:00:00

# Check across years
print(add_time(datetime(year=2015, month=12, day=25), relativedelta(months=+1))) # 2016-01-25 00:00:00

# Check leap years
print(add_time(datetime(year=2016, month=1, day=29), relativedelta(years=+4, months=+1))) # 2020-02-29 00:00:00

How to increment datetime by custom months in python without using library

Edit - based on your comment of dates being needed to be rounded down if there are fewer days in the next month, here is a solution:

import datetime
import calendar

def add_months(sourcedate, months):
month = sourcedate.month - 1 + months
year = sourcedate.year + month // 12
month = month % 12 + 1
day = min(sourcedate.day, calendar.monthrange(year,month)[1])
return datetime.date(year, month, day)

In use:

>>> somedate = datetime.date.today()
>>> somedate
datetime.date(2010, 11, 9)
>>> add_months(somedate,1)
datetime.date(2010, 12, 9)
>>> add_months(somedate,23)
datetime.date(2012, 10, 9)
>>> otherdate = datetime.date(2010,10,31)
>>> add_months(otherdate,1)
datetime.date(2010, 11, 30)

Also, if you're not worried about hours, minutes and seconds you could use date rather than datetime. If you are worried about hours, minutes and seconds you need to modify my code to use datetime and copy hours, minutes and seconds from the source to the result.

Cumulatively add month to a Date column based off the first date of a group

  1. You can create a series m with a .groupby on the column ID1, return the cumulative count per group, and add 3 (since that is how many months you want to offset initially). With .cumcount(), the offset will grow by 1 with each additional row of your group.
  2. Then, we want to create the New Date column but ONLY Add m to the FIRST date of each group, so we use df.groupby('ID1')['Date'].transform('first') before adding m.values.astype("timedelta64[M]"):

Input (Latest Edit of your question):

ID1    ID2          Date        
1 20 5/15/2019 11:06:47 AM
1 21 5/15/2019 11:06:47 AM
1 22 5/15/2019 11:06:47 AM
1 23 5/15/2019 11:06:47 AM
1 24 5/15/2019 11:06:47 AM
1 25 5/15/2019 11:06:47 AM
1 26 6/15/2019 11:06:47 AM
1 27 6/15/2019 11:06:47 AM
1 28 6/15/2019 11:06:47 AM
1 29 6/15/2019 11:06:47 AM
1 30 6/15/2019 11:06:47 AM
1 31 6/15/2019 11:06:47 AM


# df['Date'] = pd.to_datetime(df['Date'])
m = df.groupby('ID1').cumcount() + 3
df['New Date'] = df.groupby('ID1')['Date'].transform('first') + m.values.astype("timedelta64[M]")
df
Out[1]:
ID1 ID2 Date New Date
0 1 20 2019-05-15 11:06:47 2019-08-14 18:34:05
1 1 21 2019-05-15 11:06:47 2019-09-14 05:03:11
2 1 22 2019-05-15 11:06:47 2019-10-14 15:32:17
3 1 23 2019-05-15 11:06:47 2019-11-14 02:01:23
4 1 24 2019-05-15 11:06:47 2019-12-14 12:30:29
5 1 25 2019-05-15 11:06:47 2020-01-13 22:59:35
6 1 26 2019-06-15 11:06:47 2020-02-13 09:28:41
7 1 27 2019-06-15 11:06:47 2020-03-14 19:57:47
8 1 28 2019-06-15 11:06:47 2020-04-14 06:26:53
9 1 29 2019-06-15 11:06:47 2020-05-14 16:55:59
10 1 30 2019-06-15 11:06:47 2020-06-14 03:25:05
11 1 31 2019-06-15 11:06:47 2020-07-14 13:54:11

How to add datetime and round it considering months?

Not sure what result do you want here?

What is a month in this case? February can be of various size dependent on year, for example. If you add "three month" to June, this is one case, if "to July" - another, since different months has different day numbers.

Looks like you can have "100 minutes 100 hours 100 days 100 month"? Then just add months to months, days to days?

Anyhow, adding large time ranges like month and years, I'd probably went via seconds, relative to the year 1 AD (in Python there is no 0 year). Where questions like "what does it mean to add one month and one year" will still be relevant. So one might want to "add another year in seconds", etc.

from datetime import datetime, timedelta

time1 = datetime.strptime('28 01 2021', '%d %m %Y')

time_min = datetime.min

time2 = datetime.strptime('15 01 2021', '%d %m %Y')

time = (time1 - time_min).total_seconds() + (time2 - time_min).total_seconds()

#year_of_seconds = (datetime(2,1,1) - time_min).total_seconds()

(time_min + timedelta(seconds = time)).strftime('%d %m %Y')

11 02 4041

Adding month, day, year to a date

Try this.


class Date(object):
def __init__(self, day=1, month=1, year=2015):
self.day = day
self.mon = month
self.year = year
def __str__(self):
return "Date is: %s %s %s"%(self.day,self.mon,self.year)
def IsLeapYear(self):
"""
function returns 1 if self.year is leap,0 if it's not
"""
if(not (self.year%400) or (not(self.year%4) and self.year%100)):
return 1
else:
return 0
#values by each month whether it's leap year or not
monthlength = (
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
)

#the function you probably were looking for
def AddDay(self, n):
days=n
leap_year=self.IsLeapYear()
while(self.day+days>=Date.monthlength[leap_year][self.mon-1]):
days=days-(Date.monthlength[leap_year][self.mon-1]-self.day)
self.day=0
self.mon+=1
if (self.mon>12):
self.mon=1
self.year+=1
leap_year=self.IsLeapYear()
else:
self.day+=days

find start and end date of previous 12 month from current date in python

You can use current_date.replace(day=1) to get first day in current month.

And if you substract datetime.timedelta(days=1) then you get last day in previous month.

And you can use again replace(day=1) to get first day in previous month.

If you repeate it in loop then you can get first day and last day for all 12 months.

import datetime

current = datetime.datetime(2022, 5, 5)

start = current.replace(day=1)

for x in range(1, 13):
end = start - datetime.timedelta(days=1)
start = end.replace(day=1)
print(f'{x:2} |', start.date(), '|', end.date())

Result:

 1 | 2022-04-01 | 2022-04-30
2 | 2022-03-01 | 2022-03-31
3 | 2022-02-01 | 2022-02-28
4 | 2022-01-01 | 2022-01-31
5 | 2021-12-01 | 2021-12-31
6 | 2021-11-01 | 2021-11-30
7 | 2021-10-01 | 2021-10-31
8 | 2021-09-01 | 2021-09-30
9 | 2021-08-01 | 2021-08-31
10 | 2021-07-01 | 2021-07-31
11 | 2021-06-01 | 2021-06-30
12 | 2021-05-01 | 2021-05-31

EDIT:

And if you use pandas then you can use pd.date_range() but it can't for previous dates so you would have to first get '2021.04.05' (for MS) and '2021.05.05' (for M)

import pandas as pd

#all_starts = pd.date_range('2021.04.05', '2022.04.05', freq='MS')
all_starts = pd.date_range('2021.04.05', periods=12, freq='MS')
print(all_starts)

#all_ends = pd.date_range('2021.05.05', '2022.05.05', freq='M')
all_ends = pd.date_range('2021.05.05', periods=12, freq='M')

print(all_ends)

for start, end in zip(all_starts, all_ends):
print(start.to_pydatetime().date(), '|', end.to_pydatetime().date())
DatetimeIndex(['2021-05-01', '2021-06-01', '2021-07-01', '2021-08-01',
'2021-09-01', '2021-10-01', '2021-11-01', '2021-12-01',
'2022-01-01', '2022-02-01', '2022-03-01', '2022-04-01'],
dtype='datetime64[ns]', freq='MS')

DatetimeIndex(['2021-05-31', '2021-06-30', '2021-07-31', '2021-08-31',
'2021-09-30', '2021-10-31', '2021-11-30', '2021-12-31',
'2022-01-31', '2022-02-28', '2022-03-31', '2022-04-30'],
dtype='datetime64[ns]', freq='M')

2021-05-01 | 2021-05-31
2021-06-01 | 2021-06-30
2021-07-01 | 2021-07-31
2021-08-01 | 2021-08-31
2021-09-01 | 2021-09-30
2021-10-01 | 2021-10-31
2021-11-01 | 2021-11-30
2021-12-01 | 2021-12-31
2022-01-01 | 2022-01-31
2022-02-01 | 2022-02-28
2022-03-01 | 2022-03-31
2022-04-01 | 2022-04-30

EDIT:

I found out that standard module calendar can gives number of days and weeks in month.

weeks, days = calendar.monthrange(year, month)

Working example:

import calendar

year = 2022
month = 5

for number in range(1, 13):
if month > 1:
month -= 1
else:
month = 12
year -= 1

weeks, days = calendar.monthrange(year, month)

print(f'{number:2} | {year}.{month:02}.01 | {year}.{month:02}.{days}')

Result:

 1 | 2022.04.01 | 2022.04.30
2 | 2022.03.01 | 2022.03.31
3 | 2022.02.01 | 2022.02.28
4 | 2022.01.01 | 2022.01.31
5 | 2021.12.01 | 2021.12.31
6 | 2021.11.01 | 2021.11.30
7 | 2021.10.01 | 2021.10.31
8 | 2021.09.01 | 2021.09.30
9 | 2021.08.01 | 2021.08.31
10 | 2021.07.01 | 2021.07.31
11 | 2021.06.01 | 2021.06.30
12 | 2021.05.01 | 2021.05.31

How to round a datetime up to a specific time?

If the result is a timezone-aware datetime object in a timezone with a non-fixed UTC offset then you can't just call .replace() or .combine() -- it may create a datetime with a wrong UTC offset. The issue is similar to How do I get the UTC time of "midnight" for a given timezone? (00:00 is used instead of 08:00).

Assuming 8AM always exists and unambiguous in PST:

from datetime import datetime, time as datetime_time, timedelta
import pytz # $ pip install pytz

def next_8am_in_pst(aware_dt, tz=pytz.timezone('America/Los_Angeles')):
pst_aware_dt = tz.normalize(aware_dt.astimezone(tz)) # convert to PST
naive_dt = round_up_to_8am(pst_aware_dt.replace(tzinfo=None))
return tz.localize(naive_dt, is_dst=None)

def round_up_to_8am(dt):
rounded = datetime.combine(dt, datetime_time(8))
return rounded + timedelta(rounded < dt)

Example:

>>> str(next_8am_in_pst(datetime.now(pytz.utc))) 
'2016-02-25 08:00:00-08:00'


Related Topics



Leave a reply



Submit