PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/golang/1.22.0/src/time
Viewing File: /opt/golang/1.22.0/src/time/zoneinfo.go
// Copyright 2011 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package time import ( "errors" "sync" "syscall" ) //go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go // A Location maps time instants to the zone in use at that time. // Typically, the Location represents the collection of time offsets // in use in a geographical area. For many Locations the time offset varies // depending on whether daylight savings time is in use at the time instant. // // Location is used to provide a time zone in a printed Time value and for // calculations involving intervals that may cross daylight savings time // boundaries. type Location struct { name string zone []zone tx []zoneTrans // The tzdata information can be followed by a string that describes // how to handle DST transitions not recorded in zoneTrans. // The format is the TZ environment variable without a colon; see // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html. // Example string, for America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0 extend string // Most lookups will be for the current time. // To avoid the binary search through tx, keep a // static one-element cache that gives the correct // zone for the time when the Location was created. // if cacheStart <= t < cacheEnd, // lookup can return cacheZone. // The units for cacheStart and cacheEnd are seconds // since January 1, 1970 UTC, to match the argument // to lookup. cacheStart int64 cacheEnd int64 cacheZone *zone } // A zone represents a single time zone such as CET. type zone struct { name string // abbreviated name, "CET" offset int // seconds east of UTC isDST bool // is this zone Daylight Savings Time? } // A zoneTrans represents a single time zone transition. type zoneTrans struct { when int64 // transition time, in seconds since 1970 GMT index uint8 // the index of the zone that goes into effect at that time isstd, isutc bool // ignored - no idea what these mean } // alpha and omega are the beginning and end of time for zone // transitions. const ( alpha = -1 << 63 // math.MinInt64 omega = 1<<63 - 1 // math.MaxInt64 ) // UTC represents Universal Coordinated Time (UTC). var UTC *Location = &utcLoc // utcLoc is separate so that get can refer to &utcLoc // and ensure that it never returns a nil *Location, // even if a badly behaved client has changed UTC. var utcLoc = Location{name: "UTC"} // Local represents the system's local time zone. // On Unix systems, Local consults the TZ environment // variable to find the time zone to use. No TZ means // use the system default /etc/localtime. // TZ="" means use UTC. // TZ="foo" means use file foo in the system timezone directory. var Local *Location = &localLoc // localLoc is separate so that initLocal can initialize // it even if a client has changed Local. var localLoc Location var localOnce sync.Once func (l *Location) get() *Location { if l == nil { return &utcLoc } if l == &localLoc { localOnce.Do(initLocal) } return l } // String returns a descriptive name for the time zone information, // corresponding to the name argument to LoadLocation or FixedZone. func (l *Location) String() string { return l.get().name } var unnamedFixedZones []*Location var unnamedFixedZonesOnce sync.Once // FixedZone returns a Location that always uses // the given zone name and offset (seconds east of UTC). func FixedZone(name string, offset int) *Location { // Most calls to FixedZone have an unnamed zone with an offset by the hour. // Optimize for that case by returning the same *Location for a given hour. const hoursBeforeUTC = 12 const hoursAfterUTC = 14 hour := offset / 60 / 60 if name == "" && -hoursBeforeUTC <= hour && hour <= +hoursAfterUTC && hour*60*60 == offset { unnamedFixedZonesOnce.Do(func() { unnamedFixedZones = make([]*Location, hoursBeforeUTC+1+hoursAfterUTC) for hr := -hoursBeforeUTC; hr <= +hoursAfterUTC; hr++ { unnamedFixedZones[hr+hoursBeforeUTC] = fixedZone("", hr*60*60) } }) return unnamedFixedZones[hour+hoursBeforeUTC] } return fixedZone(name, offset) } func fixedZone(name string, offset int) *Location { l := &Location{ name: name, zone: []zone{{name, offset, false}}, tx: []zoneTrans{{alpha, 0, false, false}}, cacheStart: alpha, cacheEnd: omega, } l.cacheZone = &l.zone[0] return l } // lookup returns information about the time zone in use at an // instant in time expressed as seconds since January 1, 1970 00:00:00 UTC. // // The returned information gives the name of the zone (such as "CET"), // the start and end times bracketing sec when that zone is in effect, // the offset in seconds east of UTC (such as -5*60*60), and whether // the daylight savings is being observed at that time. func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, isDST bool) { l = l.get() if len(l.zone) == 0 { name = "UTC" offset = 0 start = alpha end = omega isDST = false return } if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd { name = zone.name offset = zone.offset start = l.cacheStart end = l.cacheEnd isDST = zone.isDST return } if len(l.tx) == 0 || sec < l.tx[0].when { zone := &l.zone[l.lookupFirstZone()] name = zone.name offset = zone.offset start = alpha if len(l.tx) > 0 { end = l.tx[0].when } else { end = omega } isDST = zone.isDST return } // Binary search for entry with largest time <= sec. // Not using sort.Search to avoid dependencies. tx := l.tx end = omega lo := 0 hi := len(tx) for hi-lo > 1 { m := int(uint(lo+hi) >> 1) lim := tx[m].when if sec < lim { end = lim hi = m } else { lo = m } } zone := &l.zone[tx[lo].index] name = zone.name offset = zone.offset start = tx[lo].when // end = maintained during the search isDST = zone.isDST // If we're at the end of the known zone transitions, // try the extend string. if lo == len(tx)-1 && l.extend != "" { if ename, eoffset, estart, eend, eisDST, ok := tzset(l.extend, start, sec); ok { return ename, eoffset, estart, eend, eisDST } } return } // lookupFirstZone returns the index of the time zone to use for times // before the first transition time, or when there are no transition // times. // // The reference implementation in localtime.c from // https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz // implements the following algorithm for these cases: // 1. If the first zone is unused by the transitions, use it. // 2. Otherwise, if there are transition times, and the first // transition is to a zone in daylight time, find the first // non-daylight-time zone before and closest to the first transition // zone. // 3. Otherwise, use the first zone that is not daylight time, if // there is one. // 4. Otherwise, use the first zone. func (l *Location) lookupFirstZone() int { // Case 1. if !l.firstZoneUsed() { return 0 } // Case 2. if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST { for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- { if !l.zone[zi].isDST { return zi } } } // Case 3. for zi := range l.zone { if !l.zone[zi].isDST { return zi } } // Case 4. return 0 } // firstZoneUsed reports whether the first zone is used by some // transition. func (l *Location) firstZoneUsed() bool { for _, tx := range l.tx { if tx.index == 0 { return true } } return false } // tzset takes a timezone string like the one found in the TZ environment // variable, the time of the last time zone transition expressed as seconds // since January 1, 1970 00:00:00 UTC, and a time expressed the same way. // We call this a tzset string since in C the function tzset reads TZ. // The return values are as for lookup, plus ok which reports whether the // parse succeeded. func tzset(s string, lastTxSec, sec int64) (name string, offset int, start, end int64, isDST, ok bool) { var ( stdName, dstName string stdOffset, dstOffset int ) stdName, s, ok = tzsetName(s) if ok { stdOffset, s, ok = tzsetOffset(s) } if !ok { return "", 0, 0, 0, false, false } // The numbers in the tzset string are added to local time to get UTC, // but our offsets are added to UTC to get local time, // so we negate the number we see here. stdOffset = -stdOffset if len(s) == 0 || s[0] == ',' { // No daylight savings time. return stdName, stdOffset, lastTxSec, omega, false, true } dstName, s, ok = tzsetName(s) if ok { if len(s) == 0 || s[0] == ',' { dstOffset = stdOffset + secondsPerHour } else { dstOffset, s, ok = tzsetOffset(s) dstOffset = -dstOffset // as with stdOffset, above } } if !ok { return "", 0, 0, 0, false, false } if len(s) == 0 { // Default DST rules per tzcode. s = ",M3.2.0,M11.1.0" } // The TZ definition does not mention ';' here but tzcode accepts it. if s[0] != ',' && s[0] != ';' { return "", 0, 0, 0, false, false } s = s[1:] var startRule, endRule rule startRule, s, ok = tzsetRule(s) if !ok || len(s) == 0 || s[0] != ',' { return "", 0, 0, 0, false, false } s = s[1:] endRule, s, ok = tzsetRule(s) if !ok || len(s) > 0 { return "", 0, 0, 0, false, false } year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false) ysec := int64(yday*secondsPerDay) + sec%secondsPerDay // Compute start of year in seconds since Unix epoch. d := daysSinceEpoch(year) abs := int64(d * secondsPerDay) abs += absoluteToInternal + internalToUnix startSec := int64(tzruleTime(year, startRule, stdOffset)) endSec := int64(tzruleTime(year, endRule, dstOffset)) dstIsDST, stdIsDST := true, false // Note: this is a flipping of "DST" and "STD" while retaining the labels // This happens in southern hemispheres. The labelling here thus is a little // inconsistent with the goal. if endSec < startSec { startSec, endSec = endSec, startSec stdName, dstName = dstName, stdName stdOffset, dstOffset = dstOffset, stdOffset stdIsDST, dstIsDST = dstIsDST, stdIsDST } // The start and end values that we return are accurate // close to a daylight savings transition, but are otherwise // just the start and end of the year. That suffices for // the only caller that cares, which is Date. if ysec < startSec { return stdName, stdOffset, abs, startSec + abs, stdIsDST, true } else if ysec >= endSec { return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, stdIsDST, true } else { return dstName, dstOffset, startSec + abs, endSec + abs, dstIsDST, true } } // tzsetName returns the timezone name at the start of the tzset string s, // and the remainder of s, and reports whether the parsing is OK. func tzsetName(s string) (string, string, bool) { if len(s) == 0 { return "", "", false } if s[0] != '<' { for i, r := range s { switch r { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+': if i < 3 { return "", "", false } return s[:i], s[i:], true } } if len(s) < 3 { return "", "", false } return s, "", true } else { for i, r := range s { if r == '>' { return s[1:i], s[i+1:], true } } return "", "", false } } // tzsetOffset returns the timezone offset at the start of the tzset string s, // and the remainder of s, and reports whether the parsing is OK. // The timezone offset is returned as a number of seconds. func tzsetOffset(s string) (offset int, rest string, ok bool) { if len(s) == 0 { return 0, "", false } neg := false if s[0] == '+' { s = s[1:] } else if s[0] == '-' { s = s[1:] neg = true } // The tzdata code permits values up to 24 * 7 here, // although POSIX does not. var hours int hours, s, ok = tzsetNum(s, 0, 24*7) if !ok { return 0, "", false } off := hours * secondsPerHour if len(s) == 0 || s[0] != ':' { if neg { off = -off } return off, s, true } var mins int mins, s, ok = tzsetNum(s[1:], 0, 59) if !ok { return 0, "", false } off += mins * secondsPerMinute if len(s) == 0 || s[0] != ':' { if neg { off = -off } return off, s, true } var secs int secs, s, ok = tzsetNum(s[1:], 0, 59) if !ok { return 0, "", false } off += secs if neg { off = -off } return off, s, true } // ruleKind is the kinds of rules that can be seen in a tzset string. type ruleKind int const ( ruleJulian ruleKind = iota ruleDOY ruleMonthWeekDay ) // rule is a rule read from a tzset string. type rule struct { kind ruleKind day int week int mon int time int // transition time } // tzsetRule parses a rule from a tzset string. // It returns the rule, and the remainder of the string, and reports success. func tzsetRule(s string) (rule, string, bool) { var r rule if len(s) == 0 { return rule{}, "", false } ok := false if s[0] == 'J' { var jday int jday, s, ok = tzsetNum(s[1:], 1, 365) if !ok { return rule{}, "", false } r.kind = ruleJulian r.day = jday } else if s[0] == 'M' { var mon int mon, s, ok = tzsetNum(s[1:], 1, 12) if !ok || len(s) == 0 || s[0] != '.' { return rule{}, "", false } var week int week, s, ok = tzsetNum(s[1:], 1, 5) if !ok || len(s) == 0 || s[0] != '.' { return rule{}, "", false } var day int day, s, ok = tzsetNum(s[1:], 0, 6) if !ok { return rule{}, "", false } r.kind = ruleMonthWeekDay r.day = day r.week = week r.mon = mon } else { var day int day, s, ok = tzsetNum(s, 0, 365) if !ok { return rule{}, "", false } r.kind = ruleDOY r.day = day } if len(s) == 0 || s[0] != '/' { r.time = 2 * secondsPerHour // 2am is the default return r, s, true } offset, s, ok := tzsetOffset(s[1:]) if !ok { return rule{}, "", false } r.time = offset return r, s, true } // tzsetNum parses a number from a tzset string. // It returns the number, and the remainder of the string, and reports success. // The number must be between min and max. func tzsetNum(s string, min, max int) (num int, rest string, ok bool) { if len(s) == 0 { return 0, "", false } num = 0 for i, r := range s { if r < '0' || r > '9' { if i == 0 || num < min { return 0, "", false } return num, s[i:], true } num *= 10 num += int(r) - '0' if num > max { return 0, "", false } } if num < min { return 0, "", false } return num, "", true } // tzruleTime takes a year, a rule, and a timezone offset, // and returns the number of seconds since the start of the year // that the rule takes effect. func tzruleTime(year int, r rule, off int) int { var s int switch r.kind { case ruleJulian: s = (r.day - 1) * secondsPerDay if isLeap(year) && r.day >= 60 { s += secondsPerDay } case ruleDOY: s = r.day * secondsPerDay case ruleMonthWeekDay: // Zeller's Congruence. m1 := (r.mon+9)%12 + 1 yy0 := year if r.mon <= 2 { yy0-- } yy1 := yy0 / 100 yy2 := yy0 % 100 dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7 if dow < 0 { dow += 7 } // Now dow is the day-of-week of the first day of r.mon. // Get the day-of-month of the first "dow" day. d := r.day - dow if d < 0 { d += 7 } for i := 1; i < r.week; i++ { if d+7 >= daysIn(Month(r.mon), year) { break } d += 7 } d += int(daysBefore[r.mon-1]) if isLeap(year) && r.mon > 2 { d++ } s = d * secondsPerDay } return s + r.time - off } // lookupName returns information about the time zone with // the given name (such as "EST") at the given pseudo-Unix time // (what the given time of day would be in UTC). func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) { l = l.get() // First try for a zone with the right name that was actually // in effect at the given time. (In Sydney, Australia, both standard // and daylight-savings time are abbreviated "EST". Using the // offset helps us pick the right one for the given time. // It's not perfect: during the backward transition we might pick // either one.) for i := range l.zone { zone := &l.zone[i] if zone.name == name { nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset)) if nam == zone.name { return offset, true } } } // Otherwise fall back to an ordinary name match. for i := range l.zone { zone := &l.zone[i] if zone.name == name { return zone.offset, true } } // Otherwise, give up. return } // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment // syntax too, but I don't feel like implementing it today. var errLocation = errors.New("time: invalid location name") var zoneinfo *string var zoneinfoOnce sync.Once // LoadLocation returns the Location with the given name. // // If the name is "" or "UTC", LoadLocation returns UTC. // If the name is "Local", LoadLocation returns Local. // // Otherwise, the name is taken to be a location name corresponding to a file // in the IANA Time Zone database, such as "America/New_York". // // LoadLocation looks for the IANA Time Zone database in the following // locations in order: // // - the directory or uncompressed zip file named by the ZONEINFO environment variable // - on a Unix system, the system standard installation location // - $GOROOT/lib/time/zoneinfo.zip // - the time/tzdata package, if it was imported func LoadLocation(name string) (*Location, error) { if name == "" || name == "UTC" { return UTC, nil } if name == "Local" { return Local, nil } if containsDotDot(name) || name[0] == '/' || name[0] == '\\' { // No valid IANA Time Zone name contains a single dot, // much less dot dot. Likewise, none begin with a slash. return nil, errLocation } zoneinfoOnce.Do(func() { env, _ := syscall.Getenv("ZONEINFO") zoneinfo = &env }) var firstErr error if *zoneinfo != "" { if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil { if z, err := LoadLocationFromTZData(name, zoneData); err == nil { return z, nil } firstErr = err } else if err != syscall.ENOENT { firstErr = err } } if z, err := loadLocation(name, platformZoneSources); err == nil { return z, nil } else if firstErr == nil { firstErr = err } return nil, firstErr } // containsDotDot reports whether s contains "..". func containsDotDot(s string) bool { if len(s) < 2 { return false } for i := 0; i < len(s)-1; i++ { if s[i] == '.' && s[i+1] == '.' { return true } } return false }