test_tz.py 98 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from ._common import PicklableMixin
  4. from ._common import TZEnvContext, TZWinContext
  5. from ._common import ComparesEqual
  6. from datetime import datetime, timedelta
  7. from datetime import time as dt_time
  8. from datetime import tzinfo
  9. from six import PY2
  10. from io import BytesIO, StringIO
  11. import unittest
  12. import sys
  13. import base64
  14. import copy
  15. import gc
  16. import weakref
  17. from functools import partial
  18. IS_WIN = sys.platform.startswith('win')
  19. import pytest
  20. # dateutil imports
  21. from dateutil.relativedelta import relativedelta, SU, TH
  22. from dateutil.parser import parse
  23. from dateutil import tz as tz
  24. from dateutil import zoneinfo
  25. try:
  26. from dateutil import tzwin
  27. except ImportError as e:
  28. if IS_WIN:
  29. raise e
  30. else:
  31. pass
  32. MISSING_TARBALL = ("This test fails if you don't have the dateutil "
  33. "timezone file installed. Please read the README")
  34. TZFILE_EST5EDT = b"""
  35. VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAADrAAAABAAAABCeph5wn7rrYKCGAHCh
  36. ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e
  37. S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0
  38. YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg
  39. yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db
  40. wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW
  41. 8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b
  42. YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g
  43. BGD9cAVQ4GAGQN9wBzDCYAeNGXAJEKRgCa2U8ArwhmAL4IVwDNmi4A3AZ3AOuYTgD6mD8BCZZuAR
  44. iWXwEnlI4BNpR/AUWSrgFUkp8BY5DOAXKQvwGCIpYBkI7fAaAgtgGvIKcBvh7WAc0exwHcHPYB6x
  45. znAfobFgIHYA8CGBk2AiVeLwI2qv4CQ1xPAlSpHgJhWm8Ccqc+An/sNwKQpV4CnepXAq6jfgK76H
  46. cCzTVGAtnmlwLrM2YC9+S3AwkxhgMWdn8DJy+mAzR0nwNFLcYDUnK/A2Mr5gNwcN8Dgb2uA45u/w
  47. Ofu84DrG0fA7257gPK/ucD27gOA+j9BwP5ti4EBvsnBBhH9gQk+UcENkYWBEL3ZwRURDYEYPWHBH
  48. JCVgR/h08EkEB2BJ2FbwSuPpYEu4OPBMzQXgTZga8E6s5+BPd/zwUIzJ4FFhGXBSbKvgU0D7cFRM
  49. jeBVIN1wVixv4FcAv3BYFYxgWOChcFn1bmBawINwW9VQYFypn/BdtTJgXomB8F+VFGBgaWPwYX4w
  50. 4GJJRfBjXhLgZCkn8GU99OBmEkRwZx3W4GfyJnBo/bjgadIIcGrdmuBrsepwbMa3YG2RzHBupplg
  51. b3GucHCGe2BxWsrwcmZdYHM6rPB0Rj9gdRqO8HYvW+B2+nDweA894HjaUvB57x/gero08HvPAeB8
  52. o1Fwfa7j4H6DM3B/jsXgAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
  53. AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
  54. AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA
  55. AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
  56. AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU
  57. AEVQVAAAAAABAAAAAQ==
  58. """
  59. EUROPE_HELSINKI = b"""
  60. VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAFAAAABQAAAAAAAAB1AAAABQAAAA2kc28Yy85RYMy/hdAV
  61. I+uQFhPckBcDzZAX876QGOOvkBnToJAaw5GQG7y9EBysrhAdnJ8QHoyQEB98gRAgbHIQIVxjECJM
  62. VBAjPEUQJCw2ECUcJxAmDBgQJwVDkCf1NJAo5SWQKdUWkCrFB5ArtPiQLKTpkC2U2pAuhMuQL3S8
  63. kDBkrZAxXdkQMnK0EDM9uxA0UpYQNR2dEDYyeBA2/X8QOBuUkDjdYRA5+3aQOr1DEDvbWJA8pl+Q
  64. Pbs6kD6GQZA/mxyQQGYjkEGEORBCRgWQQ2QbEEQl55BFQ/0QRgXJkEcj3xBH7uYQSQPBEEnOyBBK
  65. 46MQS66qEEzMv5BNjowQTqyhkE9ubhBQjIOQUVeKkFJsZZBTN2yQVExHkFUXTpBWLCmQVvcwkFgV
  66. RhBY1xKQWfUoEFq29JBb1QoQXKAREF207BBef/MQX5TOEGBf1RBhfeqQYj+3EGNdzJBkH5kQZT2u
  67. kGYItZBnHZCQZ+iXkGj9cpBpyHmQat1UkGuoW5BsxnEQbYg9kG6mUxBvaB+QcIY1EHFRPBByZhcQ
  68. czEeEHRF+RB1EQAQdi8VkHbw4hB4DveQeNDEEHnu2ZB6sKYQe867kHyZwpB9rp2QfnmkkH+Of5AC
  69. AQIDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQD
  70. BAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAMEAwQDBAME
  71. AwQAABdoAAAAACowAQQAABwgAAkAACowAQQAABwgAAlITVQARUVTVABFRVQAAAAAAQEAAAABAQ==
  72. """
  73. NEW_YORK = b"""
  74. VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAABcAAADrAAAABAAAABCeph5wn7rrYKCGAHCh
  75. ms1gomXicKOD6eCkaq5wpTWnYKZTyvCnFYlgqDOs8Kj+peCqE47wqt6H4KvzcPCsvmngrdNS8K6e
  76. S+CvszTwsH4t4LGcUXCyZ0pgs3wzcLRHLGC1XBVwticOYLc793C4BvBguRvZcLnm0mC7BPXwu8a0
  77. YLzk1/C9r9DgvsS58L+PsuDApJvwwW+U4MKEffDDT3bgxGRf8MUvWODGTXxwxw864MgtXnDI+Fdg
  78. yg1AcMrYOWDLiPBw0iP0cNJg++DTdeTw1EDd4NVVxvDWIL/g1zWo8NgAoeDZFYrw2eCD4Nr+p3Db
  79. wGXg3N6JcN2pgmDevmtw34lkYOCeTXDhaUZg4n4vcONJKGDkXhFw5Vcu4OZHLfDnNxDg6CcP8OkW
  80. 8uDqBvHw6vbU4Ovm0/Ds1rbg7ca18O6/02Dvr9Jw8J+1YPGPtHDyf5dg82+WcPRfeWD1T3hw9j9b
  81. YPcvWnD4KHfg+Q88cPoIWeD6+Fjw++g74PzYOvD9yB3g/rgc8P+n/+AAl/7wAYfh4AJ34PADcP5g
  82. BGD9cAVQ4GEGQN9yBzDCYgeNGXMJEKRjCa2U9ArwhmQL4IV1DNmi5Q3AZ3YOuYTmD6mD9xCZZucR
  83. iWX4EnlI6BNpR/kUWSrpFUkp+RY5DOoXKQv6GCIpaxkI7fsaAgtsGvIKfBvh7Wwc0ex8HcHPbR6x
  84. zn0fobFtIHYA/SGBk20iVeL+I2qv7iQ1xP4lSpHuJhWm/ycqc+8n/sOAKQpV8CnepYAq6jfxK76H
  85. gSzTVHItnmmCLrM2cy9+S4MwkxhzMWdoBDJy+nQzR0oENFLcdTUnLAU2Mr51NwcOBjgb2vY45vAG
  86. Ofu89jrG0gY72572PK/uhj27gPY+j9CGP5ti9kBvsoZBhH92Qk+UhkNkYXZEL3aHRURDd0XzqQdH
  87. LV/3R9OLB0kNQfdJs20HSu0j90uciYdM1kB3TXxrh062IndPXE2HUJYEd1E8L4dSdeZ3UxwRh1RV
  88. yHdU+/OHVjWqd1blEAdYHsb3WMTyB1n+qPdapNQHW96K91yEtgddvmz3XmSYB1+eTvdgTbSHYYdr
  89. d2ItlodjZ013ZA14h2VHL3dl7VqHZycRd2fNPIdpBvN3aa0eh2rm1XdrljsHbM/x9212HQdur9P3
  90. b1X/B3CPtfdxNeEHcm+X93MVwwd0T3n3dP7fh3Y4lnd23sGHeBh4d3i+o4d5+Fp3ep6Fh3vYPHd8
  91. fmeHfbged35eSYd/mAB3AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
  92. AAEAAQABAgMBAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
  93. AAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA
  94. AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQAB
  95. AAEAAQABAAEAAQABAAEAAQABAAEAAf//x8ABAP//ubAABP//x8ABCP//x8ABDEVEVABFU1QARVdU
  96. AEVQVAAEslgAAAAAAQWk7AEAAAACB4YfggAAAAMJZ1MDAAAABAtIhoQAAAAFDSsLhQAAAAYPDD8G
  97. AAAABxDtcocAAAAIEs6mCAAAAAkVn8qJAAAACheA/goAAAALGWIxiwAAAAwdJeoMAAAADSHa5Q0A
  98. AAAOJZ6djgAAAA8nf9EPAAAAECpQ9ZAAAAARLDIpEQAAABIuE1ySAAAAEzDnJBMAAAAUM7hIlAAA
  99. ABU2jBAVAAAAFkO3G5YAAAAXAAAAAQAAAAE=
  100. """
  101. TZICAL_EST5EDT = """
  102. BEGIN:VTIMEZONE
  103. TZID:US-Eastern
  104. LAST-MODIFIED:19870101T000000Z
  105. TZURL:http://zones.stds_r_us.net/tz/US-Eastern
  106. BEGIN:STANDARD
  107. DTSTART:19671029T020000
  108. RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
  109. TZOFFSETFROM:-0400
  110. TZOFFSETTO:-0500
  111. TZNAME:EST
  112. END:STANDARD
  113. BEGIN:DAYLIGHT
  114. DTSTART:19870405T020000
  115. RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
  116. TZOFFSETFROM:-0500
  117. TZOFFSETTO:-0400
  118. TZNAME:EDT
  119. END:DAYLIGHT
  120. END:VTIMEZONE
  121. """
  122. TZICAL_PST8PDT = """
  123. BEGIN:VTIMEZONE
  124. TZID:US-Pacific
  125. LAST-MODIFIED:19870101T000000Z
  126. BEGIN:STANDARD
  127. DTSTART:19671029T020000
  128. RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
  129. TZOFFSETFROM:-0700
  130. TZOFFSETTO:-0800
  131. TZNAME:PST
  132. END:STANDARD
  133. BEGIN:DAYLIGHT
  134. DTSTART:19870405T020000
  135. RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4
  136. TZOFFSETFROM:-0800
  137. TZOFFSETTO:-0700
  138. TZNAME:PDT
  139. END:DAYLIGHT
  140. END:VTIMEZONE
  141. """
  142. EST_TUPLE = ('EST', timedelta(hours=-5), timedelta(hours=0))
  143. EDT_TUPLE = ('EDT', timedelta(hours=-4), timedelta(hours=1))
  144. SUPPORTS_SUB_MINUTE_OFFSETS = sys.version_info >= (3, 6)
  145. ###
  146. # Helper functions
  147. def get_timezone_tuple(dt):
  148. """Retrieve a (tzname, utcoffset, dst) tuple for a given DST"""
  149. return dt.tzname(), dt.utcoffset(), dt.dst()
  150. ###
  151. # Mix-ins
  152. class context_passthrough(object):
  153. def __init__(*args, **kwargs):
  154. pass
  155. def __enter__(*args, **kwargs):
  156. pass
  157. def __exit__(*args, **kwargs):
  158. pass
  159. class TzFoldMixin(object):
  160. """ Mix-in class for testing ambiguous times """
  161. def gettz(self, tzname):
  162. raise NotImplementedError
  163. def _get_tzname(self, tzname):
  164. return tzname
  165. def _gettz_context(self, tzname):
  166. return context_passthrough()
  167. def testFoldPositiveUTCOffset(self):
  168. # Test that we can resolve ambiguous times
  169. tzname = self._get_tzname('Australia/Sydney')
  170. with self._gettz_context(tzname):
  171. SYD = self.gettz(tzname)
  172. t0_u = datetime(2012, 3, 31, 15, 30, tzinfo=tz.UTC) # AEST
  173. t1_u = datetime(2012, 3, 31, 16, 30, tzinfo=tz.UTC) # AEDT
  174. t0_syd0 = t0_u.astimezone(SYD)
  175. t1_syd1 = t1_u.astimezone(SYD)
  176. self.assertEqual(t0_syd0.replace(tzinfo=None),
  177. datetime(2012, 4, 1, 2, 30))
  178. self.assertEqual(t1_syd1.replace(tzinfo=None),
  179. datetime(2012, 4, 1, 2, 30))
  180. self.assertEqual(t0_syd0.utcoffset(), timedelta(hours=11))
  181. self.assertEqual(t1_syd1.utcoffset(), timedelta(hours=10))
  182. def testGapPositiveUTCOffset(self):
  183. # Test that we don't have a problem around gaps.
  184. tzname = self._get_tzname('Australia/Sydney')
  185. with self._gettz_context(tzname):
  186. SYD = self.gettz(tzname)
  187. t0_u = datetime(2012, 10, 6, 15, 30, tzinfo=tz.UTC) # AEST
  188. t1_u = datetime(2012, 10, 6, 16, 30, tzinfo=tz.UTC) # AEDT
  189. t0 = t0_u.astimezone(SYD)
  190. t1 = t1_u.astimezone(SYD)
  191. self.assertEqual(t0.replace(tzinfo=None),
  192. datetime(2012, 10, 7, 1, 30))
  193. self.assertEqual(t1.replace(tzinfo=None),
  194. datetime(2012, 10, 7, 3, 30))
  195. self.assertEqual(t0.utcoffset(), timedelta(hours=10))
  196. self.assertEqual(t1.utcoffset(), timedelta(hours=11))
  197. def testFoldNegativeUTCOffset(self):
  198. # Test that we can resolve ambiguous times
  199. tzname = self._get_tzname('America/Toronto')
  200. with self._gettz_context(tzname):
  201. TOR = self.gettz(tzname)
  202. t0_u = datetime(2011, 11, 6, 5, 30, tzinfo=tz.UTC)
  203. t1_u = datetime(2011, 11, 6, 6, 30, tzinfo=tz.UTC)
  204. t0_tor = t0_u.astimezone(TOR)
  205. t1_tor = t1_u.astimezone(TOR)
  206. self.assertEqual(t0_tor.replace(tzinfo=None),
  207. datetime(2011, 11, 6, 1, 30))
  208. self.assertEqual(t1_tor.replace(tzinfo=None),
  209. datetime(2011, 11, 6, 1, 30))
  210. self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname())
  211. self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0))
  212. self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0))
  213. def testGapNegativeUTCOffset(self):
  214. # Test that we don't have a problem around gaps.
  215. tzname = self._get_tzname('America/Toronto')
  216. with self._gettz_context(tzname):
  217. TOR = self.gettz(tzname)
  218. t0_u = datetime(2011, 3, 13, 6, 30, tzinfo=tz.UTC)
  219. t1_u = datetime(2011, 3, 13, 7, 30, tzinfo=tz.UTC)
  220. t0 = t0_u.astimezone(TOR)
  221. t1 = t1_u.astimezone(TOR)
  222. self.assertEqual(t0.replace(tzinfo=None),
  223. datetime(2011, 3, 13, 1, 30))
  224. self.assertEqual(t1.replace(tzinfo=None),
  225. datetime(2011, 3, 13, 3, 30))
  226. self.assertNotEqual(t0, t1)
  227. self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0))
  228. self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0))
  229. def testFoldLondon(self):
  230. tzname = self._get_tzname('Europe/London')
  231. with self._gettz_context(tzname):
  232. LON = self.gettz(tzname)
  233. UTC = tz.UTC
  234. t0_u = datetime(2013, 10, 27, 0, 30, tzinfo=UTC) # BST
  235. t1_u = datetime(2013, 10, 27, 1, 30, tzinfo=UTC) # GMT
  236. t0 = t0_u.astimezone(LON)
  237. t1 = t1_u.astimezone(LON)
  238. self.assertEqual(t0.replace(tzinfo=None),
  239. datetime(2013, 10, 27, 1, 30))
  240. self.assertEqual(t1.replace(tzinfo=None),
  241. datetime(2013, 10, 27, 1, 30))
  242. self.assertEqual(t0.utcoffset(), timedelta(hours=1))
  243. self.assertEqual(t1.utcoffset(), timedelta(hours=0))
  244. def testFoldIndependence(self):
  245. tzname = self._get_tzname('America/New_York')
  246. with self._gettz_context(tzname):
  247. NYC = self.gettz(tzname)
  248. UTC = tz.UTC
  249. hour = timedelta(hours=1)
  250. # Firmly 2015-11-01 0:30 EDT-4
  251. pre_dst = datetime(2015, 11, 1, 0, 30, tzinfo=NYC)
  252. # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5
  253. in_dst = pre_dst + hour
  254. in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT
  255. # Doing the arithmetic in UTC creates a date that is unambiguously
  256. # 2015-11-01 1:30 EDT-5
  257. in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC)
  258. # Make sure the dates are actually ambiguous
  259. self.assertEqual(in_dst, in_dst_via_utc)
  260. # Make sure we got the right folding behavior
  261. self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0)
  262. # Now check to make sure in_dst's tzname hasn't changed
  263. self.assertEqual(in_dst_tzname_0, in_dst.tzname())
  264. def testInZoneFoldEquality(self):
  265. # Two datetimes in the same zone are considered to be equal if their
  266. # wall times are equal, even if they have different absolute times.
  267. tzname = self._get_tzname('America/New_York')
  268. with self._gettz_context(tzname):
  269. NYC = self.gettz(tzname)
  270. UTC = tz.UTC
  271. dt0 = datetime(2011, 11, 6, 1, 30, tzinfo=NYC)
  272. dt1 = tz.enfold(dt0, fold=1)
  273. # Make sure these actually represent different times
  274. self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC))
  275. # Test that they compare equal
  276. self.assertEqual(dt0, dt1)
  277. def _test_ambiguous_time(self, dt, tzid, ambiguous):
  278. # This is a test to check that the individual is_ambiguous values
  279. # on the _tzinfo subclasses work.
  280. tzname = self._get_tzname(tzid)
  281. with self._gettz_context(tzname):
  282. tzi = self.gettz(tzname)
  283. self.assertEqual(tz.datetime_ambiguous(dt, tz=tzi), ambiguous)
  284. def testAmbiguousNegativeUTCOffset(self):
  285. self._test_ambiguous_time(datetime(2015, 11, 1, 1, 30),
  286. 'America/New_York', True)
  287. def testAmbiguousPositiveUTCOffset(self):
  288. self._test_ambiguous_time(datetime(2012, 4, 1, 2, 30),
  289. 'Australia/Sydney', True)
  290. def testUnambiguousNegativeUTCOffset(self):
  291. self._test_ambiguous_time(datetime(2015, 11, 1, 2, 30),
  292. 'America/New_York', False)
  293. def testUnambiguousPositiveUTCOffset(self):
  294. self._test_ambiguous_time(datetime(2012, 4, 1, 3, 30),
  295. 'Australia/Sydney', False)
  296. def testUnambiguousGapNegativeUTCOffset(self):
  297. # Imaginary time
  298. self._test_ambiguous_time(datetime(2011, 3, 13, 2, 30),
  299. 'America/New_York', False)
  300. def testUnambiguousGapPositiveUTCOffset(self):
  301. # Imaginary time
  302. self._test_ambiguous_time(datetime(2012, 10, 7, 2, 30),
  303. 'Australia/Sydney', False)
  304. def _test_imaginary_time(self, dt, tzid, exists):
  305. tzname = self._get_tzname(tzid)
  306. with self._gettz_context(tzname):
  307. tzi = self.gettz(tzname)
  308. self.assertEqual(tz.datetime_exists(dt, tz=tzi), exists)
  309. def testImaginaryNegativeUTCOffset(self):
  310. self._test_imaginary_time(datetime(2011, 3, 13, 2, 30),
  311. 'America/New_York', False)
  312. def testNotImaginaryNegativeUTCOffset(self):
  313. self._test_imaginary_time(datetime(2011, 3, 13, 1, 30),
  314. 'America/New_York', True)
  315. def testImaginaryPositiveUTCOffset(self):
  316. self._test_imaginary_time(datetime(2012, 10, 7, 2, 30),
  317. 'Australia/Sydney', False)
  318. def testNotImaginaryPositiveUTCOffset(self):
  319. self._test_imaginary_time(datetime(2012, 10, 7, 1, 30),
  320. 'Australia/Sydney', True)
  321. def testNotImaginaryFoldNegativeUTCOffset(self):
  322. self._test_imaginary_time(datetime(2015, 11, 1, 1, 30),
  323. 'America/New_York', True)
  324. def testNotImaginaryFoldPositiveUTCOffset(self):
  325. self._test_imaginary_time(datetime(2012, 4, 1, 3, 30),
  326. 'Australia/Sydney', True)
  327. @unittest.skip("Known failure in Python 3.6.")
  328. def testEqualAmbiguousComparison(self):
  329. tzname = self._get_tzname('Australia/Sydney')
  330. with self._gettz_context(tzname):
  331. SYD0 = self.gettz(tzname)
  332. SYD1 = self.gettz(tzname)
  333. t0_u = datetime(2012, 3, 31, 14, 30, tzinfo=tz.UTC) # AEST
  334. t0_syd0 = t0_u.astimezone(SYD0)
  335. t0_syd1 = t0_u.astimezone(SYD1)
  336. # This is considered an "inter-zone comparison" because it's an
  337. # ambiguous datetime.
  338. self.assertEqual(t0_syd0, t0_syd1)
  339. class TzWinFoldMixin(object):
  340. def get_args(self, tzname):
  341. return (tzname, )
  342. class context(object):
  343. def __init__(*args, **kwargs):
  344. pass
  345. def __enter__(*args, **kwargs):
  346. pass
  347. def __exit__(*args, **kwargs):
  348. pass
  349. def get_utc_transitions(self, tzi, year, gap):
  350. dston, dstoff = tzi.transitions(year)
  351. if gap:
  352. t_n = dston - timedelta(minutes=30)
  353. t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.UTC)
  354. t1_u = t0_u + timedelta(hours=1)
  355. else:
  356. # Get 1 hour before the first ambiguous date
  357. t_n = dstoff - timedelta(minutes=30)
  358. t0_u = t_n.replace(tzinfo=tzi).astimezone(tz.UTC)
  359. t_n += timedelta(hours=1) # Naive ambiguous date
  360. t0_u = t0_u + timedelta(hours=1) # First ambiguous date
  361. t1_u = t0_u + timedelta(hours=1) # Second ambiguous date
  362. return t_n, t0_u, t1_u
  363. def testFoldPositiveUTCOffset(self):
  364. # Test that we can resolve ambiguous times
  365. tzname = 'AUS Eastern Standard Time'
  366. args = self.get_args(tzname)
  367. with self.context(tzname):
  368. # Calling fromutc() alters the tzfile object
  369. SYD = self.tzclass(*args)
  370. # Get the transition time in UTC from the object, because
  371. # Windows doesn't store historical info
  372. t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, False)
  373. # Using fresh tzfiles
  374. t0_syd = t0_u.astimezone(SYD)
  375. t1_syd = t1_u.astimezone(SYD)
  376. self.assertEqual(t0_syd.replace(tzinfo=None), t_n)
  377. self.assertEqual(t1_syd.replace(tzinfo=None), t_n)
  378. self.assertEqual(t0_syd.utcoffset(), timedelta(hours=11))
  379. self.assertEqual(t1_syd.utcoffset(), timedelta(hours=10))
  380. self.assertNotEqual(t0_syd.tzname(), t1_syd.tzname())
  381. def testGapPositiveUTCOffset(self):
  382. # Test that we don't have a problem around gaps.
  383. tzname = 'AUS Eastern Standard Time'
  384. args = self.get_args(tzname)
  385. with self.context(tzname):
  386. SYD = self.tzclass(*args)
  387. t_n, t0_u, t1_u = self.get_utc_transitions(SYD, 2012, True)
  388. t0 = t0_u.astimezone(SYD)
  389. t1 = t1_u.astimezone(SYD)
  390. self.assertEqual(t0.replace(tzinfo=None), t_n)
  391. self.assertEqual(t1.replace(tzinfo=None), t_n + timedelta(hours=2))
  392. self.assertEqual(t0.utcoffset(), timedelta(hours=10))
  393. self.assertEqual(t1.utcoffset(), timedelta(hours=11))
  394. def testFoldNegativeUTCOffset(self):
  395. # Test that we can resolve ambiguous times
  396. tzname = 'Eastern Standard Time'
  397. args = self.get_args(tzname)
  398. with self.context(tzname):
  399. TOR = self.tzclass(*args)
  400. t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, False)
  401. t0_tor = t0_u.astimezone(TOR)
  402. t1_tor = t1_u.astimezone(TOR)
  403. self.assertEqual(t0_tor.replace(tzinfo=None), t_n)
  404. self.assertEqual(t1_tor.replace(tzinfo=None), t_n)
  405. self.assertNotEqual(t0_tor.tzname(), t1_tor.tzname())
  406. self.assertEqual(t0_tor.utcoffset(), timedelta(hours=-4.0))
  407. self.assertEqual(t1_tor.utcoffset(), timedelta(hours=-5.0))
  408. def testGapNegativeUTCOffset(self):
  409. # Test that we don't have a problem around gaps.
  410. tzname = 'Eastern Standard Time'
  411. args = self.get_args(tzname)
  412. with self.context(tzname):
  413. TOR = self.tzclass(*args)
  414. t_n, t0_u, t1_u = self.get_utc_transitions(TOR, 2011, True)
  415. t0 = t0_u.astimezone(TOR)
  416. t1 = t1_u.astimezone(TOR)
  417. self.assertEqual(t0.replace(tzinfo=None),
  418. t_n)
  419. self.assertEqual(t1.replace(tzinfo=None),
  420. t_n + timedelta(hours=2))
  421. self.assertNotEqual(t0.tzname(), t1.tzname())
  422. self.assertEqual(t0.utcoffset(), timedelta(hours=-5.0))
  423. self.assertEqual(t1.utcoffset(), timedelta(hours=-4.0))
  424. def testFoldIndependence(self):
  425. tzname = 'Eastern Standard Time'
  426. args = self.get_args(tzname)
  427. with self.context(tzname):
  428. NYC = self.tzclass(*args)
  429. UTC = tz.UTC
  430. hour = timedelta(hours=1)
  431. # Firmly 2015-11-01 0:30 EDT-4
  432. t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2015, False)
  433. pre_dst = (t_n - hour).replace(tzinfo=NYC)
  434. # Currently, there's no way around the fact that this resolves to an
  435. # ambiguous date, which defaults to EST. I'm not hard-coding in the
  436. # answer, though, because the preferred behavior would be that this
  437. # results in a time on the EDT side.
  438. # Ambiguous between 2015-11-01 1:30 EDT-4 and 2015-11-01 1:30 EST-5
  439. in_dst = pre_dst + hour
  440. in_dst_tzname_0 = in_dst.tzname() # Stash the tzname - EDT
  441. # Doing the arithmetic in UTC creates a date that is unambiguously
  442. # 2015-11-01 1:30 EDT-5
  443. in_dst_via_utc = (pre_dst.astimezone(UTC) + 2*hour).astimezone(NYC)
  444. # Make sure we got the right folding behavior
  445. self.assertNotEqual(in_dst_via_utc.tzname(), in_dst_tzname_0)
  446. # Now check to make sure in_dst's tzname hasn't changed
  447. self.assertEqual(in_dst_tzname_0, in_dst.tzname())
  448. def testInZoneFoldEquality(self):
  449. # Two datetimes in the same zone are considered to be equal if their
  450. # wall times are equal, even if they have different absolute times.
  451. tzname = 'Eastern Standard Time'
  452. args = self.get_args(tzname)
  453. with self.context(tzname):
  454. NYC = self.tzclass(*args)
  455. UTC = tz.UTC
  456. t_n, t0_u, t1_u = self.get_utc_transitions(NYC, 2011, False)
  457. dt0 = t_n.replace(tzinfo=NYC)
  458. dt1 = tz.enfold(dt0, fold=1)
  459. # Make sure these actually represent different times
  460. self.assertNotEqual(dt0.astimezone(UTC), dt1.astimezone(UTC))
  461. # Test that they compare equal
  462. self.assertEqual(dt0, dt1)
  463. ###
  464. # Test Cases
  465. class TzUTCTest(unittest.TestCase):
  466. def testSingleton(self):
  467. UTC_0 = tz.tzutc()
  468. UTC_1 = tz.tzutc()
  469. self.assertIs(UTC_0, UTC_1)
  470. def testOffset(self):
  471. ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc())
  472. self.assertEqual(ct.utcoffset(), timedelta(seconds=0))
  473. def testDst(self):
  474. ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc())
  475. self.assertEqual(ct.dst(), timedelta(seconds=0))
  476. def testTzName(self):
  477. ct = datetime(2009, 4, 1, 12, 11, 13, tzinfo=tz.tzutc())
  478. self.assertEqual(ct.tzname(), 'UTC')
  479. def testEquality(self):
  480. UTC0 = tz.tzutc()
  481. UTC1 = tz.tzutc()
  482. self.assertEqual(UTC0, UTC1)
  483. def testInequality(self):
  484. UTC = tz.tzutc()
  485. UTCp4 = tz.tzoffset('UTC+4', 14400)
  486. self.assertNotEqual(UTC, UTCp4)
  487. def testInequalityInteger(self):
  488. self.assertFalse(tz.tzutc() == 7)
  489. self.assertNotEqual(tz.tzutc(), 7)
  490. def testInequalityUnsupported(self):
  491. self.assertEqual(tz.tzutc(), ComparesEqual)
  492. def testRepr(self):
  493. UTC = tz.tzutc()
  494. self.assertEqual(repr(UTC), 'tzutc()')
  495. def testTimeOnlyUTC(self):
  496. # https://github.com/dateutil/dateutil/issues/132
  497. # tzutc doesn't care
  498. tz_utc = tz.tzutc()
  499. self.assertEqual(dt_time(13, 20, tzinfo=tz_utc).utcoffset(),
  500. timedelta(0))
  501. def testAmbiguity(self):
  502. # Pick an arbitrary datetime, this should always return False.
  503. dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzutc())
  504. self.assertFalse(tz.datetime_ambiguous(dt))
  505. @pytest.mark.tzoffset
  506. class TzOffsetTest(unittest.TestCase):
  507. def testTimedeltaOffset(self):
  508. est = tz.tzoffset('EST', timedelta(hours=-5))
  509. est_s = tz.tzoffset('EST', -18000)
  510. self.assertEqual(est, est_s)
  511. def testTzNameNone(self):
  512. gmt5 = tz.tzoffset(None, -18000) # -5:00
  513. self.assertIs(datetime(2003, 10, 26, 0, 0, tzinfo=gmt5).tzname(),
  514. None)
  515. def testTimeOnlyOffset(self):
  516. # tzoffset doesn't care
  517. tz_offset = tz.tzoffset('+3', 3600)
  518. self.assertEqual(dt_time(13, 20, tzinfo=tz_offset).utcoffset(),
  519. timedelta(seconds=3600))
  520. def testTzOffsetRepr(self):
  521. tname = 'EST'
  522. tzo = tz.tzoffset(tname, -5 * 3600)
  523. self.assertEqual(repr(tzo), "tzoffset(" + repr(tname) + ", -18000)")
  524. def testEquality(self):
  525. utc = tz.tzoffset('UTC', 0)
  526. gmt = tz.tzoffset('GMT', 0)
  527. self.assertEqual(utc, gmt)
  528. def testUTCEquality(self):
  529. utc = tz.UTC
  530. o_utc = tz.tzoffset('UTC', 0)
  531. self.assertEqual(utc, o_utc)
  532. self.assertEqual(o_utc, utc)
  533. def testInequalityInvalid(self):
  534. tzo = tz.tzoffset('-3', -3 * 3600)
  535. self.assertFalse(tzo == -3)
  536. self.assertNotEqual(tzo, -3)
  537. def testInequalityUnsupported(self):
  538. tzo = tz.tzoffset('-5', -5 * 3600)
  539. self.assertTrue(tzo == ComparesEqual)
  540. self.assertFalse(tzo != ComparesEqual)
  541. self.assertEqual(tzo, ComparesEqual)
  542. def testAmbiguity(self):
  543. # Pick an arbitrary datetime, this should always return False.
  544. dt = datetime(2011, 9, 1, 2, 30, tzinfo=tz.tzoffset("EST", -5 * 3600))
  545. self.assertFalse(tz.datetime_ambiguous(dt))
  546. def testTzOffsetInstance(self):
  547. tz1 = tz.tzoffset.instance('EST', timedelta(hours=-5))
  548. tz2 = tz.tzoffset.instance('EST', timedelta(hours=-5))
  549. assert tz1 is not tz2
  550. def testTzOffsetSingletonDifferent(self):
  551. tz1 = tz.tzoffset('EST', timedelta(hours=-5))
  552. tz2 = tz.tzoffset('EST', -18000)
  553. assert tz1 is tz2
  554. @pytest.mark.smoke
  555. @pytest.mark.tzoffset
  556. def test_tzoffset_weakref():
  557. UTC1 = tz.tzoffset('UTC', 0)
  558. UTC_ref = weakref.ref(tz.tzoffset('UTC', 0))
  559. UTC1 is UTC_ref()
  560. del UTC1
  561. gc.collect()
  562. assert UTC_ref() is not None # Should be in the strong cache
  563. assert UTC_ref() is tz.tzoffset('UTC', 0)
  564. # Fill the strong cache with other items
  565. for offset in range(5,15):
  566. tz.tzoffset('RandomZone', offset)
  567. gc.collect()
  568. assert UTC_ref() is None
  569. assert UTC_ref() is not tz.tzoffset('UTC', 0)
  570. @pytest.mark.tzoffset
  571. @pytest.mark.parametrize('args', [
  572. ('UTC', 0),
  573. ('EST', -18000),
  574. ('EST', timedelta(hours=-5)),
  575. (None, timedelta(hours=3)),
  576. ])
  577. def test_tzoffset_singleton(args):
  578. tz1 = tz.tzoffset(*args)
  579. tz2 = tz.tzoffset(*args)
  580. assert tz1 is tz2
  581. @pytest.mark.tzoffset
  582. @pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS,
  583. reason='Sub-minute offsets not supported')
  584. def test_tzoffset_sub_minute():
  585. delta = timedelta(hours=12, seconds=30)
  586. test_datetime = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta))
  587. assert test_datetime.utcoffset() == delta
  588. @pytest.mark.tzoffset
  589. @pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS,
  590. reason='Sub-minute offsets supported')
  591. def test_tzoffset_sub_minute_rounding():
  592. delta = timedelta(hours=12, seconds=30)
  593. test_date = datetime(2000, 1, 1, tzinfo=tz.tzoffset(None, delta))
  594. assert test_date.utcoffset() == timedelta(hours=12, minutes=1)
  595. @pytest.mark.tzlocal
  596. class TzLocalTest(unittest.TestCase):
  597. def testEquality(self):
  598. tz1 = tz.tzlocal()
  599. tz2 = tz.tzlocal()
  600. # Explicitly calling == and != here to ensure the operators work
  601. self.assertTrue(tz1 == tz2)
  602. self.assertFalse(tz1 != tz2)
  603. def testInequalityFixedOffset(self):
  604. tzl = tz.tzlocal()
  605. tzos = tz.tzoffset('LST', tzl._std_offset.total_seconds())
  606. tzod = tz.tzoffset('LDT', tzl._std_offset.total_seconds())
  607. self.assertFalse(tzl == tzos)
  608. self.assertFalse(tzl == tzod)
  609. self.assertTrue(tzl != tzos)
  610. self.assertTrue(tzl != tzod)
  611. def testInequalityInvalid(self):
  612. tzl = tz.tzlocal()
  613. self.assertTrue(tzl != 1)
  614. self.assertFalse(tzl == 1)
  615. # TODO: Use some sort of universal local mocking so that it's clear
  616. # that we're expecting tzlocal to *not* be Pacific/Kiritimati
  617. LINT = tz.gettz('Pacific/Kiritimati')
  618. self.assertTrue(tzl != LINT)
  619. self.assertFalse(tzl == LINT)
  620. def testInequalityUnsupported(self):
  621. tzl = tz.tzlocal()
  622. self.assertTrue(tzl == ComparesEqual)
  623. self.assertFalse(tzl != ComparesEqual)
  624. def testRepr(self):
  625. tzl = tz.tzlocal()
  626. self.assertEqual(repr(tzl), 'tzlocal()')
  627. @pytest.mark.parametrize('args,kwargs', [
  628. (('EST', -18000), {}),
  629. (('EST', timedelta(hours=-5)), {}),
  630. (('EST',), {'offset': -18000}),
  631. (('EST',), {'offset': timedelta(hours=-5)}),
  632. (tuple(), {'name': 'EST', 'offset': -18000})
  633. ])
  634. def test_tzoffset_is(args, kwargs):
  635. tz_ref = tz.tzoffset('EST', -18000)
  636. assert tz.tzoffset(*args, **kwargs) is tz_ref
  637. def test_tzoffset_is_not():
  638. assert tz.tzoffset('EDT', -14400) is not tz.tzoffset('EST', -18000)
  639. @pytest.mark.tzlocal
  640. @unittest.skipIf(IS_WIN, "requires Unix")
  641. class TzLocalNixTest(unittest.TestCase, TzFoldMixin):
  642. # This is a set of tests for `tzlocal()` on *nix systems
  643. # POSIX string indicating change to summer time on the 2nd Sunday in March
  644. # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007)
  645. TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2'
  646. # POSIX string for AEST/AEDT (valid >= 2008)
  647. TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3'
  648. # POSIX string for BST/GMT
  649. TZ_LON = 'GMT0BST,M3.5.0,M10.5.0'
  650. # POSIX string for UTC
  651. UTC = 'UTC'
  652. def gettz(self, tzname):
  653. # Actual time zone changes are handled by the _gettz_context function
  654. return tz.tzlocal()
  655. def _gettz_context(self, tzname):
  656. tzname_map = {'Australia/Sydney': self.TZ_AEST,
  657. 'America/Toronto': self.TZ_EST,
  658. 'America/New_York': self.TZ_EST,
  659. 'Europe/London': self.TZ_LON}
  660. return TZEnvContext(tzname_map.get(tzname, tzname))
  661. def _testTzFunc(self, tzval, func, std_val, dst_val):
  662. """
  663. This generates tests about how the behavior of a function ``func``
  664. changes between STD and DST (e.g. utcoffset, tzname, dst).
  665. It assume that DST starts the 2nd Sunday in March and ends the 1st
  666. Sunday in November
  667. """
  668. with TZEnvContext(tzval):
  669. dt1 = datetime(2015, 2, 1, 12, 0, tzinfo=tz.tzlocal()) # STD
  670. dt2 = datetime(2015, 5, 1, 12, 0, tzinfo=tz.tzlocal()) # DST
  671. self.assertEqual(func(dt1), std_val)
  672. self.assertEqual(func(dt2), dst_val)
  673. def _testTzName(self, tzval, std_name, dst_name):
  674. func = datetime.tzname
  675. self._testTzFunc(tzval, func, std_name, dst_name)
  676. def testTzNameDST(self):
  677. # Test tzname in a zone with DST
  678. self._testTzName(self.TZ_EST, 'EST', 'EDT')
  679. def testTzNameUTC(self):
  680. # Test tzname in a zone without DST
  681. self._testTzName(self.UTC, 'UTC', 'UTC')
  682. def _testOffset(self, tzval, std_off, dst_off):
  683. func = datetime.utcoffset
  684. self._testTzFunc(tzval, func, std_off, dst_off)
  685. def testOffsetDST(self):
  686. self._testOffset(self.TZ_EST, timedelta(hours=-5), timedelta(hours=-4))
  687. def testOffsetUTC(self):
  688. self._testOffset(self.UTC, timedelta(0), timedelta(0))
  689. def _testDST(self, tzval, dst_dst):
  690. func = datetime.dst
  691. std_dst = timedelta(0)
  692. self._testTzFunc(tzval, func, std_dst, dst_dst)
  693. def testDSTDST(self):
  694. self._testDST(self.TZ_EST, timedelta(hours=1))
  695. def testDSTUTC(self):
  696. self._testDST(self.UTC, timedelta(0))
  697. def testTimeOnlyOffsetLocalUTC(self):
  698. with TZEnvContext(self.UTC):
  699. self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(),
  700. timedelta(0))
  701. def testTimeOnlyOffsetLocalDST(self):
  702. with TZEnvContext(self.TZ_EST):
  703. self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).utcoffset(),
  704. None)
  705. def testTimeOnlyDSTLocalUTC(self):
  706. with TZEnvContext(self.UTC):
  707. self.assertEqual(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(),
  708. timedelta(0))
  709. def testTimeOnlyDSTLocalDST(self):
  710. with TZEnvContext(self.TZ_EST):
  711. self.assertIs(dt_time(13, 20, tzinfo=tz.tzlocal()).dst(),
  712. None)
  713. def testUTCEquality(self):
  714. with TZEnvContext(self.UTC):
  715. assert tz.tzlocal() == tz.UTC
  716. # TODO: Maybe a better hack than this?
  717. def mark_tzlocal_nix(f):
  718. marks = [
  719. pytest.mark.tzlocal,
  720. pytest.mark.skipif(IS_WIN, reason='requires Unix'),
  721. ]
  722. for mark in reversed(marks):
  723. f = mark(f)
  724. return f
  725. @mark_tzlocal_nix
  726. @pytest.mark.parametrize('tzvar', ['UTC', 'GMT0', 'UTC0'])
  727. def test_tzlocal_utc_equal(tzvar):
  728. with TZEnvContext(tzvar):
  729. assert tz.tzlocal() == tz.UTC
  730. @mark_tzlocal_nix
  731. @pytest.mark.parametrize('tzvar', [
  732. 'Europe/London', 'America/New_York',
  733. 'GMT0BST', 'EST5EDT'])
  734. def test_tzlocal_utc_unequal(tzvar):
  735. with TZEnvContext(tzvar):
  736. assert tz.tzlocal() != tz.UTC
  737. @mark_tzlocal_nix
  738. def test_tzlocal_local_time_trim_colon():
  739. with TZEnvContext(':/etc/localtime'):
  740. assert tz.gettz() is not None
  741. @mark_tzlocal_nix
  742. @pytest.mark.parametrize('tzvar, tzoff', [
  743. ('EST5', tz.tzoffset('EST', -18000)),
  744. ('GMT0', tz.tzoffset('GMT', 0)),
  745. ('YAKT-9', tz.tzoffset('YAKT', timedelta(hours=9))),
  746. ('JST-9', tz.tzoffset('JST', timedelta(hours=9))),
  747. ])
  748. def test_tzlocal_offset_equal(tzvar, tzoff):
  749. with TZEnvContext(tzvar):
  750. # Including both to test both __eq__ and __ne__
  751. assert tz.tzlocal() == tzoff
  752. assert not (tz.tzlocal() != tzoff)
  753. @mark_tzlocal_nix
  754. @pytest.mark.parametrize('tzvar, tzoff', [
  755. ('EST5EDT', tz.tzoffset('EST', -18000)),
  756. ('GMT0BST', tz.tzoffset('GMT', 0)),
  757. ('EST5', tz.tzoffset('EST', -14400)),
  758. ('YAKT-9', tz.tzoffset('JST', timedelta(hours=9))),
  759. ('JST-9', tz.tzoffset('YAKT', timedelta(hours=9))),
  760. ])
  761. def test_tzlocal_offset_unequal(tzvar, tzoff):
  762. with TZEnvContext(tzvar):
  763. # Including both to test both __eq__ and __ne__
  764. assert tz.tzlocal() != tzoff
  765. assert not (tz.tzlocal() == tzoff)
  766. @pytest.mark.gettz
  767. class GettzTest(unittest.TestCase, TzFoldMixin):
  768. gettz = staticmethod(tz.gettz)
  769. def testGettz(self):
  770. # bug 892569
  771. str(self.gettz('UTC'))
  772. def testGetTzEquality(self):
  773. self.assertEqual(self.gettz('UTC'), self.gettz('UTC'))
  774. def testTimeOnlyGettz(self):
  775. # gettz returns None
  776. tz_get = self.gettz('Europe/Minsk')
  777. self.assertIs(dt_time(13, 20, tzinfo=tz_get).utcoffset(), None)
  778. def testTimeOnlyGettzDST(self):
  779. # gettz returns None
  780. tz_get = self.gettz('Europe/Minsk')
  781. self.assertIs(dt_time(13, 20, tzinfo=tz_get).dst(), None)
  782. def testTimeOnlyGettzTzName(self):
  783. tz_get = self.gettz('Europe/Minsk')
  784. self.assertIs(dt_time(13, 20, tzinfo=tz_get).tzname(), None)
  785. def testTimeOnlyFormatZ(self):
  786. tz_get = self.gettz('Europe/Minsk')
  787. t = dt_time(13, 20, tzinfo=tz_get)
  788. self.assertEqual(t.strftime('%H%M%Z'), '1320')
  789. def testPortugalDST(self):
  790. # In 1996, Portugal changed from CET to WET
  791. PORTUGAL = self.gettz('Portugal')
  792. t_cet = datetime(1996, 3, 31, 1, 59, tzinfo=PORTUGAL)
  793. self.assertEqual(t_cet.tzname(), 'CET')
  794. self.assertEqual(t_cet.utcoffset(), timedelta(hours=1))
  795. self.assertEqual(t_cet.dst(), timedelta(0))
  796. t_west = datetime(1996, 3, 31, 2, 1, tzinfo=PORTUGAL)
  797. self.assertEqual(t_west.tzname(), 'WEST')
  798. self.assertEqual(t_west.utcoffset(), timedelta(hours=1))
  799. self.assertEqual(t_west.dst(), timedelta(hours=1))
  800. def testGettzCacheTzFile(self):
  801. NYC1 = tz.gettz('America/New_York')
  802. NYC2 = tz.gettz('America/New_York')
  803. assert NYC1 is NYC2
  804. def testGettzCacheTzLocal(self):
  805. local1 = tz.gettz()
  806. local2 = tz.gettz()
  807. assert local1 is not local2
  808. @pytest.mark.gettz
  809. def test_gettz_same_result_for_none_and_empty_string():
  810. local_from_none = tz.gettz()
  811. local_from_empty_string = tz.gettz("")
  812. assert local_from_none is not None
  813. assert local_from_empty_string is not None
  814. assert local_from_none == local_from_empty_string
  815. @pytest.mark.gettz
  816. @pytest.mark.parametrize('badzone', [
  817. 'Fake.Region/Abcdefghijklmnop', # Violates several tz project name rules
  818. ])
  819. def test_gettz_badzone(badzone):
  820. # Make sure passing a bad TZ string to gettz returns None (GH #800)
  821. tzi = tz.gettz(badzone)
  822. assert tzi is None
  823. @pytest.mark.gettz
  824. def test_gettz_badzone_unicode():
  825. # Make sure a unicode string can be passed to TZ (GH #802)
  826. # When fixed, combine this with test_gettz_badzone
  827. tzi = tz.gettz('🐼')
  828. assert tzi is None
  829. @pytest.mark.gettz
  830. @pytest.mark.parametrize(
  831. "badzone,exc_reason",
  832. [
  833. pytest.param(
  834. b"America/New_York",
  835. ".*should be str, not bytes.*",
  836. id="bytes on Python 3",
  837. marks=[
  838. pytest.mark.skipif(
  839. PY2, reason="bytes arguments accepted in Python 2"
  840. )
  841. ],
  842. ),
  843. pytest.param(
  844. object(),
  845. None,
  846. id="no startswith()",
  847. marks=[
  848. pytest.mark.xfail(reason="AttributeError instead of TypeError",
  849. raises=AttributeError),
  850. ],
  851. ),
  852. ],
  853. )
  854. def test_gettz_zone_wrong_type(badzone, exc_reason):
  855. with pytest.raises(TypeError, match=exc_reason):
  856. tz.gettz(badzone)
  857. @pytest.mark.gettz
  858. @pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached')
  859. def test_gettz_cache_clear():
  860. NYC1 = tz.gettz('America/New_York')
  861. tz.gettz.cache_clear()
  862. NYC2 = tz.gettz('America/New_York')
  863. assert NYC1 is not NYC2
  864. @pytest.mark.gettz
  865. @pytest.mark.xfail(IS_WIN, reason='zoneinfo separately cached')
  866. def test_gettz_set_cache_size():
  867. tz.gettz.cache_clear()
  868. tz.gettz.set_cache_size(3)
  869. MONACO_ref = weakref.ref(tz.gettz('Europe/Monaco'))
  870. EASTER_ref = weakref.ref(tz.gettz('Pacific/Easter'))
  871. CURRIE_ref = weakref.ref(tz.gettz('Australia/Currie'))
  872. gc.collect()
  873. assert MONACO_ref() is not None
  874. assert EASTER_ref() is not None
  875. assert CURRIE_ref() is not None
  876. tz.gettz.set_cache_size(2)
  877. gc.collect()
  878. assert MONACO_ref() is None
  879. @pytest.mark.xfail(IS_WIN, reason="Windows does not use system zoneinfo")
  880. @pytest.mark.smoke
  881. @pytest.mark.gettz
  882. def test_gettz_weakref():
  883. tz.gettz.cache_clear()
  884. tz.gettz.set_cache_size(2)
  885. NYC1 = tz.gettz('America/New_York')
  886. NYC_ref = weakref.ref(tz.gettz('America/New_York'))
  887. assert NYC1 is NYC_ref()
  888. del NYC1
  889. gc.collect()
  890. assert NYC_ref() is not None # Should still be in the strong cache
  891. assert tz.gettz('America/New_York') is NYC_ref()
  892. # Populate strong cache with other timezones
  893. tz.gettz('Europe/Monaco')
  894. tz.gettz('Pacific/Easter')
  895. tz.gettz('Australia/Currie')
  896. gc.collect()
  897. assert NYC_ref() is None # Should have been pushed out
  898. assert tz.gettz('America/New_York') is not NYC_ref()
  899. class ZoneInfoGettzTest(GettzTest):
  900. def gettz(self, name):
  901. zoneinfo_file = zoneinfo.get_zonefile_instance()
  902. return zoneinfo_file.get(name)
  903. def testZoneInfoFileStart1(self):
  904. tz = self.gettz("EST5EDT")
  905. self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tz).tzname(), "EST",
  906. MISSING_TARBALL)
  907. self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tz).tzname(), "EDT")
  908. def testZoneInfoFileEnd1(self):
  909. tzc = self.gettz("EST5EDT")
  910. self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(),
  911. "EDT", MISSING_TARBALL)
  912. end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc), fold=1)
  913. self.assertEqual(end_est.tzname(), "EST")
  914. def testZoneInfoOffsetSignal(self):
  915. utc = self.gettz("UTC")
  916. nyc = self.gettz("America/New_York")
  917. self.assertNotEqual(utc, None, MISSING_TARBALL)
  918. self.assertNotEqual(nyc, None)
  919. t0 = datetime(2007, 11, 4, 0, 30, tzinfo=nyc)
  920. t1 = t0.astimezone(utc)
  921. t2 = t1.astimezone(nyc)
  922. self.assertEqual(t0, t2)
  923. self.assertEqual(nyc.dst(t0), timedelta(hours=1))
  924. def testZoneInfoCopy(self):
  925. # copy.copy() called on a ZoneInfo file was returning the same instance
  926. CHI = self.gettz('America/Chicago')
  927. CHI_COPY = copy.copy(CHI)
  928. self.assertIsNot(CHI, CHI_COPY)
  929. self.assertEqual(CHI, CHI_COPY)
  930. def testZoneInfoDeepCopy(self):
  931. CHI = self.gettz('America/Chicago')
  932. CHI_COPY = copy.deepcopy(CHI)
  933. self.assertIsNot(CHI, CHI_COPY)
  934. self.assertEqual(CHI, CHI_COPY)
  935. def testZoneInfoInstanceCaching(self):
  936. zif_0 = zoneinfo.get_zonefile_instance()
  937. zif_1 = zoneinfo.get_zonefile_instance()
  938. self.assertIs(zif_0, zif_1)
  939. def testZoneInfoNewInstance(self):
  940. zif_0 = zoneinfo.get_zonefile_instance()
  941. zif_1 = zoneinfo.get_zonefile_instance(new_instance=True)
  942. zif_2 = zoneinfo.get_zonefile_instance()
  943. self.assertIsNot(zif_0, zif_1)
  944. self.assertIs(zif_1, zif_2)
  945. def testZoneInfoDeprecated(self):
  946. with pytest.warns(DeprecationWarning):
  947. zoneinfo.gettz('US/Eastern')
  948. def testZoneInfoMetadataDeprecated(self):
  949. with pytest.warns(DeprecationWarning):
  950. zoneinfo.gettz_db_metadata()
  951. class TZRangeTest(unittest.TestCase, TzFoldMixin):
  952. TZ_EST = tz.tzrange('EST', timedelta(hours=-5),
  953. 'EDT', timedelta(hours=-4),
  954. start=relativedelta(month=3, day=1, hour=2,
  955. weekday=SU(+2)),
  956. end=relativedelta(month=11, day=1, hour=1,
  957. weekday=SU(+1)))
  958. TZ_AEST = tz.tzrange('AEST', timedelta(hours=10),
  959. 'AEDT', timedelta(hours=11),
  960. start=relativedelta(month=10, day=1, hour=2,
  961. weekday=SU(+1)),
  962. end=relativedelta(month=4, day=1, hour=2,
  963. weekday=SU(+1)))
  964. TZ_LON = tz.tzrange('GMT', timedelta(hours=0),
  965. 'BST', timedelta(hours=1),
  966. start=relativedelta(month=3, day=31, weekday=SU(-1),
  967. hours=2),
  968. end=relativedelta(month=10, day=31, weekday=SU(-1),
  969. hours=1))
  970. # POSIX string for UTC
  971. UTC = 'UTC'
  972. def gettz(self, tzname):
  973. tzname_map = {'Australia/Sydney': self.TZ_AEST,
  974. 'America/Toronto': self.TZ_EST,
  975. 'America/New_York': self.TZ_EST,
  976. 'Europe/London': self.TZ_LON}
  977. return tzname_map[tzname]
  978. def testRangeCmp1(self):
  979. self.assertEqual(tz.tzstr("EST5EDT"),
  980. tz.tzrange("EST", -18000, "EDT", -14400,
  981. relativedelta(hours=+2,
  982. month=4, day=1,
  983. weekday=SU(+1)),
  984. relativedelta(hours=+1,
  985. month=10, day=31,
  986. weekday=SU(-1))))
  987. def testRangeCmp2(self):
  988. self.assertEqual(tz.tzstr("EST5EDT"),
  989. tz.tzrange("EST", -18000, "EDT"))
  990. def testRangeOffsets(self):
  991. TZR = tz.tzrange('EST', -18000, 'EDT', -14400,
  992. start=relativedelta(hours=2, month=4, day=1,
  993. weekday=SU(+2)),
  994. end=relativedelta(hours=1, month=10, day=31,
  995. weekday=SU(-1)))
  996. dt_std = datetime(2014, 4, 11, 12, 0, tzinfo=TZR) # STD
  997. dt_dst = datetime(2016, 4, 11, 12, 0, tzinfo=TZR) # DST
  998. dst_zero = timedelta(0)
  999. dst_hour = timedelta(hours=1)
  1000. std_offset = timedelta(hours=-5)
  1001. dst_offset = timedelta(hours=-4)
  1002. # Check dst()
  1003. self.assertEqual(dt_std.dst(), dst_zero)
  1004. self.assertEqual(dt_dst.dst(), dst_hour)
  1005. # Check utcoffset()
  1006. self.assertEqual(dt_std.utcoffset(), std_offset)
  1007. self.assertEqual(dt_dst.utcoffset(), dst_offset)
  1008. # Check tzname
  1009. self.assertEqual(dt_std.tzname(), 'EST')
  1010. self.assertEqual(dt_dst.tzname(), 'EDT')
  1011. def testTimeOnlyRangeFixed(self):
  1012. # This is a fixed-offset zone, so tzrange allows this
  1013. tz_range = tz.tzrange('dflt', stdoffset=timedelta(hours=-3))
  1014. self.assertEqual(dt_time(13, 20, tzinfo=tz_range).utcoffset(),
  1015. timedelta(hours=-3))
  1016. def testTimeOnlyRange(self):
  1017. # tzrange returns None because this zone has DST
  1018. tz_range = tz.tzrange('EST', timedelta(hours=-5),
  1019. 'EDT', timedelta(hours=-4))
  1020. self.assertIs(dt_time(13, 20, tzinfo=tz_range).utcoffset(), None)
  1021. def testBrokenIsDstHandling(self):
  1022. # tzrange._isdst() was using a date() rather than a datetime().
  1023. # Issue reported by Lennart Regebro.
  1024. dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.UTC)
  1025. self.assertEqual(dt.astimezone(tz=tz.gettz("GMT+2")),
  1026. datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2")))
  1027. def testRangeTimeDelta(self):
  1028. # Test that tzrange can be specified with a timedelta instead of an int.
  1029. EST5EDT_td = tz.tzrange('EST', timedelta(hours=-5),
  1030. 'EDT', timedelta(hours=-4))
  1031. EST5EDT_sec = tz.tzrange('EST', -18000,
  1032. 'EDT', -14400)
  1033. self.assertEqual(EST5EDT_td, EST5EDT_sec)
  1034. def testRangeEquality(self):
  1035. TZR1 = tz.tzrange('EST', -18000, 'EDT', -14400)
  1036. # Standard abbreviation different
  1037. TZR2 = tz.tzrange('ET', -18000, 'EDT', -14400)
  1038. self.assertNotEqual(TZR1, TZR2)
  1039. # DST abbreviation different
  1040. TZR3 = tz.tzrange('EST', -18000, 'EMT', -14400)
  1041. self.assertNotEqual(TZR1, TZR3)
  1042. # STD offset different
  1043. TZR4 = tz.tzrange('EST', -14000, 'EDT', -14400)
  1044. self.assertNotEqual(TZR1, TZR4)
  1045. # DST offset different
  1046. TZR5 = tz.tzrange('EST', -18000, 'EDT', -18000)
  1047. self.assertNotEqual(TZR1, TZR5)
  1048. # Start delta different
  1049. TZR6 = tz.tzrange('EST', -18000, 'EDT', -14400,
  1050. start=relativedelta(hours=+1, month=3,
  1051. day=1, weekday=SU(+2)))
  1052. self.assertNotEqual(TZR1, TZR6)
  1053. # End delta different
  1054. TZR7 = tz.tzrange('EST', -18000, 'EDT', -14400,
  1055. end=relativedelta(hours=+1, month=11,
  1056. day=1, weekday=SU(+2)))
  1057. self.assertNotEqual(TZR1, TZR7)
  1058. def testRangeInequalityUnsupported(self):
  1059. TZR = tz.tzrange('EST', -18000, 'EDT', -14400)
  1060. self.assertFalse(TZR == 4)
  1061. self.assertTrue(TZR == ComparesEqual)
  1062. self.assertFalse(TZR != ComparesEqual)
  1063. @pytest.mark.tzstr
  1064. class TZStrTest(unittest.TestCase, TzFoldMixin):
  1065. # POSIX string indicating change to summer time on the 2nd Sunday in March
  1066. # at 2AM, and ending the 1st Sunday in November at 2AM. (valid >= 2007)
  1067. TZ_EST = 'EST+5EDT,M3.2.0/2,M11.1.0/2'
  1068. # POSIX string for AEST/AEDT (valid >= 2008)
  1069. TZ_AEST = 'AEST-10AEDT,M10.1.0/2,M4.1.0/3'
  1070. # POSIX string for GMT/BST
  1071. TZ_LON = 'GMT0BST,M3.5.0,M10.5.0'
  1072. def gettz(self, tzname):
  1073. # Actual time zone changes are handled by the _gettz_context function
  1074. tzname_map = {'Australia/Sydney': self.TZ_AEST,
  1075. 'America/Toronto': self.TZ_EST,
  1076. 'America/New_York': self.TZ_EST,
  1077. 'Europe/London': self.TZ_LON}
  1078. return tz.tzstr(tzname_map[tzname])
  1079. def testStrStr(self):
  1080. # Test that tz.tzstr() won't throw an error if given a str instead
  1081. # of a unicode literal.
  1082. self.assertEqual(datetime(2003, 4, 6, 1, 59,
  1083. tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EST")
  1084. self.assertEqual(datetime(2003, 4, 6, 2, 00,
  1085. tzinfo=tz.tzstr(str("EST5EDT"))).tzname(), "EDT")
  1086. def testStrInequality(self):
  1087. TZS1 = tz.tzstr('EST5EDT4')
  1088. # Standard abbreviation different
  1089. TZS2 = tz.tzstr('ET5EDT4')
  1090. self.assertNotEqual(TZS1, TZS2)
  1091. # DST abbreviation different
  1092. TZS3 = tz.tzstr('EST5EMT')
  1093. self.assertNotEqual(TZS1, TZS3)
  1094. # STD offset different
  1095. TZS4 = tz.tzstr('EST4EDT4')
  1096. self.assertNotEqual(TZS1, TZS4)
  1097. # DST offset different
  1098. TZS5 = tz.tzstr('EST5EDT3')
  1099. self.assertNotEqual(TZS1, TZS5)
  1100. def testStrInequalityStartEnd(self):
  1101. TZS1 = tz.tzstr('EST5EDT4')
  1102. # Start delta different
  1103. TZS2 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M10-5-0/02:00')
  1104. self.assertNotEqual(TZS1, TZS2)
  1105. # End delta different
  1106. TZS3 = tz.tzstr('EST5EDT4,M4.2.0/02:00:00,M11-5-0/02:00')
  1107. self.assertNotEqual(TZS1, TZS3)
  1108. def testPosixOffset(self):
  1109. TZ1 = tz.tzstr('UTC-3')
  1110. self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ1).utcoffset(),
  1111. timedelta(hours=-3))
  1112. TZ2 = tz.tzstr('UTC-3', posix_offset=True)
  1113. self.assertEqual(datetime(2015, 1, 1, tzinfo=TZ2).utcoffset(),
  1114. timedelta(hours=+3))
  1115. def testStrInequalityUnsupported(self):
  1116. TZS = tz.tzstr('EST5EDT')
  1117. self.assertFalse(TZS == 4)
  1118. self.assertTrue(TZS == ComparesEqual)
  1119. self.assertFalse(TZS != ComparesEqual)
  1120. def testTzStrRepr(self):
  1121. TZS1 = tz.tzstr('EST5EDT4')
  1122. TZS2 = tz.tzstr('EST')
  1123. self.assertEqual(repr(TZS1), "tzstr(" + repr('EST5EDT4') + ")")
  1124. self.assertEqual(repr(TZS2), "tzstr(" + repr('EST') + ")")
  1125. def testTzStrFailure(self):
  1126. with self.assertRaises(ValueError):
  1127. tz.tzstr('InvalidString;439999')
  1128. def testTzStrSingleton(self):
  1129. tz1 = tz.tzstr('EST5EDT')
  1130. tz2 = tz.tzstr('CST4CST')
  1131. tz3 = tz.tzstr('EST5EDT')
  1132. self.assertIsNot(tz1, tz2)
  1133. self.assertIs(tz1, tz3)
  1134. def testTzStrSingletonPosix(self):
  1135. tz_t1 = tz.tzstr('GMT+3', posix_offset=True)
  1136. tz_f1 = tz.tzstr('GMT+3', posix_offset=False)
  1137. tz_t2 = tz.tzstr('GMT+3', posix_offset=True)
  1138. tz_f2 = tz.tzstr('GMT+3', posix_offset=False)
  1139. self.assertIs(tz_t1, tz_t2)
  1140. self.assertIsNot(tz_t1, tz_f1)
  1141. self.assertIs(tz_f1, tz_f2)
  1142. def testTzStrInstance(self):
  1143. tz1 = tz.tzstr('EST5EDT')
  1144. tz2 = tz.tzstr.instance('EST5EDT')
  1145. tz3 = tz.tzstr.instance('EST5EDT')
  1146. assert tz1 is not tz2
  1147. assert tz2 is not tz3
  1148. # Ensure that these still are all the same zone
  1149. assert tz1 == tz2 == tz3
  1150. @pytest.mark.smoke
  1151. @pytest.mark.tzstr
  1152. def test_tzstr_weakref():
  1153. tz_t1 = tz.tzstr('EST5EDT')
  1154. tz_t2_ref = weakref.ref(tz.tzstr('EST5EDT'))
  1155. assert tz_t1 is tz_t2_ref()
  1156. del tz_t1
  1157. gc.collect()
  1158. assert tz_t2_ref() is not None
  1159. assert tz.tzstr('EST5EDT') is tz_t2_ref()
  1160. for offset in range(5,15):
  1161. tz.tzstr('GMT+{}'.format(offset))
  1162. gc.collect()
  1163. assert tz_t2_ref() is None
  1164. assert tz.tzstr('EST5EDT') is not tz_t2_ref()
  1165. @pytest.mark.tzstr
  1166. @pytest.mark.parametrize('tz_str,expected', [
  1167. # From https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
  1168. ('', tz.tzrange(None)), # TODO: Should change this so tz.tzrange('') works
  1169. ('EST+5EDT,M3.2.0/2,M11.1.0/12',
  1170. tz.tzrange('EST', -18000, 'EDT', -14400,
  1171. start=relativedelta(month=3, day=1, weekday=SU(2), hours=2),
  1172. end=relativedelta(month=11, day=1, weekday=SU(1), hours=11))),
  1173. ('WART4WARST,J1/0,J365/25', # This is DST all year, Western Argentina Summer Time
  1174. tz.tzrange('WART', timedelta(hours=-4), 'WARST',
  1175. start=relativedelta(month=1, day=1, hours=0),
  1176. end=relativedelta(month=12, day=31, days=1))),
  1177. ('IST-2IDT,M3.4.4/26,M10.5.0', # Israel Standard / Daylight Time
  1178. tz.tzrange('IST', timedelta(hours=2), 'IDT',
  1179. start=relativedelta(month=3, day=1, weekday=TH(4), days=1, hours=2),
  1180. end=relativedelta(month=10, day=31, weekday=SU(-1), hours=1))),
  1181. ('WGT3WGST,M3.5.0/2,M10.5.0/1',
  1182. tz.tzrange('WGT', timedelta(hours=-3), 'WGST',
  1183. start=relativedelta(month=3, day=31, weekday=SU(-1), hours=2),
  1184. end=relativedelta(month=10, day=31, weekday=SU(-1), hours=0))),
  1185. # Different offset specifications
  1186. ('WGT0300WGST',
  1187. tz.tzrange('WGT', timedelta(hours=-3), 'WGST')),
  1188. ('WGT03:00WGST',
  1189. tz.tzrange('WGT', timedelta(hours=-3), 'WGST')),
  1190. ('AEST-1100AEDT',
  1191. tz.tzrange('AEST', timedelta(hours=11), 'AEDT')),
  1192. ('AEST-11:00AEDT',
  1193. tz.tzrange('AEST', timedelta(hours=11), 'AEDT')),
  1194. # Different time formats
  1195. ('EST5EDT,M3.2.0/4:00,M11.1.0/3:00',
  1196. tz.tzrange('EST', timedelta(hours=-5), 'EDT',
  1197. start=relativedelta(month=3, day=1, weekday=SU(2), hours=4),
  1198. end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))),
  1199. ('EST5EDT,M3.2.0/04:00,M11.1.0/03:00',
  1200. tz.tzrange('EST', timedelta(hours=-5), 'EDT',
  1201. start=relativedelta(month=3, day=1, weekday=SU(2), hours=4),
  1202. end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))),
  1203. ('EST5EDT,M3.2.0/0400,M11.1.0/0300',
  1204. tz.tzrange('EST', timedelta(hours=-5), 'EDT',
  1205. start=relativedelta(month=3, day=1, weekday=SU(2), hours=4),
  1206. end=relativedelta(month=11, day=1, weekday=SU(1), hours=2))),
  1207. ])
  1208. def test_valid_GNU_tzstr(tz_str, expected):
  1209. tzi = tz.tzstr(tz_str)
  1210. assert tzi == expected
  1211. @pytest.mark.tzstr
  1212. @pytest.mark.parametrize('tz_str, expected', [
  1213. ('EST5EDT,5,4,0,7200,11,3,0,7200',
  1214. tz.tzrange('EST', timedelta(hours=-5), 'EDT',
  1215. start=relativedelta(month=5, day=1, weekday=SU(+4), hours=+2),
  1216. end=relativedelta(month=11, day=1, weekday=SU(+3), hours=+1))),
  1217. ('EST5EDT,5,-4,0,7200,11,3,0,7200',
  1218. tz.tzrange('EST', timedelta(hours=-5), 'EDT',
  1219. start=relativedelta(hours=+2, month=5, day=31, weekday=SU(-4)),
  1220. end=relativedelta(hours=+1, month=11, day=1, weekday=SU(+3)))),
  1221. ('EST5EDT,5,4,0,7200,11,-3,0,7200',
  1222. tz.tzrange('EST', timedelta(hours=-5), 'EDT',
  1223. start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
  1224. end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
  1225. ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600',
  1226. tz.tzrange('EST', timedelta(hours=-5), 'EDT',
  1227. start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
  1228. end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
  1229. ('EST5EDT,5,4,0,7200,11,-3,0,7200,3600',
  1230. tz.tzrange('EST', timedelta(hours=-5), 'EDT',
  1231. start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
  1232. end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
  1233. ('EST5EDT,5,4,0,7200,11,-3,0,7200,-3600',
  1234. tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-6),
  1235. start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
  1236. end=relativedelta(hours=+3, month=11, day=31, weekday=SU(-3)))),
  1237. ('EST5EDT,5,4,0,7200,11,-3,0,7200,+7200',
  1238. tz.tzrange('EST', timedelta(hours=-5), 'EDT', timedelta(hours=-3),
  1239. start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
  1240. end=relativedelta(hours=0, month=11, day=31, weekday=SU(-3)))),
  1241. ('EST5EDT,5,4,0,7200,11,-3,0,7200,+3600',
  1242. tz.tzrange('EST', timedelta(hours=-5), 'EDT',
  1243. start=relativedelta(hours=+2, month=5, day=1, weekday=SU(+4)),
  1244. end=relativedelta(hours=+1, month=11, day=31, weekday=SU(-3)))),
  1245. ])
  1246. def test_valid_dateutil_format(tz_str, expected):
  1247. # This tests the dateutil-specific format that is used widely in the tests
  1248. # and examples. It is unclear where this format originated from.
  1249. with pytest.warns(tz.DeprecatedTzFormatWarning):
  1250. tzi = tz.tzstr.instance(tz_str)
  1251. assert tzi == expected
  1252. @pytest.mark.tzstr
  1253. @pytest.mark.parametrize('tz_str', [
  1254. 'hdfiughdfuig,dfughdfuigpu87ñ::',
  1255. ',dfughdfuigpu87ñ::',
  1256. '-1:WART4WARST,J1,J365/25',
  1257. 'WART4WARST,J1,J365/-25',
  1258. 'IST-2IDT,M3.4.-1/26,M10.5.0',
  1259. 'IST-2IDT,M3,2000,1/26,M10,5,0'
  1260. ])
  1261. def test_invalid_GNU_tzstr(tz_str):
  1262. with pytest.raises(ValueError):
  1263. tz.tzstr(tz_str)
  1264. # Different representations of the same default rule set
  1265. DEFAULT_TZSTR_RULES_EQUIV_2003 = [
  1266. 'EST5EDT',
  1267. 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00',
  1268. 'EST5EDT4,95/02:00:00,298/02:00',
  1269. 'EST5EDT4,J96/02:00:00,J299/02:00',
  1270. 'EST5EDT4,J96/02:00:00,J299/02'
  1271. ]
  1272. @pytest.mark.tzstr
  1273. @pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003)
  1274. def test_tzstr_default_start(tz_str):
  1275. tzi = tz.tzstr(tz_str)
  1276. dt_std = datetime(2003, 4, 6, 1, 59, tzinfo=tzi)
  1277. dt_dst = datetime(2003, 4, 6, 2, 00, tzinfo=tzi)
  1278. assert get_timezone_tuple(dt_std) == EST_TUPLE
  1279. assert get_timezone_tuple(dt_dst) == EDT_TUPLE
  1280. @pytest.mark.tzstr
  1281. @pytest.mark.parametrize('tz_str', DEFAULT_TZSTR_RULES_EQUIV_2003)
  1282. def test_tzstr_default_end(tz_str):
  1283. tzi = tz.tzstr(tz_str)
  1284. dt_dst = datetime(2003, 10, 26, 0, 59, tzinfo=tzi)
  1285. dt_dst_ambig = datetime(2003, 10, 26, 1, 00, tzinfo=tzi)
  1286. dt_std_ambig = tz.enfold(dt_dst_ambig, fold=1)
  1287. dt_std = datetime(2003, 10, 26, 2, 00, tzinfo=tzi)
  1288. assert get_timezone_tuple(dt_dst) == EDT_TUPLE
  1289. assert get_timezone_tuple(dt_dst_ambig) == EDT_TUPLE
  1290. assert get_timezone_tuple(dt_std_ambig) == EST_TUPLE
  1291. assert get_timezone_tuple(dt_std) == EST_TUPLE
  1292. @pytest.mark.tzstr
  1293. @pytest.mark.parametrize('tzstr_1', ['EST5EDT',
  1294. 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00'])
  1295. @pytest.mark.parametrize('tzstr_2', ['EST5EDT',
  1296. 'EST5EDT4,M4.1.0/02:00:00,M10-5-0/02:00'])
  1297. def test_tzstr_default_cmp(tzstr_1, tzstr_2):
  1298. tz1 = tz.tzstr(tzstr_1)
  1299. tz2 = tz.tzstr(tzstr_2)
  1300. assert tz1 == tz2
  1301. class TZICalTest(unittest.TestCase, TzFoldMixin):
  1302. def _gettz_str_tuple(self, tzname):
  1303. TZ_EST = (
  1304. 'BEGIN:VTIMEZONE',
  1305. 'TZID:US-Eastern',
  1306. 'BEGIN:STANDARD',
  1307. 'DTSTART:19971029T020000',
  1308. 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11',
  1309. 'TZOFFSETFROM:-0400',
  1310. 'TZOFFSETTO:-0500',
  1311. 'TZNAME:EST',
  1312. 'END:STANDARD',
  1313. 'BEGIN:DAYLIGHT',
  1314. 'DTSTART:19980301T020000',
  1315. 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03',
  1316. 'TZOFFSETFROM:-0500',
  1317. 'TZOFFSETTO:-0400',
  1318. 'TZNAME:EDT',
  1319. 'END:DAYLIGHT',
  1320. 'END:VTIMEZONE'
  1321. )
  1322. TZ_PST = (
  1323. 'BEGIN:VTIMEZONE',
  1324. 'TZID:US-Pacific',
  1325. 'BEGIN:STANDARD',
  1326. 'DTSTART:19971029T020000',
  1327. 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=11',
  1328. 'TZOFFSETFROM:-0700',
  1329. 'TZOFFSETTO:-0800',
  1330. 'TZNAME:PST',
  1331. 'END:STANDARD',
  1332. 'BEGIN:DAYLIGHT',
  1333. 'DTSTART:19980301T020000',
  1334. 'RRULE:FREQ=YEARLY;BYDAY=+2SU;BYMONTH=03',
  1335. 'TZOFFSETFROM:-0800',
  1336. 'TZOFFSETTO:-0700',
  1337. 'TZNAME:PDT',
  1338. 'END:DAYLIGHT',
  1339. 'END:VTIMEZONE'
  1340. )
  1341. TZ_AEST = (
  1342. 'BEGIN:VTIMEZONE',
  1343. 'TZID:Australia-Sydney',
  1344. 'BEGIN:STANDARD',
  1345. 'DTSTART:19980301T030000',
  1346. 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=04',
  1347. 'TZOFFSETFROM:+1100',
  1348. 'TZOFFSETTO:+1000',
  1349. 'TZNAME:AEST',
  1350. 'END:STANDARD',
  1351. 'BEGIN:DAYLIGHT',
  1352. 'DTSTART:19971029T020000',
  1353. 'RRULE:FREQ=YEARLY;BYDAY=+1SU;BYMONTH=10',
  1354. 'TZOFFSETFROM:+1000',
  1355. 'TZOFFSETTO:+1100',
  1356. 'TZNAME:AEDT',
  1357. 'END:DAYLIGHT',
  1358. 'END:VTIMEZONE'
  1359. )
  1360. TZ_LON = (
  1361. 'BEGIN:VTIMEZONE',
  1362. 'TZID:Europe-London',
  1363. 'BEGIN:STANDARD',
  1364. 'DTSTART:19810301T030000',
  1365. 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10;BYHOUR=02',
  1366. 'TZOFFSETFROM:+0100',
  1367. 'TZOFFSETTO:+0000',
  1368. 'TZNAME:GMT',
  1369. 'END:STANDARD',
  1370. 'BEGIN:DAYLIGHT',
  1371. 'DTSTART:19961001T030000',
  1372. 'RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=03;BYHOUR=01',
  1373. 'TZOFFSETFROM:+0000',
  1374. 'TZOFFSETTO:+0100',
  1375. 'TZNAME:BST',
  1376. 'END:DAYLIGHT',
  1377. 'END:VTIMEZONE'
  1378. )
  1379. tzname_map = {'Australia/Sydney': TZ_AEST,
  1380. 'America/Toronto': TZ_EST,
  1381. 'America/New_York': TZ_EST,
  1382. 'America/Los_Angeles': TZ_PST,
  1383. 'Europe/London': TZ_LON}
  1384. return tzname_map[tzname]
  1385. def _gettz_str(self, tzname):
  1386. return '\n'.join(self._gettz_str_tuple(tzname))
  1387. def _tzstr_dtstart_with_params(self, tzname, param_str):
  1388. # Adds parameters to the DTSTART values of a given tzstr
  1389. tz_str_tuple = self._gettz_str_tuple(tzname)
  1390. out_tz = []
  1391. for line in tz_str_tuple:
  1392. if line.startswith('DTSTART'):
  1393. name, value = line.split(':', 1)
  1394. line = name + ';' + param_str + ':' + value
  1395. out_tz.append(line)
  1396. return '\n'.join(out_tz)
  1397. def gettz(self, tzname):
  1398. tz_str = self._gettz_str(tzname)
  1399. tzc = tz.tzical(StringIO(tz_str)).get()
  1400. return tzc
  1401. def testRepr(self):
  1402. instr = StringIO(TZICAL_PST8PDT)
  1403. instr.name = 'StringIO(PST8PDT)'
  1404. tzc = tz.tzical(instr)
  1405. self.assertEqual(repr(tzc), "tzical(" + repr(instr.name) + ")")
  1406. # Test performance
  1407. def _test_us_zone(self, tzc, func, values, start):
  1408. if start:
  1409. dt1 = datetime(2003, 3, 9, 1, 59)
  1410. dt2 = datetime(2003, 3, 9, 2, 00)
  1411. fold = [0, 0]
  1412. else:
  1413. dt1 = datetime(2003, 11, 2, 0, 59)
  1414. dt2 = datetime(2003, 11, 2, 1, 00)
  1415. fold = [0, 1]
  1416. dts = (tz.enfold(dt.replace(tzinfo=tzc), fold=f)
  1417. for dt, f in zip((dt1, dt2), fold))
  1418. for value, dt in zip(values, dts):
  1419. self.assertEqual(func(dt), value)
  1420. def _test_multi_zones(self, tzstrs, tzids, func, values, start):
  1421. tzic = tz.tzical(StringIO('\n'.join(tzstrs)))
  1422. for tzid, vals in zip(tzids, values):
  1423. tzc = tzic.get(tzid)
  1424. self._test_us_zone(tzc, func, vals, start)
  1425. def _prepare_EST(self):
  1426. tz_str = self._gettz_str('America/New_York')
  1427. return tz.tzical(StringIO(tz_str)).get()
  1428. def _testEST(self, start, test_type, tzc=None):
  1429. if tzc is None:
  1430. tzc = self._prepare_EST()
  1431. argdict = {
  1432. 'name': (datetime.tzname, ('EST', 'EDT')),
  1433. 'offset': (datetime.utcoffset, (timedelta(hours=-5),
  1434. timedelta(hours=-4))),
  1435. 'dst': (datetime.dst, (timedelta(hours=0),
  1436. timedelta(hours=1)))
  1437. }
  1438. func, values = argdict[test_type]
  1439. if not start:
  1440. values = reversed(values)
  1441. self._test_us_zone(tzc, func, values, start=start)
  1442. def testESTStartName(self):
  1443. self._testEST(start=True, test_type='name')
  1444. def testESTEndName(self):
  1445. self._testEST(start=False, test_type='name')
  1446. def testESTStartOffset(self):
  1447. self._testEST(start=True, test_type='offset')
  1448. def testESTEndOffset(self):
  1449. self._testEST(start=False, test_type='offset')
  1450. def testESTStartDST(self):
  1451. self._testEST(start=True, test_type='dst')
  1452. def testESTEndDST(self):
  1453. self._testEST(start=False, test_type='dst')
  1454. def testESTValueDatetime(self):
  1455. # Violating one-test-per-test rule because we're not set up to do
  1456. # parameterized tests and the manual proliferation is getting a bit
  1457. # out of hand.
  1458. tz_str = self._tzstr_dtstart_with_params('America/New_York',
  1459. 'VALUE=DATE-TIME')
  1460. tzc = tz.tzical(StringIO(tz_str)).get()
  1461. for start in (True, False):
  1462. for test_type in ('name', 'offset', 'dst'):
  1463. self._testEST(start=start, test_type=test_type, tzc=tzc)
  1464. def _testMultizone(self, start, test_type):
  1465. tzstrs = (self._gettz_str('America/New_York'),
  1466. self._gettz_str('America/Los_Angeles'))
  1467. tzids = ('US-Eastern', 'US-Pacific')
  1468. argdict = {
  1469. 'name': (datetime.tzname, (('EST', 'EDT'),
  1470. ('PST', 'PDT'))),
  1471. 'offset': (datetime.utcoffset, ((timedelta(hours=-5),
  1472. timedelta(hours=-4)),
  1473. (timedelta(hours=-8),
  1474. timedelta(hours=-7)))),
  1475. 'dst': (datetime.dst, ((timedelta(hours=0),
  1476. timedelta(hours=1)),
  1477. (timedelta(hours=0),
  1478. timedelta(hours=1))))
  1479. }
  1480. func, values = argdict[test_type]
  1481. if not start:
  1482. values = map(reversed, values)
  1483. self._test_multi_zones(tzstrs, tzids, func, values, start)
  1484. def testMultiZoneStartName(self):
  1485. self._testMultizone(start=True, test_type='name')
  1486. def testMultiZoneEndName(self):
  1487. self._testMultizone(start=False, test_type='name')
  1488. def testMultiZoneStartOffset(self):
  1489. self._testMultizone(start=True, test_type='offset')
  1490. def testMultiZoneEndOffset(self):
  1491. self._testMultizone(start=False, test_type='offset')
  1492. def testMultiZoneStartDST(self):
  1493. self._testMultizone(start=True, test_type='dst')
  1494. def testMultiZoneEndDST(self):
  1495. self._testMultizone(start=False, test_type='dst')
  1496. def testMultiZoneKeys(self):
  1497. est_str = self._gettz_str('America/New_York')
  1498. pst_str = self._gettz_str('America/Los_Angeles')
  1499. tzic = tz.tzical(StringIO('\n'.join((est_str, pst_str))))
  1500. # Sort keys because they are in a random order, being dictionary keys
  1501. keys = sorted(tzic.keys())
  1502. self.assertEqual(keys, ['US-Eastern', 'US-Pacific'])
  1503. # Test error conditions
  1504. def testEmptyString(self):
  1505. with self.assertRaises(ValueError):
  1506. tz.tzical(StringIO(""))
  1507. def testMultiZoneGet(self):
  1508. tzic = tz.tzical(StringIO(TZICAL_EST5EDT + TZICAL_PST8PDT))
  1509. with self.assertRaises(ValueError):
  1510. tzic.get()
  1511. def testDtstartDate(self):
  1512. tz_str = self._tzstr_dtstart_with_params('America/New_York',
  1513. 'VALUE=DATE')
  1514. with self.assertRaises(ValueError):
  1515. tz.tzical(StringIO(tz_str))
  1516. def testDtstartTzid(self):
  1517. tz_str = self._tzstr_dtstart_with_params('America/New_York',
  1518. 'TZID=UTC')
  1519. with self.assertRaises(ValueError):
  1520. tz.tzical(StringIO(tz_str))
  1521. def testDtstartBadParam(self):
  1522. tz_str = self._tzstr_dtstart_with_params('America/New_York',
  1523. 'FOO=BAR')
  1524. with self.assertRaises(ValueError):
  1525. tz.tzical(StringIO(tz_str))
  1526. # Test Parsing
  1527. def testGap(self):
  1528. tzic = tz.tzical(StringIO('\n'.join((TZICAL_EST5EDT, TZICAL_PST8PDT))))
  1529. keys = sorted(tzic.keys())
  1530. self.assertEqual(keys, ['US-Eastern', 'US-Pacific'])
  1531. class TZTest(unittest.TestCase):
  1532. def testFileStart1(self):
  1533. tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
  1534. self.assertEqual(datetime(2003, 4, 6, 1, 59, tzinfo=tzc).tzname(), "EST")
  1535. self.assertEqual(datetime(2003, 4, 6, 2, 00, tzinfo=tzc).tzname(), "EDT")
  1536. def testFileEnd1(self):
  1537. tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
  1538. self.assertEqual(datetime(2003, 10, 26, 0, 59, tzinfo=tzc).tzname(),
  1539. "EDT")
  1540. end_est = tz.enfold(datetime(2003, 10, 26, 1, 00, tzinfo=tzc))
  1541. self.assertEqual(end_est.tzname(), "EST")
  1542. def testFileLastTransition(self):
  1543. # After the last transition, it goes to standard time in perpetuity
  1544. tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
  1545. self.assertEqual(datetime(2037, 10, 25, 0, 59, tzinfo=tzc).tzname(),
  1546. "EDT")
  1547. last_date = tz.enfold(datetime(2037, 10, 25, 1, 00, tzinfo=tzc), fold=1)
  1548. self.assertEqual(last_date.tzname(),
  1549. "EST")
  1550. self.assertEqual(datetime(2038, 5, 25, 12, 0, tzinfo=tzc).tzname(),
  1551. "EST")
  1552. def testInvalidFile(self):
  1553. # Should throw a ValueError if an invalid file is passed
  1554. with self.assertRaises(ValueError):
  1555. tz.tzfile(BytesIO(b'BadFile'))
  1556. def testFilestreamWithNameRepr(self):
  1557. # If fileobj is a filestream with a "name" attribute this name should
  1558. # be reflected in the tz object's repr
  1559. fileobj = BytesIO(base64.b64decode(TZFILE_EST5EDT))
  1560. fileobj.name = 'foo'
  1561. tzc = tz.tzfile(fileobj)
  1562. self.assertEqual(repr(tzc), 'tzfile(' + repr('foo') + ')')
  1563. def testLeapCountDecodesProperly(self):
  1564. # This timezone has leapcnt, and failed to decode until
  1565. # Eugene Oden notified about the issue.
  1566. # As leap information is currently unused (and unstored) by tzfile() we
  1567. # can only indirectly test this: Take advantage of tzfile() not closing
  1568. # the input file if handed in as an opened file and assert that the
  1569. # full file content has been read by tzfile(). Note: For this test to
  1570. # work NEW_YORK must be in TZif version 1 format i.e. no more data
  1571. # after TZif v1 header + data has been read
  1572. fileobj = BytesIO(base64.b64decode(NEW_YORK))
  1573. tz.tzfile(fileobj)
  1574. # we expect no remaining file content now, i.e. zero-length; if there's
  1575. # still data we haven't read the file format correctly
  1576. remaining_tzfile_content = fileobj.read()
  1577. self.assertEqual(len(remaining_tzfile_content), 0)
  1578. def testIsStd(self):
  1579. # NEW_YORK tzfile contains this isstd information:
  1580. isstd_expected = (0, 0, 0, 1)
  1581. tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK)))
  1582. # gather the actual information as parsed by the tzfile class
  1583. isstd = []
  1584. for ttinfo in tzc._ttinfo_list:
  1585. # ttinfo objects contain boolean values
  1586. isstd.append(int(ttinfo.isstd))
  1587. # ttinfo list may contain more entries than isstd file content
  1588. isstd = tuple(isstd[:len(isstd_expected)])
  1589. self.assertEqual(
  1590. isstd_expected, isstd,
  1591. "isstd UTC/local indicators parsed: %s != tzfile contents: %s"
  1592. % (isstd, isstd_expected))
  1593. def testGMTHasNoDaylight(self):
  1594. # tz.tzstr("GMT+2") improperly considered daylight saving time.
  1595. # Issue reported by Lennart Regebro.
  1596. dt = datetime(2007, 8, 6, 4, 10)
  1597. self.assertEqual(tz.gettz("GMT+2").dst(dt), timedelta(0))
  1598. def testGMTOffset(self):
  1599. # GMT and UTC offsets have inverted signal when compared to the
  1600. # usual TZ variable handling.
  1601. dt = datetime(2007, 8, 6, 4, 10, tzinfo=tz.UTC)
  1602. self.assertEqual(dt.astimezone(tz=tz.tzstr("GMT+2")),
  1603. datetime(2007, 8, 6, 6, 10, tzinfo=tz.tzstr("GMT+2")))
  1604. self.assertEqual(dt.astimezone(tz=tz.gettz("UTC-2")),
  1605. datetime(2007, 8, 6, 2, 10, tzinfo=tz.tzstr("UTC-2")))
  1606. @unittest.skipIf(IS_WIN, "requires Unix")
  1607. def testTZSetDoesntCorrupt(self):
  1608. # if we start in non-UTC then tzset UTC make sure parse doesn't get
  1609. # confused
  1610. with TZEnvContext('UTC'):
  1611. # this should parse to UTC timezone not the original timezone
  1612. dt = parse('2014-07-20T12:34:56+00:00')
  1613. self.assertEqual(str(dt), '2014-07-20 12:34:56+00:00')
  1614. @pytest.mark.tzfile
  1615. @pytest.mark.skipif(not SUPPORTS_SUB_MINUTE_OFFSETS,
  1616. reason='Sub-minute offsets not supported')
  1617. def test_tzfile_sub_minute_offset():
  1618. # If user running python 3.6 or newer, exact offset is used
  1619. tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
  1620. offset = timedelta(hours=1, minutes=39, seconds=52)
  1621. assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset
  1622. @pytest.mark.tzfile
  1623. @pytest.mark.skipif(SUPPORTS_SUB_MINUTE_OFFSETS,
  1624. reason='Sub-minute offsets supported.')
  1625. def test_sub_minute_rounding_tzfile():
  1626. # This timezone has an offset of 5992 seconds in 1900-01-01.
  1627. # For python version pre-3.6, this will be rounded
  1628. tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
  1629. offset = timedelta(hours=1, minutes=40)
  1630. assert datetime(1900, 1, 1, 0, 0, tzinfo=tzc).utcoffset() == offset
  1631. @pytest.mark.tzfile
  1632. def test_samoa_transition():
  1633. # utcoffset() was erroneously returning +14:00 an hour early (GH #812)
  1634. APIA = tz.gettz('Pacific/Apia')
  1635. dt = datetime(2011, 12, 29, 23, 59, tzinfo=APIA)
  1636. assert dt.utcoffset() == timedelta(hours=-10)
  1637. # Make sure the transition actually works, too
  1638. dt_after = (dt.astimezone(tz.UTC) + timedelta(minutes=1)).astimezone(APIA)
  1639. assert dt_after == datetime(2011, 12, 31, tzinfo=APIA)
  1640. assert dt_after.utcoffset() == timedelta(hours=14)
  1641. @unittest.skipUnless(IS_WIN, "Requires Windows")
  1642. class TzWinTest(unittest.TestCase, TzWinFoldMixin):
  1643. def setUp(self):
  1644. self.tzclass = tzwin.tzwin
  1645. def testTzResLoadName(self):
  1646. # This may not work right on non-US locales.
  1647. tzr = tzwin.tzres()
  1648. self.assertEqual(tzr.load_name(112), "Eastern Standard Time")
  1649. def testTzResNameFromString(self):
  1650. tzr = tzwin.tzres()
  1651. self.assertEqual(tzr.name_from_string('@tzres.dll,-221'),
  1652. 'Alaskan Daylight Time')
  1653. self.assertEqual(tzr.name_from_string('Samoa Daylight Time'),
  1654. 'Samoa Daylight Time')
  1655. with self.assertRaises(ValueError):
  1656. tzr.name_from_string('@tzres.dll,100')
  1657. def testIsdstZoneWithNoDaylightSaving(self):
  1658. tz = tzwin.tzwin("UTC")
  1659. dt = parse("2013-03-06 19:08:15")
  1660. self.assertFalse(tz._isdst(dt))
  1661. def testOffset(self):
  1662. tz = tzwin.tzwin("Cape Verde Standard Time")
  1663. self.assertEqual(tz.utcoffset(datetime(1995, 5, 21, 12, 9, 13)),
  1664. timedelta(-1, 82800))
  1665. def testTzwinName(self):
  1666. # https://github.com/dateutil/dateutil/issues/143
  1667. tw = tz.tzwin('Eastern Standard Time')
  1668. # Cover the transitions for at least two years.
  1669. ESTs = 'Eastern Standard Time'
  1670. EDTs = 'Eastern Daylight Time'
  1671. transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs),
  1672. (datetime(2015, 3, 8, 3, 1), EDTs),
  1673. (datetime(2015, 11, 1, 0, 59), EDTs),
  1674. (datetime(2015, 11, 1, 3, 1), ESTs),
  1675. (datetime(2016, 3, 13, 0, 59), ESTs),
  1676. (datetime(2016, 3, 13, 3, 1), EDTs),
  1677. (datetime(2016, 11, 6, 0, 59), EDTs),
  1678. (datetime(2016, 11, 6, 3, 1), ESTs)]
  1679. for t_date, expected in transition_dates:
  1680. self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected)
  1681. def testTzwinRepr(self):
  1682. tw = tz.tzwin('Yakutsk Standard Time')
  1683. self.assertEqual(repr(tw), 'tzwin(' +
  1684. repr('Yakutsk Standard Time') + ')')
  1685. def testTzWinEquality(self):
  1686. # https://github.com/dateutil/dateutil/issues/151
  1687. tzwin_names = ('Eastern Standard Time',
  1688. 'West Pacific Standard Time',
  1689. 'Yakutsk Standard Time',
  1690. 'Iran Standard Time',
  1691. 'UTC')
  1692. for tzwin_name in tzwin_names:
  1693. # Get two different instances to compare
  1694. tw1 = tz.tzwin(tzwin_name)
  1695. tw2 = tz.tzwin(tzwin_name)
  1696. self.assertEqual(tw1, tw2)
  1697. def testTzWinInequality(self):
  1698. # https://github.com/dateutil/dateutil/issues/151
  1699. # Note these last two currently differ only in their name.
  1700. tzwin_names = (('Eastern Standard Time', 'Yakutsk Standard Time'),
  1701. ('Greenwich Standard Time', 'GMT Standard Time'),
  1702. ('GMT Standard Time', 'UTC'),
  1703. ('E. South America Standard Time',
  1704. 'Argentina Standard Time'))
  1705. for tzwn1, tzwn2 in tzwin_names:
  1706. # Get two different instances to compare
  1707. tw1 = tz.tzwin(tzwn1)
  1708. tw2 = tz.tzwin(tzwn2)
  1709. self.assertNotEqual(tw1, tw2)
  1710. def testTzWinEqualityInvalid(self):
  1711. # Compare to objects that do not implement comparison with this
  1712. # (should default to False)
  1713. UTC = tz.UTC
  1714. EST = tz.tzwin('Eastern Standard Time')
  1715. self.assertFalse(EST == UTC)
  1716. self.assertFalse(EST == 1)
  1717. self.assertFalse(UTC == EST)
  1718. self.assertTrue(EST != UTC)
  1719. self.assertTrue(EST != 1)
  1720. def testTzWinInequalityUnsupported(self):
  1721. # Compare it to an object that is promiscuous about equality, but for
  1722. # which tzwin does not implement an equality operator.
  1723. EST = tz.tzwin('Eastern Standard Time')
  1724. self.assertTrue(EST == ComparesEqual)
  1725. self.assertFalse(EST != ComparesEqual)
  1726. def testTzwinTimeOnlyDST(self):
  1727. # For zones with DST, .dst() should return None
  1728. tw_est = tz.tzwin('Eastern Standard Time')
  1729. self.assertIs(dt_time(14, 10, tzinfo=tw_est).dst(), None)
  1730. # This zone has no DST, so .dst() can return 0
  1731. tw_sast = tz.tzwin('South Africa Standard Time')
  1732. self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).dst(),
  1733. timedelta(0))
  1734. def testTzwinTimeOnlyUTCOffset(self):
  1735. # For zones with DST, .utcoffset() should return None
  1736. tw_est = tz.tzwin('Eastern Standard Time')
  1737. self.assertIs(dt_time(14, 10, tzinfo=tw_est).utcoffset(), None)
  1738. # This zone has no DST, so .utcoffset() returns standard offset
  1739. tw_sast = tz.tzwin('South Africa Standard Time')
  1740. self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).utcoffset(),
  1741. timedelta(hours=2))
  1742. def testTzwinTimeOnlyTZName(self):
  1743. # For zones with DST, the name defaults to standard time
  1744. tw_est = tz.tzwin('Eastern Standard Time')
  1745. self.assertEqual(dt_time(14, 10, tzinfo=tw_est).tzname(),
  1746. 'Eastern Standard Time')
  1747. # For zones with no DST, this should work normally.
  1748. tw_sast = tz.tzwin('South Africa Standard Time')
  1749. self.assertEqual(dt_time(14, 10, tzinfo=tw_sast).tzname(),
  1750. 'South Africa Standard Time')
  1751. @unittest.skipUnless(IS_WIN, "Requires Windows")
  1752. class TzWinLocalTest(unittest.TestCase, TzWinFoldMixin):
  1753. def setUp(self):
  1754. self.tzclass = tzwin.tzwinlocal
  1755. self.context = TZWinContext
  1756. def get_args(self, tzname):
  1757. return ()
  1758. def testLocal(self):
  1759. # Not sure how to pin a local time zone, so for now we're just going
  1760. # to run this and make sure it doesn't raise an error
  1761. # See GitHub Issue #135: https://github.com/dateutil/dateutil/issues/135
  1762. datetime.now(tzwin.tzwinlocal())
  1763. def testTzwinLocalUTCOffset(self):
  1764. with TZWinContext('Eastern Standard Time'):
  1765. tzwl = tzwin.tzwinlocal()
  1766. self.assertEqual(datetime(2014, 3, 11, tzinfo=tzwl).utcoffset(),
  1767. timedelta(hours=-4))
  1768. def testTzwinLocalName(self):
  1769. # https://github.com/dateutil/dateutil/issues/143
  1770. ESTs = 'Eastern Standard Time'
  1771. EDTs = 'Eastern Daylight Time'
  1772. transition_dates = [(datetime(2015, 3, 8, 0, 59), ESTs),
  1773. (datetime(2015, 3, 8, 3, 1), EDTs),
  1774. (datetime(2015, 11, 1, 0, 59), EDTs),
  1775. (datetime(2015, 11, 1, 3, 1), ESTs),
  1776. (datetime(2016, 3, 13, 0, 59), ESTs),
  1777. (datetime(2016, 3, 13, 3, 1), EDTs),
  1778. (datetime(2016, 11, 6, 0, 59), EDTs),
  1779. (datetime(2016, 11, 6, 3, 1), ESTs)]
  1780. with TZWinContext('Eastern Standard Time'):
  1781. tw = tz.tzwinlocal()
  1782. for t_date, expected in transition_dates:
  1783. self.assertEqual(t_date.replace(tzinfo=tw).tzname(), expected)
  1784. def testTzWinLocalRepr(self):
  1785. tw = tz.tzwinlocal()
  1786. self.assertEqual(repr(tw), 'tzwinlocal()')
  1787. def testTzwinLocalRepr(self):
  1788. # https://github.com/dateutil/dateutil/issues/143
  1789. with TZWinContext('Eastern Standard Time'):
  1790. tw = tz.tzwinlocal()
  1791. self.assertEqual(str(tw), 'tzwinlocal(' +
  1792. repr('Eastern Standard Time') + ')')
  1793. with TZWinContext('Pacific Standard Time'):
  1794. tw = tz.tzwinlocal()
  1795. self.assertEqual(str(tw), 'tzwinlocal(' +
  1796. repr('Pacific Standard Time') + ')')
  1797. def testTzwinLocalEquality(self):
  1798. tw_est = tz.tzwin('Eastern Standard Time')
  1799. tw_pst = tz.tzwin('Pacific Standard Time')
  1800. with TZWinContext('Eastern Standard Time'):
  1801. twl1 = tz.tzwinlocal()
  1802. twl2 = tz.tzwinlocal()
  1803. self.assertEqual(twl1, twl2)
  1804. self.assertEqual(twl1, tw_est)
  1805. self.assertNotEqual(twl1, tw_pst)
  1806. with TZWinContext('Pacific Standard Time'):
  1807. twl1 = tz.tzwinlocal()
  1808. twl2 = tz.tzwinlocal()
  1809. tw = tz.tzwin('Pacific Standard Time')
  1810. self.assertEqual(twl1, twl2)
  1811. self.assertEqual(twl1, tw)
  1812. self.assertEqual(twl1, tw_pst)
  1813. self.assertNotEqual(twl1, tw_est)
  1814. def testTzwinLocalTimeOnlyDST(self):
  1815. # For zones with DST, .dst() should return None
  1816. with TZWinContext('Eastern Standard Time'):
  1817. twl = tz.tzwinlocal()
  1818. self.assertIs(dt_time(14, 10, tzinfo=twl).dst(), None)
  1819. # This zone has no DST, so .dst() can return 0
  1820. with TZWinContext('South Africa Standard Time'):
  1821. twl = tz.tzwinlocal()
  1822. self.assertEqual(dt_time(14, 10, tzinfo=twl).dst(), timedelta(0))
  1823. def testTzwinLocalTimeOnlyUTCOffset(self):
  1824. # For zones with DST, .utcoffset() should return None
  1825. with TZWinContext('Eastern Standard Time'):
  1826. twl = tz.tzwinlocal()
  1827. self.assertIs(dt_time(14, 10, tzinfo=twl).utcoffset(), None)
  1828. # This zone has no DST, so .utcoffset() returns standard offset
  1829. with TZWinContext('South Africa Standard Time'):
  1830. twl = tz.tzwinlocal()
  1831. self.assertEqual(dt_time(14, 10, tzinfo=twl).utcoffset(),
  1832. timedelta(hours=2))
  1833. def testTzwinLocalTimeOnlyTZName(self):
  1834. # For zones with DST, the name defaults to standard time
  1835. with TZWinContext('Eastern Standard Time'):
  1836. twl = tz.tzwinlocal()
  1837. self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(),
  1838. 'Eastern Standard Time')
  1839. # For zones with no DST, this should work normally.
  1840. with TZWinContext('South Africa Standard Time'):
  1841. twl = tz.tzwinlocal()
  1842. self.assertEqual(dt_time(14, 10, tzinfo=twl).tzname(),
  1843. 'South Africa Standard Time')
  1844. class TzPickleTest(PicklableMixin, unittest.TestCase):
  1845. _asfile = False
  1846. def setUp(self):
  1847. self.assertPicklable = partial(self.assertPicklable,
  1848. asfile=self._asfile)
  1849. def testPickleTzUTC(self):
  1850. self.assertPicklable(tz.tzutc(), singleton=True)
  1851. def testPickleTzOffsetZero(self):
  1852. self.assertPicklable(tz.tzoffset('UTC', 0), singleton=True)
  1853. def testPickleTzOffsetPos(self):
  1854. self.assertPicklable(tz.tzoffset('UTC+1', 3600), singleton=True)
  1855. def testPickleTzOffsetNeg(self):
  1856. self.assertPicklable(tz.tzoffset('UTC-1', -3600), singleton=True)
  1857. @pytest.mark.tzlocal
  1858. def testPickleTzLocal(self):
  1859. self.assertPicklable(tz.tzlocal())
  1860. def testPickleTzFileEST5EDT(self):
  1861. tzc = tz.tzfile(BytesIO(base64.b64decode(TZFILE_EST5EDT)))
  1862. self.assertPicklable(tzc)
  1863. def testPickleTzFileEurope_Helsinki(self):
  1864. tzc = tz.tzfile(BytesIO(base64.b64decode(EUROPE_HELSINKI)))
  1865. self.assertPicklable(tzc)
  1866. def testPickleTzFileNew_York(self):
  1867. tzc = tz.tzfile(BytesIO(base64.b64decode(NEW_YORK)))
  1868. self.assertPicklable(tzc)
  1869. @unittest.skip("Known failure")
  1870. def testPickleTzICal(self):
  1871. tzc = tz.tzical(StringIO(TZICAL_EST5EDT)).get()
  1872. self.assertPicklable(tzc)
  1873. def testPickleTzGettz(self):
  1874. self.assertPicklable(tz.gettz('America/New_York'))
  1875. def testPickleZoneFileGettz(self):
  1876. zoneinfo_file = zoneinfo.get_zonefile_instance()
  1877. tzi = zoneinfo_file.get('America/New_York')
  1878. self.assertIsNot(tzi, None)
  1879. self.assertPicklable(tzi)
  1880. class TzPickleFileTest(TzPickleTest):
  1881. """ Run all the TzPickleTest tests, using a temporary file """
  1882. _asfile = True
  1883. class DatetimeAmbiguousTest(unittest.TestCase):
  1884. """ Test the datetime_exists / datetime_ambiguous functions """
  1885. def testNoTzSpecified(self):
  1886. with self.assertRaises(ValueError):
  1887. tz.datetime_ambiguous(datetime(2016, 4, 1, 2, 9))
  1888. def _get_no_support_tzinfo_class(self, dt_start, dt_end, dst_only=False):
  1889. # Generates a class of tzinfo with no support for is_ambiguous
  1890. # where dates between dt_start and dt_end are ambiguous.
  1891. class FoldingTzInfo(tzinfo):
  1892. def utcoffset(self, dt):
  1893. if not dst_only:
  1894. dt_n = dt.replace(tzinfo=None)
  1895. if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0):
  1896. return timedelta(hours=-1)
  1897. return timedelta(hours=0)
  1898. def dst(self, dt):
  1899. dt_n = dt.replace(tzinfo=None)
  1900. if dt_start <= dt_n < dt_end and getattr(dt_n, 'fold', 0):
  1901. return timedelta(hours=1)
  1902. else:
  1903. return timedelta(0)
  1904. return FoldingTzInfo
  1905. def _get_no_support_tzinfo(self, dt_start, dt_end, dst_only=False):
  1906. return self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only)()
  1907. def testNoSupportAmbiguityFoldNaive(self):
  1908. dt_start = datetime(2018, 9, 1, 1, 0)
  1909. dt_end = datetime(2018, 9, 1, 2, 0)
  1910. tzi = self._get_no_support_tzinfo(dt_start, dt_end)
  1911. self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
  1912. tz=tzi))
  1913. def testNoSupportAmbiguityFoldAware(self):
  1914. dt_start = datetime(2018, 9, 1, 1, 0)
  1915. dt_end = datetime(2018, 9, 1, 2, 0)
  1916. tzi = self._get_no_support_tzinfo(dt_start, dt_end)
  1917. self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30,
  1918. tzinfo=tzi)))
  1919. def testNoSupportAmbiguityUnambiguousNaive(self):
  1920. dt_start = datetime(2018, 9, 1, 1, 0)
  1921. dt_end = datetime(2018, 9, 1, 2, 0)
  1922. tzi = self._get_no_support_tzinfo(dt_start, dt_end)
  1923. self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
  1924. tz=tzi))
  1925. def testNoSupportAmbiguityUnambiguousAware(self):
  1926. dt_start = datetime(2018, 9, 1, 1, 0)
  1927. dt_end = datetime(2018, 9, 1, 2, 0)
  1928. tzi = self._get_no_support_tzinfo(dt_start, dt_end)
  1929. self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30,
  1930. tzinfo=tzi)))
  1931. def testNoSupportAmbiguityFoldDSTOnly(self):
  1932. dt_start = datetime(2018, 9, 1, 1, 0)
  1933. dt_end = datetime(2018, 9, 1, 2, 0)
  1934. tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True)
  1935. self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
  1936. tz=tzi))
  1937. def testNoSupportAmbiguityUnambiguousDSTOnly(self):
  1938. dt_start = datetime(2018, 9, 1, 1, 0)
  1939. dt_end = datetime(2018, 9, 1, 2, 0)
  1940. tzi = self._get_no_support_tzinfo(dt_start, dt_end, dst_only=True)
  1941. self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
  1942. tz=tzi))
  1943. def testSupportAmbiguityFoldNaive(self):
  1944. tzi = tz.gettz('US/Eastern')
  1945. dt = datetime(2011, 11, 6, 1, 30)
  1946. self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi))
  1947. def testSupportAmbiguityFoldAware(self):
  1948. tzi = tz.gettz('US/Eastern')
  1949. dt = datetime(2011, 11, 6, 1, 30, tzinfo=tzi)
  1950. self.assertTrue(tz.datetime_ambiguous(dt))
  1951. def testSupportAmbiguityUnambiguousAware(self):
  1952. tzi = tz.gettz('US/Eastern')
  1953. dt = datetime(2011, 11, 6, 4, 30)
  1954. self.assertFalse(tz.datetime_ambiguous(dt, tz=tzi))
  1955. def testSupportAmbiguityUnambiguousNaive(self):
  1956. tzi = tz.gettz('US/Eastern')
  1957. dt = datetime(2011, 11, 6, 4, 30, tzinfo=tzi)
  1958. self.assertFalse(tz.datetime_ambiguous(dt))
  1959. def _get_ambig_error_tzinfo(self, dt_start, dt_end, dst_only=False):
  1960. cTzInfo = self._get_no_support_tzinfo_class(dt_start, dt_end, dst_only)
  1961. # Takes the wrong number of arguments and raises an error anyway.
  1962. class FoldTzInfoRaises(cTzInfo):
  1963. def is_ambiguous(self, dt, other_arg):
  1964. raise NotImplementedError('This is not implemented')
  1965. return FoldTzInfoRaises()
  1966. def testIncompatibleAmbiguityFoldNaive(self):
  1967. dt_start = datetime(2018, 9, 1, 1, 0)
  1968. dt_end = datetime(2018, 9, 1, 2, 0)
  1969. tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
  1970. self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
  1971. tz=tzi))
  1972. def testIncompatibleAmbiguityFoldAware(self):
  1973. dt_start = datetime(2018, 9, 1, 1, 0)
  1974. dt_end = datetime(2018, 9, 1, 2, 0)
  1975. tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
  1976. self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30,
  1977. tzinfo=tzi)))
  1978. def testIncompatibleAmbiguityUnambiguousNaive(self):
  1979. dt_start = datetime(2018, 9, 1, 1, 0)
  1980. dt_end = datetime(2018, 9, 1, 2, 0)
  1981. tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
  1982. self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
  1983. tz=tzi))
  1984. def testIncompatibleAmbiguityUnambiguousAware(self):
  1985. dt_start = datetime(2018, 9, 1, 1, 0)
  1986. dt_end = datetime(2018, 9, 1, 2, 0)
  1987. tzi = self._get_ambig_error_tzinfo(dt_start, dt_end)
  1988. self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30,
  1989. tzinfo=tzi)))
  1990. def testIncompatibleAmbiguityFoldDSTOnly(self):
  1991. dt_start = datetime(2018, 9, 1, 1, 0)
  1992. dt_end = datetime(2018, 9, 1, 2, 0)
  1993. tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True)
  1994. self.assertTrue(tz.datetime_ambiguous(datetime(2018, 9, 1, 1, 30),
  1995. tz=tzi))
  1996. def testIncompatibleAmbiguityUnambiguousDSTOnly(self):
  1997. dt_start = datetime(2018, 9, 1, 1, 0)
  1998. dt_end = datetime(2018, 9, 1, 2, 0)
  1999. tzi = self._get_ambig_error_tzinfo(dt_start, dt_end, dst_only=True)
  2000. self.assertFalse(tz.datetime_ambiguous(datetime(2018, 10, 1, 12, 30),
  2001. tz=tzi))
  2002. def testSpecifiedTzOverridesAttached(self):
  2003. # If a tz is specified, the datetime will be treated as naive.
  2004. # This is not ambiguous in the local zone
  2005. dt = datetime(2011, 11, 6, 1, 30, tzinfo=tz.gettz('Australia/Sydney'))
  2006. self.assertFalse(tz.datetime_ambiguous(dt))
  2007. tzi = tz.gettz('US/Eastern')
  2008. self.assertTrue(tz.datetime_ambiguous(dt, tz=tzi))
  2009. class DatetimeExistsTest(unittest.TestCase):
  2010. def testNoTzSpecified(self):
  2011. with self.assertRaises(ValueError):
  2012. tz.datetime_exists(datetime(2016, 4, 1, 2, 9))
  2013. def testInGapNaive(self):
  2014. tzi = tz.gettz('Australia/Sydney')
  2015. dt = datetime(2012, 10, 7, 2, 30)
  2016. self.assertFalse(tz.datetime_exists(dt, tz=tzi))
  2017. def testInGapAware(self):
  2018. tzi = tz.gettz('Australia/Sydney')
  2019. dt = datetime(2012, 10, 7, 2, 30, tzinfo=tzi)
  2020. self.assertFalse(tz.datetime_exists(dt))
  2021. def testExistsNaive(self):
  2022. tzi = tz.gettz('Australia/Sydney')
  2023. dt = datetime(2012, 10, 7, 10, 30)
  2024. self.assertTrue(tz.datetime_exists(dt, tz=tzi))
  2025. def testExistsAware(self):
  2026. tzi = tz.gettz('Australia/Sydney')
  2027. dt = datetime(2012, 10, 7, 10, 30, tzinfo=tzi)
  2028. self.assertTrue(tz.datetime_exists(dt))
  2029. def testSpecifiedTzOverridesAttached(self):
  2030. EST = tz.gettz('US/Eastern')
  2031. AEST = tz.gettz('Australia/Sydney')
  2032. dt = datetime(2012, 10, 7, 2, 30, tzinfo=EST) # This time exists
  2033. self.assertFalse(tz.datetime_exists(dt, tz=AEST))
  2034. class TestEnfold:
  2035. def test_enter_fold_default(self):
  2036. dt = tz.enfold(datetime(2020, 1, 19, 3, 32))
  2037. assert dt.fold == 1
  2038. def test_enter_fold(self):
  2039. dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1)
  2040. assert dt.fold == 1
  2041. def test_exit_fold(self):
  2042. dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=0)
  2043. # Before Python 3.6, dt.fold won't exist if fold is 0.
  2044. assert getattr(dt, 'fold', 0) == 0
  2045. def test_defold(self):
  2046. dt = tz.enfold(datetime(2020, 1, 19, 3, 32), fold=1)
  2047. dt2 = tz.enfold(dt, fold=0)
  2048. assert getattr(dt2, 'fold', 0) == 0
  2049. def test_fold_replace_args(self):
  2050. # This test can be dropped when Python < 3.6 is dropped, since it
  2051. # is mainly to cover the `replace` method on _DatetimeWithFold
  2052. dt = tz.enfold(datetime(1950, 1, 2, 12, 30, 15, 8), fold=1)
  2053. dt2 = dt.replace(1952, 2, 3, 13, 31, 16, 9)
  2054. assert dt2 == tz.enfold(datetime(1952, 2, 3, 13, 31, 16, 9), fold=1)
  2055. assert dt2.fold == 1
  2056. def test_fold_replace_exception_duplicate_args(self):
  2057. dt = tz.enfold(datetime(1999, 1, 3), fold=1)
  2058. with pytest.raises(TypeError):
  2059. dt.replace(1950, year=2000)
  2060. @pytest.mark.tz_resolve_imaginary
  2061. class ImaginaryDateTest(unittest.TestCase):
  2062. def testCanberraForward(self):
  2063. tzi = tz.gettz('Australia/Canberra')
  2064. dt = datetime(2018, 10, 7, 2, 30, tzinfo=tzi)
  2065. dt_act = tz.resolve_imaginary(dt)
  2066. dt_exp = datetime(2018, 10, 7, 3, 30, tzinfo=tzi)
  2067. self.assertEqual(dt_act, dt_exp)
  2068. def testLondonForward(self):
  2069. tzi = tz.gettz('Europe/London')
  2070. dt = datetime(2018, 3, 25, 1, 30, tzinfo=tzi)
  2071. dt_act = tz.resolve_imaginary(dt)
  2072. dt_exp = datetime(2018, 3, 25, 2, 30, tzinfo=tzi)
  2073. self.assertEqual(dt_act, dt_exp)
  2074. def testKeivForward(self):
  2075. tzi = tz.gettz('Europe/Kiev')
  2076. dt = datetime(2018, 3, 25, 3, 30, tzinfo=tzi)
  2077. dt_act = tz.resolve_imaginary(dt)
  2078. dt_exp = datetime(2018, 3, 25, 4, 30, tzinfo=tzi)
  2079. self.assertEqual(dt_act, dt_exp)
  2080. @pytest.mark.tz_resolve_imaginary
  2081. @pytest.mark.parametrize('dt', [
  2082. datetime(2017, 11, 5, 1, 30, tzinfo=tz.gettz('America/New_York')),
  2083. datetime(2018, 10, 28, 1, 30, tzinfo=tz.gettz('Europe/London')),
  2084. datetime(2017, 4, 2, 2, 30, tzinfo=tz.gettz('Australia/Sydney')),
  2085. ])
  2086. def test_resolve_imaginary_ambiguous(dt):
  2087. assert tz.resolve_imaginary(dt) is dt
  2088. dt_f = tz.enfold(dt)
  2089. assert dt is not dt_f
  2090. assert tz.resolve_imaginary(dt_f) is dt_f
  2091. @pytest.mark.tz_resolve_imaginary
  2092. @pytest.mark.parametrize('dt', [
  2093. datetime(2017, 6, 2, 12, 30, tzinfo=tz.gettz('America/New_York')),
  2094. datetime(2018, 4, 2, 9, 30, tzinfo=tz.gettz('Europe/London')),
  2095. datetime(2017, 2, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')),
  2096. datetime(2017, 12, 2, 12, 30, tzinfo=tz.gettz('America/New_York')),
  2097. datetime(2018, 12, 2, 9, 30, tzinfo=tz.gettz('Europe/London')),
  2098. datetime(2017, 6, 2, 16, 30, tzinfo=tz.gettz('Australia/Sydney')),
  2099. datetime(2025, 9, 25, 1, 17, tzinfo=tz.UTC),
  2100. datetime(2025, 9, 25, 1, 17, tzinfo=tz.tzoffset('EST', -18000)),
  2101. datetime(2019, 3, 4, tzinfo=None)
  2102. ])
  2103. def test_resolve_imaginary_existing(dt):
  2104. assert tz.resolve_imaginary(dt) is dt
  2105. def __get_kiritimati_resolve_imaginary_test():
  2106. # In the 2018d release of the IANA database, the Kiritimati "imaginary day"
  2107. # data was corrected, so if the system zoneinfo is older than 2018d, the
  2108. # Kiritimati test will fail.
  2109. tzi = tz.gettz('Pacific/Kiritimati')
  2110. new_version = False
  2111. if not tz.datetime_exists(datetime(1995, 1, 1, 12, 30), tzi):
  2112. zif = zoneinfo.get_zonefile_instance()
  2113. if zif.metadata is not None:
  2114. new_version = zif.metadata['tzversion'] >= '2018d'
  2115. if new_version:
  2116. tzi = zif.get('Pacific/Kiritimati')
  2117. else:
  2118. new_version = True
  2119. if new_version:
  2120. dates = (datetime(1994, 12, 31, 12, 30), datetime(1995, 1, 1, 12, 30))
  2121. else:
  2122. dates = (datetime(1995, 1, 1, 12, 30), datetime(1995, 1, 2, 12, 30))
  2123. return (tzi, ) + dates
  2124. resolve_imaginary_tests = [
  2125. (tz.gettz('Europe/London'),
  2126. datetime(2018, 3, 25, 1, 30), datetime(2018, 3, 25, 2, 30)),
  2127. (tz.gettz('America/New_York'),
  2128. datetime(2017, 3, 12, 2, 30), datetime(2017, 3, 12, 3, 30)),
  2129. (tz.gettz('Australia/Sydney'),
  2130. datetime(2014, 10, 5, 2, 0), datetime(2014, 10, 5, 3, 0)),
  2131. __get_kiritimati_resolve_imaginary_test(),
  2132. ]
  2133. if SUPPORTS_SUB_MINUTE_OFFSETS:
  2134. resolve_imaginary_tests.append(
  2135. (tz.gettz('Africa/Monrovia'),
  2136. datetime(1972, 1, 7, 0, 30), datetime(1972, 1, 7, 1, 14, 30)))
  2137. @pytest.mark.tz_resolve_imaginary
  2138. @pytest.mark.parametrize('tzi, dt, dt_exp', resolve_imaginary_tests)
  2139. def test_resolve_imaginary(tzi, dt, dt_exp):
  2140. dt = dt.replace(tzinfo=tzi)
  2141. dt_exp = dt_exp.replace(tzinfo=tzi)
  2142. dt_r = tz.resolve_imaginary(dt)
  2143. assert dt_r == dt_exp
  2144. assert dt_r.tzname() == dt_exp.tzname()
  2145. assert dt_r.utcoffset() == dt_exp.utcoffset()