tscizzle
tscizzle

Reputation: 12271

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?

Upvotes: 0

Views: 1344

Answers (2)

Kevin Christopher Henry
Kevin Christopher Henry

Reputation: 49132

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.

Upvotes: 1

maxymoo
maxymoo

Reputation: 36555

Named timezones such as "US/Pacific" are by definition daylight-savings aware. If you wish to use a fixed non-daylight-savings-aware offset from GMT you can use the timezones "Etc/GMT+*", where * is the desired offset. For example for US Pacific Standard Time you would use "Etc/GMT+8":

import pandas as pd
point_in_time = pd.to_datetime('2017-03-12 16:00:00').tz_localize('UTC')

# not what you want
local_time = point_in_time.tz_convert("US/Pacific")
(local_time - pd.Timedelta(hours=local_time.hour)).tz_convert('UTC')
# Timestamp('2017-03-12 07:00:00+0000', tz='UTC')

# what you want
local_time = point_in_time.tz_convert("Etc/GMT+8")
(local_time - pd.Timedelta(hours=local_time.hour)).tz_convert('UTC')
# Timestamp('2017-03-12 08:00:00+0000', tz='UTC')

See the docs at http://pvlib-python.readthedocs.io/en/latest/timetimezones.html for more info.

EDIT Now that I think about it, Midnight PST will always be 8am UTC, so you could simplify this as

if point_in_time.hour >=8:
    local_midnight = point_in_time - point_in_time.hour + 8
else:
    local_midnight = point_in_time - point_in_time.hour - 16

Upvotes: 0

Related Questions