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