81

The best I can come up with for now is this monstrosity:

>>> datetime.utcnow() \ ... .replace(tzinfo=pytz.UTC) \ ... .astimezone(pytz.timezone("Australia/Melbourne")) \ ... .replace(hour=0,minute=0,second=0,microsecond=0) \ ... .astimezone(pytz.UTC) \ ... .replace(tzinfo=None) datetime.datetime(2008, 12, 16, 13, 0) 

I.e., in English, get the current time (in UTC), convert it to some other timezone, set the time to midnight, then convert back to UTC.

I'm not just using now() or localtime() as that would use the server's timezone, not the user's timezone.

I can't help feeling I'm missing something, any ideas?

6 Answers 6

118

I think you can shave off a few method calls if you do it like this:

>>> from datetime import datetime >>> datetime.now(pytz.timezone("Australia/Melbourne")) \ .replace(hour=0, minute=0, second=0, microsecond=0) \ .astimezone(pytz.utc) 

BUT… there is a bigger problem than aesthetics in your code: it will give the wrong result on the day of the switch to or from Daylight Saving Time.

The reason for this is that neither the datetime constructors nor replace() take DST changes into account.

For example:

>>> now = datetime(2012, 4, 1, 5, 0, 0, 0, tzinfo=pytz.timezone("Australia/Melbourne")) >>> print now 2012-04-01 05:00:00+10:00 >>> print now.replace(hour=0) 2012-04-01 00:00:00+10:00 # wrong! midnight was at 2012-04-01 00:00:00+11:00 >>> print datetime(2012, 3, 1, 0, 0, 0, 0, tzinfo=tz) 2012-03-01 00:00:00+10:00 # wrong again! 

However, the documentation for tz.localize() states:

This method should be used to construct localtimes, rather than passing a tzinfo argument to a datetime constructor.

Thus, your problem is solved like so:

>>> import pytz >>> from datetime import datetime, date, time >>> tz = pytz.timezone("Australia/Melbourne") >>> the_date = date(2012, 4, 1) # use date.today() here >>> midnight_without_tzinfo = datetime.combine(the_date, time()) >>> print midnight_without_tzinfo 2012-04-01 00:00:00 >>> midnight_with_tzinfo = tz.localize(midnight_without_tzinfo) >>> print midnight_with_tzinfo 2012-04-01 00:00:00+11:00 >>> print midnight_with_tzinfo.astimezone(pytz.utc) 2012-03-31 13:00:00+00:00 

No guarantees for dates before 1582, though.

Sign up to request clarification or add additional context in comments.

5 Comments

it seems it ignores DST. Somewhere .localize()/.normalize() might be necessary.
@J.F.Sebastian: interesting! are you sure? do you have an example? it's entirely possible.
@hop: yes. Your code fails on Apr 1, 2012. See my answer
the current version returns a wrong result silently for 2014/10/19 in Brazil/East timezone because you don't use is_dst=None as in my answer.
to the first comment: there is no attribute milisecond, only microsecond
36

@hop's answer is wrong on the day of transition from Daylight Saving Time (DST) e.g., Apr 1, 2012. To fix it tz.localize() could be used:

tz = pytz.timezone("Australia/Melbourne") today = datetime.now(tz).date() midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None) utc_dt = midnight.astimezone(pytz.utc) 

The same with comments:

#!/usr/bin/env python from datetime import datetime, time import pytz # pip instal pytz tz = pytz.timezone("Australia/Melbourne") # choose timezone # 1. get correct date for the midnight using given timezone. today = datetime.now(tz).date() # 2. get midnight in the correct timezone (taking into account DST) #NOTE: tzinfo=None and tz.localize() # assert that there is no dst transition at midnight (`is_dst=None`) midnight = tz.localize(datetime.combine(today, time(0, 0)), is_dst=None) # 3. convert to UTC (no need to call `utc.normalize()` due to UTC has no # DST transitions) fmt = '%Y-%m-%d %H:%M:%S %Z%z' print midnight.astimezone(pytz.utc).strftime(fmt) 

3 Comments

I'm a bit confused. The DST switch happened at 3 a.m., so midnight on that day should still be at 14:00 UTC, not 13:00. no?
@hop: convert 2012 Mar 31 13:00 UTC to Melbourne timezone and see for yourself (it is still +11 timezone (DST), not +10 (standard))
time() is the same as time(0, 0), but shorter
4

This is more straightforward with dateutil.tz than pytz:

>>>import datetime >>>import dateutil.tz >>>midnight=(datetime.datetime .now(dateutil.tz.gettz('Australia/Melbourne')) .replace(hour=0, minute=0, second=0, microsecond=0) .astimezone(dateutil.tz.tzutc())) >>>print(midnight) 2019-04-26 14:00:00+00:00 

The tzinfo documentation recommends dateutil.tz since Python 3.6. The tzinfo objects from dateutil.tz have no problems with anomalies like DST without requiring the localize functionality of pytz. Using the example from user3850:

>>> now = (datetime.datetime(2012, 4, 1, 5, ... tzinfo = dateutil.tz.gettz('Australia/Melbourne'))) >>> print(now.replace(hour = 0).astimezone(dateutil.tz.tzutc())) 2012-03-31 13:00:00+00:00 

Comments

0

Setting the TZ environment variable modifies what timezone Python's date and time functions work with.

>>> time.gmtime() (2008, 12, 17, 1, 16, 46, 2, 352, 0) >>> time.localtime() (2008, 12, 16, 20, 16, 47, 1, 351, 0) >>> os.environ['TZ']='Australia/Melbourne' >>> time.localtime() (2008, 12, 17, 12, 16, 53, 2, 352, 1) 

1 Comment

Aside from not wanting to use the TZ variable to control this, that doesn't actually tell me how to find midnight, just the current time.
0

Each time zone has a number, eg US/Central = -6. This is defined as the offset in hours from UTC. Since 0000 is midnight, you can simply use this offset to find the time in any time zone when it is midnight UTC. To access that, I believe you can use

 time.timezone

According to The Python Docs, time.timezone actually gives the negative value of this number:

time.timezone

The offset of the local (non-DST) timezone, in seconds west of UTC (negative in most of Western Europe, positive in the US, zero in the UK).

So you would simply use that number for the time in hours if it's positive (i.e., if it's midnight in Chicago (which has a +6 timezone value), then it's 6000 = 6am UTC).

If the number is negative, subtract from 24. For example, Berlin would give -1, so 24 - 1 => 2300 = 11pm.

3 Comments

I think you're on the right track, but how do you know what date to start with? i.e. a few hours ago, it was the 17th here in Melbourne, while it was still the 16th in UTC.
The question is about local Midnight. The day relationship is fixed by the UTC offset for the timezone -- at local midnight.
adding/substracting tz differences by hand might have issues around the switch from and to DST
0

It's worth remarking that we can adapt the answer given by @jfs to find tomorrow's midnight or yesterday's midnight, etc. The trick is to add a certain number of days to the aware timezone. This works because although this usually adds 24 hours, sometimes it might add 23 or 25 based on DST issues.

from datetime import datetime, time, timedelta import pytz def midnight_UTC(offset): # Construct a timezone object tz = pytz.timezone('Australia/Melbourne') # Work out today/now as a timezone-aware datetime today = datetime.now(tz) # Adjust by the offset. Note that that adding 1 day might actually move us 23 or 25 # hours into the future, depending on daylight savings. This works because the {today} # variable is timezone aware target_day = today + timedelta(days=1) * offset # Discard hours, minutes, seconds and microseconds midnight_aware = tz.localize( datetime.combine(target_day, time(0, 0, 0, 0)), is_dst=None) # Convert to UTC midnight_UTC = midnight_aware.astimezone(pytz.utc) return midnight_UTC print("The UTC time of the previous midnight is:", midnight_UTC(0)) print("The UTC time of the upcoming midnight is:", midnight_UTC(1)) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.