Monday, February 11, 2008

Converting from Local Time to UTC

OK, this problem took me way, WAY too long to figure out, so I'll post it here for future reference. I was struggling with following question: How do I convert back and forth between local time and Coordinated Universal Time (UTC)?

The solution is actually fairly simple:

def local_to_utc(t):
    """Make sure that the dst flag is -1 -- this tells mktime to take daylight
    savings into account"""
    secs = time.mktime(t)
    return time.gmtime(secs)

def utc_to_local(t):
    secs = calendar.timegm(t)
    return time.localtime(secs)

OK, simple enough, but I think it's kind of weird -- why the heck do I have to use a function from the calendar module to convert from UTC to local time? Why is the timegm function in calendar instead of time?

Even though my solution looked correct (as per my understanding from reading the Python docs), I decided I needed to do a sanity check. I compared the results of my local_to_utc function with .NET's DateTime.ToUniversalTime method. Meanwhile, I also compared my utc_to_local with .NET's DateTime.ToLocalTime method.

Frankly, I was surprised by the results of my first comparison:

   Local time           Python UTC            .NET UTC
----------------     ----------------     ----------------
2000-03-12 02:00     2000-03-12 07:00     2000-03-12 08:00
2001-03-11 02:00     2001-03-11 07:00     2001-03-11 08:00
2002-03-10 02:00     2002-03-10 07:00     2002-03-10 08:00
2003-03-09 02:00     2003-03-09 07:00     2003-03-09 08:00
2004-03-14 02:00     2004-03-14 07:00     2004-03-14 08:00
2005-03-13 02:00     2005-03-13 07:00     2005-03-13 08:00
2006-03-12 02:00     2006-03-12 07:00     2006-03-12 08:00
2007-03-11 02:00     2007-03-11 07:00     2007-03-11 08:00
2008-03-09 02:00     2008-03-09 07:00     2008-03-09 08:00
2009-03-08 02:00     2009-03-08 07:00     2009-03-08 08:00
2010-03-14 02:00     2010-03-14 07:00     2010-03-14 08:00

The table above shows that Python and .NET disagree on a single hour in March of every year. That particular hour is always 2 AM on the start of Daylight Savings Time [1].

I was surprised yet again when I ran the UTC to local comparison. Having already witnessed the one-day-per-year discrepancy, I figured I would see something similar in this comparison. But there were no discrepancies at all! So I won't bother showing the table.

When I thought about it, though, this all makes sense. That 2 AM on the start of DST is a magical date/time. From our perspective, it's 1:59 AM and then the minute hand crosses over the 12, and all of a sudden it's 3 AM. So it's like that 2 AM never existed at all. Since this date/time doesn't exist in local time, there shouldn't be a way to convert to UTC at all. Technically, the Python function should have returned None, and .NET's DateTime.ToUniversalTime method should have returned null. But instead of doing that, the designers decided to just fudge it and return a UTC value that was somewhere in the ballpark. And since it was two different designers, they chose two different values to fudge on.

There are no discrepancies in the conversion from UTC to local time because here, there are no values to fudge. UTC doesn't have magical date/time values. It just keeps ticking along, and never misses a beat.

I hope this helps other people out. I googled for this information and didn't find any results that clearly explained how to convert in both directions. As for the whole magic local time issue, I think it's not a big deal as long as you're aware of it. The designers of both libraries probably figured that most people would not bother to check if a date conversion function was returning None.

[1]Daylight Savings Time is set individually by each country. Here in the US, our daylight savings period begins at 2:00 am of the second Sunday of March, and ends at 2:00 am of the first Sunday of November. The length of DST was actually changed in 2007 in an attempt to reduce energy consumption.
Converting from Local Time to UTC
=================================
.. labels:: python, utc, datetime

OK, this problem took me way, WAY too long to figure out, so I'll post it here for future reference. I was struggling with following question: How do I convert back and forth between local time and `Coordinated Universal Time`_ (UTC)?

.. _Coordinated Universal Time: http://en.wikipedia.org/wiki/Utc

The solution is actually fairly simple:

.. code:: python

    def local_to_utc(t):
        """Make sure that the dst flag is -1 -- this tells mktime to take daylight
        savings into account"""
        secs = time.mktime(t)
        return time.gmtime(secs)

    def utc_to_local(t):
        secs = calendar.timegm(t)
        return time.localtime(secs)

OK, simple enough, but I think it's kind of weird -- why the heck do I have to use a function from the ``calendar`` module to convert from UTC to local time? Why is the ``timegm`` function in ``calendar`` instead of ``time``?

Even though my solution looked correct (as per my understanding from reading the Python docs), I decided I needed to do a sanity check. I compared the results of my ``local_to_utc`` function with .NET's ``DateTime.ToUniversalTime`` method. Meanwhile, I also compared my ``utc_to_local`` with .NET's ``DateTime.ToLocalTime`` method. 

Frankly, I was surprised by the results of my first comparison::

       Local time           Python UTC            .NET UTC
    ----------------     ----------------     ----------------
    2000-03-12 02:00     2000-03-12 07:00     2000-03-12 08:00
    2001-03-11 02:00     2001-03-11 07:00     2001-03-11 08:00
    2002-03-10 02:00     2002-03-10 07:00     2002-03-10 08:00
    2003-03-09 02:00     2003-03-09 07:00     2003-03-09 08:00
    2004-03-14 02:00     2004-03-14 07:00     2004-03-14 08:00
    2005-03-13 02:00     2005-03-13 07:00     2005-03-13 08:00
    2006-03-12 02:00     2006-03-12 07:00     2006-03-12 08:00
    2007-03-11 02:00     2007-03-11 07:00     2007-03-11 08:00
    2008-03-09 02:00     2008-03-09 07:00     2008-03-09 08:00
    2009-03-08 02:00     2009-03-08 07:00     2009-03-08 08:00
    2010-03-14 02:00     2010-03-14 07:00     2010-03-14 08:00

The table above shows that Python and .NET disagree on a single hour in March of every year. That particular hour is always 2 AM on the start of Daylight Savings Time [#]_. 

I was surprised yet again when I ran the UTC to local comparison. Having already witnessed the one-day-per-year discrepancy, I figured I would see something similar in this comparison. But there were no discrepancies at all! So I won't bother showing the table.

When I thought about it, though, this all makes sense. That 2 AM on the start of DST is a magical date/time. From our perspective, it's 1:59 AM and then the minute hand crosses over the 12, and all of a sudden it's 3 AM. So it's like that 2 AM never existed at all. Since this date/time doesn't exist in local time, there shouldn't be a way to convert to UTC at all. Technically, the Python function should have returned ``None``, and .NET's ``DateTime.ToUniversalTime`` method should have returned ``null``. But instead of doing that, the designers decided to just fudge it and return a UTC value that was somewhere in the ballpark. And since it was two different designers, they chose two different values to fudge on. 

There are no discrepancies in the conversion from UTC to local time because here, there are no values to fudge. UTC doesn't have magical date/time values. It just keeps ticking along, and never misses a beat.

I hope this helps other people out. I googled for this information and didn't find any results that clearly explained how to convert in both directions. As for the whole magic local time issue, I think it's not a big deal as long as you're aware of it. The designers of both libraries probably figured that most people would not bother to check if a date conversion function was returning ``None``.

.. [#] Daylight Savings Time is set individually by each country. Here in the US, our daylight savings period begins at 2:00 am of the second Sunday of March, and ends at 2:00 am of the first Sunday of November. The length of DST was actually `changed in 2007`_ in an attempt to reduce energy consumption.

.. _changed in 2007: http://tf.nist.gov/general/dst.htm

No comments: