You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bazarr/libs/dateutil/test/test_tz.py

2115 lines
76 KiB

# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from ._common import unittest, PicklableMixin
from ._common import total_seconds
from ._common import TZEnvContext, TZWinContext
from ._common import WarningTestMixin
from ._common import ComparesEqual
from datetime import datetime, timedelta
from datetime import time as dt_time
from datetime import tzinfo
from six import BytesIO, StringIO
import os
import subprocess
import sys
import base64
import copy
import itertools
from functools import partial
IS_WIN = sys.platform.startswith('win')
# dateutil imports
from dateutil.relativedelta import relativedelta, SU
from dateutil.parser import parse
from dateutil import tz as tz
from dateutil import zoneinfo
try:
from dateutil import tzwin
except ImportError as e:
if IS_WIN:
raise e
else:
pass
MISSING_TARBALL = ("This test fails if you don't have the dateutil "
"timezone file installed. Please read the README")
TZFILE_EST5EDT = b"""
VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAADrAAAABAAAABCeph5wn7rrYKCGAHCh
ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e
S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0
YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg
yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db
wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW
8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b
YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g
BGD9cAVQ4GAGQN9wBzDCYAeNGXAJEKRgCa2U8ArwhmAL4IVwDNmi4A3AZ3AOuYTgD6mD8BCZZuAR
iWXwEnlI4BNpR/AUWSrgFUkp8BY5DOAXKQvwGCIpYBkI7fAaAgtgGvIKcBvh7WAc0exwHcHPYB6x
znAfobFgIHYA8CGBk2AiVeLwI2qv4CQ1xPAlSpHgJhWm8Ccqc+An/sNwKQpV4CnepXAq6jfgK76H
cCzTVGAtnmlwLrM2YC9+S3AwkxhgMWdn8DJy+mAzR0nwNFLcYDUnK/A2Mr5gNwcN8Dgb2uA45u/w
Ofu84DrG0fA7257gPK/ucD27gOA+j9BwP5ti4EBvsnBBhH9gQk+UcENkYWBEL3ZwRURDYEYPWHBH
JCVgR/h08EkEB2BJ2FbwSuPpYEu4OPBMzQXgTZga8E6s5+BPd/zwUIzJ4FFhGXBSbKvgU0D7cFRM
jeBVIN1wVixv4FcAv3BYFYxgWOChcFn1bmBawINwW9VQYFypn/BdtTJgXomB8F+VFGBgaWPwYX4w
4GJJRfBjXhLgZCkn8GU99OBmEkRwZx3W4GfyJnBo/bjgadIIcGrdmuBrsepwbMa3YG2RzHBupplg
b3GucHCGe2BxWsrwcmZdYHM6rPB0Rj9gdRqO8HYvW+B2+nDweA894HjaUvB57x/gero08HvPAeB8
o1Fwfa7j4H6DM3B/jsXgAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA
AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU
AEVQVAAAAAABAAAAAQ==
"""
EUROPE_HELSINKI = b"""
VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAB1AAAABQAAAA2kc28Yy85RYMy/hdAV
I+uQFhPckBcDzZAX876QGOOvkBnToJAaw5GQG7y9EBysrhAdnJ8QHoyQEB98gRAgbHIQIVxjECJM
VBAjPEUQJCw2ECUcJxAmDBgQJwVDkCf1NJAo5SWQKdUWkCrFB5ArtPiQLKTpkC2U2pAuhMuQL3S8
kDBkrZAxXdkQMnK0EDM9uxA0UpYQNR2dEDYyeBA2/X8QOBuUkDjdYRA5+3aQOr1DEDvbWJA8pl+Q
Pbs6kD6GQZA/mxyQQGYjkEGEORBCRgWQQ2QbEEQl55BFQ/0QRgXJkEcj3xBH7uYQSQPBEEnOyBBK
46MQS66qEEzMv5BNjowQTqyhkE9ubhBQjIOQUVeKkFJsZZBTN2yQVExHkFUXTpBWLCmQVvcwkFgV
RhBY1xKQWfUoEFq29JBb1QoQXKAREF207BBef/MQX5TOEGBf1RBhfeqQYj+3EGNdzJBkH5kQZT2u
kGYItZBnHZCQZ+iXkGj9cpBpyHmQat1UkGuoW5BsxnEQbYg9kG6mUxBvaB+QcIY1EHFRPBByZhcQ
czEeEHRF+RB1EQAQdi8VkHbw4hB4DveQeNDEEHnu2ZB6sKYQe867kHyZwpB9rp2QfnmkkH+Of5AC
AQIDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQD
BAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAME
AwQAABdoAAAAACowAQQAABwgAAkAACowAQQAABwgAAlITVQARUVTVABFRVQAAAAAAQEAAAABAQ==
"""
NEW_YORK = b"""
VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAABcAAADrAAAABAAAABCeph5wn7rrYKCGAHCh
ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e
S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0
YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg
yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db
wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW
8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b
YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g
BGD9cAVQ4GEGQN9yBzDCYgeNGXMJEKRjCa2U9ArwhmQL4IV1DNmi5Q3AZ3YOuYTmD6mD9xCZZucR
iWX4EnlI6BNpR/kUWSrpFUkp+RY5DOoXKQv6GCIpaxkI7fsaAgtsGvIKfBvh7Wwc0ex8HcHPbR6x
zn0fobFtIHYA/SGBk20iVeL+I2qv7iQ1xP4lSpHuJhWm/ycqc+8n/sOAKQpV8CnepYAq6jfxK76H
gSzTVHItnmmCLrM2cy9+S4MwkxhzMWdoBDJy+nQzR0oENFLcdTUnLAU2Mr51NwcOBjgb2vY45vAG
Ofu89jrG0gY72572PK/uhj27gPY+j9CGP5ti9kBvsoZBhH92Qk+UhkNkYXZEL3aHRURDd0XzqQdH
LV/3R9OLB0kNQfdJs20HSu0j90uciYdM1kB3TXxrh062IndPXE2HUJYEd1E8L4dSdeZ3UxwRh1RV
yHdU+/OHVjWqd1blEAdYHsb3WMTyB1n+qPdapNQHW96K91yEtgddvmz3XmSYB1+eTvdgTbSHYYdr
d2ItlodjZ013ZA14h2VHL3dl7VqHZycRd2fNPIdpBvN3aa0eh2rm1XdrljsHbM/x9212HQdur9P3
b1X/B3CPtfdxNeEHcm+X93MVwwd0T3n3dP7fh3Y4lnd23sGHeBh4d3i+o4d5+Fp3ep6Fh3vYPHd8
fmeHfbged35eSYd/mAB3AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA
AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU
AEVQVAAEslgAAAAAAQWk7AEAAAACB4YfggAAAAMJZ1MDAAAABAtIhoQAAAAFDSsLhQAAAAYPDD8G
AAAABxDtcocAAAAIEs6mCAAAAAkVn8qJAAAACheA/goAAAALGWIxiwAAAAwdJeoMAAAADSHa5Q0A
AAAOJZ6djgAAAA8nf9EPAAAAECpQ9ZAAAAARLDIpEQAAABIuE1ySAAAAEzDnJBMAAAAUM7hIlAAA
ABU2jBAVAAAAFkO3G5YAAAAXAAAAAQAAAAE=
"""
TZICAL_EST5EDT = """
BEGIN:VTIMEZONE
TZID:US-Eastern
LAST-MODIFIED:19870101T000000Z
TZURL:http://zones.stds_r_us.net/tz/US-Eastern
BEGIN:STANDARD
DTSTART:19671029T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZOFFSETFROM:-0400
TZOFFSETTO:-0500
TZNAME:EST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19870405T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
TZOFFSETFROM:-0500
TZOFFSETTO:-0400
TZNAME:EDT
END:DAYLIGHT
END:VTIMEZONE
"""
TZICAL_PST8PDT = """
BEGIN:VTIMEZONE
TZID:US-Pacific
LAST-MODIFIED:19870101T000000Z
BEGIN:STANDARD
DTSTART:19671029T020000
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
TZNAME:PST
END:STANDARD
BEGIN:DAYLIGHT
DTSTART:19870405T020000
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
TZNAME:PDT
END:DAYLIGHT
END:VTIMEZONE
"""
###
# Mix-ins
class context_passthrough(object):
def __init__(*args, **kwargs):
pass
def __enter__(*args, **kwargs):
pass
def __exit__(*args, **kwargs):
pass
class TzFoldMixin(object):
""" Mix-in class for testing ambiguous times """
def gettz(self, tzname):
raise NotImplementedError
def _get_tzname(self, tzname):
return tzname
def _gettz_context(self, tzname):
return context_passthrough()
def testFoldPositiveUTCOffset(self):
# Test that we can resolve ambiguous times
tzname = self._get_tzname('Australia/Sydney')
with self._gettz_context(tzname):
SYD0 = self.gettz(tzname)
SYD1 = self.gettz(tzname)
t0_u = datetime(2012, 3, 31, 15, 30, tzinfo=tz.tzutc()) # AEST
t1_u = datetime(2012, 3, 31, 16, 30, tzinfo=tz.tzutc()) # AEDT
# Using fresh tzfiles
t0_syd0 = t0_u.astimezone(SYD0)
t1_syd1 = t1_u.astimezone(SYD1)
self.assertEqual(t0_syd0.replace(tzinfo=None),
datetime(2012, 4, 1, 2, 30))
self.assertEqual(t1_syd1.replace(tzinfo=None),
datetime(2012, 4, 1, 2, 30))
self.assertEqual(t0_syd0.utcoffset(), timedelta(hours=11))
self.assertEqual(t1_syd1.utcoffset(), timedelta(hours=10))
def testGapPositiveUTCOffset(self):
# Test that we don't have a problem around gaps.
tzname = self._get_tzname('Australia/Sydney')
with self._gettz_context(tzname):
SYD0 = self.gettz(tzname)
SYD1 = self.gettz(tzname)
t0_u = datetime(2012, 10, 6, 15, 30, tzinfo=tz.tzutc()) # AEST
t1_u = datetime(2012, 10, 6, 16, 30, tzinfo=tz.tzutc()) # AEDT
# Using fresh tzfiles
t0 = t0_u.astimezone(SYD0)
t1 = t1_u.astimezone(SYD1)
self.assertEqual(t0.replace(tzinfo=None),
datetime(2012, 10, 7, 1, 30))
self.assertEqual(t1.replace(tzinfo=None),
datetime(2012, 10, 7, 3, 30))
self.assertEqual(t0.utcoffset(), timedelta(hours=10))
self.assertEqual(t1.utcoffset(), timedelta(hours=11))
def testFoldNegativeUTCOffset(self):
# Test that we can resolve ambiguous times
tzname = self._get_tzname('America/Toronto')
with self._gettz_context(tzname):
# Calling fromutc() alters the tzfile object
TOR0 = self.gettz(tzname)
TOR1 = self.gettz(tzname)
t0_u = datetime(2011, 11, 6, 5, 30, tzinfo=tz.tzutc())
t1_u = datetime(2011, 11, 6, 6, 30, tzinfo=tz.tzutc())
# Using fresh tzfiles
t0_tor0 = t0_u.astimezone(TOR0)
t1_tor1 = t1_u.astimezone(TOR1)
self.assertEqual(t0_tor0.replace(tzinfo=None),
datetime(2011, 11, 6, 1, 30))
self.assertEqual(t1_tor1.replace(tzinfo=None),
datetime(2011, 11, 6, 1, 30))
self.assertEqual(t0_tor0.utcoffset(), timedelta(hours=-4.0))
self.assertEqual(t1_tor1.utcoffset(), timedelta(hours=-5.0))
def testGapNegativeUTCOffset(self):
# Test that we don't have a problem around gaps.
tzname = self._get_tzname('America/Toronto')
with self._gettz_context(tzname):
# Calling fromutc() alters the tzfile object
TOR0 = self.gettz(tzname)
TOR1 = self.gettz(tzname)
t0_u = datetime(2011, 3, 13, 6, 30, tzinfo=tz.tzutc())
t1_u = datetime(2011, 3, 13, 7, 30, tzinfo=tz.tzutc())
# Using fresh tzfiles
t0 = t0_u.astimezone(TOR0)
t1 = t1_u.astimezone(TOR1)
self.assertEqual(t0.replace(tzinfo=None),
datetime(2011, 3, 13, 1, 30))
self.assertEqual(t1.replace(tzinfo=None),
datetime(2011, 3, 13, 3, 30))
self.assertNotEqual(t0, t1)
self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0))
self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0))
def testFoldIndependence(self):
tzname = self._get_tzname('America/New_York')
with self._gettz_context(tzname):
NYC = self.gettz(tzname)
UTC = tz.tzutc()
hour = timedelta(hours=1)
# Firmly 2015-11-01 0:30 EDT-4
pre_dst = datetime(2015, 11, 1, 0, 30, tzinfo=NYC)
# Currently, there's no way around the fact that this resolves to an
# ambiguous date, which defaults to EST. I'm not hard-coding in the
# answer, though, because the preferred behavior would be that this
# results in a time on the EDT side.
# Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5
in_dst = pre_dst + hour
in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT
# Doing the arithmetic in UTC creates a date that is unambiguously
# 2015-11-01 1:30 EDT-5
in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC)
# Make sure the dates are actually ambiguous
self.assertEqual(in_dst, in_dst_via_utc)
# Make sure we got the right folding behavior
self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0)
# Now check to make sure in_dst's tzname hasn't changed
self.assertEqual(in_dst_tzname_0, in_dst.tzname())
def _test_ambiguous_time(self, dt, tzid, ambiguous):
# This is a test to check that the individual is_ambiguous values
# on the _tzinfo subclasses work.
tzname = self._get_tzname(tzid)
with self._gettz_context(tzname):
tzi = self.gettz(tzname)
self.assertEqual(tz.datetime_ambiguous(dt, tz=tzi), ambiguous)
def testAmbiguousNegativeUTCOffset(self):
self._test_ambiguous_time(datetime(2015, 11, 1, 1, 30),
'America/New_York', True)
def testAmbiguousPositiveUTCOffset(self):
self._test_ambiguous_time(datetime(2012, 4, 1, 2, 30),
'Australia/Sydney', True)
def testUnambiguousNegativeUTCOffset(self):
self._test_ambiguous_time(datetime(2015, 11, 1, 2, 30),
'America/New_York', False)
def testUnambiguousPositiveUTCOffset(self):
self._test_ambiguous_time(datetime(2012, 4, 1, 3, 30),
'Australia/Sydney', False)
def testUnambiguousGapNegativeUTCOffset(self):
# Imaginary time
self._test_ambiguous_time(datetime(2011, 3, 13, 2, 30),
'America/New_York', False)
def testUnambiguousGapPositiveUTCOffset(self):
# Imaginary time
self._test_ambiguous_time(datetime(2012, 10, 7, 2, 30),
'Australia/Sydney', False)
def _test_imaginary_time(self, dt, tzid, exists):
tzname = self._get_tzname(tzid)
with self._gettz_context(tzname):
tzi = self.gettz(tzname)
self.assertEqual(tz.datetime_exists(dt, tz=tzi), exists)
def testImaginaryNegativeUTCOffset(self):
self._test_imaginary_time(datetime(2011, 3, 13, 2, 30),
'America/New_York', False)
def testNotImaginaryNegativeUTCOffset(self):
self._test_imaginary_time(datetime(2011, 3, 13, 1, 30),
'America/New_York', True)
def testImaginaryPositiveUTCOffset(self):
self._test_imaginary_time(datetime(2012, 10, 7, 2, 30),
'Australia/Sydney', False)
def testNotImaginaryPositiveUTCOffset(self):
self._test_imaginary_time(datetime(2012, 10, 7, 1, 30),
'Australia/Sydney', True)
def testNotImaginaryFoldNegativeUTCOffset(self):
self._test_imaginary_time(datetime(2015, 11, 1, 1, 30),
'America/New_York', True)
def testNotImaginaryFoldPositiveUTCOffset(self):
self._test_imaginary_time(datetime(2012, 4, 1, 3, 30),
'Australia/Sydney', True)
@unittest.skip("Known failure in Python 3.6.")
def testEqualAmbiguousComparison(self):
tzname = self._get_tzname('Australia/Sydney')
with self._gettz_context(tzname):
SYD0 = self.gettz(tzname)
SYD1 = self.gettz(tzname)
t0_u = datetime(2012, 3, 31, 14, 30, tzinfo=tz.tzutc()) # AEST
t1_u = datetime(2012, 3, 31, 16, 30, tzinfo=tz.tzutc()) # AEDT
t0_syd0 = t0_u.astimezone(SYD0)
t0_syd1 = t0_u.astimezone(SYD1)
# This is considered an "inter-zone comparison" because it's an
# ambiguous datetime.
self.assertEqual(t0_syd0, t0_syd1)
class TzWinFoldMixin(object):
def get_args(self, tzname):
return (tzname, )
class context(object):
def __init__(*args, **kwargs):
pass
def __enter__(*args, **kwargs):
pass
def __exit__(*args, **kwargs):
pass
def get_utc_transitions(self, tzi, year, gap):
dston, dstoff = tzi.transitions(year)
if gap:
t_n = dston - timedelta(minutes=30)
t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.tzutc())
t1_u = t0_u + timedelta(hours=1)
else:
# Get 1 hour before the first ambiguous date
t_n = dstoff - timedelta(minutes=30)
t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.tzutc())
t_n += timedelta(hours=1) # Naive ambiguous date
t0_u = t0_u + timedelta(hours=1) # First ambiguous date
t1_u = t0_u + timedelta(hours=1) # Second ambiguous date
return t_n, t0_u, t1_u
def testFoldPositiveUTCOffset(self):
# Test that we can resolve ambiguous times
tzname = 'AUS Eastern Standard Time'
args = self.get_args(tzname)
with self.context(tzname):
# Calling fromutc() alters the tzfile object
SYD = self.tzclass(*args)
SYD0 = self.tzclass(*args)
SYD1 = self.tzclass(*args)
self.assertIsNot(SYD0, SYD1)
# Get the transition time in UTC from the object, because
# Windows doesn't store historical info
t_n, t0_u, t1_u = self.get_utc_transitions(SYD0, 2012, False)
# Using fresh tzfiles
t0_syd0 = t0_u.astimezone(SYD0)
t1_syd1 = t1_u.astimezone(SYD1)
self.assertEqual(t0_syd0.replace(tzinfo=None), t_n)
self.assertEqual(t1_syd1.replace(tzinfo=None), t_n)
self.assertNotEqual(t0_syd0, t1_syd1)
self.assertEqual(t0_syd0.utcoffset(), timedelta(hours=11))
self.assertEqual(t1_syd1.utcoffset(), timedelta(hours=10))
# Re-using them across (make sure there's no cache problem)
t0_syd1 = t0_u.astimezone(SYD1)
t1_syd0 = t1_u.astimezone(SYD0)
self.assertEqual(t0_syd0, t0_syd1)
self.assertEqual(t1_syd1, t1_syd0)
def testGapPositiveUTCOffset(self):
# Test that we don't have a problem around gaps.
tzname = 'AUS Eastern Standard Time'
args = self.get_args(tzname)
with self.context(tzname):
# Calling fromutc() alters the tzfile object
SYD = self.tzclass(*args)
SYD0 = self.tzclass(*args)
SYD1 = self.tzclass(*args)
self.assertIsNot(SYD0, SYD1)
t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, True)
# Using fresh tzfiles
t0 = t0_u.astimezone(SYD0)
t1 = t1_u.astimezone(SYD1)
self.assertEqual(t0.replace(tzinfo=None), t_n)
self.assertEqual(t1.replace(tzinfo=None), t_n + timedelta(hours=2))
self.assertEqual(t0.utcoffset(), timedelta(hours=10))
self.assertEqual(t1.utcoffset(), timedelta(hours=11))
def testFoldNegativeUTCOffset(self):
# Test that we can resolve ambiguous times
tzname = 'Eastern Standard Time'
args = self.get_args(tzname)
# Calling fromutc() alters the tzfile object
with self.context(tzname):
TOR = self.tzclass(*args)
TOR0 = self.tzclass(*args)
TOR1 = self.tzclass(*args)
t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, False)
# Using fresh tzfiles
t0_tor0 = t0_u.astimezone(TOR0)
t1_tor1 = t1_u.astimezone(TOR1)
self.assertEqual(t0_tor0.replace(tzinfo=None), t_n)
self.assertEqual(t1_tor1.replace(tzinfo=None), t_n)
self.assertNotEqual(t0_tor0.tzname(), t1_tor1.tzname())
self.assertEqual(t0_tor0.utcoffset(), timedelta(hours=-4.0))
self.assertEqual(t1_tor1.utcoffset(), timedelta(hours=-5.0))
# Re-using them across (make sure there's no cache problem)
t0_tor1 = t0_u.astimezone(TOR1)
t1_tor0 = t1_u.astimezone(TOR0)
self.assertEqual(t0_tor0, t0_tor1)
self.assertEqual(t1_tor1, t1_tor0)
def testGapNegativeUTCOffset(self):
# Test that we don't have a problem around gaps.
tzname = 'Eastern Standard Time'
args = self.get_args(tzname)
# Calling fromutc() alters the tzfile object
with self.context(tzname):
TOR = self.tzclass(*args)
TOR0 = self.tzclass(*args)
TOR1 = self.tzclass(*args)
t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, True)
# Using fresh tzfiles
t0 = t0_u.astimezone(TOR0)
t1 = t1_u.astimezone(TOR1)
self.assertEqual(t0.replace(tzinfo=None),
t_n)
self.assertEqual(t1.replace(tzinfo=None),
t_n + timedelta(hours=2))
self.assertNotEqual(t0, t1)
self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0))
self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0))
def testFoldIndependence(self):
tzname = 'Eastern Standard Time'
args = self.get_args(tzname)
with self.context(tzname):
NYC = self.tzclass(*args)
UTC = tz.tzutc()
hour = timedelta(hours=1)
# Firmly 2015-11-01 0:30 EDT-4
t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2015, False)
pre_dst = (t_n - hour).replace(tzinfo=NYC)
# Currently, there's no way around the fact that this resolves to an
# ambiguous date, which defaults to EST. I'm not hard-coding in the
# answer, though, because the preferred behavior would be that this
# results in a time on the EDT side.
# Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5
in_dst = pre_dst + hour
in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT
# Doing the arithmetic in UTC creates a date that is unambiguously
# 2015-11-01 1:30 EDT-5
in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC)
# Make sure we got the right folding behavior
self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0)
# Now check to make sure in_dst's tzname hasn't changed
self.assertEqual(in_dst_tzname_0, in_dst.tzname())
###
# Test Cases
class TzUTCTest(unittest.TestCase):
def testOffset(self):
ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc())
self.assertEqual(ct.utcoffset(), timedelta(seconds=0))
def testDst(self):
ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc())
self.assertEqual(ct.dst(), timedelta(seconds=0))
def testTzName(self):
ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc())
self.assertEqual(ct.tzname(), 'UTC')
def testEquality(self):
UTC0 = tz.tzutc()
UTC1 = tz.tzutc()
self.assertIsNot(UTC0, UTC1)
self.assertEqual(UTC0, UTC1)
def testInequality(self):
UTC = tz.tzutc()
UTCp4 = tz.tzoffset('UTC+4', 14400)
self.assertNotEqual(UTC, UTCp4)
def testInequalityInteger(self):
self.assertFalse(tz.tzutc() == 7)
self.assertNotEqual(tz.tzutc(), 7)
def testInequalityUnsupported(self):
self.assertEqual(tz.tzutc(), ComparesEqual)
def testRepr(self):
UTC = tz.tzutc()
self.assertEqual(repr(UTC), 'tzutc()')
def testTimeOnlyUTC(self):
# https://github.com/dateutil/dateutil/issues/132
# tzutc doesn't care
tz_utc = tz.tzutc()
self.assertEqual(dt_time(13, 20, tzinfo=tz_utc).utcoffset(),
timedelta(0))
def testAmbiguity(self):
# Pick an arbitrary datetime, this should always return False.
dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzutc())
self.assertFalse(tz.datetime_ambiguous(dt))
class TzOffsetTest(unittest.TestCase):
def testTimedeltaOffset(self):
est = tz.tzoffset('EST', timedelta(hours=-5))
est_s = tz.tzoffset('EST', -18000)
self.assertEqual(est, est_s)
def testTzNameNone(self):
gmt5 = tz.tzoffset(None, -18000) # -5:00
self.assertIs(datetime(2003, 10, 26, 0, 0, tzinfo=gmt5).tzname(),
None)
def testTimeOnlyOffset(self):
# tzoffset doesn't care
tz_offset = tz.tzoffset('+3', 3600)
self.assertEqual(dt_time(13, 20, tzinfo=tz_offset).utcoffset(),
timedelta(seconds=3600))
def testTzOffsetRepr(self):
tname = 'EST'
tzo = tz.tzoffset(tname, -5 * 3600)
self.assertEqual(repr(tzo), "tzoffset(" + repr(tname) + ", -18000)")
def testEquality(self):
utc = tz.tzoffset('UTC', 0)
gmt = tz.tzoffset('GMT', 0)
self.assertEqual(utc, gmt)
def testUTCEquality(self):
utc = tz.tzutc()
o_utc = tz.tzoffset('UTC', 0)
self.assertEqual(utc, o_utc)
self.assertEqual(o_utc, utc)
def testInequalityInvalid(self):
tzo = tz.tzoffset('-3', -3 * 3600)
self.assertFalse(tzo == -3)
self.assertNotEqual(tzo, -3)
def testInequalityUnsupported(self):
tzo = tz.tzoffset('-5', -5 * 3600)
self.assertTrue(tzo == ComparesEqual)
self.assertFalse(tzo != ComparesEqual)
self.assertEqual(tzo, ComparesEqual)
def testAmbiguity(self):
# Pick an arbitrary datetime, this should always return False.
dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzoffset("EST", -5 * 3600))
self.assertFalse(tz.datetime_ambiguous(dt))
class TzLocalTest(unittest.TestCase):
def testEquality(self):
tz1 = tz.tzlocal()
tz2 = tz.tzlocal()
# Explicitly calling == and != here to ensure the operators work
self.assertTrue(tz1 == tz2)
self.assertFalse(tz1 != tz2)
def testInequalityFixedOffset(self):
tzl = tz.tzlocal()
tzos = tz.tzoffset('LST', total_seconds(tzl._std_offset))
tzod = tz.tzoffset('LDT', total_seconds(tzl._std_offset))
self.assertFalse(tzl == tzos)
self.assertFalse(tzl == tzod)
self.assertTrue(tzl != tzos)
self.assertTrue(tzl != tzod)
def testInequalityInvalid(self):
tzl = tz.tzlocal()
UTC = tz.tzutc()
self.assertTrue(tzl != 1)
self.assertTrue(tzl != tz.tzutc())
self.assertFalse(tzl == 1)
self.assertFalse(tzl == UTC)
def testInequalityUnsupported(self):
tzl = tz.tzlocal()
self.assertTrue(tzl == ComparesEqual)
self.assertFalse(tzl != ComparesEqual)
def testRepr(self):
tzl = tz.tzlocal()
self.assertEqual(repr(tzl), 'tzlocal()')
@unittest.skipIf(IS_WIN, "requires Unix")
@unittest.skipUnless(TZEnvContext.tz_change_allowed(),
TZEnvContext.tz_change_disallowed_message())
class TzLocalNixTest(unittest.TestCase, TzFoldMixin):
# This is a set of tests for `tzlocal()` on *nix systems
# POSIX string indicating change to summer time on the 2nd Sunday in March
# at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007)
TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2'
# POSIX string for AEST/AEDT (valid >= 2008)
TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3'
# POSIX string for UTC
UTC = 'UTC'
def gettz(self, tzname):
# Actual time zone changes are handled by the _gettz_context function
return tz.tzlocal()
def _gettz_context(self, tzname):
tzname_map = {'Australia/Sydney': self.TZ_AEST,
'America/Toronto': self.TZ_EST,
'America/New_York': self.TZ_EST}
return TZEnvContext(tzname_map.get(tzname, tzname))
def _testTzFunc(self, tzval, func, std_val, dst_val):
"""
This generates tests about how the behavior of a function ``func``
changes between STD and DST (e.g. utcoffset, tzname, dst).
It assume that DST starts the 2nd Sunday in March and ends the 1st
Sunday in November
"""
with TZEnvContext(tzval):
dt1 = datetime(2015, 2, 1, 12, 0, tzinfo=tz.tzlocal()) # STD
dt2 = datetime(2015, 5, 1, 12, 0, tzinfo=tz.tzlocal()) # DST
self.assertEqual(func(dt1), std_val)
self.assertEqual(func(dt2), dst_val)
def _testTzName(self, tzval, std_name, dst_name):
func = datetime.tzname
self._testTzFunc(tzval, func, std_name, dst_name)
def testTzNameDST(self):
# Test tzname in a zone with DST
self._testTzName(self.TZ_EST, 'EST', 'EDT')
def testTzNameUTC(self):
# Test tzname in a zone without DST
self._testTzName(self.UTC, 'UTC', 'UTC')
def _testOffset(self, tzval, std_off, dst_off):
func = datetime.utcoffset
self._testTzFunc(tzval, func, std_off, dst_off)
def testOffsetDST(self):
self._testOffset(self.TZ_EST, timedelta(hours=-5), timedelta(hours=-4))
def testOffsetUTC(self):
self._testOffset(self.UTC, timedelta(0), timedelta(0))
def _testDST(self, tzval, dst_dst):
func = datetime.dst
std_dst = timedelta(0)
self._testTzFunc(tzval, func, std_dst, dst_dst)
def testDSTDST(self):
self._testDST(self.TZ_EST, timedelta(hours=1))
def testDSTUTC(self):
self._testDST(self.UTC, timedelta(0))
def testTimeOnlyOffsetLocalUTC(self):
with TZEnvContext(self.UTC):
self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(),
timedelta(0))
def testTimeOnlyOffsetLocalDST(self):
with TZEnvContext(self.TZ_EST):
self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(),
None)
def testTimeOnlyDSTLocalUTC(self):
with TZEnvContext(self.UTC):
self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(),
timedelta(0))
def testTimeOnlyDSTLocalDST(self):
with TZEnvContext(self.TZ_EST):
self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(),
None)
class GettzTest(unittest.TestCase, TzFoldMixin):
gettz = staticmethod(tz.gettz)
def testGettz(self):
# bug 892569
str(self.gettz('UTC'))
def testGetTzEquality(self):
self.assertEqual(self.gettz('UTC'), self.gettz('UTC'))
def testTimeOnlyGettz(self):
# gettz returns None
tz_get = self.gettz('Europe/Minsk')
self.assertIs(dt_time(13, 20, tzinfo=tz_get).utcoffset(), None)
def testTimeOnlyGettzDST(self):
# gettz returns None
tz_get = self.gettz('Europe/Minsk')
self.assertIs(dt_time(13, 20, tzinfo=tz_get).dst(), None)
def testTimeOnlyGettzTzName(self):
tz_get = self.gettz('Europe/Minsk')
self.assertIs(dt_time(13, 20, tzinfo=tz_get).tzname(), None)
def testTimeOnlyFormatZ(self):
tz_get = self.gettz('Europe/Minsk')
t = dt_time(13, 20, tzinfo=tz_get)
self.assertEqual(t.strftime('%H%M%Z'), '1320')
def testPortugalDST(self):
# In 1996, Portugal changed from CET to WET
PORTUGAL = self.gettz('Portugal')
t_cet = datetime(1996, 3, 31, 1, 59, tzinfo=PORTUGAL)
self.assertEqual(t_cet.tzname(), 'CET')
self.assertEqual(t_cet.utcoffset(), timedelta(hours=1))
self.assertEqual(t_cet.dst(), timedelta(0))
t_west = datetime(1996, 3, 31, 2, 1, tzinfo=PORTUGAL)
self.assertEqual(t_west.tzname(), 'WEST')
self.assertEqual(t_west.utcoffset(), timedelta(hours=1))
self.assertEqual(t_west.dst(), timedelta(hours=1))
class ZoneInfoGettzTest(GettzTest, WarningTestMixin):
def gettz(self, name):
zoneinfo_file = zoneinfo.get_zonefile_instance()
return zoneinfo_file.get(name)
def testZoneInfoFileStart1(self):
tz = self.gettz("EST5EDT")
self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST",
MISSING_TARBALL)
self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT")
def testZoneInfoFileEnd1(self):
tzc = self.gettz("EST5EDT")
self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(),
"EDT", MISSING_TARBALL)
end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc), fold=1)
self.assertEqual(end_est.tzname(), "EST")
def testZoneInfoOffsetSignal(self):
utc = self.gettz("UTC")
nyc = self.gettz("America/New_York")
self.assertNotEqual(utc, None, MISSING_TARBALL)
self.assertNotEqual(nyc, None)
t0 = datetime(2007, 11, 4, 0, 30, tzinfo=nyc)
t1 = t0.astimezone(utc)
t2 = t1.astimezone(nyc)
self.assertEqual(t0, t2)
self.assertEqual(nyc.dst(t0), timedelta(hours=1))
def testZoneInfoCopy(self):
# copy.copy() called on a ZoneInfo file was returning the same instance
CHI = self.gettz('America/Chicago')
CHI_COPY = copy.copy(CHI)
self.assertIsNot(CHI, CHI_COPY)
self.assertEqual(CHI, CHI_COPY)
def testZoneInfoDeepCopy(self):
CHI = self.gettz('America/Chicago')
CHI_COPY = copy.deepcopy(CHI)
self.assertIsNot(CHI, CHI_COPY)
self.assertEqual(CHI, CHI_COPY)
def testZoneInfoInstanceCaching(self):
zif_0 = zoneinfo.get_zonefile_instance()
zif_1 = zoneinfo.get_zonefile_instance()
self.assertIs(zif_0, zif_1)
def testZoneInfoNewInstance(self):
zif_0 = zoneinfo.get_zonefile_instance()
zif_1 = zoneinfo.get_zonefile_instance(new_instance=True)
zif_2 = zoneinfo.get_zonefile_instance()
self.assertIsNot(zif_0, zif_1)
self.assertIs(zif_1, zif_2)
def testZoneInfoDeprecated(self):
with self.assertWarns(DeprecationWarning):
tzi = zoneinfo.gettz('US/Eastern')
def testZoneInfoMetadataDeprecated(self):
with self.assertWarns(DeprecationWarning):
tzdb_md = zoneinfo.gettz_db_metadata()
class TZRangeTest(unittest.TestCase, TzFoldMixin):
TZ_EST = tz.tzrange('EST', timedelta(hours=-5),
'EDT', timedelta(hours=-4),
start=relativedelta(month=3, day=1, hour=2,
weekday=SU(+2)),
end=relativedelta(month=11, day=1, hour=1,
weekday=SU(+1)))
TZ_AEST = tz.tzrange('AEST', timedelta(hours=10),
'AEDT', timedelta(hours=11),
start=relativedelta(month=10, day=1, hour=2,
weekday=SU(+1)),
end=relativedelta(month=4, day=1, hour=2,
weekday=SU(+1)))
# POSIX string for UTC
UTC = 'UTC'
def gettz(self, tzname):
tzname_map = {'Australia/Sydney': self.TZ_AEST,
'America/Toronto': self.TZ_EST,
'America/New_York': self.TZ_EST}
return tzname_map[tzname]
def testRangeCmp1(self):
self.assertEqual(tz.tzstr("EST5EDT"),
tz.tzrange("EST", -18000, "EDT", -14400,
relativedelta(hours=+2,
month=4, day=1,
weekday=SU(+1)),
relativedelta(hours=+1,
month=10, day=31,
weekday=SU(-1))))
def testRangeCmp2(self):
self.assertEqual(tz.tzstr("EST5EDT"),
tz.tzrange("EST", -18000, "EDT"))
def testRangeOffsets(self):
TZR = tz.tzrange('EST', -18000, 'EDT', -14400,
start=relativedelta(hours=2, month=4, day=1,
weekday=SU(+2)),
end=relativedelta(hours=1, month=10, day=31,
weekday=SU(-1)))
dt_std = datetime(2014, 4, 11, 12, 0, tzinfo=TZR) # STD
dt_dst = datetime(2016, 4, 11, 12, 0, tzinfo=TZR) # DST
dst_zero = timedelta(0)
dst_hour = timedelta(hours=1)
std_offset = timedelta(hours=-5)
dst_offset = timedelta(hours=-4)
# Check dst()
self.assertEqual(dt_std.dst(), dst_zero)
self.assertEqual(dt_dst.dst(), dst_hour)
# Check utcoffset()
self.assertEqual(dt_std.utcoffset(), std_offset)
self.assertEqual(dt_dst.utcoffset(), dst_offset)
# Check tzname
self.assertEqual(dt_std.tzname(), 'EST')
self.assertEqual(dt_dst.tzname(), 'EDT')
def testTimeOnlyRangeFixed(self):
# This is a fixed-offset zone, so tzrange allows this
tz_range = tz.tzrange('dflt', stdoffset=timedelta(hours=-3))
self.assertEqual(dt_time(13, 20, tzinfo=tz_range).utcoffset(),
timedelta(hours=-3))
def testTimeOnlyRange(self):
# tzrange returns None because this zone has DST
tz_range = tz.tzrange('EST', timedelta(hours=-5),
'EDT', timedelta(hours=-4))
self.assertIs(dt_time(13, 20, tzinfo=tz_range).utcoffset(), None)
def testBrokenIsDstHandling(self):
# tzrange._isdst() was using a date() rather than a datetime().
# Issue reported by Lennart Regebro.
dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.tzutc())
self.assertEqual(dt.astimezone(tz=tz.gettz("GMT+2")),
datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2")))
def testRangeTimeDelta(self):
# Test that tzrange can be specified with a timedelta instead of an int.
EST5EDT_td = tz.tzrange('EST', timedelta(hours=-5),
'EDT', timedelta(hours=-4))
EST5EDT_sec = tz.tzrange('EST', -18000,
'EDT', -14400)
self.assertEqual(EST5EDT_td, EST5EDT_sec)
def testRangeEquality(self):
TZR1 = tz.tzrange('EST', -18000, 'EDT', -14400)
# Standard abbreviation different
TZR2 = tz.tzrange('ET', -18000, 'EDT', -14400)
self.assertNotEqual(TZR1, TZR2)
# DST abbreviation different
TZR3 = tz.tzrange('EST', -18000, 'EMT', -14400)
self.assertNotEqual(TZR1, TZR3)
# STD offset different
TZR4 = tz.tzrange('EST', -14000, 'EDT', -14400)
self.assertNotEqual(TZR1, TZR4)
# DST offset different
TZR5 = tz.tzrange('EST', -18000, 'EDT', -18000)
self.assertNotEqual(TZR1, TZR5)
# Start delta different
TZR6 = tz.tzrange('EST', -18000, 'EDT', -14400,
start=relativedelta(hours=+1, month=3,
day=1, weekday=SU(+2)))
self.assertNotEqual(TZR1, TZR6)
# End delta different
TZR7 = tz.tzrange('EST', -18000, 'EDT', -14400,
end=relativedelta(hours=+1, month=11,
day=1, weekday=SU(+2)))
self.assertNotEqual(TZR1, TZR7)
def testRangeInequalityUnsupported(self):
TZR = tz.tzrange('EST', -18000, 'EDT', -14400)
self.assertFalse(TZR == 4)
self.assertTrue(TZR == ComparesEqual)
self.assertFalse(TZR != ComparesEqual)
class TZStrTest(unittest.TestCase, TzFoldMixin):
# POSIX string indicating change to summer time on the 2nd Sunday in March
# at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007)
TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2'
# POSIX string for AEST/AEDT (valid >= 2008)
TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3'
def gettz(self, tzname):
# Actual time zone changes are handled by the _gettz_context function
tzname_map = {'Australia/Sydney': self.TZ_AEST,
'America/Toronto': self.TZ_EST,
'America/New_York': self.TZ_EST}
return tz.tzstr(tzname_map[tzname])
def testStrStart1(self):
self.assertEqual(datetime(2003, 4, 6, 1, 59,
tzinfo=tz.tzstr("EST5EDT")).tzname(), "EST")
self.assertEqual(datetime(2003, 4, 6, 2, 00,
tzinfo=tz.tzstr("EST5EDT")).tzname(), "EDT")
def testStrEnd1(self):
self.assertEqual(datetime(2003, 10, 26, 0, 59,
tzinfo=tz.tzstr("EST5EDT")).tzname(), "EDT")
end = tz.enfold(datetime(2003, 10, 26, 1, 00,
tzinfo=tz.tzstr("EST5EDT")), fold=1)
self.assertEqual(end.tzname(), "EST")
def testStrStart2(self):
s = "EST5EDT,4,0,6,7200,10,0,26,7200,3600"
self.assertEqual(datetime(2003, 4, 6, 1, 59,
tzinfo=tz.tzstr(s)).tzname(), "EST")
self.assertEqual(datetime(2003, 4, 6, 2, 00,
tzinfo=tz.tzstr(s)).tzname(), "EDT")
def testStrEnd2(self):
s = "EST5EDT,4,0,6,7200,10,0,26,7200,3600"
self.assertEqual(datetime(2003, 10, 26, 0, 59,
tzinfo=tz.tzstr(s)).tzname(), "EDT")
end = tz.enfold(datetime(2003, 10, 26, 1, 00,
tzinfo=tz.tzstr(s)), fold=1)
self.assertEqual(end.tzname(), "EST")
def testStrStart3(self):
s = "EST5EDT,4,1,0,7200,10,-1,0,7200,3600"
self.assertEqual(datetime(2003, 4, 6, 1, 59,
tzinfo=tz.tzstr(s)).tzname(), "EST")
self.assertEqual(datetime(2003, 4, 6, 2, 00,
tzinfo=tz.tzstr(s)).tzname(), "EDT")
def testStrEnd3(self):
s = "EST5EDT,4,1,0,7200,10,-1,0,7200,3600"
self.assertEqual(datetime(2003, 10, 26, 0, 59,
tzinfo=tz.tzstr(s)).tzname(), "EDT")
end = tz.enfold(datetime(2003, 10, 26, 1, 00,
tzinfo=tz.tzstr(s)), fold=1)
self.assertEqual(end.tzname(), "EST")
def testStrStart4(self):
s = "EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00"
self.assertEqual(datetime(2003, 4, 6, 1, 59,
tzinfo=tz.tzstr(s)).tzname(), "EST")
self.assertEqual(datetime(2003, 4, 6, 2, 00,
tzinfo=tz.tzstr(s)).tzname(), "EDT")
def testStrEnd4(self):
s = "EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00"
self.assertEqual(datetime(2003, 10, 26, 0, 59,
tzinfo=tz.tzstr(s)).tzname(), "EDT")
end = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tz.tzstr(s)),
fold=1)
self.assertEqual(end.tzname(), "EST")
def testStrStart5(self):
s = "EST5EDT4,95/02:00:00,298/02:00"
self.assertEqual(datetime(2003, 4, 6, 1, 59,
tzinfo=tz.tzstr(s)).tzname(), "EST")
self.assertEqual(datetime(2003, 4, 6, 2, 00,
tzinfo=tz.tzstr(s)).tzname(), "EDT")
def testStrEnd5(self):
s = "EST5EDT4,95/02:00:00,298/02"
self.assertEqual(datetime(2003, 10, 26, 0, 59,
tzinfo=tz.tzstr(s)).tzname(), "EDT")
end = tz.enfold(datetime(2003, 10, 26, 1, 00,
tzinfo=tz.tzstr(s)), fold=1)
self.assertEqual(end.tzname(), "EST")
def testStrStart6(self):
s = "EST5EDT4,J96/02:00:00,J299/02:00"
self.assertEqual(datetime(2003, 4, 6, 1, 59,
tzinfo=tz.tzstr(s)).tzname(), "EST")
self.assertEqual(datetime(2003, 4, 6, 2, 00,
tzinfo=tz.tzstr(s)).tzname(), "EDT")
def testStrEnd6(self):
s = "EST5EDT4,J96/02:00:00,J299/02"
self.assertEqual(datetime(2003, 10, 26, 0, 59,
tzinfo=tz.tzstr(s)).tzname(), "EDT")
end = tz.enfold(datetime(2003, 10, 26, 1, 00,
tzinfo=tz.tzstr(s)), fold=1)
self.assertEqual(end.tzname(), "EST")
def testStrStr(self):
# Test that tz.tzstr() won't throw an error if given a str instead
# of a unicode literal.
self.assertEqual(datetime(2003, 4, 6, 1, 59,
tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EST")
self.assertEqual(datetime(2003, 4, 6, 2, 00,
tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EDT")
def testStrCmp1(self):
self.assertEqual(tz.tzstr("EST5EDT"),
tz.tzstr("EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00"))
def testStrCmp2(self):
# TODO: This is parsing the default arguments.
self.assertEqual(tz.tzstr("EST5EDT"),
tz.tzstr("EST5EDT,4,1,0,7200,10,-1,0,7200,3600"))
def testStrInequality(self):
TZS1 = tz.tzstr('EST5EDT4')
# Standard abbreviation different
TZS2 = tz.tzstr('ET5EDT4')
self.assertNotEqual(TZS1, TZS2)
# DST abbreviation different
TZS3 = tz.tzstr('EST5EMT')
self.assertNotEqual(TZS1, TZS3)
# STD offset different
TZS4 = tz.tzstr('EST4EDT4')
self.assertNotEqual(TZS1, TZS4)
# DST offset different
TZS5 = tz.tzstr('EST5EDT3')
self.assertNotEqual(TZS1, TZS5)
def testStrInequalityStartEnd(self):
TZS1 = tz.tzstr('EST5EDT4')
# Start delta different
TZS2 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M10-5-0/02:00')
self.assertNotEqual(TZS1, TZS2)
# End delta different
TZS3 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M11-5-0/02:00')
self.assertNotEqual(TZS1, TZS3)
def testPosixOffset(self):
TZ1 = tz.tzstr('UTC-3')
self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ1).utcoffset(),
timedelta(hours=-3))
TZ2 = tz.tzstr('UTC-3', posix_offset=True)
self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ2).utcoffset(),
timedelta(hours=+3))
def testStrInequalityUnsupported(self):
TZS = tz.tzstr('EST5EDT')
self.assertFalse(TZS == 4)
self.assertTrue(TZS == ComparesEqual)
self.assertFalse(TZS != ComparesEqual)
def testTzStrRepr(self):
TZS1 = tz.tzstr('EST5EDT4')
TZS2 = tz.tzstr('EST')
self.assertEqual(repr(TZS1), "tzstr(" + repr('EST5EDT4') + ")")
self.assertEqual(repr(TZS2), "tzstr(" + repr('EST') + ")")
def testTzStrFailure(self):
with self.assertRaises(ValueError):
tz.tzstr('InvalidString;439999')
class TZICalTest(unittest.TestCase, TzFoldMixin):
def gettz(self, tzname):
TZ_EST = (
'BEGIN:VTIMEZONE',
'TZID:US-Eastern',
'BEGIN:STANDARD',
'DTSTART:19971029T020000',
'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11',
'TZOFFSETFROM:-0400',
'TZOFFSETTO:-0500',
'TZNAME:EST',
'END:STANDARD',
'BEGIN:DAYLIGHT',
'DTSTART:19980301T020000',
'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03',
'TZOFFSETFROM:-0500',
'TZOFFSETTO:-0400',
'TZNAME:EDT',
'END:DAYLIGHT',
'END:VTIMEZONE'
)
TZ_AEST = (
'BEGIN:VTIMEZONE',
'TZID:Australia-Sydney',
'BEGIN:STANDARD',
'DTSTART:19980301T030000',
'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=04',
'TZOFFSETFROM:+1100',
'TZOFFSETTO:+1000',
'TZNAME:AEST',
'END:STANDARD',
'BEGIN:DAYLIGHT',
'DTSTART:19971029T020000',
'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=10',
'TZOFFSETFROM:+1000',
'TZOFFSETTO:+1100',
'TZNAME:AEDT',
'END:DAYLIGHT',
'END:VTIMEZONE'
)
tzname_map = {'Australia/Sydney': TZ_AEST,
'America/Toronto': TZ_EST,
'America/New_York': TZ_EST}
tzc = tz.tzical(StringIO('\n'.join(tzname_map[tzname]))).get()
return tzc
def testRepr(self):
instr = StringIO(TZICAL_PST8PDT)
instr.name = 'StringIO(PST8PDT)'
tzc = tz.tzical(instr)
self.assertEqual(repr(tzc), "tzical(" + repr(instr.name) + ")")
# Test performance
def _test_us_zone(self, tzc, func, values, start):
if start:
dt1 = datetime(2003, 4, 6, 1, 59)
dt2 = datetime(2003, 4, 6, 2, 00)
fold = [0, 0]
else:
dt1 = datetime(2003, 10, 26, 0, 59)
dt2 = datetime(2003, 10, 26, 1, 00)
fold = [0, 1]
dts = (tz.enfold(dt.replace(tzinfo=tzc), fold=f)
for dt, f in zip((dt1, dt2), fold))
for value, dt in zip(values, dts):
self.assertEqual(func(dt), value)
def _test_multi_zones(self, tzstrs, tzids, func, values, start):
tzic = tz.tzical(StringIO(''.join(tzstrs)))
for tzid, vals in zip(tzids, values):
tzc = tzic.get(tzid)
self._test_us_zone(tzc, func, vals, start)
def _prepare_EST(self):
return tz.tzical(StringIO(TZICAL_EST5EDT)).get()
def _testEST(self, start, test_type):
tzc = self._prepare_EST()
argdict = {
'name': (datetime.tzname, ('EST', 'EDT')),
'offset': (datetime.utcoffset, (timedelta(hours=-5),
timedelta(hours=-4))),
'dst': (datetime.dst, (timedelta(hours=0),
timedelta(hours=1)))
}
func, values = argdict[test_type]
if not start:
values = reversed(values)
self._test_us_zone(tzc, func, values, start=start)
def testESTStartName(self):
self._testEST(start=True, test_type='name')
def testESTEndName(self):
self._testEST(start=False, test_type='name')
def testESTStartOffset(self):
self._testEST(start=True, test_type='offset')
def testESTEndOffset(self):
self._testEST(start=False, test_type='offset')
def testESTStartDST(self):
self._testEST(start=True, test_type='dst')
def testESTEndDST(self):
self._testEST(start=False, test_type='dst')
def _testMultizone(self, start, test_type):
tzstrs = (TZICAL_EST5EDT, TZICAL_PST8PDT)
tzids = ('US-Eastern', 'US-Pacific')
argdict = {
'name': (datetime.tzname, (('EST', 'EDT'),
('PST', 'PDT'))),
'offset': (datetime.utcoffset, ((timedelta(hours=-5),
timedelta(hours=-4)),
(timedelta(hours=-8),
timedelta(hours=-7)))),
'dst': (datetime.dst, ((timedelta(hours=0),
timedelta(hours=1)),
(timedelta(hours=0),
timedelta(hours=1))))
}
func, values = argdict[test_type]
if not start:
values = map(reversed, values)
self._test_multi_zones(tzstrs, tzids, func, values, start)
def testMultiZoneStartName(self):
self._testMultizone(start=True, test_type='name')
def testMultiZoneEndName(self):
self._testMultizone(start=False, test_type='name')
def testMultiZoneStartOffset(self):
self._testMultizone(start=True, test_type='offset')
def testMultiZoneEndOffset(self):
self._testMultizone(start=False, test_type='offset')
def testMultiZoneStartDST(self):
self._testMultizone(start=True, test_type='dst')
def testMultiZoneEndDST(self):
self._testMultizone(start=False, test_type='dst')
def testMultiZoneKeys(self):
tzic = tz.tzical(StringIO(''.join((TZICAL_EST5EDT, TZICAL_PST8PDT))))
# Sort keys because they are in a random order, being dictionary keys
keys = sorted(tzic.keys())
self.assertEqual(keys, ['US-Eastern', 'US-Pacific'])
# Test error conditions
def testEmptyString(self):
with self.assertRaises(ValueError):
tz.tzical(StringIO(""))
def testMultiZoneGet(self):
tzic = tz.tzical(StringIO(TZICAL_EST5EDT + TZICAL_PST8PDT))
with self.assertRaises(ValueError):
tzic.get()
# Test Parsing
def testGap(self):
tzic = tz.tzical(StringIO('\n'.join((TZICAL_EST5EDT, TZICAL_PST8PDT))))
keys = sorted(tzic.keys())
self.assertEqual(keys, ['US-Eastern', 'US-Pacific'])
class TZTest(unittest.TestCase):
def testFileStart1(self):
tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzc).tzname(), "EST")
self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzc).tzname(), "EDT")
def testFileEnd1(self):
tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(),
"EDT")
end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc))
self.assertEqual(end_est.tzname(), "EST")
def testFileLastTransition(self):
# After the last transition, it goes to standard time in perpetuity
tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
self.assertEqual(datetime(2037, 10, 25, 0, 59, tzinfo=tzc).tzname(),
"EDT")
last_date = tz.enfold(datetime(2037, 10, 25, 1, 00, tzinfo=tzc), fold=1)
self.assertEqual(last_date.tzname(),
"EST")
self.assertEqual(datetime(2038, 5, 25, 12, 0, tzinfo=tzc).tzname(),
"EST")
def testInvalidFile(self):
# Should throw a ValueError if an invalid file is passed
with self.assertRaises(ValueError):
tz.tzfile(BytesIO(b'BadFile'))
def testRoundNonFullMinutes(self):
# This timezone has an offset of 5992 seconds in 1900-01-01.
tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
self.assertEqual(str(datetime(1900, 1, 1, 0, 0, tzinfo=tzc)),
"1900-01-01 00:00:00+01:40")
def testLeapCountDecodesProperly(self):
# This timezone has leapcnt, and failed to decode until
# Eugene Oden notified about the issue.
# As leap information is currently unused (and unstored) by tzfile() we
# can only indirectly test this: Take advantage of tzfile() not closing
# the input file if handed in as an opened file and assert that the
# full file content has been read by tzfile(). Note: For this test to
# work NEW_YORK must be in TZif version 1 format i.e. no more data
# after TZif v1 header + data has been read
fileobj = BytesIO(base64.b64decode(NEW_YORK))
tzc = tz.tzfile(fileobj)
# we expect no remaining file content now, i.e. zero-length; if there's
# still data we haven't read the file format correctly
remaining_tzfile_content = fileobj.read()
self.assertEqual(len(remaining_tzfile_content), 0)
def testIsStd(self):
# NEW_YORK tzfile contains this isstd information:
isstd_expected = (0, 0, 0, 1)
tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK)))
# gather the actual information as parsed by the tzfile class
isstd = []
for ttinfo in tzc._ttinfo_list:
# ttinfo objects contain boolean values
isstd.append(int(ttinfo.isstd))
# ttinfo list may contain more entries than isstd file content
isstd = tuple(isstd[:len(isstd_expected)])
self.assertEqual(
isstd_expected, isstd,
"isstd UTC/local indicators parsed: %s != tzfile contents: %s"
% (isstd, isstd_expected))
def testGMTHasNoDaylight(self):
# tz.tzstr("GMT+2") improperly considered daylight saving time.
# Issue reported by Lennart Regebro.
dt = datetime(2007, 8, 6, 4, 10)
self.assertEqual(tz.gettz("GMT+2").dst(dt), timedelta(0))
def testGMTOffset(self):
# GMT and UTC offsets have inverted signal when compared to the
# usual TZ variable handling.
dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.tzutc())
self.assertEqual(dt.astimezone(tz=tz.tzstr("GMT+2")),
datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2")))
self.assertEqual(dt.astimezone(tz=tz.gettz("UTC-2")),
datetime(2007, 8, 6, 2, 10, tzinfo=tz.tzstr("UTC-2")))
@unittest.skipIf(IS_WIN, "requires Unix")
@unittest.skipUnless(TZEnvContext.tz_change_allowed(),
TZEnvContext.tz_change_disallowed_message())
def testTZSetDoesntCorrupt(self):
# if we start in non-UTC then tzset UTC make sure parse doesn't get
# confused
with TZEnvContext('UTC'):
# this should parse to UTC timezone not the original timezone
dt = parse('2014-07-20T12:34:56+00:00')
self.assertEqual(str(dt), '2014-07-20 12:34:56+00:00')
@unittest.skipUnless(IS_WIN, "Requires Windows")
class TzWinTest(unittest.TestCase, TzWinFoldMixin):
def setUp(self):
self.tzclass = tzwin.tzwin
def testTzResLoadName(self):
# This may not work right on non-US locales.
tzr = tzwin.tzres()
self.assertEqual(tzr.load_name(112), "Eastern Standard Time")
def testTzResNameFromString(self):
tzr = tzwin.tzres()
self.assertEqual(tzr.name_from_string('@tzres.dll,-221'),
'Alaskan Daylight Time')
self.assertEqual(tzr.name_from_string('Samoa Daylight Time'),
'Samoa Daylight Time')
with self.assertRaises(ValueError):
tzr.name_from_string('@tzres.dll,100')
def testIsdstZoneWithNoDaylightSaving(self):
tz = tzwin.tzwin("UTC")
dt = parse("2013-03-06 19:08:15")
self.assertFalse(tz._isdst(dt))
def testOffset(self):
tz = tzwin.tzwin("Cape Verde Standard Time")
self.assertEqual(tz.utcoffset(datetime(1995, 5, 21, 12, 9, 13)),
timedelta(-1, 82800))
def testTzwinName(self):
# https://github.com/dateutil/dateutil/issues/143
tw = tz.tzwin('Eastern Standard Time')
# Cover the transitions for at least two years.
ESTs = 'Eastern Standard Time'
EDTs = 'Eastern Daylight Time'
transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs),
(datetime(2015, 3, 8, 3, 1), EDTs),
(datetime(2015, 11, 1, 0, 59), EDTs),
(datetime(2015, 11, 1, 3, 1), ESTs),
(datetime(2016, 3, 13, 0, 59), ESTs),
(datetime(2016, 3, 13, 3, 1), EDTs),
(datetime(2016, 11, 6, 0, 59), EDTs),
(datetime(2016, 11, 6, 3, 1), ESTs)]
for t_date, expected in transition_dates:
self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected)
def testTzwinRepr(self):
tw = tz.tzwin('Yakutsk Standard Time')
self.assertEqual(repr(tw), 'tzwin(' +
repr('Yakutsk Standard Time') + ')')
def testTzWinEquality(self):
# https://github.com/dateutil/dateutil/issues/151
tzwin_names = ('Eastern Standard Time',
'West Pacific Standard Time',
'Yakutsk Standard Time',
'Iran Standard Time',
'UTC')
for tzwin_name in tzwin_names:
# Get two different instances to compare
tw1 = tz.tzwin(tzwin_name)
tw2 = tz.tzwin(tzwin_name)
self.assertEqual(tw1, tw2)
def testTzWinInequality(self):
# https://github.com/dateutil/dateutil/issues/151
# Note these last two currently differ only in their name.
tzwin_names = (('Eastern Standard Time', 'Yakutsk Standard Time'),
('Greenwich Standard Time', 'GMT Standard Time'),
('GMT Standard Time', 'UTC'),
('E. South America Standard Time',
'Argentina Standard Time'))
for tzwn1, tzwn2 in tzwin_names:
# Get two different instances to compare
tw1 = tz.tzwin(tzwn1)
tw2 = tz.tzwin(tzwn2)
self.assertNotEqual(tw1, tw2)
def testTzWinEqualityInvalid(self):
# Compare to objects that do not implement comparison with this
# (should default to False)
UTC = tz.tzutc()
EST = tz.tzwin('Eastern Standard Time')
self.assertFalse(EST == UTC)
self.assertFalse(EST == 1)
self.assertFalse(UTC == EST)
self.assertTrue(EST != UTC)
self.assertTrue(EST != 1)
def testTzWinInequalityUnsupported(self):
# Compare it to an object that is promiscuous about equality, but for
# which tzwin does not implement an equality operator.
EST = tz.tzwin('Eastern Standard Time')
self.assertTrue(EST == ComparesEqual)
self.assertFalse(EST != ComparesEqual)
def testTzwinTimeOnlyDST(self):
# For zones with DST, .dst() should return None
tw_est = tz.tzwin('Eastern Standard Time')
self.assertIs(dt_time(14, 10, tzinfo=tw_est).dst(), None)
# This zone has no DST, so .dst() can return 0
tw_sast = tz.tzwin('South Africa Standard Time')
self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).dst(),
timedelta(0))
def testTzwinTimeOnlyUTCOffset(self):
# For zones with DST, .utcoffset() should return None
tw_est = tz.tzwin('Eastern Standard Time')
self.assertIs(dt_time(14, 10, tzinfo=tw_est).utcoffset(), None)
# This zone has no DST, so .utcoffset() returns standard offset
tw_sast = tz.tzwin('South Africa Standard Time')
self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).utcoffset(),
timedelta(hours=2))
def testTzwinTimeOnlyTZName(self):
# For zones with DST, the name defaults to standard time
tw_est = tz.tzwin('Eastern Standard Time')
self.assertEqual(dt_time(14, 10, tzinfo=tw_est).tzname(),
'Eastern Standard Time')
# For zones with no DST, this should work normally.
tw_sast = tz.tzwin('South Africa Standard Time')
self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).tzname(),
'South Africa Standard Time')
@unittest.skipUnless(IS_WIN, "Requires Windows")
@unittest.skipUnless(TZWinContext.tz_change_allowed(),
TZWinContext.tz_change_disallowed_message())
class TzWinLocalTest(unittest.TestCase, TzWinFoldMixin):
def setUp(self):
self.tzclass = tzwin.tzwinlocal
self.context = TZWinContext
def get_args(self, tzname):
return tuple()
def testLocal(self):
# Not sure how to pin a local time zone, so for now we're just going
# to run this and make sure it doesn't raise an error
# See Github Issue #135: https://github.com/dateutil/dateutil/issues/135
datetime.now(tzwin.tzwinlocal())
def testTzwinLocalUTCOffset(self):
with TZWinContext('Eastern Standard Time'):
tzwl = tzwin.tzwinlocal()
self.assertEqual(datetime(2014, 3, 11, tzinfo=tzwl).utcoffset(),
timedelta(hours=-4))
def testTzwinLocalName(self):
# https://github.com/dateutil/dateutil/issues/143
ESTs = 'Eastern Standard Time'
EDTs = 'Eastern Daylight Time'
transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs),
(datetime(2015, 3, 8, 3, 1), EDTs),
(datetime(2015, 11, 1, 0, 59), EDTs),
(datetime(2015, 11, 1, 3, 1), ESTs),
(datetime(2016, 3, 13, 0, 59), ESTs),
(datetime(2016, 3, 13, 3, 1), EDTs),
(datetime(2016, 11, 6, 0, 59), EDTs),
(datetime(2016, 11, 6, 3, 1), ESTs)]
with TZWinContext('Eastern Standard Time'):
tw = tz.tzwinlocal()
for t_date, expected in transition_dates:
self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected)
def testTzWinLocalRepr(self):
tw = tz.tzwinlocal()
self.assertEqual(repr(tw), 'tzwinlocal()')
def testTzwinLocalRepr(self):
# https://github.com/dateutil/dateutil/issues/143
with TZWinContext('Eastern Standard Time'):
tw = tz.tzwinlocal()
self.assertEqual(str(tw), 'tzwinlocal(' +
repr('Eastern Standard Time') + ')')
with TZWinContext('Pacific Standard Time'):
tw = tz.tzwinlocal()
self.assertEqual(str(tw), 'tzwinlocal(' +
repr('Pacific Standard Time') + ')')
def testTzwinLocalEquality(self):
tw_est = tz.tzwin('Eastern Standard Time')
tw_pst = tz.tzwin('Pacific Standard Time')
with TZWinContext('Eastern Standard Time'):
twl1 = tz.tzwinlocal()
twl2 = tz.tzwinlocal()
self.assertEqual(twl1, twl2)
self.assertEqual(twl1, tw_est)
self.assertNotEqual(twl1, tw_pst)
with TZWinContext('Pacific Standard Time'):
twl1 = tz.tzwinlocal()
twl2 = tz.tzwinlocal()
tw = tz.tzwin('Pacific Standard Time')
self.assertEqual(twl1, twl2)
self.assertEqual(twl1, tw)
self.assertEqual(twl1, tw_pst)
self.assertNotEqual(twl1, tw_est)
def testTzwinLocalTimeOnlyDST(self):
# For zones with DST, .dst() should return None
with TZWinContext('Eastern Standard Time'):
twl = tz.tzwinlocal()
self.assertIs(dt_time(14, 10, tzinfo=twl).dst(), None)
# This zone has no DST, so .dst() can return 0
with TZWinContext('South Africa Standard Time'):
twl = tz.tzwinlocal()
self.assertEqual(dt_time(14, 10, tzinfo=twl).dst(), timedelta(0))
def testTzwinLocalTimeOnlyUTCOffset(self):
# For zones with DST, .utcoffset() should return None
with TZWinContext('Eastern Standard Time'):
twl = tz.tzwinlocal()
self.assertIs(dt_time(14, 10, tzinfo=twl).utcoffset(), None)
# This zone has no DST, so .utcoffset() returns standard offset
with TZWinContext('South Africa Standard Time'):
twl = tz.tzwinlocal()
self.assertEqual(dt_time(14, 10, tzinfo=twl).utcoffset(),
timedelta(hours=2))
def testTzwinLocalTimeOnlyTZName(self):
# For zones with DST, the name defaults to standard time
with TZWinContext('Eastern Standard Time'):
twl = tz.tzwinlocal()
self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(),
'Eastern Standard Time')
# For zones with no DST, this should work normally.
with TZWinContext('South Africa Standard Time'):
twl = tz.tzwinlocal()
self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(),
'South Africa Standard Time')
class TzPickleTest(PicklableMixin, unittest.TestCase):
_asfile = False
def setUp(self):
self.assertPicklable = partial(self.assertPicklable,
asfile=self._asfile)
def testPickleTzUTC(self):
self.assertPicklable(tz.tzutc())
def testPickleTzOffsetZero(self):
self.assertPicklable(tz.tzoffset('UTC', 0))
def testPickleTzOffsetPos(self):
self.assertPicklable(tz.tzoffset('UTC+1', 3600))
def testPickleTzOffsetNeg(self):
self.assertPicklable(tz.tzoffset('UTC-1', -3600))
def testPickleTzLocal(self):
self.assertPicklable(tz.tzlocal())
def testPickleTzFileEST5EDT(self):
tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
self.assertPicklable(tzc)
def testPickleTzFileEurope_Helsinki(self):
tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
self.assertPicklable(tzc)
def testPickleTzFileNew_York(self):
tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK)))
self.assertPicklable(tzc)
@unittest.skip("Known failure")
def testPickleTzICal(self):
tzc = tz.tzical(StringIO(TZICAL_EST5EDT)).get()
self.assertPicklable(tzc)
def testPickleTzGettz(self):
self.assertPicklable(tz.gettz('America/New_York'))
def testPickleZoneFileGettz(self):
zoneinfo_file = zoneinfo.get_zonefile_instance()
tzi = zoneinfo_file.get('America/New_York')
self.assertIsNot(tzi, None)
self.assertPicklable(tzi)
class TzPickleFileTest(TzPickleTest):
""" Run all the TzPickleTest tests, using a temporary file """
_asfile = True
class DatetimeAmbiguousTest(unittest.TestCase):
""" Test the datetime_exists / datetime_ambiguous functions """
def testNoTzSpecified(self):
with self.assertRaises(ValueError):
tz.datetime_ambiguous(datetime(2016, 4, 1, 2, 9))
def _get_no_support_tzinfo_class(self, dt_start, dt_end, dst_only=False):
# Generates a class of tzinfo with no support for is_ambiguous
# where dates between dt_start and dt_end are ambiguous.
class FoldingTzInfo(tzinfo):
def utcoffset(self, dt):
if not dst_only:
dt_n = dt.replace(tzinfo=None)
if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0):
return timedelta(hours=-1)
return timedelta(hours=0)
def dst(self, dt):
dt_n = dt.replace(tzinfo=None)
if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0):
return timedelta(hours=1)
else:
return timedelta(0)
return FoldingTzInfo
def _get_no_support_tzinfo(self, dt_start, dt_end, dst_only=False):
return self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only)()
def testNoSupportAmbiguityFoldNaive(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_no_support_tzinfo(dt_start, dt_end)
self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
tz=tzi))
def testNoSupportAmbiguityFoldAware(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_no_support_tzinfo(dt_start, dt_end)
self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30,
tzinfo=tzi)))
def testNoSupportAmbiguityUnambiguousNaive(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_no_support_tzinfo(dt_start, dt_end)
self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
tz=tzi))
def testNoSupportAmbiguityUnambiguousAware(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_no_support_tzinfo(dt_start, dt_end)
self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30,
tzinfo=tzi)))
def testNoSupportAmbiguityFoldDSTOnly(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True)
self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
tz=tzi))
def testNoSupportAmbiguityUnambiguousDSTOnly(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True)
self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
tz=tzi))
def testSupportAmbiguityFoldNaive(self):
tzi = tz.gettz('US/Eastern')
dt = datetime(2011, 11, 6, 1, 30)
self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi))
def testSupportAmbiguityFoldAware(self):
tzi = tz.gettz('US/Eastern')
dt = datetime(2011, 11, 6, 1, 30, tzinfo=tzi)
self.assertTrue(tz.datetime_ambiguous(dt))
def testSupportAmbiguityUnambiguousAware(self):
tzi = tz.gettz('US/Eastern')
dt = datetime(2011, 11, 6, 4, 30)
self.assertFalse(tz.datetime_ambiguous(dt, tz=tzi))
def testSupportAmbiguityUnambiguousNaive(self):
tzi = tz.gettz('US/Eastern')
dt = datetime(2011, 11, 6, 4, 30, tzinfo=tzi)
self.assertFalse(tz.datetime_ambiguous(dt))
def _get_ambig_error_tzinfo(self, dt_start, dt_end, dst_only=False):
cTzInfo = self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only)
# Takes the wrong number of arguments and raises an error anyway.
class FoldTzInfoRaises(cTzInfo):
def is_ambiguous(self, dt, other_arg):
raise NotImplementedError('This is not implemented')
return FoldTzInfoRaises()
def testIncompatibleAmbiguityFoldNaive(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
tz=tzi))
def testIncompatibleAmbiguityFoldAware(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30,
tzinfo=tzi)))
def testIncompatibleAmbiguityUnambiguousNaive(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
tz=tzi))
def testIncompatibleAmbiguityUnambiguousAware(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30,
tzinfo=tzi)))
def testIncompatibleAmbiguityFoldDSTOnly(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True)
self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
tz=tzi))
def testIncompatibleAmbiguityUnambiguousDSTOnly(self):
dt_start = datetime(2018, 9, 1, 1, 0)
dt_end = datetime(2018, 9, 1, 2, 0)
tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True)
self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
tz=tzi))
def testSpecifiedTzOverridesAttached(self):
# If a tz is specified, the datetime will be treated as naive.
# This is not ambiguous in the local zone
dt = datetime(2011, 11, 6, 1, 30, tzinfo=tz.gettz('Australia/Sydney'))
self.assertFalse(tz.datetime_ambiguous(dt))
tzi = tz.gettz('US/Eastern')
self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi))
class DatetimeExistsTest(unittest.TestCase):
def testNoTzSpecified(self):
with self.assertRaises(ValueError):
tz.datetime_exists(datetime(2016, 4, 1, 2, 9))
def testInGapNaive(self):
tzi = tz.gettz('Australia/Sydney')
dt = datetime(2012, 10, 7, 2, 30)
self.assertFalse(tz.datetime_exists(dt, tz=tzi))
def testInGapAware(self):
tzi = tz.gettz('Australia/Sydney')
dt = datetime(2012, 10, 7, 2, 30, tzinfo=tzi)
self.assertFalse(tz.datetime_exists(dt))
def testExistsNaive(self):
tzi = tz.gettz('Australia/Sydney')
dt = datetime(2012, 10, 7, 10, 30)
self.assertTrue(tz.datetime_exists(dt, tz=tzi))
def testExistsAware(self):
tzi = tz.gettz('Australia/Sydney')
dt = datetime(2012, 10, 7, 10, 30, tzinfo=tzi)
self.assertTrue(tz.datetime_exists(dt))
def testSpecifiedTzOverridesAttached(self):
EST = tz.gettz('US/Eastern')
AEST = tz.gettz('Australia/Sydney')
dt = datetime(2012, 10, 7, 2, 30, tzinfo=EST) # This time exists
self.assertFalse(tz.datetime_exists(dt, tz=AEST))
class EnfoldTest(unittest.TestCase):
def testEnterFoldDefault(self):
dt = tz.enfold(datetime(2020, 1, 19, 3, 32))
self.assertEqual(dt.fold, 1)
def testEnterFold(self):
dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1)
self.assertEqual(dt.fold, 1)
def testExitFold(self):
dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=0)
# Before Python 3.6, dt.fold won't exist if fold is 0.
self.assertEqual(getattr(dt, 'fold', 0), 0)