| import base64 | |
| import copy | |
| import gc | |
| import os | |
| import pickle | |
| import sys | |
| import tempfile | |
| import unittest | |
| import warnings | |
| import weakref | |
| from datetime import datetime | |
| from datetime import time as dt_time | |
| from datetime import timedelta, tzinfo | |
| from functools import partial | |
| from io import BytesIO, StringIO | |
| from ._common import ( | |
| ComparesEqual, | |
| PicklableMixin, | |
| TZEnvContext, | |
| TzSourceMixin, | |
| TZWinContext, | |
| ) | |
| IS_WIN = sys.platform.startswith('win') | |
| import pytest | |
| from dateutil import tz as tz | |
| from dateutil.parser import parse | |
| with warnings.catch_warnings(): | |
| warnings.simplefilter("ignore", category=DeprecationWarning) | |
| from dateutil import zoneinfo | |
| try: | |
| import tzdata | |
| except ImportError: | |
| tzdata = None | |
| # The deprecated dateutil.zoneinfo module is a wrapper around tzdata. | |
| requires_tzdata = pytest.mark.skipif( | |
| tzdata is None, reason="dateutil.zoneinfo requires tzdata" | |
| ) | |
| # dateutil imports | |
| from dateutil.relativedelta import SU, TH, relativedelta | |
| 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: | |
| def __init__(*args, **kwargs): | |
| pass | |
| def __enter__(*args, **kwargs): | |
| pass | |
| def __exit__(*args, **kwargs): | |
| pass | |
| class TzFoldMixin: | |
| """ 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( | |
| "PEP 495 inter-zone comparisons are false for ambiguous times." | |
| ) | |
| 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: | |
| def get_args(self, tzname): | |
| return (tzname, ) | |
| class context: | |
| 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 | |
| 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.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(TzSourceMixin, unittest.TestCase, TzFoldMixin): | |
| gettz = staticmethod(tz.gettz) | |
| def testGettz(self): | |
| # bug 892569 | |
| str(self.gettz("Etc/UTC")) | |
| def testGetTzEquality(self): | |
| self.assertEqual(self.gettz("Etc/UTC"), self.gettz("Etc/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("Europe/Lisbon") | |
| 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 | |
| @pytest.mark.xfail( | |
| IS_WIN and ("/" in os.environ.get("TZ", "")), | |
| reason="On Windows IANA zones are currently always singletons", | |
| ) | |
| def testGettzCacheTzLocal(self): | |
| local1 = tz.gettz() | |
| local2 = tz.gettz() | |
| assert local1 is not local2 | |
| def test_key_attribute(self): | |
| # Also tested more thoroughly in the property tests. | |
| NYC = tz.gettz("America/New_York") | |
| assert NYC.key == "America/New_York" | |
| @pytest.mark.gettz | |
| class GettzTzdataTest(GettzTest): | |
| tz_source = "tzdata" | |
| # Links from the "backward" file in the tz database, which some builds leave | |
| # out, and which may be removed in the future. | |
| LINKS = [ | |
| ("US/Eastern", "America/New_York"), | |
| ("US/Pacific", "America/Los_Angeles"), | |
| ("Portugal", "Europe/Lisbon"), | |
| ("Europe/Kiev", "Europe/Kyiv"), | |
| ("Australia/Canberra", "Australia/Sydney"), | |
| ("Australia/Currie", "Australia/Hobart"), | |
| ("Europe/Monaco", "Europe/Paris"), | |
| ("UTC", "Etc/UTC"), | |
| ("GMT", "Etc/GMT"), | |
| ] | |
| @pytest.mark.gettz | |
| @pytest.mark.parametrize("link, target", LINKS) | |
| def test_gettz_link(tz_source, link, target): | |
| """A link behaves like its target, but keeps its own key.""" | |
| if link not in tz.available_iana_timezones(): | |
| pytest.skip("%s is not in this time zone data" % link) | |
| link_zone = tz.gettz(link) | |
| target_zone = tz.gettz(target) | |
| assert isinstance(link_zone, tz.tzfile) | |
| assert isinstance(target_zone, tz.tzfile) | |
| assert link_zone == target_zone | |
| assert link_zone.key == link | |
| assert target_zone.key == target | |
| @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", | |
| ), | |
| 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 | |
| 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 | |
| def test_gettz_set_cache_size(): | |
| tz.gettz.cache_clear() | |
| tz.gettz.set_cache_size(3) | |
| PARIS_ref = weakref.ref(tz.gettz("Europe/Paris")) | |
| EASTER_ref = weakref.ref(tz.gettz("Pacific/Easter")) | |
| HOBART_ref = weakref.ref(tz.gettz("Australia/Hobart")) | |
| gc.collect() | |
| assert PARIS_ref() is not None | |
| assert EASTER_ref() is not None | |
| assert HOBART_ref() is not None | |
| tz.gettz.set_cache_size(2) | |
| gc.collect() | |
| assert PARIS_ref() is None | |
| @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/Paris") | |
| tz.gettz("Pacific/Easter") | |
| tz.gettz("Australia/Hobart") | |
| gc.collect() | |
| assert NYC_ref() is None # Should have been pushed out | |
| assert tz.gettz('America/New_York') is not NYC_ref() | |
| @requires_tzdata | |
| class ZoneInfoGettzTest(GettzTest): | |
| # This reads the tzdata package through dateutil.zoneinfo directly | |
| tz_source = None | |
| def gettz(self, name): | |
| zoneinfo_file = zoneinfo.get_zonefile_instance() | |
| return zoneinfo_file.get(name) | |
| def testZoneInfoFileStart1(self): | |
| tzc = self.gettz("EST5EDT") | |
| self.assertEqual( | |
| datetime(2003, 4, 6, 1, 59, tzinfo=tzc).tzname(), | |
| "EST", | |
| MISSING_TARBALL, | |
| ) | |
| self.assertEqual( | |
| tz.enfold(datetime(2003, 4, 6, 2, tzinfo=tzc), fold=0).tzname(), | |
| "EST", | |
| ) | |
| self.assertEqual( | |
| tz.enfold(datetime(2003, 4, 6, 2, tzinfo=tzc), fold=1).tzname(), | |
| "EDT", | |
| ) | |
| self.assertEqual(datetime(2003, 4, 6, 3, 0, tzinfo=tzc).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, 0, 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() | |
| def testZoneInfoMetadataFreshInstance(self): | |
| # gettz_db_metadata must be able to create the module-level | |
| # instance itself if gettz has not been called first. | |
| old_instances = list(zoneinfo._CLASS_ZONE_INSTANCE) | |
| del zoneinfo._CLASS_ZONE_INSTANCE[:] | |
| try: | |
| with pytest.warns(DeprecationWarning): | |
| metadata = zoneinfo.gettz_db_metadata() | |
| finally: | |
| zoneinfo._CLASS_ZONE_INSTANCE[:] = old_instances | |
| assert metadata["tzversion"] | |
| 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 testStrTransition(self): | |
| # Check the abbreviation on either side of the DST transition. | |
| self.assertEqual( | |
| datetime(2003, 4, 6, 1, 59, tzinfo=tz.tzstr("EST5EDT")).tzname(), | |
| "EST", | |
| ) | |
| self.assertEqual( | |
| datetime(2003, 4, 6, 2, 00, tzinfo=tz.tzstr("EST5EDT")).tzname(), | |
| "EDT", | |
| ) | |
| def 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']) | |
| @pytest.mark.parametrize( | |
| "offset", | |
| [ | |
| "+-500", | |
| "--500", | |
| "+ 500", | |
| "-05 0", | |
| "+\u0660\u0665\u0660\u0660", # Non-ASCII (Arabic-Indic) digits | |
| ], | |
| ) | |
| def test_tzical_offset_non_digit(offset): | |
| tz_str = TZICAL_EST5EDT.replace("TZOFFSETTO:-0500", "TZOFFSETTO:" + offset) | |
| with pytest.raises(ValueError): | |
| tz.tzical(StringIO(tz_str)) | |
| 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( | |
| tz.enfold(datetime(2003, 4, 6, 2, 0, tzinfo=tzc), fold=0).tzname(), | |
| "EST", | |
| ) | |
| self.assertEqual( | |
| tz.enfold(datetime(2003, 4, 6, 2, 0, tzinfo=tzc), fold=1).tzname(), | |
| "EDT", | |
| ) | |
| self.assertEqual(datetime(2003, 4, 6, 3, 0, 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, 0, 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, 0, 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 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 | |
| def test_tzfile_sub_minute_offset(): | |
| 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 | |
| def test_samoa_transition(tz_source): | |
| # utcoffset() was erroneously returning +14:00 an hour early (GH #812) | |
| APIA = tz.gettz('Pacific/Apia') | |
| assert APIA is not None | |
| 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) | |
| @requires_tzdata | |
| 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 | |
| @pytest.mark.parametrize("asfile", [False, True]) | |
| def test_pickle_gettz(tz_source, asfile): | |
| zone = tz.gettz("America/New_York") | |
| assert zone is not None | |
| if asfile: | |
| with tempfile.TemporaryFile("w+b") as f: | |
| pickle.dump(zone, f) | |
| f.seek(0) | |
| unpickled = pickle.load(f) | |
| else: | |
| unpickled = pickle.loads(pickle.dumps(zone)) | |
| assert unpickled is not zone | |
| assert unpickled == zone | |
| class DatetimeAmbiguousTest(TzSourceMixin, 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 dt_n.fold: | |
| 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 dt_n.fold: | |
| 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("America/New_York") | |
| dt = datetime(2011, 11, 6, 1, 30) | |
| self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi)) | |
| def testSupportAmbiguityFoldAware(self): | |
| tzi = tz.gettz("America/New_York") | |
| dt = datetime(2011, 11, 6, 1, 30, tzinfo=tzi) | |
| self.assertTrue(tz.datetime_ambiguous(dt)) | |
| def testSupportAmbiguityUnambiguousAware(self): | |
| tzi = tz.gettz("America/New_York") | |
| dt = datetime(2011, 11, 6, 4, 30) | |
| self.assertFalse(tz.datetime_ambiguous(dt, tz=tzi)) | |
| def testSupportAmbiguityUnambiguousNaive(self): | |
| tzi = tz.gettz("America/New_York") | |
| 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("America/New_York") | |
| self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi)) | |
| class DatetimeAmbiguousTzdataTest(DatetimeAmbiguousTest): | |
| tz_source = "tzdata" | |
| class DatetimeExistsTest(TzSourceMixin, 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("America/New_York") | |
| 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 DatetimeExistsTzdataTest(DatetimeExistsTest): | |
| tz_source = "tzdata" | |
| 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) | |
| assert dt.fold == 0 | |
| def test_defold(self): | |
| dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1) | |
| dt2 = tz.enfold(dt, fold=0) | |
| assert dt2.fold == 0 | |
| @pytest.mark.tz_resolve_imaginary | |
| class ImaginaryDateTest(TzSourceMixin, unittest.TestCase): | |
| def testSydneyForward(self): | |
| tzi = tz.gettz("Australia/Sydney") | |
| 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 testKyivForward(self): | |
| tzi = tz.gettz("Europe/Kyiv") | |
| 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) | |
| class ImaginaryDateTzdataTest(ImaginaryDateTest): | |
| tz_source = "tzdata" | |
| def _gettz(key): | |
| # A key missing from the data should fail the test, not pass it with a | |
| # naive datetime. | |
| zone = tz.gettz(key) | |
| assert zone is not None, key | |
| return zone | |
| @pytest.mark.tz_resolve_imaginary | |
| @pytest.mark.parametrize( | |
| "key, dt", | |
| [ | |
| ("America/New_York", datetime(2017, 11, 5, 1, 30)), | |
| ("Europe/London", datetime(2018, 10, 28, 1, 30)), | |
| ("Australia/Sydney", datetime(2017, 4, 2, 2, 30)), | |
| ], | |
| ) | |
| def test_resolve_imaginary_ambiguous(tz_source, key, dt): | |
| dt = dt.replace(tzinfo=_gettz(key)) | |
| 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( | |
| "key, dt", | |
| [ | |
| ("America/New_York", datetime(2017, 6, 2, 12, 30)), | |
| ("Europe/London", datetime(2018, 4, 2, 9, 30)), | |
| ("Australia/Sydney", datetime(2017, 2, 2, 16, 30)), | |
| ("America/New_York", datetime(2017, 12, 2, 12, 30)), | |
| ("Europe/London", datetime(2018, 12, 2, 9, 30)), | |
| ("Australia/Sydney", datetime(2017, 6, 2, 16, 30)), | |
| ], | |
| ) | |
| def test_resolve_imaginary_existing(tz_source, key, dt): | |
| dt = dt.replace(tzinfo=_gettz(key)) | |
| assert tz.resolve_imaginary(dt) is dt | |
| @pytest.mark.tz_resolve_imaginary | |
| @pytest.mark.parametrize('dt', [ | |
| 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_fixed(dt): | |
| assert tz.resolve_imaginary(dt) is dt | |
| resolve_imaginary_tests = [ | |
| ( | |
| "Europe/London", | |
| datetime(2018, 3, 25, 1, 30), | |
| datetime(2018, 3, 25, 2, 30), | |
| ), | |
| ( | |
| "America/New_York", | |
| datetime(2017, 3, 12, 2, 30), | |
| datetime(2017, 3, 12, 3, 30), | |
| ), | |
| ( | |
| "Australia/Sydney", | |
| datetime(2014, 10, 5, 2, 0), | |
| datetime(2014, 10, 5, 3, 0), | |
| ), | |
| # Kiritimati skipped 1994-12-31 when it moved across the date line. Data | |
| # older than 2018d had the skipped day wrong. | |
| ( | |
| "Pacific/Kiritimati", | |
| datetime(1994, 12, 31, 12, 30), | |
| datetime(1995, 1, 1, 12, 30), | |
| ), | |
| ] | |
| resolve_imaginary_tests.append( | |
| ( | |
| "Africa/Monrovia", | |
| datetime(1972, 1, 7, 0, 30), | |
| datetime(1972, 1, 7, 1, 14, 30), | |
| ) | |
| ) | |
| @pytest.mark.tz_resolve_imaginary | |
| @pytest.mark.parametrize("key, dt, dt_exp", resolve_imaginary_tests) | |
| def test_resolve_imaginary(tz_source, key, dt, dt_exp): | |
| tzi = _gettz(key) | |
| 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() |