# -*- 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 BytesIO, StringIO import unittest import sys import base64 import copy 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)) ### # 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.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.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.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 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.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 testRoundNonFullMinutes(self): # This timezone has an offset of 5992 seconds in 1900-01-01. tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI))) self.assertEqual(str(datetime(1900, 1, 1, 0, 0, tzinfo=tzc)), "1900-01-01 00:00:00+01:40") def testLeapCountDecodesProperly(self): # This timezone has leapcnt, and failed to decode until # Eugene Oden notified about the issue. # As leap information is currently unused (and unstored) by tzfile() we # can only indirectly test this: Take advantage of tzfile() not closing # the input file if handed in as an opened file and assert that the # full file content has been read by tzfile(). Note: For this test to # work NEW_YORK must be in TZif version 1 format i.e. no more data # after TZif v1 header + data has been read fileobj = BytesIO(base64.b64decode(NEW_YORK)) tz.tzfile(fileobj) # we expect no remaining file content now, i.e. zero-length; if there's # still data we haven't read the file format correctly remaining_tzfile_content = fileobj.read() self.assertEqual(len(remaining_tzfile_content), 0) def testIsStd(self): # NEW_YORK tzfile contains this isstd information: isstd_expected = (0, 0, 0, 1) tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK))) # gather the actual information as parsed by the tzfile class isstd = [] for ttinfo in tzc._ttinfo_list: # ttinfo objects contain boolean values isstd.append(int(ttinfo.isstd)) # ttinfo list may contain more entries than isstd file content isstd = tuple(isstd[:len(isstd_expected)]) self.assertEqual( isstd_expected, isstd, "isstd UTC/local indicators parsed: %s != tzfile contents: %s" % (isstd, isstd_expected)) def testGMTHasNoDaylight(self): # tz.tzstr("GMT+2") improperly considered daylight saving time. # Issue reported by Lennart Regebro. dt = datetime(2007, 8, 6, 4, 10) self.assertEqual(tz.gettz("GMT+2").dst(dt), timedelta(0)) def testGMTOffset(self): # GMT and UTC offsets have inverted signal when compared to the # usual TZ variable handling. dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.tzutc()) self.assertEqual(dt.astimezone(tz=tz.tzstr("GMT+2")), datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2"))) self.assertEqual(dt.astimezone(tz=tz.gettz("UTC-2")), datetime(2007, 8, 6, 2, 10, tzinfo=tz.tzstr("UTC-2"))) @unittest.skipIf(IS_WIN, "requires Unix") @unittest.skipUnless(TZEnvContext.tz_change_allowed(), TZEnvContext.tz_change_disallowed_message()) def testTZSetDoesntCorrupt(self): # if we start in non-UTC then tzset UTC make sure parse doesn't get # confused with TZEnvContext('UTC'): # this should parse to UTC timezone not the original timezone dt = parse('2014-07-20T12:34:56+00:00') self.assertEqual(str(dt), '2014-07-20 12:34:56+00:00') @unittest.skipUnless(IS_WIN, "Requires Windows") class TzWinTest(unittest.TestCase, TzWinFoldMixin): def setUp(self): self.tzclass = tzwin.tzwin def testTzResLoadName(self): # This may not work right on non-US locales. tzr = tzwin.tzres() self.assertEqual(tzr.load_name(112), "Eastern Standard Time") def testTzResNameFromString(self): tzr = tzwin.tzres() self.assertEqual(tzr.name_from_string('@tzres.dll,-221'), 'Alaskan Daylight Time') self.assertEqual(tzr.name_from_string('Samoa Daylight Time'), 'Samoa Daylight Time') with self.assertRaises(ValueError): tzr.name_from_string('@tzres.dll,100') def testIsdstZoneWithNoDaylightSaving(self): tz = tzwin.tzwin("UTC") dt = parse("2013-03-06 19:08:15") self.assertFalse(tz._isdst(dt)) def testOffset(self): tz = tzwin.tzwin("Cape Verde Standard Time") self.assertEqual(tz.utcoffset(datetime(1995, 5, 21, 12, 9, 13)), timedelta(-1, 82800)) def testTzwinName(self): # https://github.com/dateutil/dateutil/issues/143 tw = tz.tzwin('Eastern Standard Time') # Cover the transitions for at least two years. ESTs = 'Eastern Standard Time' EDTs = 'Eastern Daylight Time' transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs), (datetime(2015, 3, 8, 3, 1), EDTs), (datetime(2015, 11, 1, 0, 59), EDTs), (datetime(2015, 11, 1, 3, 1), ESTs), (datetime(2016, 3, 13, 0, 59), ESTs), (datetime(2016, 3, 13, 3, 1), EDTs), (datetime(2016, 11, 6, 0, 59), EDTs), (datetime(2016, 11, 6, 3, 1), ESTs)] for t_date, expected in transition_dates: self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected) def testTzwinRepr(self): tw = tz.tzwin('Yakutsk Standard Time') self.assertEqual(repr(tw), 'tzwin(' + repr('Yakutsk Standard Time') + ')') def testTzWinEquality(self): # https://github.com/dateutil/dateutil/issues/151 tzwin_names = ('Eastern Standard Time', 'West Pacific Standard Time', 'Yakutsk Standard Time', 'Iran Standard Time', 'UTC') for tzwin_name in tzwin_names: # Get two different instances to compare tw1 = tz.tzwin(tzwin_name) tw2 = tz.tzwin(tzwin_name) self.assertEqual(tw1, tw2) def testTzWinInequality(self): # https://github.com/dateutil/dateutil/issues/151 # Note these last two currently differ only in their name. tzwin_names = (('Eastern Standard Time', 'Yakutsk Standard Time'), ('Greenwich Standard Time', 'GMT Standard Time'), ('GMT Standard Time', 'UTC'), ('E. South America Standard Time', 'Argentina Standard Time')) for tzwn1, tzwn2 in tzwin_names: # Get two different instances to compare tw1 = tz.tzwin(tzwn1) tw2 = tz.tzwin(tzwn2) self.assertNotEqual(tw1, tw2) def testTzWinEqualityInvalid(self): # Compare to objects that do not implement comparison with this # (should default to False) UTC = tz.tzutc() EST = tz.tzwin('Eastern Standard Time') self.assertFalse(EST == UTC) self.assertFalse(EST == 1) self.assertFalse(UTC == EST) self.assertTrue(EST != UTC) self.assertTrue(EST != 1) def testTzWinInequalityUnsupported(self): # Compare it to an object that is promiscuous about equality, but for # which tzwin does not implement an equality operator. EST = tz.tzwin('Eastern Standard Time') self.assertTrue(EST == ComparesEqual) self.assertFalse(EST != ComparesEqual) def testTzwinTimeOnlyDST(self): # For zones with DST, .dst() should return None tw_est = tz.tzwin('Eastern Standard Time') self.assertIs(dt_time(14, 10, tzinfo=tw_est).dst(), None) # This zone has no DST, so .dst() can return 0 tw_sast = tz.tzwin('South Africa Standard Time') self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).dst(), timedelta(0)) def testTzwinTimeOnlyUTCOffset(self): # For zones with DST, .utcoffset() should return None tw_est = tz.tzwin('Eastern Standard Time') self.assertIs(dt_time(14, 10, tzinfo=tw_est).utcoffset(), None) # This zone has no DST, so .utcoffset() returns standard offset tw_sast = tz.tzwin('South Africa Standard Time') self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).utcoffset(), timedelta(hours=2)) def testTzwinTimeOnlyTZName(self): # For zones with DST, the name defaults to standard time tw_est = tz.tzwin('Eastern Standard Time') self.assertEqual(dt_time(14, 10, tzinfo=tw_est).tzname(), 'Eastern Standard Time') # For zones with no DST, this should work normally. tw_sast = tz.tzwin('South Africa Standard Time') self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).tzname(), 'South Africa Standard Time') @unittest.skipUnless(IS_WIN, "Requires Windows") @unittest.skipUnless(TZWinContext.tz_change_allowed(), TZWinContext.tz_change_disallowed_message()) class TzWinLocalTest(unittest.TestCase, TzWinFoldMixin): def setUp(self): self.tzclass = tzwin.tzwinlocal self.context = TZWinContext def get_args(self, tzname): return () 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 EnfoldTest(unittest.TestCase): def testEnterFoldDefault(self): dt = tz.enfold(datetime(2020, 1, 19, 3, 32)) self.assertEqual(dt.fold, 1) def testEnterFold(self): dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1) self.assertEqual(dt.fold, 1) def testExitFold(self): dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=0) # Before Python 3.6, dt.fold won't exist if fold is 0. self.assertEqual(getattr(dt, 'fold', 0), 0) @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 @pytest.mark.tz_resolve_imaginary @pytest.mark.parametrize('tzi, dt, dt_exp', [ (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(), ]) 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() @pytest.mark.xfail @pytest.mark.tz_resolve_imaginary def test_resolve_imaginary_monrovia(): # See GH #582 - When that is resolved, move this into test_resolve_imaginary tzi = tz.gettz('Africa/Monrovia') dt = datetime(1972, 1, 7, hour=0, minute=30, second=0, tzinfo=tzi) dt_exp = datetime(1972, 1, 7, hour=1, minute=14, second=30, 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()