{"id":500,"date":"2018-02-04T21:03:33","date_gmt":"2018-02-04T13:03:33","guid":{"rendered":"https:\/\/vinta.ws\/code\/?p=500"},"modified":"2026-03-17T01:20:01","modified_gmt":"2026-03-16T17:20:01","slug":"timezone-in-python-offset-naive-and-offset-aware-datetimes","status":"publish","type":"post","link":"https:\/\/vinta.ws\/code\/timezone-in-python-offset-naive-and-offset-aware-datetimes.html","title":{"rendered":"Timezone in Python: Offset-naive and Offset-aware datetimes"},"content":{"rendered":"<p>TL;DR: You should always store datetimes in UTC and convert to proper timezone on display.<\/p>\n<p>A timezone offset refers to how many hours the timezone is from Coordinated Universal Time (UTC). The offset of UTC is <code>+00:00<\/code>, and the offset of <code>Asia\/Taipei<\/code> timezone is <code>UTC+08:00<\/code> (you could also present it as <code>GMT+08:00<\/code>). Basically, there is no perceptible difference between Greenwich Mean Time (GMT) and UTC.<\/p>\n<p>The local time minus the offset of its timezone is UTC time. For instance, <code>18:00+08:00<\/code> of <code>Asia\/Taipei<\/code> minuses timezone offset <code>+08:00<\/code> is <code>10:00+00:00<\/code>, 10 o'clock of UTC. On the other hand, UTC time plus local timezone offset is local time.<\/p>\n<p>ref:<br \/>\n<a href=\"https:\/\/opensource.com\/article\/17\/5\/understanding-datetime-python-primer\">https:\/\/opensource.com\/article\/17\/5\/understanding-datetime-python-primer<\/a><br \/>\n<a href=\"https:\/\/julien.danjou.info\/blog\/2015\/python-and-timezones\">https:\/\/julien.danjou.info\/blog\/2015\/python-and-timezones<\/a><\/p>\n<p>\u5230\u5e95\u662f GMT+8 \u9084\u662f UTC+8\uff1f<br \/>\n<a href=\"http:\/\/pansci.asia\/archives\/84978\">http:\/\/pansci.asia\/archives\/84978<\/a><\/p>\n<h2>Installation<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-bash\">$ pip install -U python-dateutil pytz tzlocal<\/code><\/pre>\n<h2>Show System Timezone<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-py\">import tzlocal\n\ntzlocal.get_localzone()\n# &lt;DstTzInfo 'Asia\/Taipei' LMT+8:06:00 STD&gt;\n\ntzlocal.get_localzone().zone\n# 'Asia\/Taipei'\n\nfrom time import gmtime, strftime\nprint(strftime(\"%z\", gmtime()))\n# +0800<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/github.com\/regebro\/tzlocal\">https:\/\/github.com\/regebro\/tzlocal<\/a><br \/>\n<a href=\"https:\/\/stackoverflow.com\/questions\/13218506\/how-to-get-system-timezone-setting-and-pass-it-to-pytz-timezone\/\">https:\/\/stackoverflow.com\/questions\/13218506\/how-to-get-system-timezone-setting-and-pass-it-to-pytz-timezone\/<\/a><\/p>\n<h2>Find Timezones Of A Certain Country<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-py\">import pytz\n\npytz.country_timezones('tw')\n# ['Asia\/Taipei']\n\npytz.country_timezones('cn')\n# ['Asia\/Shanghai', 'Asia\/Urumqi']<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/pythonhosted.org\/pytz\/#country-information\">https:\/\/pythonhosted.org\/pytz\/#country-information<\/a><\/p>\n<h2>Offset-naive Datetime<\/h2>\n<p>Any naive <code>datetime<\/code> would be present as local timezone but without <code>tzinfo<\/code>, so it is buggy.<\/p>\n<p>A naive <code>datetime<\/code> object contains no timezone information. The <code>datetime_obj.tzinfo<\/code> will be set to <code>None<\/code> if the object is naive. Actually, <code>datetime<\/code> objects without timezone should be considered as a &quot;bug&quot; in your application. It is up for the programmer to keep track of which timezone users are working in.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-py\">import datetime\n\nimport dateutil.parser\n\ndatetime.datetime.now()\n# return the current date and time in local timezone, in this example: Asia\/Taipei (UTC+08:00)\n# datetime.datetime(2018, 2, 2, 9, 15, 6, 211358)), naive\n\ndatetime.datetime.utcnow()\n# return the current date and time in UTC\n# datetime.datetime(2018, 2, 2, 1, 15, 6, 211358), naive\n\ndateutil.parser.parse('2018-02-04T16:30:00')\n# datetime.datetime(2018, 2, 4, 16, 30), naive<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/docs.python.org\/3\/library\/datetime.html\">https:\/\/docs.python.org\/3\/library\/datetime.html<\/a><br \/>\n<a href=\"https:\/\/dateutil.readthedocs.io\/en\/stable\/\">https:\/\/dateutil.readthedocs.io\/en\/stable\/<\/a><\/p>\n<h2>Offset-aware Datetime<\/h2>\n<p>A aware <code>datetime<\/code> object embeds a timezone information. Rules of thumb for timezone in Python:<\/p>\n<ul>\n<li>Always work with &quot;offset-aware&quot; <code>datetime<\/code> objects.<\/li>\n<li>Always store datetime in UTC and do timezone conversion only when interacting with users.<\/li>\n<li>Always use ISO 8601 as input and output string format.<\/li>\n<\/ul>\n<p>There are two useful methods: <code>pytz.utc.localize(naive_dt)<\/code> for converting naive <code>datetime<\/code> to timezone be offset-aware, and <code>aware_dt.astimezone(pytz.timezone(&#039;Asia\/Taipei&#039;))<\/code> for adjusting timezones of offset-aware objects.<\/p>\n<p>You should avoid <code>naive_dt.astimezone(some_tzinfo)<\/code> which would be converted to aware datetime as system timezone then convert to <code>some_tzinfo<\/code> timezone.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-py\">import datetime\n\nimport pytz\n\nnow_utc = pytz.utc.localize(datetime.datetime.utcnow())\n# equals to datetime.datetime.now(pytz.utc)\n# equals to datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)\n# datetime.datetime(2018, 2, 4, 10, 17, 40, 679562, tzinfo=&lt;UTC&gt;), aware\n\nnow_taipei = now_utc.astimezone(pytz.timezone('Asia\/Taipei'))\n# convert to another timezone\n# datetime.datetime(2018, 2, 4, 18, 17, 40, 679562, tzinfo=&lt;DstTzInfo 'Asia\/Taipei' CST+8:00:00 STD&gt;), aware\n\nnow_utc.isoformat()\n# '2018-02-04T10:17:40.679562+00:00'\n\nnow_taipei.isoformat()\n# '2018-02-04T18:17:40.679562+08:00'\n\nnow_utc == now_taipei\n# True<\/code><\/pre>\n<p>For working with <code>pytz<\/code>, it is recommended to call <code>tz.localize(naive_dt)<\/code> instead of <code>naive_dt.replace(tzinfo=tz)<\/code>. <code>dt.replace(tzinfo=tz)<\/code> does not handle daylight savings time correctly. <\/p>\n<pre class=\"line-numbers\"><code class=\"language-py\">dt1 = datetime.datetime.now(pytz.timezone('Asia\/Taipei'))\n# datetime.datetime(2018, 2, 4, 18, 22, 28, 409332, tzinfo=&lt;DstTzInfo 'Asia\/Taipei' CST+8:00:00 STD&gt;), aware\n\ndt2 = datetime.datetime(2018, 2, 4, 18, 22, 28, 409332, tzinfo=pytz.timezone('Asia\/Taipei'))\n# datetime.datetime(2018, 2, 4, 18, 22, 28, 409332, tzinfo=&lt;DstTzInfo 'Asia\/Taipei' LMT+8:06:00 STD&gt;), aware\n\ndt1 == dt2\n# False<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/pythonhosted.org\/pytz\/\">https:\/\/pythonhosted.org\/pytz\/<\/a><\/p>\n<p>Naive and aware <code>datetime<\/code> objects are not comparable.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-py\">naive = datetime.datetime.utcnow()\naware = pytz.utc.localize(naive)\n\nnaive == aware\n# False\n\nnaive &gt;= aware\n# TypeError: can't compare offset-naive and offset-aware datetimes<\/code><\/pre>\n<h3>Parse String to Datetime<\/h3>\n<p><code>python-dateutil<\/code> usually comes in handy.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-py\">import dateutil.parser\nimport dateutil.tz\n\ndt1 = dateutil.parser.parse('2018-02-04T19:30:00+08:00')\n# datetime.datetime(2018, 2, 4, 19, 30, tzinfo=tzoffset(None, 28800)), aware\n\ndt2 = dateutil.parser.parse('2018-02-04T11:30:00+00:00')\n# datetime.datetime(2018, 2, 4, 11, 30, tzinfo=tzutc()), aware\n\ndt3 = dateutil.parser.parse('2018-02-04T11:30:00Z')\n# datetime.datetime(2018, 2, 4, 11, 30, tzinfo=tzutc()), aware\n\ndt1 == dt2 == dt3\n# True<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/dateutil.readthedocs.io\/en\/stable\/\">https:\/\/dateutil.readthedocs.io\/en\/stable\/<\/a><\/p>\n<h2>Convert Datetime To Unix Timestamp<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-py\">import datetime\n\nnaive_dt = datetime.datetime(2018, 9, 10, 0, 0, 0)\nnaive_timestamp = aware_dt.timestamp()\n# naive_dt would be in local timezone, in this example: Asia\/Taipei (UTC+08:00)\n\naware_dt = datetime.datetime(2018, 9, 10, 0, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(hours=8)))\naware_timestamp = aware_dt.timestamp()\n\nnaive_timestamp == aware_timestamp\n# True\n\n# MongoDB stores all datetimes in UTC timezone\ndt_fetched_from_mongodb.replace(tzinfo=datetime.timezone.utc).timestamp()<\/code><\/pre>\n<h2>Parse Unix Timestamp To Datetime<\/h2>\n<pre class=\"line-numbers\"><code class=\"language-py\">import datetime\nimport time\n\nimport pytz\n\nts = time.time()\n# seconds since the Epoch (1970-01-01T00:00:00 in UTC)\n# 1517748706.063205\n\ndt1 = datetime.datetime.fromtimestamp(ts)\n# return the date and time of the timestamp in local timezone, in this example: Asia\/Taipei (UTC+08:00)\n# datetime.datetime(2018, 2, 4, 20, 51, 46, 63205), naive\n\ndt2 = datetime.datetime.utcfromtimestamp(ts)\n# return the date and time of the timestamp in UTC timezone\n# datetime.datetime(2018, 2, 4, 12, 51, 46, 63205), naive\n\npytz.timezone('Asia\/Taipei').localize(dt1) == pytz.utc.localize(dt2)\n# True<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/stackoverflow.com\/questions\/13890935\/does-pythons-time-time-return-the-local-or-utc-timestamp\">https:\/\/stackoverflow.com\/questions\/13890935\/does-pythons-time-time-return-the-local-or-utc-timestamp<\/a><\/p>\n<p>We might receive a Unix timestamp from a JavaScript client.<\/p>\n<pre class=\"line-numbers\"><code class=\"language-js\">var moment = require('moment')\nvar ts = moment('2018-02-02').unix()\n\/\/ 1517500800<\/code><\/pre>\n<p>ref:<br \/>\n<a href=\"https:\/\/momentjs.com\/docs\/#\/parsing\/unix-timestamp\/\">https:\/\/momentjs.com\/docs\/#\/parsing\/unix-timestamp\/<\/a><\/p>\n<h2>Store Datetime In Databases<\/h2>\n<ul>\n<li>MySQL lets developers decide what timezone should be used, and you should convert datetime to UTC before saving into database.<\/li>\n<li>MongoDB assumes that all timestamps are in UTC, and you have to normalize datetime to UTC.<\/li>\n<\/ul>\n<p>ref:<br \/>\n<a href=\"https:\/\/tommikaikkonen.github.io\/timezones\/\">https:\/\/tommikaikkonen.github.io\/timezones\/<\/a><br \/>\n<a href=\"https:\/\/blog.elsdoerfer.name\/2008\/03\/03\/fun-with-timezones-in-django-mysql\/\">https:\/\/blog.elsdoerfer.name\/2008\/03\/03\/fun-with-timezones-in-django-mysql\/<\/a><\/p>\n<h2>Tools<\/h2>\n<p>ref:<br \/>\n<a href=\"https:\/\/www.epochconverter.com\/\">https:\/\/www.epochconverter.com\/<\/a><br \/>\n<a href=\"https:\/\/www.timeanddate.com\/worldclock\/converter.html\">https:\/\/www.timeanddate.com\/worldclock\/converter.html<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>You should always store datetimes in UTC and convert to proper timezone on display.<\/p>\n","protected":false},"author":1,"featured_media":501,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[50,2],"class_list":["post-500","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-about-python","tag-datetime","tag-python"],"_links":{"self":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts\/500","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/comments?post=500"}],"version-history":[{"count":0,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/posts\/500\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/media\/501"}],"wp:attachment":[{"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/media?parent=500"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/categories?post=500"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vinta.ws\/code\/wp-json\/wp\/v2\/tags?post=500"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}