Search code examples
pythondatetimetimezonepython-2.x

Python datetime not correct with regards to timezones when running in docker


I have a python 2.7 codebase that I'm trying to containerize. Much as I'd like to, our devs cannot move to Python 3.

When running natively in their dev environments, datetimes respect timezones. I can confirm that the output is as expected on a Mac running Python 3.9.6. But when we containerize this on Ubuntu base images, this no longer works correctly.

Using python:2.7.18-buster works correctly, but that is a 3 year old image that doesn't get updates. Both ubuntu:18.04 and ubuntu:16.04 fail.

The incorrect output when run at this time is

UTC Hour:  22
NY Hour: 22
1609459200.0
London Hour:  22
1609459200.0

The code to repro the issue is

import os
import datetime
import time
from dateutil.parser import parse

date_string="2021-01-01"


os.environ.get("TZ") # should be none, use system TZ, which is UTC
print ("UTC Hour: ", datetime.datetime.now().hour) # should be whatever hour it is in UTC

os.environ["TZ"] = "America/New_York"
print ("NY Hour:", datetime.datetime.now().hour)  # should be whatever hour it is in EST
print (time.mktime(parse(date_string).timetuple()))  # should be 1609477200.0

os.environ["TZ"] = "Europe/London"
print ("London Hour: ", datetime.datetime.now().hour) # should be whatever hour it is in GMT
print (time.mktime(parse(date_string).timetuple()))   # should be 1609459200.0

Solution

  • You can avoid changing environment variables by using aware datetime consistently. To calculate Unix time, derive it from a timedelta.

    from datetime import datetime
    from dateutil import tz
    
    def to_unix(dt, _epoch=datetime(1970, 1, 1, tzinfo=tz.UTC)):
        """convert aware datetime object to seconds since the unix epoch"""
        return (dt-_epoch).total_seconds()
    
    # reference date for demonstration
    refdate = datetime(2021, 1, 1)
    
    zone = tz.gettz("America/New_York")
    dt_zone = refdate.replace(tzinfo=zone) # 2021-01-01 00:00:00-05:00
    print(to_unix(dt_zone)) # 1609477200.0
    print("UTC hour:", datetime.now(tz.UTC).hour, "NY hour:", datetime.now(zone).hour)
    # -->> -5 hour offset on 11 Nov 2022
    
    zone = tz.gettz("Europe/London")
    dt_zone = refdate.replace(tzinfo=zone) # 2021-01-01 00:00:00+00:00
    print(to_unix(dt_zone)) # 1609459200.0
    print("UTC hour:", datetime.now(tz.UTC).hour, "London hour:", datetime.now(zone).hour)
    # -->> 0 hour offset on 11 Nov 2022
    

    Note: tested with Python 2.7.18 on Linux, but not in a Docker environment.