Search code examples
pythondatetimetimezonetimezone-offsetpytz

Given a UTC time, get a specified timezone's midnight


Note this is not quite the same as this question. That question assumes the time you want is "now", which is not the same as for an arbitrary point in time.

I have a UTC, aware, datetime object, call it point_in_time (e.g. datetime(2017, 3, 12, 16, tzinfo=tz.tzutc())).

I have a timezone, call it location (e.g. 'US/Pacific'), because I care about where it is, but its hours offset from UTC may change throughout the year with daylight savings and whatnot.

I want to
1) get the date of point_in_time if I'm standing in location,
2) get midnight of that date if I'm standing in location.

===

I tried to simply use .astimezone(timezone('US/Pacific')) and then .replace(hours=0, ...) to move to midnight, but as you might notice about my example point_in_time, the midnight for that date is on the other side of a daylight savings switch!

The result was that I got a time representing UTC datetime(2017, 3, 12, 7), instead of a time representing UTC datetime(2017, 3, 12, 8), which is the true midnight.

EDIT:
I'm actually thinking the difference between mine and the linked question is that I'm looking for the most recent midnight in the past. That question's answer seems to be able to give a midnight that could be in the past or future, perhaps?


Solution

  • Your example highlights the perils of doing datetime arithmetic in a local time zone.

    You can probably achieve this using pytz's normalize() function, but here's the method that occurs to me:

    point_in_time = datetime(2017, 3, 12, 16, tzinfo=pytz.utc)
    pacific = pytz.timezone("US/Pacific")
    pacific_time = point_in_time.astimezone(pacific)
    pacific_midnight_naive = pacific_time.replace(hour=0, tzinfo=None)
    pacific_midnight_aware = pacific.localize(pacific_midnight_naive)
    pacific_midnight_aware.astimezone(pytz.utc)  # datetime(2017, 3, 12, 8)
    

    In other words, you first convert to Pacific time to figure out the right date; then you convert again from midnight on that date to get the correct local time.