How do I know if today is a day due to change civil local time e.g. daylight saving time in standard python and pandas timestamps? - python-3.x

According to the rules of British Summer Time / daylight saving time (https://www.gov.uk/when-do-the-clocks-change) the clocks:
go forward 1 hour at 1am on the last Sunday in March,
go back 1 hour at 2am on the last Sunday in October.
In 2019 this civil local time change happens on March 31st and October 27th, but the days slightly change every year. Is there a clean way to know these dates for each input year?
I need to check these "changing time" dates in an automatic way, is there a way to avoid a for loop to check the details of each date to see if it is a "changing time" date?
At the moment I am exploring these dates for 2019 just to try to figure out a reproducible/automatic procedure and I found this:
# using datetime from the standard library
march_utc_30 = datetime.datetime(2019, 3, 30, 0, 0, 0, 0, tzinfo=datetime.timezone.utc)
march_utc_31 = datetime.datetime(2019, 3, 31, 0, 0, 0, 0, tzinfo=datetime.timezone.utc)
april_utc_1 = datetime.datetime(2019, 4, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc)
# using pandas timestamps
pd_march_utc_30 = pd.Timestamp(march_utc_30) #, tz='UTC')
pd_march_utc_31 = pd.Timestamp(march_utc_31) #, tz='UTC')
pd_april_utc_1 = pd.Timestamp(april_utc_1) #, tz='UTC')
# using pandas wrappers
pd_local_march_utc_30 = pd_march_utc_30.tz_convert('Europe/London')
pd_local_march_utc_31 = pd_march_utc_31.tz_convert('Europe/London')
pd_local_april_utc_1 = pd_april_utc_1.tz_convert('Europe/London')
# then printing all these dates
print("march_utc_30 {} pd_march_utc_30 {} pd_local_march_utc_30 {}".format(march_utc_30, pd_march_utc_30, pd_local_march_utc_30))
print("march_utc_31 {} pd_march_utc_31 {} pd_local_march_utc_31 {}".format(march_utc_31, pd_march_utc_31, pd_local_march_utc_31))
print("april_utc_1 {} pd_april_utc_1 {} pd_local_april_utc_1 {}".format(april_utc_1, pd_april_utc_1, pd_local_april_utc_1))
The output of those print statements is:
march_utc_30 2019-03-30 00:00:00+00:00 pd_march_utc_30 2019-03-30 00:00:00+00:00 pd_local_march_utc_30 2019-03-30 00:00:00+00:00
march_utc_31 2019-03-31 00:00:00+00:00 pd_march_utc_31 2019-03-31 00:00:00+00:00 pd_local_march_utc_31 2019-03-31 00:00:00+00:00
april_utc_1 2019-04-01 00:00:00+00:00 pd_april_utc_1 2019-04-01 00:00:00+00:00 pd_local_april_utc_1 2019-04-01 01:00:00+01:00
I could use a for loop to find out if the current date is the last Sunday of the month, or compare the "hour delta" between the current date and the date of the day after to see if there is a +1, but I am wondering if there is a cleaner way to do this?
Is there something attached to the year e.g. knowing the input year is 2019 then we know for sure the "change date" in March will be day 31st?

Using dateutil.rrule can help (install with pip install python-dateutil).
Because we can fetch dates by weeks, we don't need any loops,
from dateutil.rrule import rrule, WEEKLY
from dateutil.rrule import SU as Sunday
from datetime import date
import datetime
def get_last_sunday(year, month):
date = datetime.datetime(year=year, month=month, day=1)
# we can find max 5 sundays in a months
days = rrule(freq=WEEKLY, dtstart=date, byweekday=Sunday, count=5)
# Check if last date is same month,
# If not this couple year/month only have 4 Sundays
if days[-1].month == month:
return days[-1]
else:
return days[-2]
def get_march_switch(year):
# Get 5 next Sundays from first March
day = get_last_sunday(year, 3)
return day.replace(hour=1, minute=0, second=0, microsecond=0)
def get_october_switch(year):
day = get_last_sunday(year, 10)
return day.replace(hour=2, minute=0, second=0, microsecond=0)
print('2019:')
print(' {}'.format(get_march_switch(2019)))
print(' {}'.format(get_october_switch(2019)))
print('2021:')
print(' {}'.format(get_march_switch(2021)))
print(' {}'.format(get_october_switch(2021)))
get_sundays() returns the 5 next sundays from the first day of the given month, because a month can have maximum 5 sundays.
Then I just check (within get_(march|october)_switch()) if the last given sunday is from the expected month, if not well this month only have 4 sunday, I took this one.
Finally I fix the hours, seconds and microseconds.
Output:
2019:
2019-03-31 01:00:00
2019-10-27 02:00:00
2021:
2021-03-28 01:00:00
2021-10-24 02:00:00

I know the topic is quite old now. However, I had the same question today, and at the end I found a solution which seems quite simple to me, using only the standard datetime:
I want to check whether my date refdate is the October DST day - I did it in the following way:
refdate is my standard datetime object.
If you have a panda timestamp, you can convert it to native datetime using .to_pydatetime()
if refdate.month == 10 and refdate.weekday() == 6 and (refdate + dt.timedelta(weeks = 1)).month == 11:
oct_dst = 1

Related

Python3 Get epoch time first and last day of month

Given a month and a year, such as 03 2022, I need to get the epoch time of the first day of the month and the last day of the month. I am not sure how to do that. Any help would be appreciated. Thank you.
You can get the beginning of the month easily by setting the day to 1.
To get the end of the month conveniently, you can calculate the first day of the next month, then go back one day.
Then set the time zone (tzinfo) to UTC to prevent Python using local time.
Finally a call to .timestamp()
import datetime
def date_to_endofmonth(
dt: datetime.datetime, offset: datetime.timedelta = datetime.timedelta(days=1)
) -> datetime.datetime:
"""
Roll a datetime to the end of the month.
Parameters
----------
dt : datetime.datetime
datetime object to use.
offset : datetime.timedelta, optional.
Offset to the next month. The default is 1 day; datetime.timedelta(days=1).
Returns
-------
datetime.datetime
End of month datetime.
"""
# reset day to first day of month, add one month and subtract offset duration
return (
datetime.datetime(
dt.year + ((dt.month + 1) // 12), ((dt.month + 1) % 12) or 12, 1
)
- offset
)
year, month = 2022, 11
# make datetime objects; make sure to set UTC
dt_month_begin = datetime.datetime(year, month, 1, tzinfo=datetime.timezone.utc)
dt_month_end = date_to_endofmonth(dt_month_begin).replace(tzinfo=datetime.timezone.utc)
ts_month_begin = dt_month_begin.timestamp()
ts_month_end = dt_month_end.timestamp()
print(ts_month_begin, ts_month_end)
# 1667260800.0 1701302400.0
Unable to comment due to reputation but #FObersteiner is excellent just I would recommend a small change.
For example running the current code it would produce this for Nov 2022
print(dt_month_begin.timestamp())
print(dt_month_begin)
print(dt_month_end.timestamp())
print(dt_month_end)
--->
1667260800.0
2022-11-01 00:00:00+00:00
1701302400.0
2023-11-30 00:00:00+00:00
Note the year field
I'd suggest the following
import datetime
def date_to_endofmonth(
dt: datetime.datetime, offset: datetime.timedelta = datetime.timedelta(seconds=1)
) -> datetime.datetime:
"""
Roll a datetime to the end of the month.
Parameters
----------
dt : datetime.datetime
datetime object to use.
offset : datetime.timedelta, optional.
Offset to the next month. The default is 1 second; datetime.timedelta(seconds=1).
Returns
-------
datetime.datetime
End of month datetime.
"""
# reset day to first day of month, add one month and subtract offset duration
return (
datetime.datetime(
dt.year + ((dt.month + 1) // 13), ((dt.month + 1) % 12) or 12, 1
)
- offset
)
year, month = 2022, 11
# make datetime objects; make sure to set UTC
dt_month_begin = datetime.datetime(year, month, 1, tzinfo=datetime.timezone.utc)
dt_month_end = date_to_endofmonth(dt_month_begin).replace(tzinfo=datetime.timezone.utc)
Differences being floor division by 13 instead of 12 to handle the month of November.
Changing the offset to seconds delta because I felt the user ( and myself who came looking for the answer) wanted the starting epoch time and the ending epoch time so
Nov 1st 00:00:00 --> Nov 30th 23:59:59 would be better than
Nov 1st 00:00:00 --> Nov 30th 00:00:00 ( Losing a day worth of seconds)
Output of the above would be
:
1667260800.0
2022-11-01 00:00:00+00:00
1669852799.0
2022-11-30 23:59:59+00:00

How to calculate date of birth, given age expressed in years, months, days, as reported on a given date?

I have a 2 months old dataset, from 15 October 2020, containing the ages of different persons. The age of each person is given in years, months, days.
Example
Input: Reference date = 15 October 2020
One entry in the input data set could be:
Input: Age = 21 years, 0 months, 10 days
Corresponding output: Date of Birth = 25 September 1999
This takes into account that their age was reported on 15 October 2020.
I have to convert this data set to get the Date of Birth of each person. How can I accomplish that?
You can use the dateutil.relativedelta function, which you can use to subtract years, months and days from another date.
I'll assume that you know the exact date on which the data was collected, ... let's say 15 October 2020:
from datetime import date
from dateutil.relativedelta import relativedelta
# Let's say the data was taken on 15 October 2020:
refdate = date(2020, 10, 15)
# The data itself: triplets of years, months, days as collected on 15 October 2020:
data = [
[20, 2, 1],
[53, 5, 10],
]
# Extend the data with the birthdate:
for person in data:
person.append(refdate - relativedelta(years=person[0],
months=person[1],
days=person[2]))
# Print the data:
for person in data:
print("{} years, {} months, {} days: born on {}".format(*person))

Round Pandas date to nearest year/month

I am trying to round a pandas datetime column to its nearest year or month but I cannot figure out how to do it. For instance, this minimal example rounds to the closest hour:
pd.Timestamp.now().round('60min')
What I'd like is a way to replace the '60min' in order to round pd.Timestamp.now() to obtain either 2020-01-01 (for the year case) or 2019-08-01 (for the month case) (note that now() is exactly 2019-07-30 16:41:23.612004 at the time of asking!).
The pandas.Series.dt.round doc suggest a freq argument linking to this page, but trying the months/years options there return this error:
ValueError: is a non-fixed frequency
Any idea what I am missing?
If the column is really DateTime column (check with df.dtypes), you can get the year, month & day with the code below.
df['Year'] = df['Date'].dt.year
df['Month'] = df['Date'].dt.month
df['Day'] = df['Date'].dt.day
df['round_Year'] = df['Date']+ pd.offsets.YearBegin(-1)
rounds off to start of current year. Change -1 to 0 rounds off to start of next year.
df['round_Month'] = df['Date'] + pd.offsets.MonthBegin(-1)
rounds off to start of current Month. Change -1 to 0 rounds off to start of next Month
Example of rounding a Python Timestamp to the nearest half year:
from Pandas import Timestamp
def round_date_to_nearest_half_year(ts: Timestamp) -> Timestamp:
if 4 <= ts.month <=8:
return Timestamp(ts.year, 7, 1)
elif ts.month >=9:
return Timestamp(ts.year+1, 1, 1)
elif ts.month <= 3:
return Timestamp(ts.year, 1, 1)
else:
raise Exception("Logic error.")
Test:
print(round_date_to_nearest_half_year(Timestamp("2022-6-5")))
print(round_date_to_nearest_half_year(Timestamp("2022-7-3")))
print(round_date_to_nearest_half_year(Timestamp("2022-12-15")))
print(round_date_to_nearest_half_year(Timestamp("2023-1-5")))
Out:
2022-07-01 00:00:00
2022-07-01 00:00:00
2023-01-01 00:00:00
2023-01-01 00:00:00

find the last business date for June and Dec of any given year in Python

I want to find the last business day for the month of June and Dec for 2019. my below code gives me the last business day for last month and the current month. Ideally i want a code which allows me to input a year and month and it will tell me the last business day for the month and year i input.
i want my output to be like this for June 2019 and Dec 2019
2906
3112
hope someone can help me here, thanks
from pandas.tseries.offsets import BMonthEnd
from datetime import date
d=date.today()
offset = BMonthEnd()
#Last day of current month
current_month = offset.rollforward(d)
print(current_month)
#Last day of previous month
last_month = offset.rollback(d)
print(last_month)
Here's a function that will find the last weekday in a given month and format it as a string in the format given
import datetime
def last_weekday(year, month):
# Add a month
if month == 12:
month = 1
year += 1
else:
month += 1
d = datetime.date(year, month, 1)
# Subtract a day
d -= datetime.timedelta(days=1)
# Subtract more days if we have a weekend
while d.weekday() >= 5:
d -= datetime.timedelta(days=1)
return '{:02d}{:02d}'.format(d.month, d.day)
# Examples:
last_weekday(2019, 6) # -> '0628'
last_weekday(2019, 12) # -> '1231'

total number of days from current date if we want to know like after 2 weeks, 3 months , 1 year

I want to know the total number of days from current date.
if given date is 2 Feb 2018. and i want to calculate number of days after 2 weeks/3 months/1 year. keeping in mind of leap year or not.
Language - python3
Is there any library in python which give me this.
The dateutil module has a way to do this very conveniently. Sample code:
import datetime
import dateutil
today = datetime.date.today()
delta = dateutil.relativedelta.relativedelta(years=1, months=3, weeks=2)
desired_date = today + delta
desired_date
Output:
datetime.date(2019, 5, 16)

Resources