Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django timezone.make_aware raised AmbiguousTimeError for 2014-10-26 1:45:00

I found some strange things. Here some examples.

from django.utils import timezone
value = u'2014-10-26 01:45:00'
#I know that a variable has  'Europe / Moscow' timezone. Let's tell Django about it.
TZ = timezone.pytz.timezone('Europe/Moscow')
d = timezone.datetime.strptime(value,'%Y-%m-%d %H:%M:%S')
print timezone.make_aware(d,TZ)
#raised AmbiguousTimeError: 2014-10-26 01:45:00

And then the fun begins

print timezone.make_aware(d+timezone.timedelta(minutes=15),TZ)
#out: 2014-10-26 02:00:00+03:00
print timezone.make_aware(d+timezone.timedelta(minutes=14),TZ)
#raised AmbiguousTimeError
print timezone.make_aware(d-timezone.timedelta(minutes=46),TZ)
#out: 2014-10-26 00:59:00+04:00
print timezone.make_aware(d-timezone.timedelta(minutes=45),TZ)
#raised AmbiguousTimeError     

So AmbiguousTimeError raised between 2014-10-26 00:59:00 and 2014-10-26 02:00:00

WHY? And how solve it?

like image 837
hloroform Avatar asked Oct 20 '14 11:10

hloroform


1 Answers

timezon.make_aware(d, TZ) is equivalent to TZ.localize(d, is_dst=None) that raises an error for ambiguous times: 2014-10-26 01:45:00 happens twice in Europe/Moscow timezone:

# Europe/Moscow               UTC                           timestamp
2014-10-26 00:45:00 MSK+0400; 2014-10-25 20:45:00 UTC+0000; 1414269900
2014-10-26 01:00:00 MSK+0400; 2014-10-25 21:00:00 UTC+0000; 1414270800
2014-10-26 01:15:00 MSK+0400; 2014-10-25 21:15:00 UTC+0000; 1414271700
2014-10-26 01:30:00 MSK+0400; 2014-10-25 21:30:00 UTC+0000; 1414272600
2014-10-26 01:45:00 MSK+0400; 2014-10-25 21:45:00 UTC+0000; 1414273500
2014-10-26 01:15:00 MSK+0300; 2014-10-25 22:15:00 UTC+0000; 1414275300
2014-10-26 01:30:00 MSK+0300; 2014-10-25 22:30:00 UTC+0000; 1414276200
2014-10-26 01:45:00 MSK+0300; 2014-10-25 22:45:00 UTC+0000; 1414277100
2014-10-26 02:00:00 MSK+0300; 2014-10-25 23:00:00 UTC+0000; 1414278000

Notice: the utc offset is changed from +0400 to +0300 at 2am (Федеральный закон от 21 июля 2014 г. N 248-ФЗ).

To avoid the exception, you could call TZ.localize(d) (note: no is_dst=None) that works fine for existing non-ambiguous times but may fail (return wrong answer) for non-existing or ambiguous times.

If pytz Bug #1378150: Enhance support for end-of-DST-like ambiguous time is fixed then you could use TZ.localize(d, is_dst=True), TZ.localize(d, is_dst=False) to get time before and after the transition correspondingly.

If the bug is not fixed you could use my answer from Parsing of Ordered Timestamps in Local Time (to UTC) While Observing Daylight Saving Time to get the time after the transition:

# `naive` is a naive datetime object in local (Europe/Moscow) time
if tz.localize(naive, is_dst=False) == tz.localize(naive, is_dst=True):
    # Example: 2014/10/26 in Europe/Moscow timezone
    # ambiguous time but is_dst=False/True yield the same result
    # i.e., tz.localize() can't help, find UTC time  manually
    #NOTE: assume there is no other changes to UTC offset today (local.day)
    new_offset = tz.localize(naive + timedelta(1), is_dst=None).utcoffset()
    assert tz.localize(naive).utcoffset() != new_offset
    utc = (naive - new_offset).replace(tzinfo=pytz.utc)
    local = utc.astimezone(tz)
like image 161
jfs Avatar answered Sep 24 '22 15:09

jfs