Time difference between two timedate columns without considering Non-business hours - python

I want to calculate difference between two time columns without considering non-business hours. I have used pyholidays, which worked totally fine. But even when i define starttime and endtime for Business-duration, Result still includes Non-Business Hours as you shown in attached photos.
for index, row in df.iterrows():
first=row['New']
second=row['Assigned']
third=row['In Progress']
if(pd.notnull(second)):
starttime = (8,0,0)
endtime = (17,0,0)
holidaylist = pyholidays.Germany()
unit='hour'
row['AP'] = businessDuration(first,second,holidaylist=holidaylist,unit=unit)
else:
starttime = (8,0,0)
endtime = (17,0,0)
holidaylist = pyholidays.Germany()
unit='hour'
row['AP'] = businessDuration(first,third,holidaylist=holidaylist,unit=unit)
ap.append(row['AP'])
DataFrame
Printed Result

Thank you for your suggestion. I have tried your method, i have also defined calendar instance. Later i was getting 'relativedelta' error which i have somehow solved by 'dateutil'. Now i am at final stage to compute business-hour difference between two columns.
`de_holidays = pyholidays.Germany()
cal = Calendar(holidays=de_holidays, weekdays=['Saturday', 'Sunday'])
df['rp'] = df.apply(lambda row: compute_bizhours_diff(row['Resolved'], row['Pending'], cal=cal, biz_open_time = time(8, 0, 0), biz_close_time = time(17, 0, 0)), axis=1)`
Now i am getting error about month number, which can not be nan. I have also attached photo of errors.
Pic1
Pic2

I do not know if this works, but try this:
# == Imports needed ===========================
from __future__ import annotations
from typing import Any
import pandas as pd
import holidays as pyholidays
from datetime import time
from bizdays import Calendar
from dateutil.relativedelta import relativedelta
# == Functions ==================================
def is_null_dates(*dates: Any) -> bool:
"""Determine whether objects are valid dates.
Parameters
----------
dates : Any
Variables to check whether they hold a valid date, or not.
Returns
-------
bool
True, if at least one informed value is not a date.
False otherwise.
"""
for date in dates:
if pd.isna(pd.to_datetime(date, errors='coerce')):
return True
return False
def compute_bizhours_diff(
start_date: str | pd.Timestamp,
end_date: str | pd.Timestamp,
biz_open_time: datetime.time | None = None,
biz_close_time: datetime.time | None = None,
cal: bizdays.Calendar | None = None,
) -> float:
"""Compute the number of business hours between two dates.
Parameters
----------
start_date : str | pd.Timestamp
The first date.
end_date : str | pd.Timestamp
The final date.
biz_open_time : datetime.time | None
The beginning hour/minute of a business day.
biz_close_time : datetime.time | None
The ending hour/minute of a business day.
cal : bizdays.Calendar | None
The calendar object used to figure out the number of days between `start_date`
and `end_date` that are not holidays. If None, consider every day as a business day,
except Saturdays, or Sundays.
Returns
-------
float
The total number of business hours between `start_date`, and `end_date`.
Examples
--------
>>> import holidays as pyholidays
>>> from datetime import time
>>> from bizdays import Calendar
>>> # 2022-09-07 is a national holiday in Brazil, therefore only
>>> # the hours between 2022-09-08 09:00:00, and 2022-09-08 15:48:00
>>> # should be considered. This should equal 6.8 hours.
>>> start_date = pd.to_datetime('2022-09-07 15:55:00')
>>> end_date = pd.to_datetime('2022-09-08 15:48:00')
>>> BR_holiday_list = pyholidays.BR(years={start_date.year, end_date.year}, state='RJ')
>>> cal = Calendar(holidays=BR_holiday_list, weekdays=['Saturday', 'Sunday'])
>>> print(compute_bizhours_diff(start_date, end_date, cal=cal))
6.8
>>> # Both dates in the next example are holidays, therefore, the result should be 0.0
>>> start_date = pd.to_datetime('2022-09-07 15:55:00')
>>> end_date = pd.to_datetime('2022-09-07 15:48:00')
>>> print(compute_bizhours_diff(start_date, end_date, cal=cal))
0.0
>>> # What if the end_date preceeds start_date by mistake?
>>> # In such cases, we switch start_date to end_date, and vice-versa.
>>> start_date = pd.to_datetime('2022-09-02 00:00:00')
>>> end_date = pd.to_datetime('2022-09-01 15:55:00')
>>> print(compute_bizhours_diff(start_date, end_date, cal=cal))
2.0833333333333335
>>> # What if the start_date, and end_date begin and finish on the same day, but they both have timestamps that end before
>>> # or after the business hours?
>>> # In such cases, the total number of hours is equal to 0.0
>>> start_date = pd.to_datetime('2022-09-02 00:00:00')
>>> end_date = pd.to_datetime('2022-09-02 8:00:00')
>>> print(compute_bizhours_diff(start_date, end_date, cal=cal))
0.0
"""
if is_null_dates(start_date, end_date):
return pd.NA
if biz_open_time is None:
biz_open_time = time(9, 0, 0)
if biz_close_time is None:
biz_close_time = time(18, 0, 0)
if cal is None:
cal = Calendar(weekdays=['Saturday', 'Sunday'])
open_delta = relativedelta(hour=biz_open_time.hour, minute=biz_open_time.minute)
end_delta = relativedelta(hour=biz_close_time.hour, minute=biz_close_time.minute)
start_date = pd.to_datetime(start_date)
end_date = pd.to_datetime(end_date)
_end_date = max(start_date, end_date)
_start_date = min(start_date, end_date)
start_date = _start_date
end_date = _end_date
start_date = (
start_date if cal.isbizday(start_date) else cal.following(start_date) + open_delta
)
end_date = (
end_date if cal.isbizday(end_date) else cal.preceding(end_date) + end_delta
)
if end_date < start_date:
return 0.00
start_date_biz = max(start_date, start_date + open_delta)
end_first_day = start_date_biz + end_delta
end_date_biz = min(
end_date,
end_date + end_delta
)
start_last_day = end_date_biz + open_delta
if start_last_day > end_date:
end_date_biz = start_last_day
if end_first_day < start_date:
end_first_day = start_date_biz
if end_first_day.date() == end_date_biz.date():
return (end_date_biz - start_date_biz).seconds / 3600
return (
(end_first_day - start_date_biz).seconds
+ (end_date_biz - start_last_day).seconds
+ (
max((len(list(cal.seq(start_date, end_date))) - 2), 0)
* (end_first_day - (start_date + open_delta)).seconds
)
) / 3600
Before running the preceding code, you need to install the following packages, if you do not already have them:
pip install holidays bizdays
Link to both packages' documentation:
bizdays
python-holidays
Examples
Here is how you can use compute_bizhours_diff:
import pandas as pd
import holidays as pyholidays
from datetime import time
from bizdays import Calendar
# OPTIONAL: define custom start, and end to your business hours.
biz_open_time = time(9, 0, 0)
biz_close_time = time(18, 0, 0)
# Define your start, and end dates.
start_date = pd.to_datetime('2022-09-07 04:48:00')
end_date = pd.to_datetime('2022-09-10 15:55:00')
# Create a list of holidays, and create a Calendar instance.
BR_holiday_list = pyholidays.BR(years={start_date.year, end_date.year}, state='RJ')
# For German holidays, you can use something like:
German_holiday_list = pyholidays.Germany(years={start_date.year, end_date.year})
# Define the Calendar instance. Here, we use the German holidays, excluding Saturday, and Sunday from weekdays.
cal = Calendar(holidays=German_holiday_list, weekdays=['Saturday', 'Sunday'])
# Finally, compute the total number of working hours between your two dates:
compute_bizhours_diff(start_date, end_date, cal=cal)
# Returns: 27.0
You can also use the function with pandas dataframes, using apply:
df['working_hours_delta'] = df.apply(lambda row: compute_bizhours_diff(row[START_DATE_COLNAME], row[END_DATE_COLNAME], cal=cal), axis=1)
Notes
The function compute_bizhours_diff is far from perfect. Before using it in any production environment, or for any serious use case, I strongly recommend refactoring it.
Edit
I made some changes to the original answer, to account for instances where start_date, or end_date have null or invalid representations of dates.
Using the example dataframe from your question it now runs fine:
de_holidays = pyholidays.Germany()
cal = Calendar(holidays=de_holidays, weekdays=['Saturday', 'Sunday'])
df = pd.DataFrame(
{
'Assigned': [None, '2022-07-28 10:53:00', '2022-07-28 18:08:00', None, '2022-07-29 12:56:00'],
'In Progress': ['2022-08-01 10:53:00', '2022-08-02 09:32:00', '2022-07-29 12:08:00', '2022-08-02 10:23:00', '2022-07-29 14:54:00'],
'New': ['2022-07-27 15:01:00', '2022-07-28 10:09:00', '2022-07-28 13:37:00', '2022-07-29 00:12:00', '2022-07-29 09:51:00'],
}
).apply(pd.to_datetime)
df['rp'] = df.apply(
lambda row: compute_bizhours_diff(
row['Assigned'], row['In Progress'], cal=cal, biz_open_time = time(8, 0, 0), biz_close_time = time(17, 0, 0)
), axis=1
)
print(df)
# Prints:
# Assigned In Progress New rp
# 0 NaT 2022-08-01 10:53:00 2022-07-27 15:01:00 <NA>
# 1 2022-07-28 10:53:00 2022-08-02 09:32:00 2022-07-28 10:09:00 25.65
# 2 2022-07-28 18:08:00 2022-07-29 12:08:00 2022-07-28 13:37:00 4.133333
# 3 NaT 2022-08-02 10:23:00 2022-07-29 00:12:00 <NA>
# 4 2022-07-29 12:56:00 2022-07-29 14:54:00 2022-07-29 09:51:00 1.966667

Related

Python/Discord.py remove milliseconds from time.time()

I did this for my python Discord bot (basically it's a voice activity tracker), everything works fine but I want to remove the milliseconds from total_time. I would like to get something in this format '%H:%M:%S'
Is this possible ?
Here's a part of the code:
if(before.channel == None):
join_time = round(time.time())
userdata["join_time"] = join_time
elif(after.channel == None):
if(userdata["join_time"] == None): return
userdata = voice_data[guild_id][new_user]
leave_time = time.time()
passed_time = leave_time - userdata["join_time"]
userdata["total_time"] += passed_time
userdata["join_time"] = None
And here's the output:
{
"total_time": 7.4658853358879,
}
You can use a datetime.timedelta object, with some caveats.
>>> import datetime as dt
>>> data = {"total_time": 7.4658853358879}
>>> data["total_time"] = str(dt.timedelta(seconds=int(data["total_time"])))
>>> data
{'total_time': '0:00:07'}
If your time is greater than 1 day, or less than zero, the format starts including days
>>> str(dt.timedelta(days=1))
'1 day, 0:00:00'
>>> str(dt.timedelta(seconds=-1))
'-1 day, 23:59:59'
>>>

Converting imported dates from .txt to date format python

I have issues with converting dates in an imported .txt file and I wonder what I'm doing wrong.
I import the data by:
df_TradingMonthlyDates = pd.read_csv(TradingMonthlyDates, dtype=str, sep=',') # header=True,
and it looks like the following table (dates represents start/end of month and have a header Date):
Date
0 2008-12-30
1 2008-12-31
2 2009-01-01
3 2009-01-02
4 2009-01-29
.. ...
557 2020-06-29
558 2020-06-30
559 2020-07-01
560 2020-07-02
561 2020-07-30
.. ...
624 2021-11-30
625 2021-12-01
626 2021-12-02
627 2021-12-30
628 2021-12-31
[629 rows x 1 columns]
<class 'pandas.core.frame.DataFrame'>
I then calculate today's date:
df_EndDate = datetime.now().date()
I'm trying to apply the data above in this function to get the closest date before a given date (given date = today's date in my case):
# https://stackoverflow.com/questions/32237862/find-the-closest-date-to-a-given-date
def nearest(items, pivot):
return min([i for i in items if i < pivot], key=lambda x: abs(x - pivot))
date_output = nearest(df_TradingMonthlyDates, df_EndDate)
# date_output should be = 2020-07-02 given today's date of 2020-07-12
The error messages I receive is that the df_TradingMonthlyDates is not in date format. So I have tried to convert the dataframe to datetime format but can't make it work.
What I have tried to convert the data to date format:
# df_TradingMonthlyDates["Date"] = pd.to_datetime(df_TradingMonthlyDates["Date"], format="%Y-%m-%d")
# df_TradingMonthlyDates = datetime.strptime(df_TradingMonthlyDates, "%Y-%m-%d").date()
# df_TradingMonthlyDates['Date'] = df_TradingMonthlyDates['Date'].apply(lambda x: pd.to_datetime(x[0], format="%Y-%m-%d"))
# df_TradingMonthlyDates = df_TradingMonthlyDates.iloc[1:]
# print(df_TradingMonthlyDates)
# df_TradingMonthlyDates = datetime.strptime(str(df_TradingMonthlyDates), "%Y-%m-%d").date()
# for line in split_source[1:]: # skip the first line
Code:
import pandas as pd
from datetime import datetime
# Version 1
TradingMonthlyDates = "G:/MonthlyDates.txt"
# Import file where all the first/end month date exists
df_TradingMonthlyDates = pd.read_csv(TradingMonthlyDates, dtype=str, sep=',') # header=True,
print(df_TradingMonthlyDates)
# https://community.dataquest.io/t/datetime-and-conversion/213425
# df_TradingMonthlyDates["Date"] = pd.to_datetime(df_TradingMonthlyDates["Date"], format="%Y-%m-%d")
# df_TradingMonthlyDates = datetime.strptime(df_TradingMonthlyDates, "%Y-%m-%d").date()
# df_TradingMonthlyDates['Date'] = df_TradingMonthlyDates['Date'].apply(lambda x: pd.to_datetime(x[0], format="%Y-%m-%d"))
# df_TradingMonthlyDates = df_TradingMonthlyDates.iloc[1:]
# print(df_TradingMonthlyDates)
# df_TradingMonthlyDates = datetime.strptime(str(df_TradingMonthlyDates), "%Y-%m-%d").date()
# for line in split_source[1:]: # skip the first line # maybe header is the problem
print(type(df_TradingMonthlyDates))
df_TradingMonthlyDates = df_TradingMonthlyDates.datetime.strptime(df_TradingMonthlyDates, "%Y-%m-%d")
df_TradingMonthlyDates = df_TradingMonthlyDates.time()
print(df_TradingMonthlyDates)
df_EndDate = datetime.now().date()
print(type(df_EndDate))
# https://stackoverflow.com/questions/32237862/find-the-closest-date-to-a-given-date
def nearest(items, pivot):
return min([i for i in items if i < pivot], key=lambda x: abs(x - pivot))
date_output = nearest(df_TradingMonthlyDates, df_EndDate)
Error messages are different depending on how I tried to convert data type, but I interpret that they all notice that my date format is not successful :
df_TradingMonthlyDates = df_TradingMonthlyDates.datetime.strptime(df_TradingMonthlyDates, "%Y-%m-%d")
Traceback (most recent call last):
File "g:/till2.py", line 25, in <module>
df_TradingMonthlyDates = df_TradingMonthlyDates.datetime.strptime(df_TradingMonthlyDates, "%Y-%m-%d")
File "C:\Users\ID\AppData\Roaming\Python\Python38\site-packages\pandas\core\generic.py", line 5274, in __getattr__
return object.__getattribute__(self, name)
AttributeError: 'DataFrame' object has no attribute 'datetime'
df_TradingMonthlyDates["Date"] = pd.to_datetime(df_TradingMonthlyDates["Date"], format="%Y-%m-%d")
Traceback (most recent call last):
File "g:/till2.py", line 40, in <module>
date_output = nearest(df_TradingMonthlyDates, df_EndDate)
File "g:/till2.py", line 38, in nearest
return min([i for i in items if i < pivot], key=lambda x: abs(x - pivot))
File "g:/till2.py", line 38, in <listcomp>
return min([i for i in items if i < pivot], key=lambda x: abs(x - pivot))
TypeError: '<' not supported between instances of 'str' and 'datetime.date'
Edit: Added Method 3, which might be the easiest with.loc and then .iloc
You could take a slightly different approach (with Method #1 or Method #2 below) by taking the absolute minimum of the difference between today's date and the data, but a key thing you weren't doing was wrapping pd.to_datetime() around the datetime.date object df_EndDate in order to transform it into a DatetimeArray so that it could be compared against your Date column. They both have to be in the same format of DatetimeArray in order to be compared.
Method 1:
import pandas as pd
import datetime as dt
df_TradingMonthlyDates = pd.DataFrame({'Date': {'0': '2008-12-30',
'1': '2008-12-31',
'2': '2009-01-01',
'3': '2009-01-02',
'4': '2009-01-29',
'557': '2020-06-29',
'558': '2020-06-30',
'559': '2020-07-01',
'560': '2020-07-02',
'561': '2020-07-30',
'624': '2021-11-30',
'625': '2021-12-01',
'626': '2021-12-02',
'627': '2021-12-30',
'628': '2021-12-31'}})
df_TradingMonthlyDates['Date'] = pd.to_datetime(df_TradingMonthlyDates['Date'])
df_TradingMonthlyDates['EndDate'] = pd.to_datetime(dt.datetime.now().date())
df_TradingMonthlyDates['diff'] = (df_TradingMonthlyDates['Date'] - df_TradingMonthlyDates['EndDate'])
a=min(abs(df_TradingMonthlyDates['diff']))
df_TradingMonthlyDates = df_TradingMonthlyDates.loc[(df_TradingMonthlyDates['diff'] == a)
| (df_TradingMonthlyDates['diff'] == -a)]
df_TradingMonthlyDates
output 1:
Date EndDate diff
560 2020-07-02 2020-07-11 -9 days
If you don't want the extra columns and just the date, then assign variables to create series rather than new columns:
Method 2:
d = pd.to_datetime(df_TradingMonthlyDates['Date'])
t = pd.to_datetime(dt.datetime.now().date())
e = (d-t)
a=min(abs(e))
df_TradingMonthlyDates = df_TradingMonthlyDates.loc[(e == a) | (e == -a)]
df_TradingMonthlyDates
output 2:
Date
560 2020-07-02
Method 3:
df_TradingMonthlyDates['Date'] = pd.to_datetime(df_TradingMonthlyDates['Date'])
date_output = df_TradingMonthlyDates.sort_values('Date') \
.loc[df_TradingMonthlyDates['Date'] <=
pd.to_datetime(dt.datetime.now().date())] \
.iloc[-1,:]
date_output
output 3:
Date 2020-07-02
Name: 560, dtype: datetime64[ns]

Class Holiday in Pandas doesn't manage leap year -> ValueError

This code return ValueError: day is out of range for month :
from pandas.tseries.holiday import Holiday, AbstractHolidayCalendar
from pandas.tseries.offsets import MonthEnd
import datetime as dt
class MyHolidays(AbstractHolidayCalendar):
rules = [Holiday('Sapeur',month=2,day=29)]
cal=MyHolidays()
if __name__ == '__main__':
start = dt.date(2020, 2, 1)
myholidays =cal.holidays(start, start + MonthEnd())
How do you make this code work in non-leap years?
I thought class Holiday handled that, didn't you?
Thx a lot for advance.
ps: 2020 is a leap year but not 2019 or 2021.
ps2: I seek a better solution than :
rules = [Holiday('Sapeur',year=2020, month=2,day=29),
Holiday('NoSapeur',year=2021, month=2,day=28),
Holiday('NoSapeur',year=2022, month=2,day=28),
Holiday('NoSapeur',year=2023, month=2,day=28),
Holiday('Sapeur',year=2024, month=2,day=29),
Holiday('NoSapeur',year=2025, month=2,day=28),
Holiday('NoSapeur',year=2026, month=2,day=28),
Holiday('NoSapeur',year=2027, month=2,day=28),
Holiday('Sapeur',year=2028, month=2,day=29),
...]
Because I'm on vacation every February and I make my rules with:
cejour=pd.Timestamp.today()
RÈGLES=[]
mois=2
for jour in pd.date_range(début_mois:=cejour.replace(month=mois, day=1), début_mois+MonthEnd(), normalize=True):
RÈGLES+=[Holiday('Vacances Février',month=mois, day=jour.day)]
class MyHolidays(AbstractHolidayCalendar):
rules = RÈGLES
Define your own observance
def leap_year(dt):
if dt.is_leap_year:
return dt + MonthEnd()
class MyHolidays(AbstractHolidayCalendar):
rules = [Holiday('Sapeur', month=2, day=28, observance=leap_year)]
cal = MyHolidays()
start = dt.date(2020, 2, 1)
myholidays =cal.holidays(start, start + MonthEnd())
print(myholidays) # DatetimeIndex(['2020-02-29'], dtype='datetime64[ns]', freq=None)
If you try this with a non leap year then it will assign nothing
start = dt.date(2019, 2, 1)
myholidays =cal.holidays(start, start + MonthEnd())
print(myholidays) # DatetimeIndex([], dtype='datetime64[ns]', freq=None)

Parse Timestamp String with Quarter to Python datetime

I am searching for a way to parse a rather unusual timestamp string to a Python datetime object. The problem here is, that this string includes the corresponding quarter, which seems not to be supported by the datetime.strptime function. The format of the string is as follows: YYYY/qq/mm/dd/HH/MM e.g 1970/Q1/01/01/00/00. I am searching for a function, which is allows me to parse string in such a format, including a validity check, if the quarter is correct for the date.
Question: Datetime String with Quarter to Python datetime
This implements a OOP solution which extends Python datetime with a directive: %Q.
Possible values: Q1|Q2|Q3|Q4, for example:
data_string = '1970/Q1/01/01/00/00'
# '%Y/%Q/%m/%d/%H/%M'
Note: This depends on the module _strptime class TimeRE and may fail if the internal implementation changes!
from datetime import datetime
class Qdatetime(datetime):
re_compile = None
#classmethod
def _strptime(cls):
import _strptime
_class = _strptime.TimeRE
if not 'strptime_compile' in _class.__dict__:
setattr(_class, 'strptime_compile', getattr(_class, 'compile'))
setattr(_class, 'compile', cls.compile)
def compile(self, format):
import _strptime
self = _strptime._TimeRE_cache
# Add directive %Q
if not 'Q' in self:
self.update({'Q': r"(?P<Q>Q[1-4])"})
Qdatetime.re_compile = self.strptime_compile(format)
return Qdatetime.re_compile
def validate(self, quarter):
# 1970, 1, 1 is the lowest date used in timestamp
month = [1, 4, 7, 10][quarter - 1]
day = [31, 30, 30, 31][quarter - 1]
q_start = datetime(self.year, month, 1).timestamp()
q_end = datetime(self.year, month + 2, day).timestamp()
dtt = self.timestamp()
return dtt >= q_start and dtt<= q_end
#property
def quarter(self): return self._quarter
#quarter.setter
def quarter(self, data):
found_dict = Qdatetime.re_compile.match(data).groupdict()
self._quarter = int(found_dict['Q'][1])
#property
def datetime(self):
return datetime(self.year, self.month, self.day,
hour=self.hour, minute=self.minute, second=self.second)
def __str__(self):
return 'Q{} {}'.format(self.quarter, super().__str__())
#classmethod
def strptime(cls, data_string, _format):
cls._strptime()
dt = super().strptime(data_string, _format)
dt.quarter = data_string
if not dt.validate(dt.quarter):
raise ValueError("time data '{}' does not match quarter 'Q{}'"\
.format(data_string, dt.quarter))
return dt
Usage:
for data_string in ['1970/Q1/01/01/00/00',
'1970/Q3/12/31/00/00',
'1970/Q2/05/05/00/00',
'1970/Q3/07/01/00/00',
'1970/Q4/12/31/00/00',
]:
try:
d = Qdatetime.strptime(data_string, '%Y/%Q/%m/%d/%H/%M')
except ValueError as e:
print(e)
else:
print(d, d.datetime)
Output:
Q1 1970-01-01 00:00:00 1970-01-01 00:00:00
time data '1970/Q3/12/31/00/00' does not match quarter 'Q3'
Q2 1970-05-05 00:00:00 1970-05-05 00:00:00
Q3 1970-07-01 00:00:00 1970-07-01 00:00:00
Q4 1970-12-31 00:00:00 1970-12-31 00:00:00
Tested with Python: 3.6 - verified with Python 3.8 source

How to increment a datetime by one day?

How to increment the day of a datetime?
for i in range(1, 35)
date = datetime.datetime(2003, 8, i)
print(date)
But I need pass through months and years correctly? Any ideas?
date = datetime.datetime(2003,8,1,12,4,5)
for i in range(5):
date += datetime.timedelta(days=1)
print(date)
Incrementing dates can be accomplished using timedelta objects:
import datetime
datetime.datetime.now() + datetime.timedelta(days=1)
Look up timedelta objects in the Python docs: http://docs.python.org/library/datetime.html
All of the current answers are wrong in some cases as they do not consider that timezones change their offset relative to UTC. So in some cases adding 24h is different from adding a calendar day.
Proposed solution
The following solution works for Samoa and keeps the local time constant.
def add_day(today):
"""
Add a day to the current day.
This takes care of historic offset changes and DST.
Parameters
----------
today : timezone-aware datetime object
Returns
-------
tomorrow : timezone-aware datetime object
"""
today_utc = today.astimezone(datetime.timezone.utc)
tz = today.tzinfo
tomorrow_utc = today_utc + datetime.timedelta(days=1)
tomorrow_utc_tz = tomorrow_utc.astimezone(tz)
tomorrow_utc_tz = tomorrow_utc_tz.replace(hour=today.hour,
minute=today.minute,
second=today.second)
return tomorrow_utc_tz
Tested Code
# core modules
import datetime
# 3rd party modules
import pytz
# add_day methods
def add_day(today):
"""
Add a day to the current day.
This takes care of historic offset changes and DST.
Parameters
----------
today : timezone-aware datetime object
Returns
-------
tomorrow : timezone-aware datetime object
"""
today_utc = today.astimezone(datetime.timezone.utc)
tz = today.tzinfo
tomorrow_utc = today_utc + datetime.timedelta(days=1)
tomorrow_utc_tz = tomorrow_utc.astimezone(tz)
tomorrow_utc_tz = tomorrow_utc_tz.replace(hour=today.hour,
minute=today.minute,
second=today.second)
return tomorrow_utc_tz
def add_day_datetime_timedelta_conversion(today):
# Correct for Samoa, but dst shift
today_utc = today.astimezone(datetime.timezone.utc)
tz = today.tzinfo
tomorrow_utc = today_utc + datetime.timedelta(days=1)
tomorrow_utc_tz = tomorrow_utc.astimezone(tz)
return tomorrow_utc_tz
def add_day_dateutil_relativedelta(today):
# WRONG!
from dateutil.relativedelta import relativedelta
return today + relativedelta(days=1)
def add_day_datetime_timedelta(today):
# WRONG!
return today + datetime.timedelta(days=1)
# Test cases
def test_samoa(add_day):
"""
Test if add_day properly increases the calendar day for Samoa.
Due to economic considerations, Samoa went from 2011-12-30 10:00-11:00
to 2011-12-30 10:00+13:00. Hence the country skipped 2011-12-30 in its
local time.
See https://stackoverflow.com/q/52084423/562769
A common wrong result here is 2011-12-30T23:59:00-10:00. This date never
happened in Samoa.
"""
tz = pytz.timezone('Pacific/Apia')
today_utc = datetime.datetime(2011, 12, 30, 9, 59,
tzinfo=datetime.timezone.utc)
today_tz = today_utc.astimezone(tz) # 2011-12-29T23:59:00-10:00
tomorrow = add_day(today_tz)
return tomorrow.isoformat() == '2011-12-31T23:59:00+14:00'
def test_dst(add_day):
"""Test if add_day properly increases the calendar day if DST happens."""
tz = pytz.timezone('Europe/Berlin')
today_utc = datetime.datetime(2018, 3, 25, 0, 59,
tzinfo=datetime.timezone.utc)
today_tz = today_utc.astimezone(tz) # 2018-03-25T01:59:00+01:00
tomorrow = add_day(today_tz)
return tomorrow.isoformat() == '2018-03-26T01:59:00+02:00'
to_test = [(add_day_dateutil_relativedelta, 'relativedelta'),
(add_day_datetime_timedelta, 'timedelta'),
(add_day_datetime_timedelta_conversion, 'timedelta+conversion'),
(add_day, 'timedelta+conversion+dst')]
print('{:<25}: {:>5} {:>5}'.format('Method', 'Samoa', 'DST'))
for method, name in to_test:
print('{:<25}: {:>5} {:>5}'
.format(name,
test_samoa(method),
test_dst(method)))
Test results
Method : Samoa DST
relativedelta : 0 0
timedelta : 0 0
timedelta+conversion : 1 0
timedelta+conversion+dst : 1 1
Here is another method to add days on date using dateutil's relativedelta.
from datetime import datetime
from dateutil.relativedelta import relativedelta
print 'Today: ',datetime.now().strftime('%d/%m/%Y %H:%M:%S')
date_after_month = datetime.now()+ relativedelta(day=1)
print 'After a Days:', date_after_month.strftime('%d/%m/%Y %H:%M:%S')
Output:
Today: 25/06/2015 20:41:44
After a Days: 01/06/2015 20:41:44
Most Simplest solution
from datetime import timedelta, datetime
date = datetime(2003,8,1,12,4,5)
for i in range(5):
date += timedelta(days=1)
print(date)
This was a straightforward solution for me:
from datetime import timedelta, datetime
today = datetime.today().strftime("%Y-%m-%d")
tomorrow = datetime.today() + timedelta(1)
You can also import timedelta so the code is cleaner.
from datetime import datetime, timedelta
date = datetime.now() + timedelta(seconds=[delta_value])
Then convert to date to string
date = date.strftime('%Y-%m-%d %H:%M:%S')
Python one liner is
date = (datetime.now() + timedelta(seconds=[delta_value])).strftime('%Y-%m-%d %H:%M:%S')
A short solution without libraries at all. :)
d = "8/16/18"
day_value = d[(d.find('/')+1):d.find('/18')]
tomorrow = f"{d[0:d.find('/')]}/{int(day_value)+1}{d[d.find('/18'):len(d)]}".format()
print(tomorrow)
# 8/17/18
Make sure that "string d" is actually in the form of %m/%d/%Y so that you won't have problems transitioning from one month to the next.

Categories

Resources