80

We can use time.tzname get a local timezone name, but that name is not compatible with pytz.timezone.

In fact, the name returned by time.tzname is ambiguous. This method returns ('CST', 'CST') in my system, but 'CST' can indicate four timezones:

  • Central Time Zone (North America) - observed in North America's Central Time Zone
  • China Standard Time
  • Chungyuan Standard Time - the term "Chungyuan Standard Time" is now rarely in use in Taiwan
  • Australian Central Standard Time (ACST)
user805627
  • 4,247
  • 6
  • 32
  • 43
  • related: [Get the Olson TZ name for the local timezone?](http://stackoverflow.com/q/7669938/4279) – jfs Nov 04 '12 at 13:32
  • related: [Getting computer's utc offset in Python](http://stackoverflow.com/q/3168096/4279) – jfs Oct 01 '14 at 09:31

6 Answers6

84

tzlocal module returns pytz tzinfo's object corresponding to the local timezone:

import time
from datetime import datetime

import pytz # $ pip install pytz
from tzlocal import get_localzone # $ pip install tzlocal

# get local timezone    
local_tz = get_localzone() 

# test it
# utc_now, now = datetime.utcnow(), datetime.now()
ts = time.time()
utc_now, now = datetime.utcfromtimestamp(ts), datetime.fromtimestamp(ts)

local_now = utc_now.replace(tzinfo=pytz.utc).astimezone(local_tz) # utc -> local
assert local_now.replace(tzinfo=None) == now

It works even during daylight savings time transitions when local time may be ambiguous.

local_tz also works for past dates even if utc offset for the local timezone was different at the time. dateutil.tz.tzlocal()-based solution fails in this case e.g., in Europe/Moscow timezone (example from 2013):

>>> import os, time
>>> os.environ['TZ'] = 'Europe/Moscow'
>>> time.tzset()
>>> from datetime import datetime
>>> from dateutil.tz import tzlocal
>>> from tzlocal import get_localzone
>>> dateutil_tz = tzlocal()
>>> tzlocal_tz = get_localzone()
>>> datetime.fromtimestamp(0, dateutil_tz)                              
datetime.datetime(1970, 1, 1, 4, 0, tzinfo=tzlocal())
>>> datetime.fromtimestamp(0, tzlocal_tz)
datetime.datetime(1970, 1, 1, 3, 0, tzinfo=<DstTzInfo 'Europe/Moscow' MSK+3:00:00 STD>)

dateutil returns wrong UTC+4 offset instead of the correct UTC+3 on 1970-01-01.

For those bumping into this in 2017 dateutil.tz.tzlocal() is still broken. The above example works now because the current utf offset is UTC+3 in Moscow (that by accident is equal to the utc offset from 1970). To demonstrate the error we can choose a date when utc offset is UTC+4:

>>> import os, time
>>> os.environ['TZ'] = 'Europe/Moscow'
>>> time.tzset()
>>> from datetime import datetime
>>> from dateutil.tz import tzlocal
>>> from tzlocal import get_localzone
>>> dateutil_tz = tzlocal()
>>> tzlocal_tz = get_localzone()
>>> ts = datetime(2014, 6,1).timestamp() # get date in 2014 when gmtoff=14400 in Moscow
>>> datetime.fromtimestamp(ts, dateutil_tz)
datetime.datetime(2014, 5, 31, 23, 0, tzinfo=tzlocal())
>>> datetime.fromtimestamp(ts, tzlocal_tz)
datetime.datetime(2014, 6, 1, 0, 0, tzinfo=<DstTzInfo 'Europe/Moscow' MSK+4:00:00 STD>)

dateutil returns wrong UTC+3 offset instead of the correct UTC+4 on 2014-06-01.

jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • 1
    Interesting, so the `tzlocal` module returns a `pytz` zone? – Martijn Pieters Jun 28 '13 at 13:31
  • 1
    yes, pytz tzinfo's object. I've update the answer, to mention it explicitly – jfs Jun 28 '13 at 13:38
  • 1
    I have a great deal of respect towards Lennart Regebro, but this functionality should have been an integral part of `pytz` package. – wswld Oct 28 '15 at 14:01
  • 18
    Time zones seem to behave according to something like Hoffstadter's Law: they're more complicated than you think, even when you allow for the fact they're more complicated than you think. – Jason Sep 28 '16 at 01:19
  • Yeah, I just had my app blow up because I was trying [this approach](http://stackoverflow.com/a/13218525/344286) but surprise! 'EDT' isn't really a timezone. Gorram DST >:( – Wayne Werner Mar 14 '17 at 13:47
  • 5
    For those bumping into this in 2017, `dateutil.tz.tzlocal()` is no longer broken and does not exhibit the above mentioned behaviour. – WhyNotHugo Mar 18 '17 at 18:14
  • 4
    @WhyNotHugo: `dateutil.tz.tzlocal()` is still broken. I've updated the answer. – jfs Oct 12 '17 at 06:41
42

Since Python 3.6, you can simply run naive_datetime.astimezone() and system time zone will be added to naive_datetime object.

If called without arguments (or with tz=None) the system local timezone is assumed for the target timezone. The .tzinfo attribute of the converted datetime instance will be set to an instance of timezone with the zone name and offset obtained from the OS.

https://docs.python.org/3/library/datetime.html#datetime.datetime.astimezone

Example:

>>> import datetime
>>> datetime.datetime.now().astimezone().isoformat(timespec='minutes')
'2018-10-02T13:09+03:00'
sirex
  • 4,593
  • 2
  • 32
  • 21
39

Use the tzlocal function from the python-dateutil package:

from dateutil.tz import tzlocal

localtimezone = tzlocal()

Internally, this is a class that uses time.timezone and time.altzone (switching based on time.daylight), but creates a suitable timezone object from that.

You use this instead of a pytz timezone.

The alternative is to read the currently configured timezone from the operating system instead, but this differs widely from OS to OS. On Mac OS X you need to read the output of systemsetup -gettimezone:

$ systemsetup -gettimezone
Time Zone: Europe/Copenhagen

On Debian and Ubuntu systems, you can read /etc/timezone:

$ cat /etc/timezone
Europe/Oslo

On RedHat and direved systems, you'll need to read it from /etc/sysconfig/clock:

$ grep ZONE /etc/sysconfig/clock
ZONE="Europe/Oslo"
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
10

A very simple method to solve this question:

import time

def localTzname():
    offsetHour = time.timezone / 3600
    return 'Etc/GMT%+d' % offsetHour

Update: @MartijnPieters said 'This won't work with DST / summertime.' So how about this version?

import time

def localTzname():
    if time.daylight:
        offsetHour = time.altzone / 3600
    else:
        offsetHour = time.timezone / 3600
    return 'Etc/GMT%+d' % offsetHour
user805627
  • 4,247
  • 6
  • 32
  • 43
  • Uh, should timezone change with DST? I've never used DST. I think DST only changes time, doesn't change timezone. – user805627 Nov 04 '12 at 17:12
  • `time.altzone` is the DST zone, `time.daylight` is the DST indicator. But yes, the timezone changes with DST. – Martijn Pieters Nov 04 '12 at 17:57
  • 6
    `time.daylight` doesn't say whether DST is in effect right now, it just says whether the timezone has DST at all. You could compute utc offset as follows: `is_dst = time.daylight and time.localtime().tm_isdst > 0`. You could pass POSIX timestamp to `localtime()`, to compute `is_dst` at a different time moment. `utc_offset = - (time.altzone if is_dst else time.timezone)` (note: the sign is the opposite of that of used in the name) – jfs Jun 28 '13 at 12:30
  • Note: In [rare circumstances `time.altzone` and `time.timezone` may have wrong values](http://bugs.python.org/msg31138) – jfs Apr 21 '14 at 18:21
  • 5
    This will not work with [half-hour and 45-minute timezones](http://www.timeanddate.com/time/time-zones-interesting.html). – sam hocevar May 31 '16 at 09:36
  • 18
    Date & time math is like cryptography: DIY is dangerous. Leave it to the experts. – Mark E. Haase Feb 16 '17 at 19:25
1

i don't know if this is useful for you or not, but i think it answers your more general problem:

if you have a date that is in an ambiguous timezone, like CST, simple-date (python 3.2+ only, sorry) can automate the search, and allows you to do things like prefer certain countries.

for example:

>>> SimpleDate('2013-07-04 18:53 CST')
Traceback [...
simpledate.AmbiguousTimezone: 3 distinct timezones found: <DstTzInfo 'Australia/Broken_Hill' CST+9:30:00 STD>; <DstTzInfo 'America/Regina' LMT-1 day, 17:01:00 STD>; <DstTzInfo 'Asia/Harbin' LMT+8:27:00 STD> (timezones=('CST',), datetime=datetime.datetime(2013, 7, 4, 18, 53), is_dst=False, country=None, unsafe=False)
>>> SimpleDate('2013-07-04 18:53 CST', country='CN')
SimpleDate('2013-07-04 18:53 CST')
>>> SimpleDate('2013-07-04 18:53 CST', country='CN').utc
SimpleDate('2013-07-04 10:53 UTC', tz='UTC')

note how, by specifying a country you reduce the range of possible values sufficiently to allow conversion to UTC.

it's implemented by doing a search over the timezones in PyTZ:

>>> SimpleDate('2013-07-04 18:53 CST', country='CN', debug=True)
...
PyTzFactory: Have country code CN
PyTzFactory: Country code CN has 5 timezones
PyTzFactory: Expanded country codes to 5 timezones
PyTzFactory: Expanding ('CST',)
PyTzFactory: Name lookup failed for CST
PyTzFactory: Found CST using Asia/Shanghai
PyTzFactory: Found CST using Asia/Harbin
PyTzFactory: Found CST using Asia/Chongqing
PyTzFactory: Found CST using Asia/Urumqi
PyTzFactory: Found CST using Asia/Kashgar
PyTzFactory: Expanded timezone to 5 timezones
PyTzFactory: New offset 8:00:00 for Asia/Shanghai
PyTzFactory: Known offset 8:00:00 for Asia/Harbin
PyTzFactory: Known offset 8:00:00 for Asia/Chongqing
PyTzFactory: Known offset 8:00:00 for Asia/Urumqi
PyTzFactory: Known offset 8:00:00 for Asia/Kashgar
PyTzFactory: Have 1 distinct timezone(s)
PyTzFactory: Found Asia/Shanghai
...
SimpleDate('2013-07-04 18:53 CST')

finally, to answer the question asked directly, it also wraps tzlocal, as mentioned in another answer here, so will automatically do what you expect if you don't give a timezone. for example, i live in chile, so

>>> SimpleDate()
SimpleDate('2013-07-04 19:21:25.757222 CLT', tz='America/Santiago')
>>> SimpleDate().tzinfo
<DstTzInfo 'America/Santiago' CLT-1 day, 20:00:00 STD>

gives my locale's timezone (ambiguous or not).

andrew cooke
  • 45,717
  • 10
  • 93
  • 143
-6
import pytz

say you have list of utc DateTime values in OBJ list object.

tz=pytz.timezone('Asia/Singapore')

find below url to get respective timezone positional string parameter Is there a list of Pytz Timezones?

Now our tz is object having singapore Time

result=[]
for i in OBJ:
    i=i+tz.utcoffset(i)
    result.append(i)

result list object has DateTime value of your respective timezone

Suraj Rao
  • 29,388
  • 11
  • 94
  • 103