Getting unix timestamps for the start and end of a month in Python

Question:

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?

Asked By: nikobelia

||

Answers:

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.

Answered By: Green Cloak Guy

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
Answered By: Ralf

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
Answered By: Evan Byrne
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.