jellyfin-kodi/resources/lib/libraries/dateutil/test/test_tz.py
angelblue05 bab67ddf9b
Version 4.0.0 (#182)
* Adjust refresh behavior

* Fix favorites

* Add option to mask info

* Fix keymap delete

* Fix empty show

* Version bump 3.1.14

* Reset rescan flag

* Fix subtitles encoding

* Fix path verification

* Fix update library

Plug in remove library percentage

* Fix unauthorized behavior

Reprompt user with login

* Fix series pooling

* Version bump 3.1.15

* Fix for additional users

Return all users, not just public users

* Fix http potential errors

Prevent from going further if {server} or {userid} is requested but not filled to avoid 401 errors

* Fix extra fanart

* Fix patch

make a case insensitive search

* Version bump 3.1.16

Additional logging, fix kodi source.

* Fix library tags on update

* Version bump 3.1.17

* Fix season artwork

* Fix season artwork

* Fix logging

* Fix blank files sources

* Add backup option

* Fix userdata song

* Transfer data.txt to data.json

Use default port for webserver caching

* Fix mixed content shortcut

* Fix path encoding for patch

Hopefully this works...

* Fix source nonetype error

Just incase, wrap in a try/except because it's not important.

* Base fast sync on server time

Try to fix music video refresh to prevent cursor from moving up.

* Prep subfolders for dynamic

Support homevideos for now

* Fix empty artist, missing Title

* Version bump 3.1.18a

* Version bump for objects

171076013

* Notify user of large updates

Give option to back out if the user wants to manually update the libraries

* Fix sources.xml verification

* Prevent error in monitor

Put in place try/except in case data is None

* Remember sync position for manual triggers

Allow to resume sync on restart for manual user triggers (update, repair). Automatically refresh boxsets if movie library is selected.

use waitForAbort and emby_should_stop prop to terminate threads

* Update string for sync later

* Add subfolders for dynamic movies

* Small fixes

* Version bump 3.1.19

* Fix fast sync

try/except, default back to previous behavior.

* Fix artwork

* Change settings name

To ensure it takes default value instead of previous value set in 3.0.34

* Fix transcode

flac and live tv

* Fix episodes for series pooling

* Add live tv support

* Version bump 3.1.20

* Revert "Small fixes"

This reverts commit 9ec1fa3585.

* Version bump 3.1.21

* Fix playback starting server connection instance

* Fix show update

* Fix boxsets

* Fix lastplayed

* Patch to support pre 3.6 libraries

* Fix slowness

* Plug in settings for threading

* Plug in settings for threading

* Adjust sleep behavior

* Version bump 3.1.22

* Fix server detection in monitor

* Version bump 3.1.23

* Fix potential error with checksum

* Fix missing new artists

* Fix library sync

Adjust lock, re-add screensaver deactivated during sync, prep compare sync, stop library updates from being processed before startup sync is completed

* Version bump 3.1.25

* Fix local trailers

* Adjust lock modification

* Check db version

* Prevent error from creating nodes

The addon automatically creates nodes at startup with prefilled information. Prevent errors in the event something goes wrong. It will fix itself down the line, after user has logged in.

* Version bump 3.1.26

* Revert "Version bump 3.1.26"

This reverts commit c583a69a4b.

* Fix screensaver toggle

* Fix source selection for direct stream

* Version bump 3.1.26

* Add progress for updates

* Revise progress bar

Fix typos and subsetting

* content notification

* Remove content with update library

Now remove irrelevant content as well

* Fix slowness

* Version bump 3.1.27

* Stop trying to get items if server offline

* Fix content type for dynamic music

* Fix resume sync

Now save progress, unless exited due to path validation

* Fix artwork for shortcuts on profile switch

* Add force transcode settings

* Fix audiobooks back to video type

Add shortcuts. Audiobook can't be music type otherwise it break resume behavior and it won't play the right item. Has to be video type.

* Update general info

To finish, download and installation

* Update README.md

* Move welcome message to service

* Prevent patch loop

Try once, then let it go, to avoid locking user in a restart loop

* Review library threads

* Prep for audiobook transcode

Still need to implement universal for audio transcode

* Version bump 3.1.28

* Fix emby database locked

* Fix regression to welcome message

* Version bump 3.1.29

* Adjust playback

Allow direct play for http streams

* Ensure all threads are terminated correctly

* Fix empty results due to error 500

* Fix boxset refresh

* Fix resume sync behavior

Allow to complete the startup sync in the event user backs out of resume sync

* Version bump 3.1.30

* Update patch

Move patch from cache to addon_data. No longer need to restart Kodi to apply the first patch.

* Fix inital sync leading to fast sync

* Fix user settings

Due to api change in 3.6.0.55

* krypton update

* Adjust for resume settings

With .55 the resume setting is set per library. Instead query server to see if the item is played to offer delete

* Restart service upon check for updates

To reload the new objects module.

* Fix update library

Only do the compare when user selects update library, also add a restart service option in the add-on settings > advanced

* Version bump 3.1.31

* Update dependencies

* Update FR translation

* Update DE translation

* Add translation

* Support up next

* Small service adjustment

* Krypton update to support upnext

* Add a verification onwake

Somehow, Kodi can trigger OnWake without first trigger OnSleep.

* Fix loading if special char in path

* Add logging and small fixes

Prepare userdata by date modified

* Version bump 3.1.32

* Change default behavior of startup dialog

In case it is forced closed by Kodi, allow the sync to proceed

* Ensure deliveryurl is an actual url

* Update README.md

* Fix nextup

* Fix dynamic widgets

* Detect coreelect, etc

* Fix progress report

Silent RefreshProgress in websocket

* Follow emby settings for subtitles

* Version bump 3.1.33

* Add Italian translation

* Fix playback for server 3.6.0.61

* Version bump 3.1.34a

* Add silent catch for errors

* Adjust playback progress monitor

Only track progress report if the item is an emby item

* Fix subtitles not following server settings

* Add remove libraries, fix mixed libraries

* Fix live tv

For now, use transcode since direct play returns a 127.0.0.1 unusable address as the path.

* Allow live tv to direct stream

* Fix LiveTV

* Add setting to sync during playback

* Fix updates

* Fix encoding error

* Add optional rotten tomatoes option

* Version bump 3.1.35

* Fix emby connect auth string

Was preventing proper device detection when using emby connect, play to, etc.

* Add setup RT

* Fix audio/sub change

Only for addon playback

* Add developer mode

* Update patch

Check for updates + dev = forced grab from github

* Fix RT string

* Fix patch

Allow dev mode to redownload zip

* Fix patch

ugh sleep!!

* Verify patch connection

* Version bump 3.1.36

* Fix libraries being wiped

Catch errors to prevent false positive

* Add dateutil library

* Prep convert to local time

* Fix string

* Prep for multi db version support

* Fix service restart

* Add shortcut restart addon

Add notification

* Add database discovery

* Ensure previous playback terminated

* Update translation

New: Polish, Dutch Updated: German, French, Italian

* Version bump 3.1.37

* Quick fix for new library dateutil

* Catch error for dateutil

In the event the server has some weird date that can't be converted

* Version bump 3.1.38

* Fix dateutil import

* Fix db discovery

Ignore emby.db

* Version bump 3.1.39

* Add a delay if setup not completed

Avoid crash from everything loading at once.

* Fix database discovery

Add table verification + date modified verification

* Container optional playutils

* Version bump 3.1.40

* Adjust database discovery

Compare loaded vs discovered to avoid loading old databases by accident.

* Version bump 3.1.41

* Fix discovery toggle

* Version bump 3.1.42

* Add webservice for playback prep

* Fix service restart

* Version bump 3.1.43

* Update default sync indicator

Based on overall feedback

* Fix check update

* Fix if server is selected but unavailable

* Support songs without albums

* Fix encode and params

* Increase retry timeout

* Fix update generating duplicates

* Add manage libraries

Too many entries

* Fix database discovery

* Fixed transcode via context menu

* Fix context transcode

* Quiet webservice

* Update Krypton objects

* Fix database discovery prompt

* fixed video listitem issues for krypton

* load all item details for playlists

* Fix playlist

* Version bump 3.1.44

* Fix force hi10p transcoding behavior

Fixes the "Force Hi10p transcoding" option to only apply to h264 video codecs

* Clear playlist on player.onstop

* Don't clear playlist if busy spinner is active

* Fix case sensitive issue at calling the log function

* fix db stuff (#164)

* Reload objects upon initial setup

* Fix database discovery

ignore db-journal

* Update translation

German, Italian

* Use LastConnectionMode for server test

* Fix compare sync

* Version bump 3.1.45

* Ensure widgets get updated

Container.Refresh alone doesn't seem to work

* Update database discovery

* Re-add texture to database discovery

* Add option to enable/disable service

* Remove unused strings

* Fix object reload upon restart service

* Update Krypton objects

* Update translation

Dutch, Polish

* Version bump 3.1.46

* Adjust client api

* Adjust subtitles behavior

* Fix string typo

* Only run one full sync instance

Prevent user from launching multiple syncs and freezing the add-on.

* added "playlists" to wnodes

* Disable Audiobooks

Server doesn't have a set structure yet. This feature is broken atm.

* Version bump 4.0.0

* License GPL v3

* Update readme
2019-01-24 07:04:48 -06:00

2603 lines
93 KiB
Python

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