Reputation: 4887
Hello StackOverflow Python community!
I want to generate Unix timestamps for the start and end of a month. I'm looking for a way to retrieve the last day (or second, or minute...) in an arbitrary month in Python.
Given datetime.date
values for two consecutive months, I can get to the last value in one month by subtracting a delta value from the next like this:
import datetime
# dates without time
jan = datetime.date(2019, 1, 1)
feb = datetime.date(2019, 2, 1)
# start/end times
start_time = datetime.time(0, 0, 0, 0, tzinfo=datetime.timezone.utc)
end_time = datetime.time(23, 59, 59, 999, tzinfo=datetime.timezone.utc)
jan_start = datetime.datetime.combine(jan, start_time)
last_day = feb - datetime.timedelta (days = 1)
jan_end = datetime.datetime.combine(last_day, end_time)
do_stuff(jan_start, jan_end)
But my minimally-python-literate self is stumbling trying to find a way to plug arbitrary months in and get the same results. I'd like to end up with something like:
import datetime
# start/end times
start_time = datetime.time(0, 0, 0, 0, tzinfo=datetime.timezone.utc)
end_time = datetime.time(23, 59, 59, 999, tzinfo=datetime.timezone.utc)
dates = {
"Jan19": datetime.date(2019, 1, 1),
"Feb19": datetime.date(2019, 2, 1),
"Mar19": datetime.date(2019, 3, 1),
"Apr19": datetime.date(2019, 4, 1)
}
for i in dates:
start = datetime.datetime.combine(dates[i], start_time)
end_day = dates[i].next() - datetime.timedelta (days = 1)
end = datetime.datetime.combine(end_day, end_time)
do_stuff(start, end)
Only, whoops - Python dictionaries are unordered, so there isn't a next() method I can call on i
!
It's possible the conventional way to do this is to use midnight on February 1st as equivalent to the last second in January 31st, but I'm curious as to how one can retrieve the last day (or second, or minute...) in an arbitrary month in Python.
Would I need to use an OrderedList, or is there some module out there immune to my googling that has a month.max()
property I could use instead of working it out dynamically?
Upvotes: 1
Views: 2866
Reputation: 11
This can be achieved by using calendar module. This can handle leap years as well:
import calendar
def last_day(year, month):
num_days = calendar.monthrange(year, month)[1]
return num_days
end_day=last_day(2023, 5)
print(end_day)
# Leap Year
leap_year_feb=last_day(2024, 2)
print (leap_year_feb)
Upvotes: 0
Reputation: 1155
Because years always have 12 months, you can increment the month and avoid making assumptions about the number of days in a month altogether. Then use timestamp from Python 3.3+ to get the Unix time:
import datetime
def add_month(month: int, year: int) -> tuple[int, int]:
if month < 12:
return (month + 1, year)
return (1, year + 1)
# ...
feb = datetime.date(2019, 2, 1)
end_month, end_year = add_month(feb.month, feb.year)
end_of_month = (datetime.datetime(end_year, end_month, 1, tzinfo=datetime.timezone.utc) - datetime.timedelta(seconds=1)).timestamp() # 1551398399.0
start_of_month = datetime.datetime(feb.year, feb.month, 1, tzinfo=datetime.timezone.utc).timestamp() # 1548979200.0
Upvotes: 0
Reputation: 16505
To get the first day of the next month, one option (there are others) could be:
>>> import datetime
>>> my_date = datetime.datetime(2019, 4, 5)
>>> my_date
datetime.datetime(2019, 4, 5, 0, 0)
>>> first_day_this_month = datetime.datetime(my_date.year, my_date.month, 1)
>>> first_day_this_month
datetime.datetime(2019, 4, 1, 0, 0)
>>> some_day_next_month = first_day_this_month + datetime.timedelta(days=32)
>>> some_day_next_month
datetime.datetime(2019, 5, 3, 0, 0)
>>> first_day_next_month = datetime.datetime(some_day_next_month.year, some_day_next_month.month, 1)
>>> first_day_next_month
datetime.datetime(2019, 5, 1, 0, 0)
Now to get the last second of the current month, one could do:
>>> last_second_this_month = first_day_next_month - datetime.timedelta(seconds=1)
>>> last_second_this_month
datetime.datetime(2019, 4, 30, 23, 59, 59)
>>> import time
>>> time.mktime(last_second_this_month.timetuple())
1556683199.0
Upvotes: 4
Reputation: 24691
Rather than trying to find "the last second of the month", you'd be better off just getting "the first moment of the next month" (which is much easier to find), and then modify your do_stuff()
function to simply exclude the second argument.
You can do this in one of a couple ways. First, using <
instead of <=
. Or second, by simply subtracting an arbitrarily small timedelta from the first moment of the next month:
# microseconds is the smallest granularity that python datetime supports
end = datetime.datetime.combine(end_day, start_time) - datetime.timedelta(microseconds=1)
Depending on what you actually want to do here, I'm personally working on a module called ranges
which would provide a continuous Range
class (so you could go from January 1st inclusive to February 1st exclusive), and will probably be released by mid-september 2019. This might be an easier way to cover your use case once it's finished, I dunno.
Upvotes: 0