How to convert a datetime object between CEST and UTC timezones

11,925

Solution 1

Just complementing @Sergey's answer.

To know when you should apply DST or not, you should get the historical data of the date/times when DST starts and ends, and check if it must be applied or not to the specified datetime. You can get those from IANA database or consult in many online sources, such as timeanddate website.

Another detail is that CET is an ambiguous name, because it's used by more than one timezone. Real timezones names use the ones defined by IANA database (always in the format Region/City, like Europe/Paris or Europe/Berlin).


Gaps and Overlaps

I'm going to use Europe/Berlin timezone as an example. In this year (2017), DST in Berlin started in March 26th: at 2 AM, clocks shifted 1 hour forward to 3 AM, and the offset changed from +01:00 to +02:00.

You can also think that clocks jumped directly from 1:59 AM to 3 AM, which means that all the local times between 2 AM and 2:59 AM didn't exist in that day, at that timezone. That's called a gap, and you should check for this situation (one common approach is to adjust 2 AM to the next valid hour - in this case, 3 AM in DST).

In the same timezone, in 2017, DST will end in October 29th: at 3 AM, the clocks will shift back 1 hour to 2 AM, and the offset will change from +02:00 to +01:00.

This means that all the local times between 2 AM and 2:59 AM will exist twice: once in DST (+02:00) and once in non-DST (+01:00). That's called an overlap, and when creating a datetime that falls in this case, you must decide which one you'll choose to create (should it be 2 AM in DST or non-DST offset? you decide!).


PS: Each european country that uses CET nowadays adopted it in a different year, so if you're dealing with old dates you must consider that too.

Another detail is that DST is defined by governments, and there's no guarantee that the rules will be like this forever, and you'll have to update the rules accordingly - another advantage of using pytz, as its updates are generated from IANA releases and published in PyPI (thanks @Matt Johnson for pointing this info).

Are you sure the paperwork is worse than coding these rules by hand? Just check how to read IANA tz files and perhaps you'll change your mind.

Solution 2

You should read the tzinfo manual carefully, as it is explained there.

First, dst() should usually return a timedelta(0) or timedelta(hours=1), never 2 or more hours, and rarely something in between (e.g. 30 mins). This is only a dst shift relative to the normal TZ offset, not the full TZ offset in the dst mode.

Second, utcoffset() must include dst correction already. The dst() method on its own is also used in some cases described in the manual (e.g. for detection if dst is in effect or not), but it is NOT automatically applied to the TZ offset unless you do so.

Your code should look like this:

from datetime import datetime, tzinfo, timedelta

class CET(tzinfo):
    def utcoffset(self, dt):
        return timedelta(hours=1) + self.dst(dt)

    def dst(self, dt):
        dston = datetime(year=dt.year, month=3, day=20)
        dstoff = datetime(year=dt.year, month=10, day=20)
        if dston <= dt.replace(tzinfo=None) < dstoff:
            return timedelta(hours=1)
        else:
            return timedelta(0)

class UTC(tzinfo):
    def utcoffset(self, dt):
        return timedelta(0)

    def dst(self, dt):
        return timedelta(0)

def from_cet_to_utc(year, month, day, hour, minute):
    cet = datetime(year, month, day, hour, minute, tzinfo=CET())
    utc = cet.astimezone(tz=UTC())
    return '{:%Y-%m-%d:T%H:%MZ}'.format(utc)


print from_cet_to_utc(year=2017, month=7, day=24, hour=10, minute=30)
# 2017-07-24:T08:30Z

Here, I bravely assume that the DST is in effect 20.03.YYYY - 19.10.YYYY inclusive, where YYYY is the year of the date/datetime object.

Usually this logic of the on & off dates is much more complicated: the shifts happen on the Sunday nights only, sometimes on the last Sunday of the month, and vary between the years, sometimes between the sovereignty of the states (with borderlines also changing over the years), and with the daylight saving laws and timezoning changing every few years (hello, Russia).

Share:
11,925
Peter Wood
Author by

Peter Wood

#SOreadytohelp I currently work fulltime in Python, although for most of my career I have mainly worked in C++. I like SICP, eXtreme Programming, and simplicity. I'm a fan of Seth Godin. I play Bruce Cockburn covers on my acoustic guitar, and on my PRS am trying to learn some simpler Joe Satriani.

Updated on June 14, 2022

Comments

  • Peter Wood
    Peter Wood almost 2 years

    I don't want to use the pytz library as the project I am working on requires paperwork to introduce dependencies. If I can achieve this without a third party library I'll be happier.

    I'm having trouble converting a date between CET and UTC when the date is in daylight savings. It's an hour different to what I expect:

    >>> print from_cet_to_utc(year=2017, month=7, day=24, hour=10, minute=30)
    2017-07-24T09:30Z
    
    2017-07-24T08:30Z  # expected
    

    CET is an hour ahead of UTC and in summertime is 2 hours ahead. So I would expect 10:30 in central Europe in midsummer to actually to 8:30 UTC.

    The function is:

    from datetime import datetime, tzinfo, timedelta
    
    def from_cet_to_utc(year, month, day, hour, minute):
        cet = datetime(year, month, day, hour, minute, tzinfo=CET())
        utc = cet.astimezone(tz=UTC())
        return '{:%Y-%m-%d:T%H:%MZ}'.format(utc)
    

    I use two timezone info objects:

    class CET(tzinfo):
        def utcoffset(self, dt):
            return timedelta(hours=1)
    
        def dst(self, dt):
            return timedelta(hours=2)
    
    class UTC(tzinfo):
        def utcoffset(self, dt):
            return timedelta(0)
    
        def dst(self, dt):
            return timedelta(0)
    
    • FObersteiner
      FObersteiner almost 3 years
      Note: Python 3.9+ has the zoneinfo module in the standard library which has all you need for time zone handling.
    • Peter Wood
      Peter Wood almost 3 years
      @MrFuppes thanks for this. I'm only just in the process of migrating from 2.7 to 3.8. Some day...
    • FObersteiner
      FObersteiner almost 3 years
      ok, no worries ;-) just in case, zoneinfo is also available via backports.zoneinfo for Python <3.9, and there's a deprecation shim for pytz.
    • Peter Wood
      Peter Wood almost 3 years
      @MrFuppes that's really very helpful and useful, thank you very much!
  • Admin
    Admin over 6 years
    Actually, you should also check the time of the day - in central Europe, DST changes usually occur between 2 AM and 3 AM, so checking just the date is not accurate enough. Anyway, I'll upvote as soon as the day changes (I've reached my voting limit for today).
  • Matt Johnson-Pint
    Matt Johnson-Pint over 6 years
    Just to answer your point about pytz updates, they're generated from IANA releases. IANA 2017b => pytz 2017.2. Stuart Bishop maintains them, and publishes them to pypi here: pypi.python.org/pypi/pytz#downloads
  • Admin
    Admin over 6 years
    @MattJohnson I've updated the answer with this info, thanks a lot!
  • Matt Johnson-Pint
    Matt Johnson-Pint over 6 years
    Presently, 2:00 AM in the spring, 3:00 AM in the fall. timeanddate.com/time/change/france/paris
  • Sergey Vasilyev
    Sergey Vasilyev over 6 years
    @Hugo You are right. Thanks for complementing my answer. However, even what you described, is only a tip of the iceberg. I intentionally answered the question only regarding the python & datetime.tzinfo parts, which seemed to be a problem for the question's author. Though mentioned that the actual tz calculation is a very difficult and large topic. Because it so large, that one can write a book purely on the proper work with the dates & times (not a joke).
  • Admin
    Admin over 6 years
    @SergeyVasilyev Indeed, we're just scratching the surface of timezones here. I hope we can convince the OP that it's totally worth to use a proper API for that, instead of reinventing the wheel.
  • Peter Wood
    Peter Wood over 6 years
    Both your and Sergey's answers were very helpful. I think you have done the most to convince me that I really should leave it up to the experts. Our solution is to require UTC time as input, then no conversion is required on our part (c: