0

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?

Community
  • 1
  • 1
tscizzle
  • 11,191
  • 15
  • 54
  • 88
  • Don't forget, not every day in every time zone *has* a midnight. At least, not if you define midnight as `00:00`. For example, on October 16, 2016 in `America/Sao_Paulo`, the day starts at `01:00`. [Reference here](https://www.timeanddate.com/time/change/brazil/sao-paulo?year=2016). This is more common than you might think. Several time zones around the world switch for the DST spring-forward gap right at midnight, thus going from `23:59:59.999` to `01:00:00.000`. – Matt Johnson-Pint Mar 15 '17 at 03:47

2 Answers2

1

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.

Kevin Christopher Henry
  • 46,175
  • 7
  • 116
  • 102
  • Beautiful! I actually got to this exact answer basically (but used .date() to get rid of the time/timezone as you did with your .replace()). The key thing being getting the date first with .astimezone, then clearing the time zone and re-localizing – tscizzle Mar 15 '17 at 03:00
0

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
maxymoo
  • 35,286
  • 11
  • 92
  • 119
  • Unfortunately even with that info, the problem is unresolved :( . There is some annoying combo of localizing and stripping off time that I'm sure someone has done before (and I am currently in the process of figuring out), but as noted in the question, the method I used, even when using a named timezone, yields an incorrect result. – tscizzle Mar 15 '17 at 00:29
  • i think my answer should work for you unless i've misunderstood your question, i've added an example to illustrate – maxymoo Mar 15 '17 at 00:50