“Eppur si muove!” * Or Working with time zones in Python

Original author: Armin Ronacher
  • Transfer
On our planet Earth, at the same time, different geographical points of the planet can have different times of the day. This is a consequence of the fact that our world is a rotating geoid, not a flat disk, but that our solar system has only one star - the Sun. Ever since school, everyone knows about time zones, and we all met with their manifestations in real life ("Moscow time - 15 hours, in Petropavlovsk-Kamchatsky - midnight", jetlag on long-haul flights, etc.). Unfortunately, time zones are only partially based on the physical features of our world, and in computer calculations it is necessary to take into account other, sometimes unexpected, nuances.

* "And yet she spins!" - the catch phrase that Galileo Galilei allegedly said, leaving the Inquisition process after renouncing his belief that the Earth revolves around the Sun. In our case, alas, this rotation leads to all these “wonderful” problems with time zones.

What does this article have in common with Galileo? Yes, in general, nothing. I’m afraid that if our world were the center of the universe, we would still have to deal with time zones. We will consider the title to be my oversight, which I can no longer correct (although I can).

What is a time zone?

What is your time zone? If you answer “UTC + 3” - this will be the correct answer only at the current moment in time, but in general this statement is incorrect. If you look at the database of time zones, you will see, for example, that Berlin and Vienna, despite the offset “UTC + 1”, have different time zones (“Europe / Berlin” and “Europe / Vienna”). Why is that? The reason is that they had different summer time (DST) at different periods of history. Even if today these two countries and these two cities have the same DST rules, a hundred years ago this was not so. For example, in Austria and Germany at different time periods there was no daylight saving time: in Austria since 1920, and in Germany since 1918. During World War II, both countries had the same DST rules (which is not surprising), but after its endings again out of sync.

And this is happening all over the world. For computer computing, the transition to daylight saving time is a huge problem, because we assume that time has a continuous moton ride. With daylight saving time, every year we have an hour that repeats twice, and there is an hour that we just miss. If, when writing to the log, you specify local time, you may disrupt the order of the log lines when sorting.

Quote from pytz documentation:
So, for example, in the US / Eastern timezone in 2002, at the time of the end of DST validity, on October 27, the time at 01:30 came twice, and at the time of the beginning of the DST action on April 7, it was not at 02:30, because at 02:00 the clock was moved an hour ahead.

But time zones store not only daylight saving time rules. Some countries change time zones, sometimes even without changing DST. So, for example, in 1915 Warsaw passed to Central European time. As a result, at midnight on August 5, 1915, the clock was switched 24 minutes ago (with summer time operating in Warsaw).
In general, even more hell is happening with time zones. There is at least one country whose timezone was different during the day due to the synchronization of 0:00 with the time of sunrise.

Where is the common sense?

There is common sense and it is called Coordinated Universal Time (UTC). UTC is a timezone without daylight saving time and without any changes in the past. However, due to the fact that our Earth is a rotating geoid and there are things in the world that we cannot control, there is the problem of corrective seconds (leap seconds). Whether UTC will take into account corrective seconds (which are irregular and therefore difficult to take into account when calculating them), or not (then each timezone will have a few seconds difference with UTC), as far as I know, has not been decided yet.

Despite this, right now, UTC is the safest option. From UTC, you can convert time to local time for any time zone. The inverse transformation, given the above, is not possible.

So, here is the main rule of thumb that will never let you down:
Always store and work over time in UTC . If you need to keep the original data, write them separately. Never store local time and timezone!

What is the problem?

In general, this article should have ended. But unfortunately, there are a couple things to keep in mind when you program in Python. This is a legacy of architectural decisions of those ancient times when no one thought about the practical application of the language. Motivation mattered, common sense didn't.

One fine day, the following decisions were made about the architecture of the datetime module of the Python standard library:
  1. The datetime module should not store timezone information, because timezones change too often.
  2. On the other hand, the datetime module should provide the ability to add timezone information (tzinfo).
  3. The following objects should be implemented in the datetime module: date, time, date + time, timedelta.

Unfortunately, something went wrong. The main problem is that the datetime object into which the timezone information (tzinfo) has been added will not interact with the datetime object without the timezone:
>>> import pytz, datetime
>>> a = datetime.datetime.utcnow()
>>> b = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
>>> a < b
Traceback (most recent call last):
  File "", line 1, in 
TypeError: can't compare offset-naive and offset-aware datetimes

If you close your eyes to the terrible API with which you have to add timezone information to a datetime object, there are still problems. When you work with datetime objects in Python, sooner or later you will have to add or remove tzinfo in all places of your program.

Another problem is that you have two ways to create a datetime object with the current time in Python:
>>> datetime.datetime.utcnow()
datetime.datetime(2011, 7, 15, 8, 30, 55, 375010)
>>> datetime.datetime.now()
datetime.datetime(2011, 7, 15, 10, 30, 57, 70767)

One returns time in UTC, the other - local time. However, the datetime object will not tell you what “local time” is (because it does not have timezone information, at least prior to Python 3.3), and there is no way to find out which of these objects stores time in UTC.

If you are converting a UNIX timestamp to a datetime object, you should also be careful when using the datetime.datetime.utcfromtimestamp method, because it takes a timestamp in local time.

The datetime library also provides date and time objects into which it is absolutely useless to add tzinfo. The time object cannot be transferred to another timezone, because for this you need to know the date. The date object generally only makes sense for the local timezone, because today for me can be yesterday or tomorrow for you — say thank you to the wonderful world of time zones.

So what are the recommendations of the experts?

Now we know who is to blame. But what to do? If we ignore the theoretical problems that appear only in the case of working with historical dates in the past, here are a few recommendations for you. In case you have to work with historical dates, there is an alternative module mxDateTime, which is reasonably designed and even supports various calendars (Gregorian and Julian).

Use UTC inside the program

If you need to get the current time, always use datetime.datetime.utcnow (). If you get local time from the user, always immediately convert it to UTC. If an unambiguous conversion cannot be made, inform the user about this, do not try to guess his time blindly. During the transition to daylight saving time and back, my iPhone several times could not correctly set the time. I know when to do this, because I have to translate the clock.

Never use time with a time zone

It might seem like a good idea for you to always add time zone information to datetime objects, but in fact it is a much better idea not to do this. A good solution would be to use a datetime object without tzinfo and over time in UTC. Consider the fact that you cannot compare time with a timezone with time without it, just as you cannot mix bytes and unicode in Python 3. Use this API flaw for your own purposes.
  1. Inside a program, always use datetime objects without tzinfo with UTC time.
  2. When you interact with a user, always convert his local UTC time and vice versa.

Why don't you need to add tzinfo to the datetime object? Firstly, because the vast majority of libraries expect tzinfo to be None. Secondly, it’s a terrible idea to always work with tzinfo, given the crooked API of working with it. The pytz library has alternative functions for converting timezones, because the tzinfo conversion API implemented in the standard library is not flexible enough to work with most real timezones. If we do not use tzinfo objects, there is a chance that everything will change for the better in the future.

Another reason not to use time with a timezone is that the tzinfo object is very specific and highly dependent on its implementation. There is no standard way to transmit timezone information (with the possible exception of UTC timezone) to other languages, via HTTP, etc. In addition, datetime objects with timezone information often become too huge when serialized using the pickle module, or they cannot even be serialized (this depends on the implementation of the tzinfo object).

Transformations for formatting

If you need to show the time in the user’s timezone, take the datetime object with UTC time, add the UTC timezone to it, convert the time to the user's local time and format it. Do not use timezone conversion using tzinfo methods, because they do not work correctly, use pytz. Then set the time to “naive” by discarding the timezone offset from the resulting datetime object that you created for formatting and continue to live happily ever after.

Translated Dreadatour , the text read% username%.

Bonus from the translator for those who have read to the end:

Chic video from Tom Scott about time zones:

And in my next article I will write where the author is wrong, why he is mistaken, and how, after all, must be done correctly.

Also popular now: