blob: bb0f4b7de7797c54be860049de9218c61ebd3fbd [file] [log] [blame]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from ._common import PicklableMixin
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 PY2
from io import BytesIO, StringIO
import unittest
import sys
import base64
import copy
import gc
import weakref
from functools import partial
IS_WIN = sys.platform.startswith('win')
import pytest
# dateutil imports
from dateutil.relativedelta import relativedelta, SU, TH
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
"""
EST_TUPLE = ('EST', timedelta(hours=-5), timedelta(hours=0))
EDT_TUPLE = ('EDT', timedelta(hours=-4), timedelta(hours=1))
SUPPORTS_SUB_MINUTE_OFFSETS = sys.version_info >= (3, 6)
###
# Helper functions
def get_timezone_tuple(dt):
"""Retrieve a (tzname, utcoffset, dst) tuple for a given DST"""
return dt.tzname(), dt.utcoffset(), dt.dst()
###
# 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):
SYD = 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
t0_syd0 = t0_u.astimezone(SYD)
t1_syd1 = t1_u.astimezone(SYD)
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):
SYD = 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
t0 = t0_u.astimezone(SYD)
t1 = t1_u.astimezone(SYD)
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):
TOR = 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())
t0_tor = t0_u.astimezone(TOR)
t1_tor = t1_u.astimezone(TOR)
self.assertEqual(t0_tor.replace(tzinfo=None),
datetime(2011, 11, 6, 1, 30))
self.assertEqual(t1_tor.replace(tzinfo=None),
datetime(2011, 11, 6, 1, 30))
self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname())
self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0))
self.assertEqual(t1_tor.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):
TOR = 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())
t0 = t0_u.astimezone(TOR)
t1 = t1_u.astimezone(TOR)
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 testFoldLondon(self):
tzname = self._get_tzname('Europe/London')
with self._gettz_context(tzname):
LON = self.gettz(tzname)
UTC = tz.tzutc()
t0_u = datetime(2013, 10, 27, 0, 30, tzinfo=UTC) # BST
t1_u = datetime(2013, 10, 27, 1, 30, tzinfo=UTC) # GMT
t0 = t0_u.astimezone(LON)
t1 = t1_u.astimezone(LON)
self.assertEqual(t0.replace(tzinfo=None),
datetime(2013, 10, 27, 1, 30))
self.assertEqual(t1.replace(tzinfo=None),
datetime(2013, 10, 27, 1, 30))
self.assertEqual(t0.utcoffset(), timedelta(hours=1))
self.assertEqual(t1.utcoffset(), timedelta(hours=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)
# 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 testInZoneFoldEquality(self):
# Two datetimes in the same zone are considered to be equal if their
# wall times are equal, even if they have different absolute times.
tzname = self._get_tzname('America/New_York')
with self._gettz_context(tzname):
NYC = self.gettz(tzname)
UTC = tz.tzutc()
dt0 = datetime(2011, 11, 6, 1, 30, tzinfo=NYC)
dt1 = tz.enfold(dt0, fold=1)
# Make sure these actually represent different times
self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC))
# Test that they compare equal
self.assertEqual(dt0, dt1)
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
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)
# 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(SYD, 2012, False)
# Using fresh tzfiles
t0_syd = t0_u.astimezone(SYD)
t1_syd = t1_u.astimezone(SYD)
self.assertEqual(t0_syd.replace(tzinfo=None), t_n)
self.assertEqual(t1_syd.replace(tzinfo=None), t_n)
self.assertEqual(t0_syd.utcoffset(), timedelta(hours=11))
self.assertEqual(t1_syd.utcoffset(), timedelta(hours=10))
self.assertNotEqual(t0_syd.tzname(), t1_syd.tzname())
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):
SYD = self.tzclass(*args)
t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, True)
t0 = t0_u.astimezone(SYD)
t1 = t1_u.astimezone(SYD)
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)
with self.context(tzname):
TOR = self.tzclass(*args)
t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, False)
t0_tor = t0_u.astimezone(TOR)
t1_tor = t1_u.astimezone(TOR)
self.assertEqual(t0_tor.replace(tzinfo=None), t_n)
self.assertEqual(t1_tor.replace(tzinfo=None), t_n)
self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname())
self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0))
self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0))
def testGapNegativeUTCOffset(self):
# Test that we don't have a problem around gaps.
tzname = 'Eastern Standard Time'
args = self.get_args(tzname)
with self.context(tzname):
TOR = self.tzclass(*args)
t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, True)
t0 = t0_u.astimezone(TOR)
t1 = t1_u.astimezone(TOR)
self.assertEqual(t0.replace(tzinfo=None),
t_n)
self.assertEqual(t1.replace(tzinfo=None),
t_n + timedelta(hours=2))
self.assertNotEqual(t0.tzname(), t1.tzname())
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())
def testInZoneFoldEquality(self):
# Two datetimes in the same zone are considered to be equal if their
# wall times are equal, even if they have different absolute times.
tzname = 'Eastern Standard Time'
args = self.get_args(tzname)
with self.context(tzname):
NYC = self.tzclass(*args)
UTC = tz.tzutc()
t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2011, False)
dt0 = t_n.replace(tzinfo=NYC)
dt1 = tz.enfold(dt0, fold=1)
# Make sure these actually represent different times
self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC))
# Test that they compare equal
self.assertEqual(dt0, dt1)
###
# Test Cases
class TzUTCTest(unittest.TestCase):
def testSingleton(self):
UTC_0 = tz.tzutc()
UTC_1 = tz.tzutc()
self.assertIs(UTC_0, UTC_1)
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.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))
@pytest.mark.tzoffset
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))
def testTzOffsetInstance(self):
tz1 = tz.tzoffset.instance('EST', timedelta(hours=-5))
tz2 = tz.tzoffset.instance('EST', timedelta(hours=-5))
assert tz1 is not tz2
def testTzOffsetSingletonDifferent(self):
tz1 = tz.tzoffset('EST', timedelta(hours=-5))
tz2 = tz.tzoffset('EST', -18000)
assert tz1 is tz2
@pytest.mark.smoke
@pytest.mark.tzoffset
def test_tzoffset_weakref():
UTC1 = tz.tzoffset('UTC', 0)
UTC_ref = weakref.ref(tz.tzoffset('UTC', 0))
UTC1 is UTC_ref()
del UTC1
gc.collect()
assert UTC_ref() is not None # Should be in the strong cache
assert UTC_ref() is tz.tzoffset('UTC', 0)
# Fill the strong cache with other items
for offset in range(5,15):
tz.tzoffset('RandomZone', offset)
gc.collect()
assert UTC_ref() is None
assert UTC_ref() is not tz.tzoffset('UTC', 0)
@pytest.mark.tzoffset
@pytest.mark.parametrize('args', [
('UTC', 0),
('EST', -18000),
('EST', timedelta(hours=-5)),
(None, timedelta(hours=3)),
])
def test_tzoffset_singleton(args):
tz1 = tz.tzoffset(*args)
tz2 = tz.tzoffset(*args)
assert tz1 is tz2
@pytest.mark.tzoffset
@pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS,
reason='Sub-minute offsets not supported')
def test_tzoffset_sub_minute():
delta = timedelta(hours=12, seconds=30)
test_datetime = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta))
assert test_datetime.utcoffset() == delta
@pytest.mark.tzoffset
@pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS,
reason='Sub-minute offsets supported')
def test_tzoffset_sub_minute_rounding():
delta = timedelta(hours=12, seconds=30)
test_date = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta))
assert test_date.utcoffset() == timedelta(hours=12, minutes=1)
@pytest.mark.tzlocal
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', tzl._std_offset.total_seconds())
tzod = tz.tzoffset('LDT', tzl._std_offset.total_seconds())
self.assertFalse(tzl == tzos)
self.assertFalse(tzl == tzod)
self.assertTrue(tzl != tzos)
self.assertTrue(tzl != tzod)
def testInequalityInvalid(self):
tzl = tz.tzlocal()
self.assertTrue(tzl != 1)
self.assertFalse(tzl == 1)
# TODO: Use some sort of universal local mocking so that it's clear
# that we're expecting tzlocal to *not* be Pacific/Kiritimati
LINT = tz.gettz('Pacific/Kiritimati')
self.assertTrue(tzl != LINT)
self.assertFalse(tzl == LINT)
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()')
@pytest.mark.parametrize('args,kwargs', [
(('EST', -18000), {}),
(('EST', timedelta(hours=-5)), {}),
(('EST',), {'offset': -18000}),
(('EST',), {'offset': timedelta(hours=-5)}),
(tuple(), {'name': 'EST', 'offset': -18000})
])
def test_tzoffset_is(args, kwargs):
tz_ref = tz.tzoffset('EST', -18000)
assert tz.tzoffset(*args, **kwargs) is tz_ref
def test_tzoffset_is_not():
assert tz.tzoffset('EDT', -14400) is not tz.tzoffset('EST', -18000)
@pytest.mark.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 BST/GMT
TZ_LON = 'GMT0BST,M3.5.0,M10.5.0'
# 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,
'Europe/London': self.TZ_LON}
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)
def testUTCEquality(self):
with TZEnvContext(self.UTC):
assert tz.tzlocal() == tz.tzutc()
# TODO: Maybe a better hack than this?
def mark_tzlocal_nix(f):
marks = [
pytest.mark.tzlocal,
pytest.mark.skipif(IS_WIN, reason='requires Unix'),
pytest.mark.skipif(not TZEnvContext.tz_change_allowed,
reason=TZEnvContext.tz_change_disallowed_message())
]
for mark in reversed(marks):
f = mark(f)
return f
@mark_tzlocal_nix
@pytest.mark.parametrize('tzvar', ['UTC', 'GMT0', 'UTC0'])
def test_tzlocal_utc_equal(tzvar):
with TZEnvContext(tzvar):
assert tz.tzlocal() == tz.UTC
@mark_tzlocal_nix
@pytest.mark.parametrize('tzvar', [
'Europe/London', 'America/New_York',
'GMT0BST', 'EST5EDT'])
def test_tzlocal_utc_unequal(tzvar):
with TZEnvContext(tzvar):
assert tz.tzlocal() != tz.UTC
@mark_tzlocal_nix
def test_tzlocal_local_time_trim_colon():
with TZEnvContext(':/etc/localtime'):
assert tz.gettz() is not None
@mark_tzlocal_nix
@pytest.mark.parametrize('tzvar, tzoff', [
('EST5', tz.tzoffset('EST', -18000)),
('GMT', tz.tzoffset('GMT', 0)),
('YAKT-9', tz.tzoffset('YAKT', timedelta(hours=9))),
('JST-9', tz.tzoffset('JST', timedelta(hours=9))),
])
def test_tzlocal_offset_equal(tzvar, tzoff):
with TZEnvContext(tzvar):
# Including both to test both __eq__ and __ne__
assert tz.tzlocal() == tzoff
assert not (tz.tzlocal() != tzoff)
@mark_tzlocal_nix
@pytest.mark.parametrize('tzvar, tzoff', [
('EST5EDT', tz.tzoffset('EST', -18000)),
('GMT0BST', tz.tzoffset('GMT', 0)),
('EST5', tz.tzoffset('EST', -14400)),
('YAKT-9', tz.tzoffset('JST', timedelta(hours=9))),
('JST-9', tz.tzoffset('YAKT', timedelta(hours=9))),
])
def test_tzlocal_offset_unequal(tzvar, tzoff):
with TZEnvContext(tzvar):
# Including both to test both __eq__ and __ne__
assert tz.tzlocal() != tzoff
assert not (tz.tzlocal() == tzoff)
@pytest.mark.gettz
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))
def testGettzCacheTzFile(self):
NYC1 = tz.gettz('America/New_York')
NYC2 = tz.gettz('America/New_York')
assert NYC1 is NYC2
def testGettzCacheTzLocal(self):
local1 = tz.gettz()
local2 = tz.gettz()
assert local1 is not local2
@pytest.mark.gettz
@pytest.mark.parametrize('badzone', [
'Fake.Region/Abcdefghijklmnop', # Violates several tz project name rules
])
def test_gettz_badzone(badzone):
# Make sure passing a bad TZ string to gettz returns None (GH #800)
tzi = tz.gettz(badzone)
assert tzi is None
@pytest.mark.gettz
def test_gettz_badzone_unicode():
# Make sure a unicode string can be passed to TZ (GH #802)
# When fixed, combine this with test_gettz_badzone
tzi = tz.gettz('🐼')
assert tzi is None
@pytest.mark.gettz
@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached')
def test_gettz_cache_clear():
NYC1 = tz.gettz('America/New_York')
tz.gettz.cache_clear()
NYC2 = tz.gettz('America/New_York')
assert NYC1 is not NYC2
@pytest.mark.gettz
@pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached')
def test_gettz_set_cache_size():
tz.gettz.cache_clear()
tz.gettz.set_cache_size(3)
MONACO_ref = weakref.ref(tz.gettz('Europe/Monaco'))
EASTER_ref = weakref.ref(tz.gettz('Pacific/Easter'))
CURRIE_ref = weakref.ref(tz.gettz('Australia/Currie'))
gc.collect()
assert MONACO_ref() is not None
assert EASTER_ref() is not None
assert CURRIE_ref() is not None
tz.gettz.set_cache_size(2)
gc.collect()
assert MONACO_ref() is None
@pytest.mark.xfail(IS_WIN, reason="Windows does not use system zoneinfo")
@pytest.mark.smoke
@pytest.mark.gettz
def test_gettz_weakref():
tz.gettz.cache_clear()
tz.gettz.set_cache_size(2)
NYC1 = tz.gettz('America/New_York')
NYC_ref = weakref.ref(tz.gettz('America/New_York'))
assert NYC1 is NYC_ref()
del NYC1
gc.collect()
assert NYC_ref() is not None # Should still be in the strong cache
assert tz.gettz('America/New_York') is NYC_ref()
# Populate strong cache with other timezones
tz.gettz('Europe/Monaco')
tz.gettz('Pacific/Easter')
tz.gettz('Australia/Currie')
gc.collect()
assert NYC_ref() is None # Should have been pushed out
assert tz.gettz('America/New_York') is not NYC_ref()
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):
zoneinfo.gettz('US/Eastern')
def testZoneInfoMetadataDeprecated(self):
with self.assertWarns(DeprecationWarning):
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)))
TZ_LON = tz.tzrange('GMT', timedelta(hours=0),
'BST', timedelta(hours=1),
start=relativedelta(month=3, day=31, weekday=SU(-1),
hours=2),
end=relativedelta(month=10, day=31, weekday=SU(-1),
hours=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,
'Europe/London': self.TZ_LON}
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)
@pytest.mark.tzstr
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'
# POSIX string for GMT/BST
TZ_LON = 'GMT0BST,M3.5.0,M10.5.0'
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,
'Europe/London': self.TZ_LON}
return tz.tzstr(tzname_map[tzname])
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 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')
def testTzStrSingleton(self):
tz1 = tz.tzstr('EST5EDT')
tz2 = tz.tzstr('CST4CST')
tz3 = tz.tzstr('EST5EDT')
self.assertIsNot(tz1, tz2)
self.assertIs(tz1, tz3)
def testTzStrSingletonPosix(self):
tz_t1 = tz.tzstr('GMT+3', posix_offset=True)
tz_f1 = tz.tzstr('GMT+3', posix_offset=False)
tz_t2 = tz.tzstr('GMT+3', posix_offset=True)
tz_f2 = tz.tzstr('GMT+3', posix_offset=False)
self.assertIs(tz_t1, tz_t2)
self.assertIsNot(tz_t1, tz_f1)
self.assertIs(tz_f1, tz_f2)
def testTzStrInstance(self):
tz1 = tz.tzstr('EST5EDT')
tz2 = tz.tzstr.instance('EST5EDT')
tz3 = tz.tzstr.instance('EST5EDT')
assert tz1 is not tz2
assert tz2 is not tz3
# Ensure that these still are all the same zone
assert tz1 == tz2 == tz3
@pytest.mark.smoke
@pytest.mark.tzstr
def test_tzstr_weakref():
tz_t1 = tz.tzstr('EST5EDT')
tz_t2_ref = weakref.ref(tz.tzstr('EST5EDT'))
assert tz_t1 is tz_t2_ref()
del tz_t1
gc.collect()
assert tz_t2_ref() is not None
assert tz.tzstr('EST5EDT') is tz_t2_ref()
for offset in range(5,15):
tz.tzstr('GMT+{}'.format(offset))
gc.collect()
assert tz_t2_ref() is None
assert tz.tzstr('EST5EDT') is not tz_t2_ref()
@pytest.mark.tzstr
@pytest.mark.parametrize('tz_str,expected', [
# From https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
('', tz.tzrange(None)), # TODO: Should change this so tz.tzrange('') works
('EST+5EDT,M3.2.0/2,M11.1.0/12',
tz.tzrange('EST', -18000, 'EDT', -14400,
start=relativedelta(month=3, day=1, weekday=SU(2), hours=2),
end=relativedelta(month=11, day=1, weekday=SU(1), hours=11))),
('WART4WARST,J1/0,J365/25', # This is DST all year, Western Argentina Summer Time
tz.tzrange('WART', timedelta(hours=-4), 'WARST',
start=relativedelta(month=1, day=1, hours=0),
end=relativedelta(month=12, day=31, days=1))),
('IST-2IDT,M3.4.4/26,M10.5.0', # Israel Standard / Daylight Time
tz.tzrange('IST', timedelta(hours=2), 'IDT',
start=relativedelta(month=3, day=1, weekday=TH(4), days=1, hours=2),
end=relativedelta(month=10, day=31, weekday=SU(-1), hours=1))),
('WGT3WGST,M3.5.0/2,M10.5.0/1',
tz.tzrange('WGT', timedelta(hours=-3), 'WGST',
start=relativedelta(month=3, day=31, weekday=SU(-1), hours=2),
end=relativedelta(month=10, day=31, weekday=SU(-1), hours=0))),
# Different offset specifications
('WGT0300WGST',
tz.tzrange('WGT', timedelta(hours=-3), 'WGST')),
('WGT03:00WGST',
tz.tzrange('WGT', timedelta(hours=-3), 'WGST')),
('AEST-1100AEDT',
tz.tzrange('AEST', timedelta(hours=11), 'AEDT')),
('AEST-11:00AEDT',
tz.tzrange('AEST', timedelta(hours=11), 'AEDT')),
# Different time formats
('EST5EDT,M3.2.0/4:00,M11.1.0/3:00',
tz.tzrange('EST', timedelta(hours=-5), 'EDT',
start=relativedelta(month=3, day=1, weekday=SU(2), hours=4),
end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))),
('EST5EDT,M3.2.0/04:00,M11.1.0/03:00',
tz.tzrange('EST', timedelta(hours=-5), 'EDT',
start=relativedelta(month=3, day=1, weekday=SU(2), hours=4),
end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))),
('EST5EDT,M3.2.0/0400,M11.1.0/0300',
tz.tzrange('EST', timedelta(hours=-5), 'EDT',
start=relativedelta(month=3, day=1, weekday=SU(2), hours=4),
end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))),
])
def test_valid_GNU_tzstr(tz_str, expected):
tzi = tz.tzstr(tz_str)
assert tzi == expected
@pytest.mark.tzstr
@pytest.mark.parametrize('tz_str, expected', [
('EST5EDT,5,4,0,7200,11,3,0,7200',
tz.tzrange('EST', timedelta(hours=-5), 'EDT',
start=relativedelta(month=5, day=1, weekday=SU(+4), hours=+2),
end=relativedelta(month=11, day=1, weekday=SU(+3), hours=+1))),
('EST5EDT,5,-4,0,7200,11,3,0,7200',
tz.tzrange('EST', timedelta(hours=-5), 'EDT',
start=relativedelta(hours=+2, month=5, day=31, weekday=SU(-4)),
end=relativedelta(hours=+1, month=11, day=1, weekday=SU(+3)))),
('EST5EDT,5,4,0,7200,11,-3,0,7200',
tz.tzrange('EST', timedelta(hours=-5), 'EDT',
start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
('EST5EDT,5,4,0,7200,11,-3,0,7200,3600',
tz.tzrange('EST', timedelta(hours=-5), 'EDT',
start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
('EST5EDT,5,4,0,7200,11,-3,0,7200,3600',
tz.tzrange('EST', timedelta(hours=-5), 'EDT',
start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
('EST5EDT,5,4,0,7200,11,-3,0,7200,-3600',
tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-6),
start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
end=relativedelta(hours=+3, month=11, day=31, weekday=SU(-3)))),
('EST5EDT,5,4,0,7200,11,-3,0,7200,+7200',
tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-3),
start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
end=relativedelta(hours=0, month=11, day=31, weekday=SU(-3)))),
('EST5EDT,5,4,0,7200,11,-3,0,7200,+3600',
tz.tzrange('EST', timedelta(hours=-5), 'EDT',
start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
])
def test_valid_dateutil_format(tz_str, expected):
# This tests the dateutil-specific format that is used widely in the tests
# and examples. It is unclear where this format originated from.
with pytest.warns(tz.DeprecatedTzFormatWarning):
tzi = tz.tzstr.instance(tz_str)
assert tzi == expected
@pytest.mark.tzstr
@pytest.mark.parametrize('tz_str', [
'hdfiughdfuig,dfughdfuigpu87ñ::',
',dfughdfuigpu87ñ::',
'-1:WART4WARST,J1,J365/25',
'WART4WARST,J1,J365/-25',
'IST-2IDT,M3.4.-1/26,M10.5.0',
'IST-2IDT,M3,2000,1/26,M10,5,0'
])
def test_invalid_GNU_tzstr(tz_str):
with pytest.raises(ValueError):
tz.tzstr(tz_str)
# Different representations of the same default rule set
DEFAULT_TZSTR_RULES_EQUIV_2003 = [
'EST5EDT',
'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00',
'EST5EDT4,95/02:00:00,298/02:00',
'EST5EDT4,J96/02:00:00,J299/02:00',
'EST5EDT4,J96/02:00:00,J299/02'
]
@pytest.mark.tzstr
@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003)
def test_tzstr_default_start(tz_str):
tzi = tz.tzstr(tz_str)
dt_std = datetime(2003, 4, 6, 1, 59, tzinfo=tzi)
dt_dst = datetime(2003, 4, 6, 2, 00, tzinfo=tzi)
assert get_timezone_tuple(dt_std) == EST_TUPLE
assert get_timezone_tuple(dt_dst) == EDT_TUPLE
@pytest.mark.tzstr
@pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003)
def test_tzstr_default_end(tz_str):
tzi = tz.tzstr(tz_str)
dt_dst = datetime(2003, 10, 26, 0, 59, tzinfo=tzi)
dt_dst_ambig = datetime(2003, 10, 26, 1, 00, tzinfo=tzi)
dt_std_ambig = tz.enfold(dt_dst_ambig, fold=1)
dt_std = datetime(2003, 10, 26, 2, 00, tzinfo=tzi)
assert get_timezone_tuple(dt_dst) == EDT_TUPLE
assert get_timezone_tuple(dt_dst_ambig) == EDT_TUPLE
assert get_timezone_tuple(dt_std_ambig) == EST_TUPLE
assert get_timezone_tuple(dt_std) == EST_TUPLE
@pytest.mark.tzstr
@pytest.mark.parametrize('tzstr_1', ['EST5EDT',
'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00'])
@pytest.mark.parametrize('tzstr_2', ['EST5EDT',
'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00'])
def test_tzstr_default_cmp(tzstr_1, tzstr_2):
tz1 = tz.tzstr(tzstr_1)
tz2 = tz.tzstr(tzstr_2)
assert tz1 == tz2
class TZICalTest(unittest.TestCase, TzFoldMixin):
def _gettz_str_tuple(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_PST = (
'BEGIN:VTIMEZONE',
'TZID:US-Pacific',
'BEGIN:STANDARD',
'DTSTART:19971029T020000',
'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11',
'TZOFFSETFROM:-0700',
'TZOFFSETTO:-0800',
'TZNAME:PST',
'END:STANDARD',
'BEGIN:DAYLIGHT',
'DTSTART:19980301T020000',
'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03',
'TZOFFSETFROM:-0800',
'TZOFFSETTO:-0700',
'TZNAME:PDT',
'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'
)
TZ_LON = (
'BEGIN:VTIMEZONE',
'TZID:Europe-London',
'BEGIN:STANDARD',
'DTSTART:19810301T030000',
'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10;BYHOUR=02',
'TZOFFSETFROM:+0100',
'TZOFFSETTO:+0000',
'TZNAME:GMT',
'END:STANDARD',
'BEGIN:DAYLIGHT',
'DTSTART:19961001T030000',
'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=03;BYHOUR=01',
'TZOFFSETFROM:+0000',
'TZOFFSETTO:+0100',
'TZNAME:BST',
'END:DAYLIGHT',
'END:VTIMEZONE'
)
tzname_map = {'Australia/Sydney': TZ_AEST,
'America/Toronto': TZ_EST,
'America/New_York': TZ_EST,
'America/Los_Angeles': TZ_PST,
'Europe/London': TZ_LON}
return tzname_map[tzname]
def _gettz_str(self, tzname):
return '\n'.join(self._gettz_str_tuple(tzname))
def _tzstr_dtstart_with_params(self, tzname, param_str):
# Adds parameters to the DTSTART values of a given tzstr
tz_str_tuple = self._gettz_str_tuple(tzname)
out_tz = []
for line in tz_str_tuple:
if line.startswith('DTSTART'):
name, value = line.split(':', 1)
line = name + ';' + param_str + ':' + value
out_tz.append(line)
return '\n'.join(out_tz)
def gettz(self, tzname):
tz_str = self._gettz_str(tzname)
tzc = tz.tzical(StringIO(tz_str)).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, 3, 9, 1, 59)
dt2 = datetime(2003, 3, 9, 2, 00)
fold = [0, 0]
else:
dt1 = datetime(2003, 11, 2, 0, 59)
dt2 = datetime(2003, 11, 2, 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('\n'.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):
tz_str = self._gettz_str('America/New_York')
return tz.tzical(StringIO(tz_str)).get()
def _testEST(self, start, test_type, tzc=None):
if tzc is None:
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 testESTValueDatetime(self):
# Violating one-test-per-test rule because we're not set up to do
# parameterized tests and the manual proliferation is getting a bit
# out of hand.
tz_str = self._tzstr_dtstart_with_params('America/New_York',
'VALUE=DATE-TIME')
tzc = tz.tzical(StringIO(tz_str)).get()
for start in (True, False):
for test_type in ('name', 'offset', 'dst'):
self._testEST(start=start, test_type=test_type, tzc=tzc)
def _testMultizone(self, start, test_type):
tzstrs = (self._gettz_str('America/New_York'),
self._gettz_str('America/Los_Angeles'))
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):
est_str = self._gettz_str('America/New_York')
pst_str = self._gettz_str('America/Los_Angeles')
tzic = tz.tzical(StringIO('\n'.join((est_str, pst_str))))
# 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()
def testDtstartDate(self):
tz_str = self._tzstr_dtstart_with_params('America/New_York',
'VALUE=DATE')
with self.assertRaises(ValueError):
tz.tzical(StringIO(tz_str))
def testDtstartTzid(self):
tz_str = self._tzstr_dtstart_with_params('America/New_York',
'TZID=UTC')
with self.assertRaises(ValueError):
tz.tzical(StringIO(tz_str))
def testDtstartBadParam(self):
tz_str = self._tzstr_dtstart_with_params('America/New_York',
'FOO=BAR')
with self.assertRaises(ValueError):
tz.tzical(StringIO(tz_str))
# 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 testFilestreamWithNameRepr(self):
# If fileobj is a filestream with a "name" attribute this name should
# be reflected in the tz object's repr
fileobj = BytesIO(base64.b64decode(TZFILE_EST5EDT))
fileobj.name = 'foo'
tzc = tz.tzfile(fileobj)
self.assertEqual(repr(tzc), 'tzfile(' + repr('foo') + ')')
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))
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')
@pytest.mark.tzfile
@pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS,
reason='Sub-minute offsets not supported')
def test_tzfile_sub_minute_offset():
# If user running python 3.6 or newer, exact offset is used
tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
offset = timedelta(hours=1, minutes=39, seconds=52)
assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset
@pytest.mark.tzfile
@pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS,
reason='Sub-minute offsets supported.')
def test_sub_minute_rounding_tzfile():
# This timezone has an offset of 5992 seconds in 1900-01-01.
# For python version pre-3.6, this will be rounded
tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
offset = timedelta(hours=1, minutes=40)
assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset
@pytest.mark.tzfile
def test_samoa_transition():
# utcoffset() was erroneously returning +14:00 an hour early (GH #812)
APIA = tz.gettz('Pacific/Apia')
dt = datetime(2011, 12, 29, 23, 59, tzinfo=APIA)
assert dt.utcoffset() == timedelta(hours=-10)
# Make sure the transition actually works, too
dt_after = (dt.astimezone(tz.UTC) + timedelta(minutes=1)).astimezone(APIA)
assert dt_after == datetime(2011, 12, 31, tzinfo=APIA)
assert dt_after.utcoffset() == timedelta(hours=14)
@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 ()
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(), singleton=True)
def testPickleTzOffsetZero(self):
self.assertPicklable(tz.tzoffset('UTC', 0), singleton=True)
def testPickleTzOffsetPos(self):
self.assertPicklable(tz.tzoffset('UTC+1', 3600), singleton=True)
def testPickleTzOffsetNeg(self):
self.assertPicklable(tz.tzoffset('UTC-1', -3600), singleton=True)
@pytest.mark.tzlocal
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 TestEnfold:
def test_enter_fold_default(self):
dt = tz.enfold(datetime(2020, 1, 19, 3, 32))
assert dt.fold == 1
def test_enter_fold(self):
dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1)
assert dt.fold == 1
def test_exit_fold(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.
assert getattr(dt, 'fold', 0) == 0
def test_defold(self):
dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1)
dt2 = tz.enfold(dt, fold=0)
assert getattr(dt2, 'fold', 0) == 0
def test_fold_replace_args(self):
# This test can be dropped when Python < 3.6 is dropped, since it
# is mainly to cover the `replace` method on _DatetimeWithFold
dt = tz.enfold(datetime(1950, 1, 2, 12, 30, 15, 8), fold=1)
dt2 = dt.replace(1952, 2, 3, 13, 31, 16, 9)
assert dt2 == tz.enfold(datetime(1952, 2, 3, 13, 31, 16, 9), fold=1)
assert dt2.fold == 1
def test_fold_replace_exception_duplicate_args(self):
dt = tz.enfold(datetime(1999, 1, 3), fold=1)
with pytest.raises(TypeError):
dt.replace(1950, year=2000)
@pytest.mark.tz_resolve_imaginary
class ImaginaryDateTest(unittest.TestCase):
def testCanberraForward(self):
tzi = tz.gettz('Australia/Canberra')
dt = datetime(2018, 10, 7, 2, 30, tzinfo=tzi)
dt_act = tz.resolve_imaginary(dt)
dt_exp = datetime(2018, 10, 7, 3, 30, tzinfo=tzi)
self.assertEqual(dt_act, dt_exp)
def testLondonForward(self):
tzi = tz.gettz('Europe/London')
dt = datetime(2018, 3, 25, 1, 30, tzinfo=tzi)
dt_act = tz.resolve_imaginary(dt)
dt_exp = datetime(2018, 3, 25, 2, 30, tzinfo=tzi)
self.assertEqual(dt_act, dt_exp)
def testKeivForward(self):
tzi = tz.gettz('Europe/Kiev')
dt = datetime(2018, 3, 25, 3, 30, tzinfo=tzi)
dt_act = tz.resolve_imaginary(dt)
dt_exp = datetime(2018, 3, 25, 4, 30, tzinfo=tzi)
self.assertEqual(dt_act, dt_exp)
@pytest.mark.tz_resolve_imaginary
@pytest.mark.parametrize('dt', [
datetime(2017, 11, 5, 1, 30, tzinfo=tz.gettz('America/New_York')),
datetime(2018, 10, 28, 1, 30, tzinfo=tz.gettz('Europe/London')),
datetime(2017, 4, 2, 2, 30, tzinfo=tz.gettz('Australia/Sydney')),
])
def test_resolve_imaginary_ambiguous(dt):
assert tz.resolve_imaginary(dt) is dt
dt_f = tz.enfold(dt)
assert dt is not dt_f
assert tz.resolve_imaginary(dt_f) is dt_f
@pytest.mark.tz_resolve_imaginary
@pytest.mark.parametrize('dt', [
datetime(2017, 6, 2, 12, 30, tzinfo=tz.gettz('America/New_York')),
datetime(2018, 4, 2, 9, 30, tzinfo=tz.gettz('Europe/London')),
datetime(2017, 2, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')),
datetime(2017, 12, 2, 12, 30, tzinfo=tz.gettz('America/New_York')),
datetime(2018, 12, 2, 9, 30, tzinfo=tz.gettz('Europe/London')),
datetime(2017, 6, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')),
datetime(2025, 9, 25, 1, 17, tzinfo=tz.tzutc()),
datetime(2025, 9, 25, 1, 17, tzinfo=tz.tzoffset('EST', -18000)),
datetime(2019, 3, 4, tzinfo=None)
])
def test_resolve_imaginary_existing(dt):
assert tz.resolve_imaginary(dt) is dt
def __get_kiritimati_resolve_imaginary_test():
# In the 2018d release of the IANA database, the Kiritimati "imaginary day"
# data was corrected, so if the system zoneinfo is older than 2018d, the
# Kiritimati test will fail.
tzi = tz.gettz('Pacific/Kiritimati')
new_version = False
if not tz.datetime_exists(datetime(1995, 1, 1, 12, 30), tzi):
zif = zoneinfo.get_zonefile_instance()
if zif.metadata is not None:
new_version = zif.metadata['tzversion'] >= '2018d'
if new_version:
tzi = zif.get('Pacific/Kiritimati')
else:
new_version = True
if new_version:
dates = (datetime(1994, 12, 31, 12, 30), datetime(1995, 1, 1, 12, 30))
else:
dates = (datetime(1995, 1, 1, 12, 30), datetime(1995, 1, 2, 12, 30))
return (tzi, ) + dates
resolve_imaginary_tests = [
(tz.gettz('Europe/London'),
datetime(2018, 3, 25, 1, 30), datetime(2018, 3, 25, 2, 30)),
(tz.gettz('America/New_York'),
datetime(2017, 3, 12, 2, 30), datetime(2017, 3, 12, 3, 30)),
(tz.gettz('Australia/Sydney'),
datetime(2014, 10, 5, 2, 0), datetime(2014, 10, 5, 3, 0)),
__get_kiritimati_resolve_imaginary_test(),
]
if SUPPORTS_SUB_MINUTE_OFFSETS:
resolve_imaginary_tests.append(
(tz.gettz('Africa/Monrovia'),
datetime(1972, 1, 7, 0, 30), datetime(1972, 1, 7, 1, 14, 30)))
@pytest.mark.tz_resolve_imaginary
@pytest.mark.parametrize('tzi, dt, dt_exp', resolve_imaginary_tests)
def test_resolve_imaginary(tzi, dt, dt_exp):
dt = dt.replace(tzinfo=tzi)
dt_exp = dt_exp.replace(tzinfo=tzi)
dt_r = tz.resolve_imaginary(dt)
assert dt_r == dt_exp
assert dt_r.tzname() == dt_exp.tzname()
assert dt_r.utcoffset() == dt_exp.utcoffset()