I'm facing a python timezones problem and am unsure of what is the right approach to deal with it. I have to calculate timedeltas from given start and end DateTime objects. It can happen that daylight saving time will change during the runtime of my events, so I have to take that into account.
So far I've learned that for this to work I need to save my start and end times as timezone aware DateTime objects rather than regular UTC DateTimes.
I've been looking into DateTime.tzinfo, pytz,and dateutil but from what I understand these are all mostly focused on localised display of UTC DateTime objects or calculating the offsets between different timezones. Other helpers I found expect the timezone as a UTC offset, so would already require me to know if a date is affected by daylight saving or not.
So, I guess my question is: Is there a way so save a DateTime as "Central Europe" and have it be aware of daytime savings when doing calculations with them? Or, if not, what would be the established way to check if two DateTime objects are within daylight saving, so I can manually adjust the result if necessary?
I'd be grateful for any pointers.
You just need to produce an aware (localised) datetime instance, then any calculation you do with it will take DST into account. Here as an example with pytz:
>>> import pytz
>>> from datetime import *
>>> berlin = pytz.timezone('Europe/Berlin')
>>> d1 = berlin.localize(datetime(2023, 3, 25, 12))
datetime.datetime(2023, 3, 25, 12, 0, tzinfo=<DstTzInfo 'Europe/Berlin' CET+1:00:00 STD>)
>>> d2 = berlin.localize(datetime(2023, 3, 26, 12))
datetime.datetime(2023, 3, 26, 12, 0, tzinfo=<DstTzInfo 'Europe/Berlin' CEST+2:00:00 DST>)
>>> d2 - d1
datetime.timedelta(seconds=82800)
>>> (d2 - d1).total_seconds() / 60 / 60
23.0
I have the following string representing a UTC timestamp: 2017-12-03T20:38:00.971261Z
I would like to convert it into Posix timestamp (IE: seconds since the epoch)
Using this online converter (https://www.epochconverter.com/) I know the answer is 1512333480
But when I do the following code, the result is off by 1800 seconds -- 30 minutes:
>>> temp_time1 = datetime.datetime.strptime('2017-12-03T20:38:00.971261Z', '%Y-%m-%dT%H:%M:%S.%fZ')
>>> ctime = int(datetime.datetime(temp_time1.year,
temp_time1.month,
temp_time1.day,
temp_time1.hour,
temp_time1.minute,
temp_time1.second,
temp_time1.microsecond,
pytz.timezone('Europe/London')).strftime('%s'))
>>> print ctime
1512351480
Anyone know what I'm missing here??
You created a new timestamp and put it in the Europe/London timezone. That is not the same thing as UTC. The Europe/London timezone from the PyTZ database includes historical offsets, and those affect how datetime.datetime() interprets the timezone.
Just use the datetime.timestamp() method on the datetime object you already parsed from the string:
>>> import datetime
>>> temp_time1 = datetime.datetime.strptime('2017-12-03T20:38:00.971261Z', '%Y-%m-%dT%H:%M:%S.%fZ')
>>> temp_time1.timestamp()
1512333480.971261
Your original temp_time1 datetime object is timezone agnostic, so the timestamp() object already assumes no timezone conversion has to take place.
If you must apply the Europe/London timezone first for whatever reason, then at least use the timezone.localize() method to get the right offset applied:
>>> import pytz
>>> pytz.timezone('Europe/London').localize(temp_time1)
datetime.datetime(2017, 12, 3, 20, 38, 0, 971261, tzinfo=<DstTzInfo 'Europe/London' GMT0:00:00 STD>)
>>> pytz.timezone('Europe/London').localize(temp_time1).timestamp()
1512333480.971261
See How to make an unaware datetime timezone aware in python
For Python 2 and Python 3.0, 3.1 or 3.2, where datetime.timestamp() is not available, subtract the epoch date:
>>> (temp_time1 - datetime.datetime(1970, 1, 1)).total_seconds()
1512333480.971261
Add in the UTC timezone when dealing with timezone-aware datetime instances:
>>> (pytz.timezone('Europe/London').localize(temp_time1) - datetime.datetime(1970, 1, 1, tzinfo=pytz.utc)).total_seconds()
1512333480.971261
Combined into a function:
def datetime_to_timestamp(dt, epoch=datetime.datetime(1970, 1, 1)):
if dt.tzinfo is not None:
epoch = pytz.utc.localize(epoch)
return (dt - epoch).total_seconds()
I want to compare UTC timestamps from a log file with local timestamps. When creating the local datetime object, I use something like:
>>> local_time=datetime.datetime(2010, 4, 27, 12, 0, 0, 0,
tzinfo=pytz.timezone('Israel'))
I want to find an automatic tool that would replace thetzinfo=pytz.timezone('Israel') with the current local time zone.
Any ideas?
In Python 3.x, local timezone can be figured out like this:
>>> import datetime
>>> print(datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo)
AEST
It's a tricky use of datetime's code .
For python < 3.6, you'll need
>>> import datetime
>>> print(datetime.datetime.now(datetime.timezone(datetime.timedelta(0))).astimezone().tzinfo)
AEST
Try dateutil, which has a tzlocal type that does what you need.
to compare UTC timestamps from a log file with local timestamps.
It is hard to find out Olson TZ name for a local timezone in a portable manner. Fortunately, you don't need it to perform the comparison.
tzlocal module returns a pytz timezone corresponding to the local timezone:
from datetime import datetime
import pytz # $ pip install pytz
from tzlocal import get_localzone # $ pip install tzlocal
tz = get_localzone()
local_dt = tz.localize(datetime(2010, 4, 27, 12, 0, 0, 0), is_dst=None)
utc_dt = local_dt.astimezone(pytz.utc) #NOTE: utc.normalize() is unnecessary here
Unlike other solutions presented so far the above code avoids the following issues:
local time can be ambiguous i.e., a precise comparison might be impossible for some local times
utc offset can be different for the same local timezone name for dates in the past. Some libraries that support timezone-aware datetime objects (e.g., dateutil) fail to take that into account
Note: to get timezone-aware datetime object from a naive datetime object, you should use*:
local_dt = tz.localize(datetime(2010, 4, 27, 12, 0, 0, 0), is_dst=None)
instead of:
#XXX fails for some timezones
local_dt = datetime(2010, 4, 27, 12, 0, 0, 0, tzinfo=tz)
*is_dst=None forces an exception if given local time is ambiguous or non-existent.
If you are certain that all local timestamps use the same (current) utc offset for the local timezone then you could perform the comparison using only stdlib:
# convert a naive datetime object that represents time in local timezone to epoch time
timestamp1 = (datetime(2010, 4, 27, 12, 0, 0, 0) - datetime.fromtimestamp(0)).total_seconds()
# convert a naive datetime object that represents time in UTC to epoch time
timestamp2 = (datetime(2010, 4, 27, 9, 0) - datetime.utcfromtimestamp(0)).total_seconds()
timestamp1 and timestamp2 can be compared directly.
Note:
timestamp1 formula works only if the UTC offset at epoch (datetime.fromtimestamp(0)) is the same as now
fromtimestamp() creates a naive datetime object in the current local timezone
utcfromtimestamp() creates a naive datetime object in UTC.
I was asking the same to myself, and I found the answer in 1:
Take a look at section 8.1.7: the format "%z" (lowercase, the Z uppercase returns also the time zone, but not in the 4-digit format, but in the form of timezone abbreviations, like in [3]) of strftime returns the form "+/- 4DIGIT" that is standard in email headers (see section 3.3 of RFC 2822, see [2], which obsoletes the other ways of specifying the timezone for email headers).
So, if you want your timezone in this format, use:
time.strftime("%z")
[1] http://docs.python.org/2/library/datetime.html
[2] https://www.rfc-editor.org/rfc/rfc2822#section-3.3
[3] Timezone abbreviations: http://en.wikipedia.org/wiki/List_of_time_zone_abbreviations , only for reference.
The following appears to work for 3.7+, using standard libs:
from datetime import timedelta
from datetime import timezone
import time
def currenttz():
if time.daylight:
return timezone(timedelta(seconds=-time.altzone),time.tzname[1])
else:
return timezone(timedelta(seconds=-time.timezone),time.tzname[0])
First get pytz and tzlocal modules
pip install pytz tzlocal
then
from tzlocal import get_localzone
local = get_localzone()
then you can do things like
from datetime import datetime
print(datetime.now(local))
Here's a way to get the local timezone using only the standard library, (only works in a *nix environment):
>>> '/'.join(os.path.realpath('/etc/localtime').split('/')[-2:])
'Australia/Sydney'
You can use this to create a pytz timezone:
>>> import pytz
>>> my_tz_name = '/'.join(os.path.realpath('/etc/localtime').split('/')[-2:])
>>> my_tz = pytz.timezone(my_tz_name)
>>> my_tz
<DstTzInfo 'Australia/Sydney' LMT+10:05:00 STD>
...which you can then apply to a datetime:
>>> import datetime
>>> now = datetime.datetime.now()
>>> now
datetime.datetime(2014, 9, 3, 9, 23, 24, 139059)
>>> now.replace(tzinfo=my_tz)
>>> now
datetime.datetime(2014, 9, 3, 9, 23, 24, 139059, tzinfo=<DstTzInfo 'Australia/Sydney' LMT+10:05:00 STD>)
Here's a slightly more concise version of #vbem's solution:
from datetime import datetime as dt
dt.utcnow().astimezone().tzinfo
The only substantive difference is that I replaced datetime.datetime.now(datetime.timezone.utc) with datetime.datetime.utcnow(). For brevity, I also aliased datetime.datetime as dt.
For my purposes, I want the UTC offset in seconds. Here's what that looks like:
dt.utcnow().astimezone().utcoffset().total_seconds()
Avoiding non-standard module (seems to be a missing method of datetime module):
from datetime import datetime
utcOffset_min = int(round((datetime.now() - datetime.utcnow()).total_seconds())) / 60 # round for taking time twice
utcOffset_h = utcOffset_min / 60
assert(utcOffset_min == utcOffset_h * 60) # we do not handle 1/2 h timezone offsets
print 'Local time offset is %i h to UTC.' % (utcOffset_h)
To create an ISO formatted string that includes the ISO representation of your local time zone in Israel (+04:00) :
on a server in Israel:
>>> datetime.now(datetime.now().astimezone().tzinfo).isoformat()
'2021-09-07T01:02.030042+04:00'
This will create a "timezone aware" date object that will compare to any other datetime object in UTC or local time appropriately. But the time zone ISO representation (and the date/time string itself) will change if you ran this on a server in San Francisco at the exact same time, as I did:
on a server in San Francisco, CA, USA (Pacific):
>>> datetime.now(datetime.now().astimezone().tzinfo).isoformat()
'2021-09-06T14:01:02.030042-07:00'
The datetime objects in in both cases would be compatible with each other. So if you subtracted them you'd get a time delta of 0:
On a server anywhere in Python3.6+:
>>> (datetime.fromisoformat('2021-09-06T14:01:02.030042-07:00') -
... datetime.fromisoformat('2021-09-07T01:01:02.030042+04:00'))
datetime.timedelta(0)
Based on Thoku's answer above, here's an answer that resolves the time zone to the nearest half hour (which is relevant for some timezones eg South Australia's) :
from datetime import datetime
round((round((datetime.now()-datetime.utcnow()).total_seconds())/1800)/2)
Based on J. F. Sebastian's answer, you can do this with the standard library:
import time, datetime
local_timezone = datetime.timezone(datetime.timedelta(seconds=-time.timezone))
Tested in 3.4, should work on 3.4+
You may be happy with pendulum
>>> pendulum.datetime(2015, 2, 5, tz='local').timezone.name
'Israel'
Pendulum has a well designed API for manipulating dates. Everything is TZ-aware.
I have also been looking for a simple way to read the local host configuration and get timezone aware local_time based on it. As of python 3.6+ the simplest approach is use dateutil.tz which will read /etc/localtime and assist in getting timezone aware datetime object.
Here is more info on it: https://dateutil.readthedocs.io/en/stable/tz.html
The implementation to accomplish what you're looking for is as follows:
from datetime import datetime
from dateutil import tz
local_time = datetime.now(tz.gettz())
This will provide you the following local_time:
2019-10-18 13:41:06.624536-05:00
Additional Resources I used in researching this topic:
Paul Ganssle Presentation about time zones:
https://www.youtube.com/watch?v=l4UCKCo9FWY
pytz: The Fastest Footgun in the West
https://blog.ganssle.io/articles/2018/03/pytz-fastest-footgun.html
I want to compare UTC timestamps from a log file with local timestamps
If this is your intent, then I wouldn't worry about specifying specific tzinfo parameters or any additional external libraries. Since Python 3.5, the built in datetime module is all you need to create a UTC and a local timestamp automatically.
import datetime
f = "%a %b %d %H:%M:%S %Z %Y" # Full format with timezone
# tzinfo=None
cdatetime = datetime.datetime(2010, 4, 27, 12, 0, 0, 0) # 1. Your example from log
cdatetime = datetime.datetime.now() # 2. Basic date creation (default: local time)
print(cdatetime.strftime(f)) # no timezone printed
# Tue Apr 27 12:00:00 2010
utctimestamp = cdatetime.astimezone(tz=datetime.timezone.utc) # 1. convert to UTC
utctimestamp = datetime.datetime.now(tz=datetime.timezone.utc) # 2. create in UTC
print(utctimestamp.strftime(f))
# Tue Apr 27 17:00:00 UTC 2010
localtimestamp = cdatetime.astimezone() # 1. convert to local [default]
localtimestamp = datetime.datetime.now().astimezone() # 2. create with local timezone
print(localtimestamp.strftime(f))
# Tue Apr 27 12:00:00 CDT 2010
The '%Z' parameter of datetime.strftime() prints the timezone acronym into the timestamp for humans to read.
For simple things, the following tzinfo implementation can be used, which queries the OS for time zone offsets:
import datetime
import time
class LocalTZ(datetime.tzinfo):
_unixEpochOrdinal = datetime.datetime.utcfromtimestamp(0).toordinal()
def dst(self, dt):
return datetime.timedelta(0)
def utcoffset(self, dt):
t = (dt.toordinal() - self._unixEpochOrdinal)*86400 + dt.hour*3600 + dt.minute*60 + dt.second + time.timezone
utc = datetime.datetime(*time.gmtime(t)[:6])
local = datetime.datetime(*time.localtime(t)[:6])
return local - utc
print datetime.datetime.now(LocalTZ())
print datetime.datetime(2010, 4, 27, 12, 0, 0, tzinfo=LocalTZ())
# If you're in the EU, the following datetimes are right on the DST change.
print datetime.datetime(2013, 3, 31, 0, 59, 59, tzinfo=LocalTZ())
print datetime.datetime(2013, 3, 31, 1, 0, 0, tzinfo=LocalTZ())
print datetime.datetime(2013, 3, 31, 1, 59, 59, tzinfo=LocalTZ())
# The following datetime is invalid, as the clock moves directly from
# 01:59:59 standard time to 03:00:00 daylight savings time.
print datetime.datetime(2013, 3, 31, 2, 0, 0, tzinfo=LocalTZ())
print datetime.datetime(2013, 10, 27, 0, 59, 59, tzinfo=LocalTZ())
print datetime.datetime(2013, 10, 27, 1, 0, 0, tzinfo=LocalTZ())
print datetime.datetime(2013, 10, 27, 1, 59, 59, tzinfo=LocalTZ())
# The following datetime is ambigous, as 02:00 can be either DST or standard
# time. (It is interpreted as standard time.)
print datetime.datetime(2013, 10, 27, 2, 0, 0, tzinfo=LocalTZ())
tzlocal from dateutil.
Code example follows. Last string suitable for use in filenames.
>>> from datetime import datetime
>>> from dateutil.tz import tzlocal
>>> str(datetime.now(tzlocal()))
'2015-04-01 11:19:47.980883-07:00'
>>> str(datetime.now(tzlocal())).replace(' ','-').replace(':','').replace('.','-')
'2015-04-01-111947-981879-0700'
>>>
First, note that the question presents an incorrect initialization of an aware datetime object:
>>> local_time=datetime.datetime(2010, 4, 27, 12, 0, 0, 0,
... tzinfo=pytz.timezone('Israel'))
creates an invalid instance. One can see the problem by computing the UTC offset of the resulting object:
>>> print(local_time.utcoffset())
2:21:00
(Note the result which is an odd fraction of an hour.)
To initialize an aware datetime properly using pytz one should use the localize() method as follows:
>>> local_time=pytz.timezone('Israel').localize(datetime.datetime(2010, 4, 27, 12))
>>> print(local_time.utcoffset())
3:00:00
Now, if you require a local pytz timezone as the new tzinfo, you should use the tzlocal package as others have explained, but if all you need is an instance with a correct local time zone offset and abbreviation then tarting with Python 3.3, you can call the astimezone() method with no arguments to convert an aware datetime instance to your local timezone:
>>> local_time.astimezone().strftime('%Y-%m-%d %H:%M %Z %z')
'2010-04-27 05:00 EDT -0400'
now_dt = datetime.datetime.now()
utc_now = datetime.datetime.utcnow()
now_ts, utc_ts = map(time.mktime, map(datetime.datetime.timetuple, (now_dt, utc_now)))
offset = int((now_ts - utc_ts) / 3600)
hope this will help you.
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?
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.
#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)
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
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)
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.
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))