# -*- coding: utf-8 -*- from __future__ import unicode_literals from ._common import PicklableMixin from ._common import TZEnvContext, TZWinContext 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.UTC) # AEST t1_u = datetime(2012, 3, 31, 16, 30, tzinfo=tz.UTC) # 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.UTC) # AEST t1_u = datetime(2012, 10, 6, 16, 30, tzinfo=tz.UTC) # 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.UTC) t1_u = datetime(2011, 11, 6, 6, 30, tzinfo=tz.UTC) 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.UTC) t1_u = datetime(2011, 3, 13, 7, 30, tzinfo=tz.UTC) 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.UTC 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.UTC 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.UTC 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.UTC) # 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.UTC) 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.UTC) 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.UTC 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.UTC 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.UTC 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") 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.UTC # TODO: Maybe a better hack than this? def mark_tzlocal_nix(f): marks = [ pytest.mark.tzlocal, pytest.mark.skipif(IS_WIN, reason='requires Unix'), ] 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)), ('GMT0', 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 def test_gettz_same_result_for_none_and_empty_string(): local_from_none = tz.gettz() local_from_empty_string = tz.gettz("") assert local_from_none is not None assert local_from_empty_string is not None assert local_from_none == local_from_empty_string @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.parametrize( "badzone,exc_reason", [ pytest.param( b"America/New_York", ".*should be str, not bytes.*", id="bytes on Python 3", marks=[ pytest.mark.skipif( PY2, reason="bytes arguments accepted in Python 2" ) ], ), pytest.param( object(), None, id="no startswith()", marks=[ pytest.mark.xfail(reason="AttributeError instead of TypeError", raises=AttributeError), ], ), ], ) def test_gettz_zone_wrong_type(badzone, exc_reason): with pytest.raises(TypeError, match=exc_reason): tz.gettz(badzone) @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): 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 pytest.warns(DeprecationWarning): zoneinfo.gettz('US/Eastern') def testZoneInfoMetadataDeprecated(self): with pytest.warns(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.UTC) 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.UTC) 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") 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.UTC 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") 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.UTC), 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()