clock.tcl 126 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547
  1. #----------------------------------------------------------------------
  2. #
  3. # clock.tcl --
  4. #
  5. # This file implements the portions of the [clock] ensemble that are
  6. # coded in Tcl. Refer to the users' manual to see the description of
  7. # the [clock] command and its subcommands.
  8. #
  9. #
  10. #----------------------------------------------------------------------
  11. #
  12. # Copyright (c) 2004,2005,2006,2007 by Kevin B. Kenny
  13. # See the file "license.terms" for information on usage and redistribution
  14. # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  15. #
  16. #----------------------------------------------------------------------
  17. # We must have message catalogs that support the root locale, and we need
  18. # access to the Registry on Windows systems.
  19. uplevel \#0 {
  20. package require msgcat 1.6
  21. if { $::tcl_platform(platform) eq {windows} } {
  22. if { [catch { package require registry 1.1 }] } {
  23. namespace eval ::tcl::clock [list variable NoRegistry {}]
  24. }
  25. }
  26. }
  27. # Put the library directory into the namespace for the ensemble so that the
  28. # library code can find message catalogs and time zone definition files.
  29. namespace eval ::tcl::clock \
  30. [list variable LibDir [file dirname [info script]]]
  31. #----------------------------------------------------------------------
  32. #
  33. # clock --
  34. #
  35. # Manipulate times.
  36. #
  37. # The 'clock' command manipulates time. Refer to the user documentation for
  38. # the available subcommands and what they do.
  39. #
  40. #----------------------------------------------------------------------
  41. namespace eval ::tcl::clock {
  42. # Export the subcommands
  43. namespace export format
  44. namespace export clicks
  45. namespace export microseconds
  46. namespace export milliseconds
  47. namespace export scan
  48. namespace export seconds
  49. namespace export add
  50. # Import the message catalog commands that we use.
  51. namespace import ::msgcat::mcload
  52. namespace import ::msgcat::mclocale
  53. namespace import ::msgcat::mc
  54. namespace import ::msgcat::mcpackagelocale
  55. }
  56. #----------------------------------------------------------------------
  57. #
  58. # ::tcl::clock::Initialize --
  59. #
  60. # Finish initializing the 'clock' subsystem
  61. #
  62. # Results:
  63. # None.
  64. #
  65. # Side effects:
  66. # Namespace variable in the 'clock' subsystem are initialized.
  67. #
  68. # The '::tcl::clock::Initialize' procedure initializes the namespace variables
  69. # and root locale message catalog for the 'clock' subsystem. It is broken
  70. # into a procedure rather than simply evaluated as a script so that it will be
  71. # able to use local variables, avoiding the dangers of 'creative writing' as
  72. # in Bug 1185933.
  73. #
  74. #----------------------------------------------------------------------
  75. proc ::tcl::clock::Initialize {} {
  76. rename ::tcl::clock::Initialize {}
  77. variable LibDir
  78. # Define the Greenwich time zone
  79. proc InitTZData {} {
  80. variable TZData
  81. array unset TZData
  82. set TZData(:Etc/GMT) {
  83. {-9223372036854775808 0 0 GMT}
  84. }
  85. set TZData(:GMT) $TZData(:Etc/GMT)
  86. set TZData(:Etc/UTC) {
  87. {-9223372036854775808 0 0 UTC}
  88. }
  89. set TZData(:UTC) $TZData(:Etc/UTC)
  90. set TZData(:localtime) {}
  91. }
  92. InitTZData
  93. mcpackagelocale set {}
  94. ::msgcat::mcpackageconfig set mcfolder [file join $LibDir msgs]
  95. ::msgcat::mcpackageconfig set unknowncmd ""
  96. ::msgcat::mcpackageconfig set changecmd ChangeCurrentLocale
  97. # Define the message catalog for the root locale.
  98. ::msgcat::mcmset {} {
  99. AM {am}
  100. BCE {B.C.E.}
  101. CE {C.E.}
  102. DATE_FORMAT {%m/%d/%Y}
  103. DATE_TIME_FORMAT {%a %b %e %H:%M:%S %Y}
  104. DAYS_OF_WEEK_ABBREV {
  105. Sun Mon Tue Wed Thu Fri Sat
  106. }
  107. DAYS_OF_WEEK_FULL {
  108. Sunday Monday Tuesday Wednesday Thursday Friday Saturday
  109. }
  110. GREGORIAN_CHANGE_DATE 2299161
  111. LOCALE_DATE_FORMAT {%m/%d/%Y}
  112. LOCALE_DATE_TIME_FORMAT {%a %b %e %H:%M:%S %Y}
  113. LOCALE_ERAS {}
  114. LOCALE_NUMERALS {
  115. 00 01 02 03 04 05 06 07 08 09
  116. 10 11 12 13 14 15 16 17 18 19
  117. 20 21 22 23 24 25 26 27 28 29
  118. 30 31 32 33 34 35 36 37 38 39
  119. 40 41 42 43 44 45 46 47 48 49
  120. 50 51 52 53 54 55 56 57 58 59
  121. 60 61 62 63 64 65 66 67 68 69
  122. 70 71 72 73 74 75 76 77 78 79
  123. 80 81 82 83 84 85 86 87 88 89
  124. 90 91 92 93 94 95 96 97 98 99
  125. }
  126. LOCALE_TIME_FORMAT {%H:%M:%S}
  127. LOCALE_YEAR_FORMAT {%EC%Ey}
  128. MONTHS_ABBREV {
  129. Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
  130. }
  131. MONTHS_FULL {
  132. January February March
  133. April May June
  134. July August September
  135. October November December
  136. }
  137. PM {pm}
  138. TIME_FORMAT {%H:%M:%S}
  139. TIME_FORMAT_12 {%I:%M:%S %P}
  140. TIME_FORMAT_24 {%H:%M}
  141. TIME_FORMAT_24_SECS {%H:%M:%S}
  142. }
  143. # Define a few Gregorian change dates for other locales. In most cases
  144. # the change date follows a language, because a nation's colonies changed
  145. # at the same time as the nation itself. In many cases, different
  146. # national boundaries existed; the dominating rule is to follow the
  147. # nation's capital.
  148. # Italy, Spain, Portugal, Poland
  149. ::msgcat::mcset it GREGORIAN_CHANGE_DATE 2299161
  150. ::msgcat::mcset es GREGORIAN_CHANGE_DATE 2299161
  151. ::msgcat::mcset pt GREGORIAN_CHANGE_DATE 2299161
  152. ::msgcat::mcset pl GREGORIAN_CHANGE_DATE 2299161
  153. # France, Austria
  154. ::msgcat::mcset fr GREGORIAN_CHANGE_DATE 2299227
  155. # For Belgium, we follow Southern Netherlands; Liege Diocese changed
  156. # several weeks later.
  157. ::msgcat::mcset fr_BE GREGORIAN_CHANGE_DATE 2299238
  158. ::msgcat::mcset nl_BE GREGORIAN_CHANGE_DATE 2299238
  159. # Austria
  160. ::msgcat::mcset de_AT GREGORIAN_CHANGE_DATE 2299527
  161. # Hungary
  162. ::msgcat::mcset hu GREGORIAN_CHANGE_DATE 2301004
  163. # Germany, Norway, Denmark (Catholic Germany changed earlier)
  164. ::msgcat::mcset de_DE GREGORIAN_CHANGE_DATE 2342032
  165. ::msgcat::mcset nb GREGORIAN_CHANGE_DATE 2342032
  166. ::msgcat::mcset nn GREGORIAN_CHANGE_DATE 2342032
  167. ::msgcat::mcset no GREGORIAN_CHANGE_DATE 2342032
  168. ::msgcat::mcset da GREGORIAN_CHANGE_DATE 2342032
  169. # Holland (Brabant, Gelderland, Flanders, Friesland, etc. changed at
  170. # various times)
  171. ::msgcat::mcset nl GREGORIAN_CHANGE_DATE 2342165
  172. # Protestant Switzerland (Catholic cantons changed earlier)
  173. ::msgcat::mcset fr_CH GREGORIAN_CHANGE_DATE 2361342
  174. ::msgcat::mcset it_CH GREGORIAN_CHANGE_DATE 2361342
  175. ::msgcat::mcset de_CH GREGORIAN_CHANGE_DATE 2361342
  176. # English speaking countries
  177. ::msgcat::mcset en GREGORIAN_CHANGE_DATE 2361222
  178. # Sweden (had several changes onto and off of the Gregorian calendar)
  179. ::msgcat::mcset sv GREGORIAN_CHANGE_DATE 2361390
  180. # Russia
  181. ::msgcat::mcset ru GREGORIAN_CHANGE_DATE 2421639
  182. # Romania (Transylvania changed earler - perhaps de_RO should show the
  183. # earlier date?)
  184. ::msgcat::mcset ro GREGORIAN_CHANGE_DATE 2422063
  185. # Greece
  186. ::msgcat::mcset el GREGORIAN_CHANGE_DATE 2423480
  187. #------------------------------------------------------------------
  188. #
  189. # CONSTANTS
  190. #
  191. #------------------------------------------------------------------
  192. # Paths at which binary time zone data for the Olson libraries are known
  193. # to reside on various operating systems
  194. variable ZoneinfoPaths {}
  195. foreach path {
  196. /usr/share/zoneinfo
  197. /usr/share/lib/zoneinfo
  198. /usr/lib/zoneinfo
  199. /usr/local/etc/zoneinfo
  200. } {
  201. if { [file isdirectory $path] } {
  202. lappend ZoneinfoPaths $path
  203. }
  204. }
  205. # Define the directories for time zone data and message catalogs.
  206. variable DataDir [file join $LibDir tzdata]
  207. # Number of days in the months, in common years and leap years.
  208. variable DaysInRomanMonthInCommonYear \
  209. { 31 28 31 30 31 30 31 31 30 31 30 31 }
  210. variable DaysInRomanMonthInLeapYear \
  211. { 31 29 31 30 31 30 31 31 30 31 30 31 }
  212. variable DaysInPriorMonthsInCommonYear [list 0]
  213. variable DaysInPriorMonthsInLeapYear [list 0]
  214. set i 0
  215. foreach j $DaysInRomanMonthInCommonYear {
  216. lappend DaysInPriorMonthsInCommonYear [incr i $j]
  217. }
  218. set i 0
  219. foreach j $DaysInRomanMonthInLeapYear {
  220. lappend DaysInPriorMonthsInLeapYear [incr i $j]
  221. }
  222. # Another epoch (Hi, Jeff!)
  223. variable Roddenberry 1946
  224. # Integer ranges
  225. variable MINWIDE -9223372036854775808
  226. variable MAXWIDE 9223372036854775807
  227. # Day before Leap Day
  228. variable FEB_28 58
  229. # Translation table to map Windows TZI onto cities, so that the Olson
  230. # rules can apply. In some cases the mapping is ambiguous, so it's wise
  231. # to specify $::env(TCL_TZ) rather than simply depending on the system
  232. # time zone.
  233. # The keys are long lists of values obtained from the time zone
  234. # information in the Registry. In order, the list elements are:
  235. # Bias StandardBias DaylightBias
  236. # StandardDate.wYear StandardDate.wMonth StandardDate.wDayOfWeek
  237. # StandardDate.wDay StandardDate.wHour StandardDate.wMinute
  238. # StandardDate.wSecond StandardDate.wMilliseconds
  239. # DaylightDate.wYear DaylightDate.wMonth DaylightDate.wDayOfWeek
  240. # DaylightDate.wDay DaylightDate.wHour DaylightDate.wMinute
  241. # DaylightDate.wSecond DaylightDate.wMilliseconds
  242. # The values are the names of time zones where those rules apply. There
  243. # is considerable ambiguity in certain zones; an attempt has been made to
  244. # make a reasonable guess, but this table needs to be taken with a grain
  245. # of salt.
  246. variable WinZoneInfo [dict create {*}{
  247. {-43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Kwajalein
  248. {-39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Midway
  249. {-36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Honolulu
  250. {-32400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Anchorage
  251. {-28800 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Los_Angeles
  252. {-28800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Tijuana
  253. {-25200 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Denver
  254. {-25200 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Chihuahua
  255. {-25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Phoenix
  256. {-21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Regina
  257. {-21600 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Chicago
  258. {-21600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Mexico_City
  259. {-18000 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/New_York
  260. {-18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Indianapolis
  261. {-14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Caracas
  262. {-14400 0 3600 0 3 6 2 23 59 59 999 0 10 6 2 23 59 59 999}
  263. :America/Santiago
  264. {-14400 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Manaus
  265. {-14400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Halifax
  266. {-12600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/St_Johns
  267. {-10800 0 3600 0 2 0 2 2 0 0 0 0 10 0 3 2 0 0 0} :America/Sao_Paulo
  268. {-10800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Godthab
  269. {-10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Buenos_Aires
  270. {-10800 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Bahia
  271. {-10800 0 3600 0 3 0 2 2 0 0 0 0 10 0 1 2 0 0 0} :America/Montevideo
  272. {-7200 0 3600 0 9 0 5 2 0 0 0 0 3 0 5 2 0 0 0} :America/Noronha
  273. {-3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Atlantic/Azores
  274. {-3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Atlantic/Cape_Verde
  275. {0 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :UTC
  276. {0 0 3600 0 10 0 5 2 0 0 0 0 3 0 5 1 0 0 0} :Europe/London
  277. {3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Kinshasa
  278. {3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :CET
  279. {7200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Harare
  280. {7200 0 3600 0 9 4 5 23 59 59 0 0 4 4 5 23 59 59 0}
  281. :Africa/Cairo
  282. {7200 0 3600 0 10 0 5 4 0 0 0 0 3 0 5 3 0 0 0} :Europe/Helsinki
  283. {7200 0 3600 0 9 0 3 2 0 0 0 0 3 5 5 2 0 0 0} :Asia/Jerusalem
  284. {7200 0 3600 0 9 0 5 1 0 0 0 0 3 0 5 0 0 0 0} :Europe/Bucharest
  285. {7200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Athens
  286. {7200 0 3600 0 9 5 5 1 0 0 0 0 3 4 5 0 0 0 0} :Asia/Amman
  287. {7200 0 3600 0 10 6 5 23 59 59 999 0 3 0 5 0 0 0 0}
  288. :Asia/Beirut
  289. {7200 0 -3600 0 4 0 1 2 0 0 0 0 9 0 1 2 0 0 0} :Africa/Windhoek
  290. {10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Riyadh
  291. {10800 0 3600 0 10 0 1 4 0 0 0 0 4 0 1 3 0 0 0} :Asia/Baghdad
  292. {10800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Moscow
  293. {12600 0 3600 0 9 2 4 2 0 0 0 0 3 0 1 2 0 0 0} :Asia/Tehran
  294. {14400 0 3600 0 10 0 5 5 0 0 0 0 3 0 5 4 0 0 0} :Asia/Baku
  295. {14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Muscat
  296. {14400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Tbilisi
  297. {16200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Kabul
  298. {18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Karachi
  299. {18000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yekaterinburg
  300. {19800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Calcutta
  301. {20700 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Katmandu
  302. {21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Dhaka
  303. {21600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Novosibirsk
  304. {23400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Rangoon
  305. {25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Bangkok
  306. {25200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Krasnoyarsk
  307. {28800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Chongqing
  308. {28800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Irkutsk
  309. {32400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Tokyo
  310. {32400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yakutsk
  311. {34200 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia/Adelaide
  312. {34200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia/Darwin
  313. {36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia/Brisbane
  314. {36000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Vladivostok
  315. {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 1 2 0 0 0} :Australia/Hobart
  316. {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia/Sydney
  317. {39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Noumea
  318. {43200 0 3600 0 3 0 3 3 0 0 0 0 10 0 1 2 0 0 0} :Pacific/Auckland
  319. {43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Fiji
  320. {46800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Tongatapu
  321. }]
  322. # Groups of fields that specify the date, priorities, and code bursts that
  323. # determine Julian Day Number given those groups. The code in [clock
  324. # scan] will choose the highest priority (lowest numbered) set of fields
  325. # that determines the date.
  326. variable DateParseActions {
  327. { seconds } 0 {}
  328. { julianDay } 1 {}
  329. { era century yearOfCentury month dayOfMonth } 2 {
  330. dict set date year [expr { 100 * [dict get $date century]
  331. + [dict get $date yearOfCentury] }]
  332. set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \
  333. $changeover]
  334. }
  335. { era century yearOfCentury dayOfYear } 2 {
  336. dict set date year [expr { 100 * [dict get $date century]
  337. + [dict get $date yearOfCentury] }]
  338. set date [GetJulianDayFromEraYearDay $date[set date {}] \
  339. $changeover]
  340. }
  341. { century yearOfCentury month dayOfMonth } 3 {
  342. dict set date era CE
  343. dict set date year [expr { 100 * [dict get $date century]
  344. + [dict get $date yearOfCentury] }]
  345. set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \
  346. $changeover]
  347. }
  348. { century yearOfCentury dayOfYear } 3 {
  349. dict set date era CE
  350. dict set date year [expr { 100 * [dict get $date century]
  351. + [dict get $date yearOfCentury] }]
  352. set date [GetJulianDayFromEraYearDay $date[set date {}] \
  353. $changeover]
  354. }
  355. { iso8601Century iso8601YearOfCentury iso8601Week dayOfWeek } 3 {
  356. dict set date era CE
  357. dict set date iso8601Year \
  358. [expr { 100 * [dict get $date iso8601Century]
  359. + [dict get $date iso8601YearOfCentury] }]
  360. set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \
  361. $changeover]
  362. }
  363. { yearOfCentury month dayOfMonth } 4 {
  364. set date [InterpretTwoDigitYear $date[set date {}] $baseTime]
  365. dict set date era CE
  366. set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \
  367. $changeover]
  368. }
  369. { yearOfCentury dayOfYear } 4 {
  370. set date [InterpretTwoDigitYear $date[set date {}] $baseTime]
  371. dict set date era CE
  372. set date [GetJulianDayFromEraYearDay $date[set date {}] \
  373. $changeover]
  374. }
  375. { iso8601YearOfCentury iso8601Week dayOfWeek } 4 {
  376. set date [InterpretTwoDigitYear \
  377. $date[set date {}] $baseTime \
  378. iso8601YearOfCentury iso8601Year]
  379. dict set date era CE
  380. set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \
  381. $changeover]
  382. }
  383. { month dayOfMonth } 5 {
  384. set date [AssignBaseYear $date[set date {}] \
  385. $baseTime $timeZone $changeover]
  386. set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \
  387. $changeover]
  388. }
  389. { dayOfYear } 5 {
  390. set date [AssignBaseYear $date[set date {}] \
  391. $baseTime $timeZone $changeover]
  392. set date [GetJulianDayFromEraYearDay $date[set date {}] \
  393. $changeover]
  394. }
  395. { iso8601Week dayOfWeek } 5 {
  396. set date [AssignBaseIso8601Year $date[set date {}] \
  397. $baseTime $timeZone $changeover]
  398. set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \
  399. $changeover]
  400. }
  401. { dayOfMonth } 6 {
  402. set date [AssignBaseMonth $date[set date {}] \
  403. $baseTime $timeZone $changeover]
  404. set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \
  405. $changeover]
  406. }
  407. { dayOfWeek } 7 {
  408. set date [AssignBaseWeek $date[set date {}] \
  409. $baseTime $timeZone $changeover]
  410. set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \
  411. $changeover]
  412. }
  413. {} 8 {
  414. set date [AssignBaseJulianDay $date[set date {}] \
  415. $baseTime $timeZone $changeover]
  416. }
  417. }
  418. # Groups of fields that specify time of day, priorities, and code that
  419. # processes them
  420. variable TimeParseActions {
  421. seconds 1 {}
  422. { hourAMPM minute second amPmIndicator } 2 {
  423. dict set date secondOfDay [InterpretHMSP $date]
  424. }
  425. { hour minute second } 2 {
  426. dict set date secondOfDay [InterpretHMS $date]
  427. }
  428. { hourAMPM minute amPmIndicator } 3 {
  429. dict set date second 0
  430. dict set date secondOfDay [InterpretHMSP $date]
  431. }
  432. { hour minute } 3 {
  433. dict set date second 0
  434. dict set date secondOfDay [InterpretHMS $date]
  435. }
  436. { hourAMPM amPmIndicator } 4 {
  437. dict set date minute 0
  438. dict set date second 0
  439. dict set date secondOfDay [InterpretHMSP $date]
  440. }
  441. { hour } 4 {
  442. dict set date minute 0
  443. dict set date second 0
  444. dict set date secondOfDay [InterpretHMS $date]
  445. }
  446. { } 5 {
  447. dict set date secondOfDay 0
  448. }
  449. }
  450. # Legacy time zones, used primarily for parsing RFC822 dates.
  451. variable LegacyTimeZone [dict create \
  452. gmt +0000 \
  453. ut +0000 \
  454. utc +0000 \
  455. bst +0100 \
  456. wet +0000 \
  457. wat -0100 \
  458. at -0200 \
  459. nft -0330 \
  460. nst -0330 \
  461. ndt -0230 \
  462. ast -0400 \
  463. adt -0300 \
  464. est -0500 \
  465. edt -0400 \
  466. cst -0600 \
  467. cdt -0500 \
  468. mst -0700 \
  469. mdt -0600 \
  470. pst -0800 \
  471. pdt -0700 \
  472. yst -0900 \
  473. ydt -0800 \
  474. hst -1000 \
  475. hdt -0900 \
  476. cat -1000 \
  477. ahst -1000 \
  478. nt -1100 \
  479. idlw -1200 \
  480. cet +0100 \
  481. cest +0200 \
  482. met +0100 \
  483. mewt +0100 \
  484. mest +0200 \
  485. swt +0100 \
  486. sst +0200 \
  487. fwt +0100 \
  488. fst +0200 \
  489. eet +0200 \
  490. eest +0300 \
  491. bt +0300 \
  492. it +0330 \
  493. zp4 +0400 \
  494. zp5 +0500 \
  495. ist +0530 \
  496. zp6 +0600 \
  497. wast +0700 \
  498. wadt +0800 \
  499. jt +0730 \
  500. cct +0800 \
  501. jst +0900 \
  502. kst +0900 \
  503. cast +0930 \
  504. jdt +1000 \
  505. kdt +1000 \
  506. cadt +1030 \
  507. east +1000 \
  508. eadt +1030 \
  509. gst +1000 \
  510. nzt +1200 \
  511. nzst +1200 \
  512. nzdt +1300 \
  513. idle +1200 \
  514. a +0100 \
  515. b +0200 \
  516. c +0300 \
  517. d +0400 \
  518. e +0500 \
  519. f +0600 \
  520. g +0700 \
  521. h +0800 \
  522. i +0900 \
  523. k +1000 \
  524. l +1100 \
  525. m +1200 \
  526. n -0100 \
  527. o -0200 \
  528. p -0300 \
  529. q -0400 \
  530. r -0500 \
  531. s -0600 \
  532. t -0700 \
  533. u -0800 \
  534. v -0900 \
  535. w -1000 \
  536. x -1100 \
  537. y -1200 \
  538. z +0000 \
  539. ]
  540. # Caches
  541. variable LocaleNumeralCache {}; # Dictionary whose keys are locale
  542. # names and whose values are pairs
  543. # comprising regexes matching numerals
  544. # in the given locales and dictionaries
  545. # mapping the numerals to their numeric
  546. # values.
  547. # variable CachedSystemTimeZone; # If 'CachedSystemTimeZone' exists,
  548. # it contains the value of the
  549. # system time zone, as determined from
  550. # the environment.
  551. variable TimeZoneBad {}; # Dictionary whose keys are time zone
  552. # names and whose values are 1 if
  553. # the time zone is unknown and 0
  554. # if it is known.
  555. variable TZData; # Array whose keys are time zone names
  556. # and whose values are lists of quads
  557. # comprising start time, UTC offset,
  558. # Daylight Saving Time indicator, and
  559. # time zone abbreviation.
  560. variable FormatProc; # Array mapping format group
  561. # and locale to the name of a procedure
  562. # that renders the given format
  563. }
  564. ::tcl::clock::Initialize
  565. #----------------------------------------------------------------------
  566. #
  567. # clock format --
  568. #
  569. # Formats a count of seconds since the Posix Epoch as a time of day.
  570. #
  571. # The 'clock format' command formats times of day for output. Refer to the
  572. # user documentation to see what it does.
  573. #
  574. #----------------------------------------------------------------------
  575. proc ::tcl::clock::format { args } {
  576. variable FormatProc
  577. variable TZData
  578. lassign [ParseFormatArgs {*}$args] format locale timezone
  579. set locale [string tolower $locale]
  580. set clockval [lindex $args 0]
  581. # Get the data for time changes in the given zone
  582. if {$timezone eq ""} {
  583. set timezone [GetSystemTimeZone]
  584. }
  585. if {![info exists TZData($timezone)]} {
  586. if {[catch {SetupTimeZone $timezone} retval opts]} {
  587. dict unset opts -errorinfo
  588. return -options $opts $retval
  589. }
  590. }
  591. # Build a procedure to format the result. Cache the built procedure's name
  592. # in the 'FormatProc' array to avoid losing its internal representation,
  593. # which contains the name resolution.
  594. set procName formatproc'$format'$locale
  595. set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName]
  596. if {[info exists FormatProc($procName)]} {
  597. set procName $FormatProc($procName)
  598. } else {
  599. set FormatProc($procName) \
  600. [ParseClockFormatFormat $procName $format $locale]
  601. }
  602. return [$procName $clockval $timezone]
  603. }
  604. #----------------------------------------------------------------------
  605. #
  606. # ParseClockFormatFormat --
  607. #
  608. # Builds and caches a procedure that formats a time value.
  609. #
  610. # Parameters:
  611. # format -- Format string to use
  612. # locale -- Locale in which the format string is to be interpreted
  613. #
  614. # Results:
  615. # Returns the name of the newly-built procedure.
  616. #
  617. #----------------------------------------------------------------------
  618. proc ::tcl::clock::ParseClockFormatFormat {procName format locale} {
  619. if {[namespace which $procName] ne {}} {
  620. return $procName
  621. }
  622. # Map away the locale-dependent composite format groups
  623. EnterLocale $locale
  624. # Change locale if a fresh locale has been given on the command line.
  625. try {
  626. return [ParseClockFormatFormat2 $format $locale $procName]
  627. } trap CLOCK {result opts} {
  628. dict unset opts -errorinfo
  629. return -options $opts $result
  630. }
  631. }
  632. proc ::tcl::clock::ParseClockFormatFormat2 {format locale procName} {
  633. set didLocaleEra 0
  634. set didLocaleNumerals 0
  635. set preFormatCode \
  636. [string map [list @GREGORIAN_CHANGE_DATE@ \
  637. [mc GREGORIAN_CHANGE_DATE]] \
  638. {
  639. variable TZData
  640. set date [GetDateFields $clockval \
  641. $TZData($timezone) \
  642. @GREGORIAN_CHANGE_DATE@]
  643. }]
  644. set formatString {}
  645. set substituents {}
  646. set state {}
  647. set format [LocalizeFormat $locale $format]
  648. foreach char [split $format {}] {
  649. switch -exact -- $state {
  650. {} {
  651. if { [string equal % $char] } {
  652. set state percent
  653. } else {
  654. append formatString $char
  655. }
  656. }
  657. percent { # Character following a '%' character
  658. set state {}
  659. switch -exact -- $char {
  660. % { # A literal character, '%'
  661. append formatString %%
  662. }
  663. a { # Day of week, abbreviated
  664. append formatString %s
  665. append substituents \
  666. [string map \
  667. [list @DAYS_OF_WEEK_ABBREV@ \
  668. [list [mc DAYS_OF_WEEK_ABBREV]]] \
  669. { [lindex @DAYS_OF_WEEK_ABBREV@ \
  670. [expr {[dict get $date dayOfWeek] \
  671. % 7}]]}]
  672. }
  673. A { # Day of week, spelt out.
  674. append formatString %s
  675. append substituents \
  676. [string map \
  677. [list @DAYS_OF_WEEK_FULL@ \
  678. [list [mc DAYS_OF_WEEK_FULL]]] \
  679. { [lindex @DAYS_OF_WEEK_FULL@ \
  680. [expr {[dict get $date dayOfWeek] \
  681. % 7}]]}]
  682. }
  683. b - h { # Name of month, abbreviated.
  684. append formatString %s
  685. append substituents \
  686. [string map \
  687. [list @MONTHS_ABBREV@ \
  688. [list [mc MONTHS_ABBREV]]] \
  689. { [lindex @MONTHS_ABBREV@ \
  690. [expr {[dict get $date month]-1}]]}]
  691. }
  692. B { # Name of month, spelt out
  693. append formatString %s
  694. append substituents \
  695. [string map \
  696. [list @MONTHS_FULL@ \
  697. [list [mc MONTHS_FULL]]] \
  698. { [lindex @MONTHS_FULL@ \
  699. [expr {[dict get $date month]-1}]]}]
  700. }
  701. C { # Century number
  702. append formatString %02d
  703. append substituents \
  704. { [expr {[dict get $date year] / 100}]}
  705. }
  706. d { # Day of month, with leading zero
  707. append formatString %02d
  708. append substituents { [dict get $date dayOfMonth]}
  709. }
  710. e { # Day of month, without leading zero
  711. append formatString %2d
  712. append substituents { [dict get $date dayOfMonth]}
  713. }
  714. E { # Format group in a locale-dependent
  715. # alternative era
  716. set state percentE
  717. if {!$didLocaleEra} {
  718. append preFormatCode \
  719. [string map \
  720. [list @LOCALE_ERAS@ \
  721. [list [mc LOCALE_ERAS]]] \
  722. {
  723. set date [GetLocaleEra \
  724. $date[set date {}] \
  725. @LOCALE_ERAS@]}] \n
  726. set didLocaleEra 1
  727. }
  728. if {!$didLocaleNumerals} {
  729. append preFormatCode \
  730. [list set localeNumerals \
  731. [mc LOCALE_NUMERALS]] \n
  732. set didLocaleNumerals 1
  733. }
  734. }
  735. g { # Two-digit year relative to ISO8601
  736. # week number
  737. append formatString %02d
  738. append substituents \
  739. { [expr { [dict get $date iso8601Year] % 100 }]}
  740. }
  741. G { # Four-digit year relative to ISO8601
  742. # week number
  743. append formatString %02d
  744. append substituents { [dict get $date iso8601Year]}
  745. }
  746. H { # Hour in the 24-hour day, leading zero
  747. append formatString %02d
  748. append substituents \
  749. { [expr { [dict get $date localSeconds] \
  750. / 3600 % 24}]}
  751. }
  752. I { # Hour AM/PM, with leading zero
  753. append formatString %02d
  754. append substituents \
  755. { [expr { ( ( ( [dict get $date localSeconds] \
  756. % 86400 ) \
  757. + 86400 \
  758. - 3600 ) \
  759. / 3600 ) \
  760. % 12 + 1 }] }
  761. }
  762. j { # Day of year (001-366)
  763. append formatString %03d
  764. append substituents { [dict get $date dayOfYear]}
  765. }
  766. J { # Julian Day Number
  767. append formatString %07ld
  768. append substituents { [dict get $date julianDay]}
  769. }
  770. k { # Hour (0-23), no leading zero
  771. append formatString %2d
  772. append substituents \
  773. { [expr { [dict get $date localSeconds]
  774. / 3600
  775. % 24 }]}
  776. }
  777. l { # Hour (12-11), no leading zero
  778. append formatString %2d
  779. append substituents \
  780. { [expr { ( ( ( [dict get $date localSeconds]
  781. % 86400 )
  782. + 86400
  783. - 3600 )
  784. / 3600 )
  785. % 12 + 1 }]}
  786. }
  787. m { # Month number, leading zero
  788. append formatString %02d
  789. append substituents { [dict get $date month]}
  790. }
  791. M { # Minute of the hour, leading zero
  792. append formatString %02d
  793. append substituents \
  794. { [expr { [dict get $date localSeconds]
  795. / 60
  796. % 60 }]}
  797. }
  798. n { # A literal newline
  799. append formatString \n
  800. }
  801. N { # Month number, no leading zero
  802. append formatString %2d
  803. append substituents { [dict get $date month]}
  804. }
  805. O { # A format group in the locale's
  806. # alternative numerals
  807. set state percentO
  808. if {!$didLocaleNumerals} {
  809. append preFormatCode \
  810. [list set localeNumerals \
  811. [mc LOCALE_NUMERALS]] \n
  812. set didLocaleNumerals 1
  813. }
  814. }
  815. p { # Localized 'AM' or 'PM' indicator
  816. # converted to uppercase
  817. append formatString %s
  818. append preFormatCode \
  819. [list set AM [string toupper [mc AM]]] \n \
  820. [list set PM [string toupper [mc PM]]] \n
  821. append substituents \
  822. { [expr {(([dict get $date localSeconds]
  823. % 86400) < 43200) ?
  824. $AM : $PM}]}
  825. }
  826. P { # Localized 'AM' or 'PM' indicator
  827. append formatString %s
  828. append preFormatCode \
  829. [list set am [mc AM]] \n \
  830. [list set pm [mc PM]] \n
  831. append substituents \
  832. { [expr {(([dict get $date localSeconds]
  833. % 86400) < 43200) ?
  834. $am : $pm}]}
  835. }
  836. Q { # Hi, Jeff!
  837. append formatString %s
  838. append substituents { [FormatStarDate $date]}
  839. }
  840. s { # Seconds from the Posix Epoch
  841. append formatString %s
  842. append substituents { [dict get $date seconds]}
  843. }
  844. S { # Second of the minute, with
  845. # leading zero
  846. append formatString %02d
  847. append substituents \
  848. { [expr { [dict get $date localSeconds]
  849. % 60 }]}
  850. }
  851. t { # A literal tab character
  852. append formatString \t
  853. }
  854. u { # Day of the week (1-Monday, 7-Sunday)
  855. append formatString %1d
  856. append substituents { [dict get $date dayOfWeek]}
  857. }
  858. U { # Week of the year (00-53). The
  859. # first Sunday of the year is the
  860. # first day of week 01
  861. append formatString %02d
  862. append preFormatCode {
  863. set dow [dict get $date dayOfWeek]
  864. if { $dow == 7 } {
  865. set dow 0
  866. }
  867. incr dow
  868. set UweekNumber \
  869. [expr { ( [dict get $date dayOfYear]
  870. - $dow + 7 )
  871. / 7 }]
  872. }
  873. append substituents { $UweekNumber}
  874. }
  875. V { # The ISO8601 week number
  876. append formatString %02d
  877. append substituents { [dict get $date iso8601Week]}
  878. }
  879. w { # Day of the week (0-Sunday,
  880. # 6-Saturday)
  881. append formatString %1d
  882. append substituents \
  883. { [expr { [dict get $date dayOfWeek] % 7 }]}
  884. }
  885. W { # Week of the year (00-53). The first
  886. # Monday of the year is the first day
  887. # of week 01.
  888. append preFormatCode {
  889. set WweekNumber \
  890. [expr { ( [dict get $date dayOfYear]
  891. - [dict get $date dayOfWeek]
  892. + 7 )
  893. / 7 }]
  894. }
  895. append formatString %02d
  896. append substituents { $WweekNumber}
  897. }
  898. y { # The two-digit year of the century
  899. append formatString %02d
  900. append substituents \
  901. { [expr { [dict get $date year] % 100 }]}
  902. }
  903. Y { # The four-digit year
  904. append formatString %04d
  905. append substituents { [dict get $date year]}
  906. }
  907. z { # The time zone as hours and minutes
  908. # east (+) or west (-) of Greenwich
  909. append formatString %s
  910. append substituents { [FormatNumericTimeZone \
  911. [dict get $date tzOffset]]}
  912. }
  913. Z { # The name of the time zone
  914. append formatString %s
  915. append substituents { [dict get $date tzName]}
  916. }
  917. % { # A literal percent character
  918. append formatString %%
  919. }
  920. default { # An unknown escape sequence
  921. append formatString %% $char
  922. }
  923. }
  924. }
  925. percentE { # Character following %E
  926. set state {}
  927. switch -exact -- $char {
  928. E {
  929. append formatString %s
  930. append substituents { } \
  931. [string map \
  932. [list @BCE@ [list [mc BCE]] \
  933. @CE@ [list [mc CE]]] \
  934. {[dict get {BCE @BCE@ CE @CE@} \
  935. [dict get $date era]]}]
  936. }
  937. C { # Locale-dependent era
  938. append formatString %s
  939. append substituents { [dict get $date localeEra]}
  940. }
  941. y { # Locale-dependent year of the era
  942. append preFormatCode {
  943. set y [dict get $date localeYear]
  944. if { $y >= 0 && $y < 100 } {
  945. set Eyear [lindex $localeNumerals $y]
  946. } else {
  947. set Eyear $y
  948. }
  949. }
  950. append formatString %s
  951. append substituents { $Eyear}
  952. }
  953. default { # Unknown %E format group
  954. append formatString %%E $char
  955. }
  956. }
  957. }
  958. percentO { # Character following %O
  959. set state {}
  960. switch -exact -- $char {
  961. d - e { # Day of the month in alternative
  962. # numerals
  963. append formatString %s
  964. append substituents \
  965. { [lindex $localeNumerals \
  966. [dict get $date dayOfMonth]]}
  967. }
  968. H - k { # Hour of the day in alternative
  969. # numerals
  970. append formatString %s
  971. append substituents \
  972. { [lindex $localeNumerals \
  973. [expr { [dict get $date localSeconds]
  974. / 3600
  975. % 24 }]]}
  976. }
  977. I - l { # Hour (12-11) AM/PM in alternative
  978. # numerals
  979. append formatString %s
  980. append substituents \
  981. { [lindex $localeNumerals \
  982. [expr { ( ( ( [dict get $date localSeconds]
  983. % 86400 )
  984. + 86400
  985. - 3600 )
  986. / 3600 )
  987. % 12 + 1 }]]}
  988. }
  989. m { # Month number in alternative numerals
  990. append formatString %s
  991. append substituents \
  992. { [lindex $localeNumerals [dict get $date month]]}
  993. }
  994. M { # Minute of the hour in alternative
  995. # numerals
  996. append formatString %s
  997. append substituents \
  998. { [lindex $localeNumerals \
  999. [expr { [dict get $date localSeconds]
  1000. / 60
  1001. % 60 }]]}
  1002. }
  1003. S { # Second of the minute in alternative
  1004. # numerals
  1005. append formatString %s
  1006. append substituents \
  1007. { [lindex $localeNumerals \
  1008. [expr { [dict get $date localSeconds]
  1009. % 60 }]]}
  1010. }
  1011. u { # Day of the week (Monday=1,Sunday=7)
  1012. # in alternative numerals
  1013. append formatString %s
  1014. append substituents \
  1015. { [lindex $localeNumerals \
  1016. [dict get $date dayOfWeek]]}
  1017. }
  1018. w { # Day of the week (Sunday=0,Saturday=6)
  1019. # in alternative numerals
  1020. append formatString %s
  1021. append substituents \
  1022. { [lindex $localeNumerals \
  1023. [expr { [dict get $date dayOfWeek] % 7 }]]}
  1024. }
  1025. y { # Year of the century in alternative
  1026. # numerals
  1027. append formatString %s
  1028. append substituents \
  1029. { [lindex $localeNumerals \
  1030. [expr { [dict get $date year] % 100 }]]}
  1031. }
  1032. default { # Unknown format group
  1033. append formatString %%O $char
  1034. }
  1035. }
  1036. }
  1037. }
  1038. }
  1039. # Clean up any improperly terminated groups
  1040. switch -exact -- $state {
  1041. percent {
  1042. append formatString %%
  1043. }
  1044. percentE {
  1045. append retval %%E
  1046. }
  1047. percentO {
  1048. append retval %%O
  1049. }
  1050. }
  1051. proc $procName {clockval timezone} "
  1052. $preFormatCode
  1053. return \[::format [list $formatString] $substituents\]
  1054. "
  1055. # puts [list $procName [info args $procName] [info body $procName]]
  1056. return $procName
  1057. }
  1058. #----------------------------------------------------------------------
  1059. #
  1060. # clock scan --
  1061. #
  1062. # Inputs a count of seconds since the Posix Epoch as a time of day.
  1063. #
  1064. # The 'clock format' command scans times of day on input. Refer to the user
  1065. # documentation to see what it does.
  1066. #
  1067. #----------------------------------------------------------------------
  1068. proc ::tcl::clock::scan { args } {
  1069. set format {}
  1070. # Check the count of args
  1071. if { [llength $args] < 1 || [llength $args] % 2 != 1 } {
  1072. set cmdName "clock scan"
  1073. return -code error \
  1074. -errorcode [list CLOCK wrongNumArgs] \
  1075. "wrong \# args: should be\
  1076. \"$cmdName string\
  1077. ?-base seconds?\
  1078. ?-format string? ?-gmt boolean?\
  1079. ?-locale LOCALE? ?-timezone ZONE?\""
  1080. }
  1081. # Set defaults
  1082. set base [clock seconds]
  1083. set string [lindex $args 0]
  1084. set format {}
  1085. set gmt 0
  1086. set locale c
  1087. set timezone [GetSystemTimeZone]
  1088. # Pick up command line options.
  1089. foreach { flag value } [lreplace $args 0 0] {
  1090. set saw($flag) {}
  1091. switch -exact -- $flag {
  1092. -b - -ba - -bas - -base {
  1093. set base $value
  1094. }
  1095. -f - -fo - -for - -form - -forma - -format {
  1096. set format $value
  1097. }
  1098. -g - -gm - -gmt {
  1099. set gmt $value
  1100. }
  1101. -l - -lo - -loc - -loca - -local - -locale {
  1102. set locale [string tolower $value]
  1103. }
  1104. -t - -ti - -tim - -time - -timez - -timezo - -timezon - -timezone {
  1105. set timezone $value
  1106. }
  1107. default {
  1108. return -code error \
  1109. -errorcode [list CLOCK badOption $flag] \
  1110. "bad option \"$flag\",\
  1111. must be -base, -format, -gmt, -locale or -timezone"
  1112. }
  1113. }
  1114. }
  1115. # Check options for validity
  1116. if { [info exists saw(-gmt)] && [info exists saw(-timezone)] } {
  1117. return -code error \
  1118. -errorcode [list CLOCK gmtWithTimezone] \
  1119. "cannot use -gmt and -timezone in same call"
  1120. }
  1121. if { [catch { expr { wide($base) } } result] } {
  1122. return -code error "expected integer but got \"$base\""
  1123. }
  1124. if { ![string is boolean -strict $gmt] } {
  1125. return -code error "expected boolean value but got \"$gmt\""
  1126. } elseif { $gmt } {
  1127. set timezone :GMT
  1128. }
  1129. if { ![info exists saw(-format)] } {
  1130. # Perhaps someday we'll localize the legacy code. Right now, it's not
  1131. # localized.
  1132. if { [info exists saw(-locale)] } {
  1133. return -code error \
  1134. -errorcode [list CLOCK flagWithLegacyFormat] \
  1135. "legacy \[clock scan\] does not support -locale"
  1136. }
  1137. return [FreeScan $string $base $timezone $locale]
  1138. }
  1139. # Change locale if a fresh locale has been given on the command line.
  1140. EnterLocale $locale
  1141. try {
  1142. # Map away the locale-dependent composite format groups
  1143. set scanner [ParseClockScanFormat $format $locale]
  1144. return [$scanner $string $base $timezone]
  1145. } trap CLOCK {result opts} {
  1146. # Conceal location of generation of expected errors
  1147. dict unset opts -errorinfo
  1148. return -options $opts $result
  1149. }
  1150. }
  1151. #----------------------------------------------------------------------
  1152. #
  1153. # FreeScan --
  1154. #
  1155. # Scans a time in free format
  1156. #
  1157. # Parameters:
  1158. # string - String containing the time to scan
  1159. # base - Base time, expressed in seconds from the Epoch
  1160. # timezone - Default time zone in which the time will be expressed
  1161. # locale - (Unused) Name of the locale where the time will be scanned.
  1162. #
  1163. # Results:
  1164. # Returns the date and time extracted from the string in seconds from
  1165. # the epoch
  1166. #
  1167. #----------------------------------------------------------------------
  1168. proc ::tcl::clock::FreeScan { string base timezone locale } {
  1169. variable TZData
  1170. # Get the data for time changes in the given zone
  1171. try {
  1172. SetupTimeZone $timezone
  1173. } on error {retval opts} {
  1174. dict unset opts -errorinfo
  1175. return -options $opts $retval
  1176. }
  1177. # Extract year, month and day from the base time for the parser to use as
  1178. # defaults
  1179. set date [GetDateFields $base $TZData($timezone) 2361222]
  1180. dict set date secondOfDay [expr {
  1181. [dict get $date localSeconds] % 86400
  1182. }]
  1183. # Parse the date. The parser will return a list comprising date, time,
  1184. # time zone, relative month/day/seconds, relative weekday, ordinal month.
  1185. try {
  1186. set scanned [Oldscan $string \
  1187. [dict get $date year] \
  1188. [dict get $date month] \
  1189. [dict get $date dayOfMonth]]
  1190. lassign $scanned \
  1191. parseDate parseTime parseZone parseRel \
  1192. parseWeekday parseOrdinalMonth
  1193. } on error message {
  1194. return -code error \
  1195. "unable to convert date-time string \"$string\": $message"
  1196. }
  1197. # If the caller supplied a date in the string, update the 'date' dict with
  1198. # the value. If the caller didn't specify a time with the date, default to
  1199. # midnight.
  1200. if { [llength $parseDate] > 0 } {
  1201. lassign $parseDate y m d
  1202. if { $y < 100 } {
  1203. if { $y >= 39 } {
  1204. incr y 1900
  1205. } else {
  1206. incr y 2000
  1207. }
  1208. }
  1209. dict set date era CE
  1210. dict set date year $y
  1211. dict set date month $m
  1212. dict set date dayOfMonth $d
  1213. if { $parseTime eq {} } {
  1214. set parseTime 0
  1215. }
  1216. }
  1217. # If the caller supplied a time zone in the string, it comes back as a
  1218. # two-element list; the first element is the number of minutes east of
  1219. # Greenwich, and the second is a Daylight Saving Time indicator (1 == yes,
  1220. # 0 == no, -1 == unknown). We make it into a time zone indicator of
  1221. # +-hhmm.
  1222. if { [llength $parseZone] > 0 } {
  1223. lassign $parseZone minEast dstFlag
  1224. set timezone [FormatNumericTimeZone \
  1225. [expr { 60 * $minEast + 3600 * $dstFlag }]]
  1226. SetupTimeZone $timezone
  1227. }
  1228. dict set date tzName $timezone
  1229. # Assemble date, time, zone into seconds-from-epoch
  1230. set date [GetJulianDayFromEraYearMonthDay $date[set date {}] 2361222]
  1231. if { $parseTime ne {} } {
  1232. dict set date secondOfDay $parseTime
  1233. } elseif { [llength $parseWeekday] != 0
  1234. || [llength $parseOrdinalMonth] != 0
  1235. || ( [llength $parseRel] != 0
  1236. && ( [lindex $parseRel 0] != 0
  1237. || [lindex $parseRel 1] != 0 ) ) } {
  1238. dict set date secondOfDay 0
  1239. }
  1240. dict set date localSeconds [expr {
  1241. -210866803200
  1242. + ( 86400 * wide([dict get $date julianDay]) )
  1243. + [dict get $date secondOfDay]
  1244. }]
  1245. dict set date tzName $timezone
  1246. set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) 2361222]
  1247. set seconds [dict get $date seconds]
  1248. # Do relative times
  1249. if { [llength $parseRel] > 0 } {
  1250. lassign $parseRel relMonth relDay relSecond
  1251. set seconds [add $seconds \
  1252. $relMonth months $relDay days $relSecond seconds \
  1253. -timezone $timezone -locale $locale]
  1254. }
  1255. # Do relative weekday
  1256. if { [llength $parseWeekday] > 0 } {
  1257. lassign $parseWeekday dayOrdinal dayOfWeek
  1258. set date2 [GetDateFields $seconds $TZData($timezone) 2361222]
  1259. dict set date2 era CE
  1260. set jdwkday [WeekdayOnOrBefore $dayOfWeek [expr {
  1261. [dict get $date2 julianDay] + 6
  1262. }]]
  1263. incr jdwkday [expr { 7 * $dayOrdinal }]
  1264. if { $dayOrdinal > 0 } {
  1265. incr jdwkday -7
  1266. }
  1267. dict set date2 secondOfDay \
  1268. [expr { [dict get $date2 localSeconds] % 86400 }]
  1269. dict set date2 julianDay $jdwkday
  1270. dict set date2 localSeconds [expr {
  1271. -210866803200
  1272. + ( 86400 * wide([dict get $date2 julianDay]) )
  1273. + [dict get $date secondOfDay]
  1274. }]
  1275. dict set date2 tzName $timezone
  1276. set date2 [ConvertLocalToUTC $date2[set date2 {}] $TZData($timezone) \
  1277. 2361222]
  1278. set seconds [dict get $date2 seconds]
  1279. }
  1280. # Do relative month
  1281. if { [llength $parseOrdinalMonth] > 0 } {
  1282. lassign $parseOrdinalMonth monthOrdinal monthNumber
  1283. if { $monthOrdinal > 0 } {
  1284. set monthDiff [expr { $monthNumber - [dict get $date month] }]
  1285. if { $monthDiff <= 0 } {
  1286. incr monthDiff 12
  1287. }
  1288. incr monthOrdinal -1
  1289. } else {
  1290. set monthDiff [expr { [dict get $date month] - $monthNumber }]
  1291. if { $monthDiff >= 0 } {
  1292. incr monthDiff -12
  1293. }
  1294. incr monthOrdinal
  1295. }
  1296. set seconds [add $seconds $monthOrdinal years $monthDiff months \
  1297. -timezone $timezone -locale $locale]
  1298. }
  1299. return $seconds
  1300. }
  1301. #----------------------------------------------------------------------
  1302. #
  1303. # ParseClockScanFormat --
  1304. #
  1305. # Parses a format string given to [clock scan -format]
  1306. #
  1307. # Parameters:
  1308. # formatString - The format being parsed
  1309. # locale - The current locale
  1310. #
  1311. # Results:
  1312. # Constructs and returns a procedure that accepts the string being
  1313. # scanned, the base time, and the time zone. The procedure will either
  1314. # return the scanned time or else throw an error that should be rethrown
  1315. # to the caller of [clock scan]
  1316. #
  1317. # Side effects:
  1318. # The given procedure is defined in the ::tcl::clock namespace. Scan
  1319. # procedures are not deleted once installed.
  1320. #
  1321. # Why do we parse dates by defining a procedure to parse them? The reason is
  1322. # that by doing so, we have one convenient place to cache all the information:
  1323. # the regular expressions that match the patterns (which will be compiled),
  1324. # the code that assembles the date information, everything lands in one place.
  1325. # In this way, when a given format is reused at run time, all the information
  1326. # of how to apply it is available in a single place.
  1327. #
  1328. #----------------------------------------------------------------------
  1329. proc ::tcl::clock::ParseClockScanFormat {formatString locale} {
  1330. # Check whether the format has been parsed previously, and return the
  1331. # existing recognizer if it has.
  1332. set procName scanproc'$formatString'$locale
  1333. set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName]
  1334. if { [namespace which $procName] != {} } {
  1335. return $procName
  1336. }
  1337. variable DateParseActions
  1338. variable TimeParseActions
  1339. # Localize the %x, %X, etc. groups
  1340. set formatString [LocalizeFormat $locale $formatString]
  1341. # Condense whitespace
  1342. regsub -all {[[:space:]]+} $formatString { } formatString
  1343. # Walk through the groups of the format string. In this loop, we
  1344. # accumulate:
  1345. # - a regular expression that matches the string,
  1346. # - the count of capturing brackets in the regexp
  1347. # - a set of code that post-processes the fields captured by the regexp,
  1348. # - a dictionary whose keys are the names of fields that are present
  1349. # in the format string.
  1350. set re {^[[:space:]]*}
  1351. set captureCount 0
  1352. set postcode {}
  1353. set fieldSet [dict create]
  1354. set fieldCount 0
  1355. set postSep {}
  1356. set state {}
  1357. foreach c [split $formatString {}] {
  1358. switch -exact -- $state {
  1359. {} {
  1360. if { $c eq "%" } {
  1361. set state %
  1362. } elseif { $c eq " " } {
  1363. append re {[[:space:]]+}
  1364. } else {
  1365. if { ! [string is alnum $c] } {
  1366. append re "\\"
  1367. }
  1368. append re $c
  1369. }
  1370. }
  1371. % {
  1372. set state {}
  1373. switch -exact -- $c {
  1374. % {
  1375. append re %
  1376. }
  1377. { } {
  1378. append re "\[\[:space:\]\]*"
  1379. }
  1380. a - A { # Day of week, in words
  1381. set l {}
  1382. foreach \
  1383. i {7 1 2 3 4 5 6} \
  1384. abr [mc DAYS_OF_WEEK_ABBREV] \
  1385. full [mc DAYS_OF_WEEK_FULL] {
  1386. dict set l [string tolower $abr] $i
  1387. dict set l [string tolower $full] $i
  1388. incr i
  1389. }
  1390. lassign [UniquePrefixRegexp $l] regex lookup
  1391. append re ( $regex )
  1392. dict set fieldSet dayOfWeek [incr fieldCount]
  1393. append postcode "dict set date dayOfWeek \[" \
  1394. "dict get " [list $lookup] " " \
  1395. \[ {string tolower $field} [incr captureCount] \] \
  1396. "\]\n"
  1397. }
  1398. b - B - h { # Name of month
  1399. set i 0
  1400. set l {}
  1401. foreach \
  1402. abr [mc MONTHS_ABBREV] \
  1403. full [mc MONTHS_FULL] {
  1404. incr i
  1405. dict set l [string tolower $abr] $i
  1406. dict set l [string tolower $full] $i
  1407. }
  1408. lassign [UniquePrefixRegexp $l] regex lookup
  1409. append re ( $regex )
  1410. dict set fieldSet month [incr fieldCount]
  1411. append postcode "dict set date month \[" \
  1412. "dict get " [list $lookup] \
  1413. " " \[ {string tolower $field} \
  1414. [incr captureCount] \] \
  1415. "\]\n"
  1416. }
  1417. C { # Gregorian century
  1418. append re \\s*(\\d\\d?)
  1419. dict set fieldSet century [incr fieldCount]
  1420. append postcode "dict set date century \[" \
  1421. "::scan \$field" [incr captureCount] " %d" \
  1422. "\]\n"
  1423. }
  1424. d - e { # Day of month
  1425. append re \\s*(\\d\\d?)
  1426. dict set fieldSet dayOfMonth [incr fieldCount]
  1427. append postcode "dict set date dayOfMonth \[" \
  1428. "::scan \$field" [incr captureCount] " %d" \
  1429. "\]\n"
  1430. }
  1431. E { # Prefix for locale-specific codes
  1432. set state %E
  1433. }
  1434. g { # ISO8601 2-digit year
  1435. append re \\s*(\\d\\d)
  1436. dict set fieldSet iso8601YearOfCentury \
  1437. [incr fieldCount]
  1438. append postcode \
  1439. "dict set date iso8601YearOfCentury \[" \
  1440. "::scan \$field" [incr captureCount] " %d" \
  1441. "\]\n"
  1442. }
  1443. G { # ISO8601 4-digit year
  1444. append re \\s*(\\d\\d)(\\d\\d)
  1445. dict set fieldSet iso8601Century [incr fieldCount]
  1446. dict set fieldSet iso8601YearOfCentury \
  1447. [incr fieldCount]
  1448. append postcode \
  1449. "dict set date iso8601Century \[" \
  1450. "::scan \$field" [incr captureCount] " %d" \
  1451. "\]\n" \
  1452. "dict set date iso8601YearOfCentury \[" \
  1453. "::scan \$field" [incr captureCount] " %d" \
  1454. "\]\n"
  1455. }
  1456. H - k { # Hour of day
  1457. append re \\s*(\\d\\d?)
  1458. dict set fieldSet hour [incr fieldCount]
  1459. append postcode "dict set date hour \[" \
  1460. "::scan \$field" [incr captureCount] " %d" \
  1461. "\]\n"
  1462. }
  1463. I - l { # Hour, AM/PM
  1464. append re \\s*(\\d\\d?)
  1465. dict set fieldSet hourAMPM [incr fieldCount]
  1466. append postcode "dict set date hourAMPM \[" \
  1467. "::scan \$field" [incr captureCount] " %d" \
  1468. "\]\n"
  1469. }
  1470. j { # Day of year
  1471. append re \\s*(\\d\\d?\\d?)
  1472. dict set fieldSet dayOfYear [incr fieldCount]
  1473. append postcode "dict set date dayOfYear \[" \
  1474. "::scan \$field" [incr captureCount] " %d" \
  1475. "\]\n"
  1476. }
  1477. J { # Julian Day Number
  1478. append re \\s*(\\d+)
  1479. dict set fieldSet julianDay [incr fieldCount]
  1480. append postcode "dict set date julianDay \[" \
  1481. "::scan \$field" [incr captureCount] " %ld" \
  1482. "\]\n"
  1483. }
  1484. m - N { # Month number
  1485. append re \\s*(\\d\\d?)
  1486. dict set fieldSet month [incr fieldCount]
  1487. append postcode "dict set date month \[" \
  1488. "::scan \$field" [incr captureCount] " %d" \
  1489. "\]\n"
  1490. }
  1491. M { # Minute
  1492. append re \\s*(\\d\\d?)
  1493. dict set fieldSet minute [incr fieldCount]
  1494. append postcode "dict set date minute \[" \
  1495. "::scan \$field" [incr captureCount] " %d" \
  1496. "\]\n"
  1497. }
  1498. n { # Literal newline
  1499. append re \\n
  1500. }
  1501. O { # Prefix for locale numerics
  1502. set state %O
  1503. }
  1504. p - P { # AM/PM indicator
  1505. set l [list [string tolower [mc AM]] 0 \
  1506. [string tolower [mc PM]] 1]
  1507. lassign [UniquePrefixRegexp $l] regex lookup
  1508. append re ( $regex )
  1509. dict set fieldSet amPmIndicator [incr fieldCount]
  1510. append postcode "dict set date amPmIndicator \[" \
  1511. "dict get " [list $lookup] " \[string tolower " \
  1512. "\$field" \
  1513. [incr captureCount] \
  1514. "\]\]\n"
  1515. }
  1516. Q { # Hi, Jeff!
  1517. append re {Stardate\s+([-+]?\d+)(\d\d\d)[.](\d)}
  1518. incr captureCount
  1519. dict set fieldSet seconds [incr fieldCount]
  1520. append postcode {dict set date seconds } \[ \
  1521. {ParseStarDate $field} [incr captureCount] \
  1522. { $field} [incr captureCount] \
  1523. { $field} [incr captureCount] \
  1524. \] \n
  1525. }
  1526. s { # Seconds from Posix Epoch
  1527. # This next case is insanely difficult, because it's
  1528. # problematic to determine whether the field is
  1529. # actually within the range of a wide integer.
  1530. append re {\s*([-+]?\d+)}
  1531. dict set fieldSet seconds [incr fieldCount]
  1532. append postcode {dict set date seconds } \[ \
  1533. {ScanWide $field} [incr captureCount] \] \n
  1534. }
  1535. S { # Second
  1536. append re \\s*(\\d\\d?)
  1537. dict set fieldSet second [incr fieldCount]
  1538. append postcode "dict set date second \[" \
  1539. "::scan \$field" [incr captureCount] " %d" \
  1540. "\]\n"
  1541. }
  1542. t { # Literal tab character
  1543. append re \\t
  1544. }
  1545. u - w { # Day number within week, 0 or 7 == Sun
  1546. # 1=Mon, 6=Sat
  1547. append re \\s*(\\d)
  1548. dict set fieldSet dayOfWeek [incr fieldCount]
  1549. append postcode {::scan $field} [incr captureCount] \
  1550. { %d dow} \n \
  1551. {
  1552. if { $dow == 0 } {
  1553. set dow 7
  1554. } elseif { $dow > 7 } {
  1555. return -code error \
  1556. -errorcode [list CLOCK badDayOfWeek] \
  1557. "day of week is greater than 7"
  1558. }
  1559. dict set date dayOfWeek $dow
  1560. }
  1561. }
  1562. U { # Week of year. The first Sunday of
  1563. # the year is the first day of week
  1564. # 01. No scan rule uses this group.
  1565. append re \\s*\\d\\d?
  1566. }
  1567. V { # Week of ISO8601 year
  1568. append re \\s*(\\d\\d?)
  1569. dict set fieldSet iso8601Week [incr fieldCount]
  1570. append postcode "dict set date iso8601Week \[" \
  1571. "::scan \$field" [incr captureCount] " %d" \
  1572. "\]\n"
  1573. }
  1574. W { # Week of the year (00-53). The first
  1575. # Monday of the year is the first day
  1576. # of week 01. No scan rule uses this
  1577. # group.
  1578. append re \\s*\\d\\d?
  1579. }
  1580. y { # Two-digit Gregorian year
  1581. append re \\s*(\\d\\d?)
  1582. dict set fieldSet yearOfCentury [incr fieldCount]
  1583. append postcode "dict set date yearOfCentury \[" \
  1584. "::scan \$field" [incr captureCount] " %d" \
  1585. "\]\n"
  1586. }
  1587. Y { # 4-digit Gregorian year
  1588. append re \\s*(\\d\\d)(\\d\\d)
  1589. dict set fieldSet century [incr fieldCount]
  1590. dict set fieldSet yearOfCentury [incr fieldCount]
  1591. append postcode \
  1592. "dict set date century \[" \
  1593. "::scan \$field" [incr captureCount] " %d" \
  1594. "\]\n" \
  1595. "dict set date yearOfCentury \[" \
  1596. "::scan \$field" [incr captureCount] " %d" \
  1597. "\]\n"
  1598. }
  1599. z - Z { # Time zone name
  1600. append re {(?:([-+]\d\d(?::?\d\d(?::?\d\d)?)?)|([[:alnum:]]{1,4}))}
  1601. dict set fieldSet tzName [incr fieldCount]
  1602. append postcode \
  1603. {if } \{ { $field} [incr captureCount] \
  1604. { ne "" } \} { } \{ \n \
  1605. {dict set date tzName $field} \
  1606. $captureCount \n \
  1607. \} { else } \{ \n \
  1608. {dict set date tzName } \[ \
  1609. {ConvertLegacyTimeZone $field} \
  1610. [incr captureCount] \] \n \
  1611. \} \n \
  1612. }
  1613. % { # Literal percent character
  1614. append re %
  1615. }
  1616. default {
  1617. append re %
  1618. if { ! [string is alnum $c] } {
  1619. append re \\
  1620. }
  1621. append re $c
  1622. }
  1623. }
  1624. }
  1625. %E {
  1626. switch -exact -- $c {
  1627. C { # Locale-dependent era
  1628. set d {}
  1629. foreach triple [mc LOCALE_ERAS] {
  1630. lassign $triple t symbol year
  1631. dict set d [string tolower $symbol] $year
  1632. }
  1633. lassign [UniquePrefixRegexp $d] regex lookup
  1634. append re (?: $regex )
  1635. }
  1636. E {
  1637. set l {}
  1638. dict set l [string tolower [mc BCE]] BCE
  1639. dict set l [string tolower [mc CE]] CE
  1640. dict set l b.c.e. BCE
  1641. dict set l c.e. CE
  1642. dict set l b.c. BCE
  1643. dict set l a.d. CE
  1644. lassign [UniquePrefixRegexp $l] regex lookup
  1645. append re ( $regex )
  1646. dict set fieldSet era [incr fieldCount]
  1647. append postcode "dict set date era \["\
  1648. "dict get " [list $lookup] \
  1649. { } \[ {string tolower $field} \
  1650. [incr captureCount] \] \
  1651. "\]\n"
  1652. }
  1653. y { # Locale-dependent year of the era
  1654. lassign [LocaleNumeralMatcher $locale] regex lookup
  1655. append re $regex
  1656. incr captureCount
  1657. }
  1658. default {
  1659. append re %E
  1660. if { ! [string is alnum $c] } {
  1661. append re \\
  1662. }
  1663. append re $c
  1664. }
  1665. }
  1666. set state {}
  1667. }
  1668. %O {
  1669. switch -exact -- $c {
  1670. d - e {
  1671. lassign [LocaleNumeralMatcher $locale] regex lookup
  1672. append re $regex
  1673. dict set fieldSet dayOfMonth [incr fieldCount]
  1674. append postcode "dict set date dayOfMonth \[" \
  1675. "dict get " [list $lookup] " \$field" \
  1676. [incr captureCount] \
  1677. "\]\n"
  1678. }
  1679. H - k {
  1680. lassign [LocaleNumeralMatcher $locale] regex lookup
  1681. append re $regex
  1682. dict set fieldSet hour [incr fieldCount]
  1683. append postcode "dict set date hour \[" \
  1684. "dict get " [list $lookup] " \$field" \
  1685. [incr captureCount] \
  1686. "\]\n"
  1687. }
  1688. I - l {
  1689. lassign [LocaleNumeralMatcher $locale] regex lookup
  1690. append re $regex
  1691. dict set fieldSet hourAMPM [incr fieldCount]
  1692. append postcode "dict set date hourAMPM \[" \
  1693. "dict get " [list $lookup] " \$field" \
  1694. [incr captureCount] \
  1695. "\]\n"
  1696. }
  1697. m {
  1698. lassign [LocaleNumeralMatcher $locale] regex lookup
  1699. append re $regex
  1700. dict set fieldSet month [incr fieldCount]
  1701. append postcode "dict set date month \[" \
  1702. "dict get " [list $lookup] " \$field" \
  1703. [incr captureCount] \
  1704. "\]\n"
  1705. }
  1706. M {
  1707. lassign [LocaleNumeralMatcher $locale] regex lookup
  1708. append re $regex
  1709. dict set fieldSet minute [incr fieldCount]
  1710. append postcode "dict set date minute \[" \
  1711. "dict get " [list $lookup] " \$field" \
  1712. [incr captureCount] \
  1713. "\]\n"
  1714. }
  1715. S {
  1716. lassign [LocaleNumeralMatcher $locale] regex lookup
  1717. append re $regex
  1718. dict set fieldSet second [incr fieldCount]
  1719. append postcode "dict set date second \[" \
  1720. "dict get " [list $lookup] " \$field" \
  1721. [incr captureCount] \
  1722. "\]\n"
  1723. }
  1724. u - w {
  1725. lassign [LocaleNumeralMatcher $locale] regex lookup
  1726. append re $regex
  1727. dict set fieldSet dayOfWeek [incr fieldCount]
  1728. append postcode "set dow \[dict get " [list $lookup] \
  1729. { $field} [incr captureCount] \] \n \
  1730. {
  1731. if { $dow == 0 } {
  1732. set dow 7
  1733. } elseif { $dow > 7 } {
  1734. return -code error \
  1735. -errorcode [list CLOCK badDayOfWeek] \
  1736. "day of week is greater than 7"
  1737. }
  1738. dict set date dayOfWeek $dow
  1739. }
  1740. }
  1741. y {
  1742. lassign [LocaleNumeralMatcher $locale] regex lookup
  1743. append re $regex
  1744. dict set fieldSet yearOfCentury [incr fieldCount]
  1745. append postcode {dict set date yearOfCentury } \[ \
  1746. {dict get } [list $lookup] { $field} \
  1747. [incr captureCount] \] \n
  1748. }
  1749. default {
  1750. append re %O
  1751. if { ! [string is alnum $c] } {
  1752. append re \\
  1753. }
  1754. append re $c
  1755. }
  1756. }
  1757. set state {}
  1758. }
  1759. }
  1760. }
  1761. # Clean up any unfinished format groups
  1762. append re $state \\s*\$
  1763. # Build the procedure
  1764. set procBody {}
  1765. append procBody "variable ::tcl::clock::TZData" \n
  1766. append procBody "if \{ !\[ regexp -nocase [list $re] \$string ->"
  1767. for { set i 1 } { $i <= $captureCount } { incr i } {
  1768. append procBody " " field $i
  1769. }
  1770. append procBody "\] \} \{" \n
  1771. append procBody {
  1772. return -code error -errorcode [list CLOCK badInputString] \
  1773. {input string does not match supplied format}
  1774. }
  1775. append procBody \}\n
  1776. append procBody "set date \[dict create\]" \n
  1777. append procBody {dict set date tzName $timeZone} \n
  1778. append procBody $postcode
  1779. append procBody [list set changeover [mc GREGORIAN_CHANGE_DATE]] \n
  1780. # Set up the time zone before doing anything with a default base date
  1781. # that might need a timezone to interpret it.
  1782. if { ![dict exists $fieldSet seconds]
  1783. && ![dict exists $fieldSet starDate] } {
  1784. if { [dict exists $fieldSet tzName] } {
  1785. append procBody {
  1786. set timeZone [dict get $date tzName]
  1787. }
  1788. }
  1789. append procBody {
  1790. ::tcl::clock::SetupTimeZone $timeZone
  1791. }
  1792. }
  1793. # Add code that gets Julian Day Number from the fields.
  1794. append procBody [MakeParseCodeFromFields $fieldSet $DateParseActions]
  1795. # Get time of day
  1796. append procBody [MakeParseCodeFromFields $fieldSet $TimeParseActions]
  1797. # Assemble seconds from the Julian day and second of the day.
  1798. # Convert to local time unless epoch seconds or stardate are
  1799. # being processed - they're always absolute
  1800. if { ![dict exists $fieldSet seconds]
  1801. && ![dict exists $fieldSet starDate] } {
  1802. append procBody {
  1803. if { [dict get $date julianDay] > 5373484 } {
  1804. return -code error -errorcode [list CLOCK dateTooLarge] \
  1805. "requested date too large to represent"
  1806. }
  1807. dict set date localSeconds [expr {
  1808. -210866803200
  1809. + ( 86400 * wide([dict get $date julianDay]) )
  1810. + [dict get $date secondOfDay]
  1811. }]
  1812. }
  1813. # Finally, convert the date to local time
  1814. append procBody {
  1815. set date [::tcl::clock::ConvertLocalToUTC $date[set date {}] \
  1816. $TZData($timeZone) $changeover]
  1817. }
  1818. }
  1819. # Return result
  1820. append procBody {return [dict get $date seconds]} \n
  1821. proc $procName { string baseTime timeZone } $procBody
  1822. # puts [list proc $procName [list string baseTime timeZone] $procBody]
  1823. return $procName
  1824. }
  1825. #----------------------------------------------------------------------
  1826. #
  1827. # LocaleNumeralMatcher --
  1828. #
  1829. # Composes a regexp that captures the numerals in the given locale, and
  1830. # a dictionary to map them to conventional numerals.
  1831. #
  1832. # Parameters:
  1833. # locale - Name of the current locale
  1834. #
  1835. # Results:
  1836. # Returns a two-element list comprising the regexp and the dictionary.
  1837. #
  1838. # Side effects:
  1839. # Caches the result.
  1840. #
  1841. #----------------------------------------------------------------------
  1842. proc ::tcl::clock::LocaleNumeralMatcher {l} {
  1843. variable LocaleNumeralCache
  1844. if { ![dict exists $LocaleNumeralCache $l] } {
  1845. set d {}
  1846. set i 0
  1847. set sep \(
  1848. foreach n [mc LOCALE_NUMERALS] {
  1849. dict set d $n $i
  1850. regsub -all {[^[:alnum:]]} $n \\\\& subex
  1851. append re $sep $subex
  1852. set sep |
  1853. incr i
  1854. }
  1855. append re \)
  1856. dict set LocaleNumeralCache $l [list $re $d]
  1857. }
  1858. return [dict get $LocaleNumeralCache $l]
  1859. }
  1860. #----------------------------------------------------------------------
  1861. #
  1862. # UniquePrefixRegexp --
  1863. #
  1864. # Composes a regexp that performs unique-prefix matching. The RE
  1865. # matches one of a supplied set of strings, or any unique prefix
  1866. # thereof.
  1867. #
  1868. # Parameters:
  1869. # data - List of alternating match-strings and values.
  1870. # Match-strings with distinct values are considered
  1871. # distinct.
  1872. #
  1873. # Results:
  1874. # Returns a two-element list. The first is a regexp that matches any
  1875. # unique prefix of any of the strings. The second is a dictionary whose
  1876. # keys are match values from the regexp and whose values are the
  1877. # corresponding values from 'data'.
  1878. #
  1879. # Side effects:
  1880. # None.
  1881. #
  1882. #----------------------------------------------------------------------
  1883. proc ::tcl::clock::UniquePrefixRegexp { data } {
  1884. # The 'successors' dictionary will contain, for each string that is a
  1885. # prefix of any key, all characters that may follow that prefix. The
  1886. # 'prefixMapping' dictionary will have keys that are prefixes of keys and
  1887. # values that correspond to the keys.
  1888. set prefixMapping [dict create]
  1889. set successors [dict create {} {}]
  1890. # Walk the key-value pairs
  1891. foreach { key value } $data {
  1892. # Construct all prefixes of the key;
  1893. set prefix {}
  1894. foreach char [split $key {}] {
  1895. set oldPrefix $prefix
  1896. dict set successors $oldPrefix $char {}
  1897. append prefix $char
  1898. # Put the prefixes in the 'prefixMapping' and 'successors'
  1899. # dictionaries
  1900. dict lappend prefixMapping $prefix $value
  1901. if { ![dict exists $successors $prefix] } {
  1902. dict set successors $prefix {}
  1903. }
  1904. }
  1905. }
  1906. # Identify those prefixes that designate unique values, and those that are
  1907. # the full keys
  1908. set uniquePrefixMapping {}
  1909. dict for { key valueList } $prefixMapping {
  1910. if { [llength $valueList] == 1 } {
  1911. dict set uniquePrefixMapping $key [lindex $valueList 0]
  1912. }
  1913. }
  1914. foreach { key value } $data {
  1915. dict set uniquePrefixMapping $key $value
  1916. }
  1917. # Construct the re.
  1918. return [list \
  1919. [MakeUniquePrefixRegexp $successors $uniquePrefixMapping {}] \
  1920. $uniquePrefixMapping]
  1921. }
  1922. #----------------------------------------------------------------------
  1923. #
  1924. # MakeUniquePrefixRegexp --
  1925. #
  1926. # Service procedure for 'UniquePrefixRegexp' that constructs a regular
  1927. # expresison that matches the unique prefixes.
  1928. #
  1929. # Parameters:
  1930. # successors - Dictionary whose keys are all prefixes
  1931. # of keys passed to 'UniquePrefixRegexp' and whose
  1932. # values are dictionaries whose keys are the characters
  1933. # that may follow those prefixes.
  1934. # uniquePrefixMapping - Dictionary whose keys are the unique
  1935. # prefixes and whose values are not examined.
  1936. # prefixString - Current prefix being processed.
  1937. #
  1938. # Results:
  1939. # Returns a constructed regular expression that matches the set of
  1940. # unique prefixes beginning with the 'prefixString'.
  1941. #
  1942. # Side effects:
  1943. # None.
  1944. #
  1945. #----------------------------------------------------------------------
  1946. proc ::tcl::clock::MakeUniquePrefixRegexp { successors
  1947. uniquePrefixMapping
  1948. prefixString } {
  1949. # Get the characters that may follow the current prefix string
  1950. set schars [lsort -ascii [dict keys [dict get $successors $prefixString]]]
  1951. if { [llength $schars] == 0 } {
  1952. return {}
  1953. }
  1954. # If there is more than one successor character, or if the current prefix
  1955. # is a unique prefix, surround the generated re with non-capturing
  1956. # parentheses.
  1957. set re {}
  1958. if {
  1959. [dict exists $uniquePrefixMapping $prefixString]
  1960. || [llength $schars] > 1
  1961. } then {
  1962. append re "(?:"
  1963. }
  1964. # Generate a regexp that matches the successors.
  1965. set sep ""
  1966. foreach { c } $schars {
  1967. set nextPrefix $prefixString$c
  1968. regsub -all {[^[:alnum:]]} $c \\\\& rechar
  1969. append re $sep $rechar \
  1970. [MakeUniquePrefixRegexp \
  1971. $successors $uniquePrefixMapping $nextPrefix]
  1972. set sep |
  1973. }
  1974. # If the current prefix is a unique prefix, make all following text
  1975. # optional. Otherwise, if there is more than one successor character,
  1976. # close the non-capturing parentheses.
  1977. if { [dict exists $uniquePrefixMapping $prefixString] } {
  1978. append re ")?"
  1979. } elseif { [llength $schars] > 1 } {
  1980. append re ")"
  1981. }
  1982. return $re
  1983. }
  1984. #----------------------------------------------------------------------
  1985. #
  1986. # MakeParseCodeFromFields --
  1987. #
  1988. # Composes Tcl code to extract the Julian Day Number from a dictionary
  1989. # containing date fields.
  1990. #
  1991. # Parameters:
  1992. # dateFields -- Dictionary whose keys are fields of the date,
  1993. # and whose values are the rightmost positions
  1994. # at which those fields appear.
  1995. # parseActions -- List of triples: field set, priority, and
  1996. # code to emit. Smaller priorities are better, and
  1997. # the list must be in ascending order by priority
  1998. #
  1999. # Results:
  2000. # Returns a burst of code that extracts the day number from the given
  2001. # date.
  2002. #
  2003. # Side effects:
  2004. # None.
  2005. #
  2006. #----------------------------------------------------------------------
  2007. proc ::tcl::clock::MakeParseCodeFromFields { dateFields parseActions } {
  2008. set currPrio 999
  2009. set currFieldPos [list]
  2010. set currCodeBurst {
  2011. error "in ::tcl::clock::MakeParseCodeFromFields: can't happen"
  2012. }
  2013. foreach { fieldSet prio parseAction } $parseActions {
  2014. # If we've found an answer that's better than any that follow, quit
  2015. # now.
  2016. if { $prio > $currPrio } {
  2017. break
  2018. }
  2019. # Accumulate the field positions that are used in the current field
  2020. # grouping.
  2021. set fieldPos [list]
  2022. set ok true
  2023. foreach field $fieldSet {
  2024. if { ! [dict exists $dateFields $field] } {
  2025. set ok 0
  2026. break
  2027. }
  2028. lappend fieldPos [dict get $dateFields $field]
  2029. }
  2030. # Quit if we don't have a complete set of fields
  2031. if { !$ok } {
  2032. continue
  2033. }
  2034. # Determine whether the current answer is better than the last.
  2035. set fPos [lsort -integer -decreasing $fieldPos]
  2036. if { $prio == $currPrio } {
  2037. foreach currPos $currFieldPos newPos $fPos {
  2038. if {
  2039. ![string is integer $newPos]
  2040. || ![string is integer $currPos]
  2041. || $newPos > $currPos
  2042. } then {
  2043. break
  2044. }
  2045. if { $newPos < $currPos } {
  2046. set ok 0
  2047. break
  2048. }
  2049. }
  2050. }
  2051. if { !$ok } {
  2052. continue
  2053. }
  2054. # Remember the best possibility for extracting date information
  2055. set currPrio $prio
  2056. set currFieldPos $fPos
  2057. set currCodeBurst $parseAction
  2058. }
  2059. return $currCodeBurst
  2060. }
  2061. #----------------------------------------------------------------------
  2062. #
  2063. # EnterLocale --
  2064. #
  2065. # Switch [mclocale] to a given locale if necessary
  2066. #
  2067. # Parameters:
  2068. # locale -- Desired locale
  2069. #
  2070. # Results:
  2071. # Returns the locale that was previously current.
  2072. #
  2073. # Side effects:
  2074. # Does [mclocale]. If necessary, loades the designated locale's files.
  2075. #
  2076. #----------------------------------------------------------------------
  2077. proc ::tcl::clock::EnterLocale { locale } {
  2078. if { $locale eq {system} } {
  2079. if { $::tcl_platform(platform) ne {windows} } {
  2080. # On a non-windows platform, the 'system' locale is the same as
  2081. # the 'current' locale
  2082. set locale current
  2083. } else {
  2084. # On a windows platform, the 'system' locale is adapted from the
  2085. # 'current' locale by applying the date and time formats from the
  2086. # Control Panel. First, load the 'current' locale if it's not yet
  2087. # loaded
  2088. mcpackagelocale set [mclocale]
  2089. # Make a new locale string for the system locale, and get the
  2090. # Control Panel information
  2091. set locale [mclocale]_windows
  2092. if { ! [mcpackagelocale present $locale] } {
  2093. LoadWindowsDateTimeFormats $locale
  2094. }
  2095. }
  2096. }
  2097. if { $locale eq {current}} {
  2098. set locale [mclocale]
  2099. }
  2100. # Eventually load the locale
  2101. mcpackagelocale set $locale
  2102. }
  2103. #----------------------------------------------------------------------
  2104. #
  2105. # LoadWindowsDateTimeFormats --
  2106. #
  2107. # Load the date/time formats from the Control Panel in Windows and
  2108. # convert them so that they're usable by Tcl.
  2109. #
  2110. # Parameters:
  2111. # locale - Name of the locale in whose message catalog
  2112. # the converted formats are to be stored.
  2113. #
  2114. # Results:
  2115. # None.
  2116. #
  2117. # Side effects:
  2118. # Updates the given message catalog with the locale strings.
  2119. #
  2120. # Presumes that on entry, [mclocale] is set to the current locale, so that
  2121. # default strings can be obtained if the Registry query fails.
  2122. #
  2123. #----------------------------------------------------------------------
  2124. proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } {
  2125. # Bail out if we can't find the Registry
  2126. variable NoRegistry
  2127. if { [info exists NoRegistry] } return
  2128. if { ![catch {
  2129. registry get "HKEY_CURRENT_USER\\Control Panel\\International" \
  2130. sShortDate
  2131. } string] } {
  2132. set quote {}
  2133. set datefmt {}
  2134. foreach { unquoted quoted } [split $string '] {
  2135. append datefmt $quote [string map {
  2136. dddd %A
  2137. ddd %a
  2138. dd %d
  2139. d %e
  2140. MMMM %B
  2141. MMM %b
  2142. MM %m
  2143. M %N
  2144. yyyy %Y
  2145. yy %y
  2146. y %y
  2147. gg {}
  2148. } $unquoted]
  2149. if { $quoted eq {} } {
  2150. set quote '
  2151. } else {
  2152. set quote $quoted
  2153. }
  2154. }
  2155. ::msgcat::mcset $locale DATE_FORMAT $datefmt
  2156. }
  2157. if { ![catch {
  2158. registry get "HKEY_CURRENT_USER\\Control Panel\\International" \
  2159. sLongDate
  2160. } string] } {
  2161. set quote {}
  2162. set ldatefmt {}
  2163. foreach { unquoted quoted } [split $string '] {
  2164. append ldatefmt $quote [string map {
  2165. dddd %A
  2166. ddd %a
  2167. dd %d
  2168. d %e
  2169. MMMM %B
  2170. MMM %b
  2171. MM %m
  2172. M %N
  2173. yyyy %Y
  2174. yy %y
  2175. y %y
  2176. gg {}
  2177. } $unquoted]
  2178. if { $quoted eq {} } {
  2179. set quote '
  2180. } else {
  2181. set quote $quoted
  2182. }
  2183. }
  2184. ::msgcat::mcset $locale LOCALE_DATE_FORMAT $ldatefmt
  2185. }
  2186. if { ![catch {
  2187. registry get "HKEY_CURRENT_USER\\Control Panel\\International" \
  2188. sTimeFormat
  2189. } string] } {
  2190. set quote {}
  2191. set timefmt {}
  2192. foreach { unquoted quoted } [split $string '] {
  2193. append timefmt $quote [string map {
  2194. HH %H
  2195. H %k
  2196. hh %I
  2197. h %l
  2198. mm %M
  2199. m %M
  2200. ss %S
  2201. s %S
  2202. tt %p
  2203. t %p
  2204. } $unquoted]
  2205. if { $quoted eq {} } {
  2206. set quote '
  2207. } else {
  2208. set quote $quoted
  2209. }
  2210. }
  2211. ::msgcat::mcset $locale TIME_FORMAT $timefmt
  2212. }
  2213. catch {
  2214. ::msgcat::mcset $locale DATE_TIME_FORMAT "$datefmt $timefmt"
  2215. }
  2216. catch {
  2217. ::msgcat::mcset $locale LOCALE_DATE_TIME_FORMAT "$ldatefmt $timefmt"
  2218. }
  2219. return
  2220. }
  2221. #----------------------------------------------------------------------
  2222. #
  2223. # LocalizeFormat --
  2224. #
  2225. # Map away locale-dependent format groups in a clock format.
  2226. #
  2227. # Parameters:
  2228. # locale -- Current [mclocale] locale, supplied to avoid
  2229. # an extra call
  2230. # format -- Format supplied to [clock scan] or [clock format]
  2231. #
  2232. # Results:
  2233. # Returns the string with locale-dependent composite format groups
  2234. # substituted out.
  2235. #
  2236. # Side effects:
  2237. # None.
  2238. #
  2239. #----------------------------------------------------------------------
  2240. proc ::tcl::clock::LocalizeFormat { locale format } {
  2241. # message catalog key to cache this format
  2242. set key FORMAT_$format
  2243. if { [::msgcat::mcexists -exactlocale -exactnamespace $key] } {
  2244. return [mc $key]
  2245. }
  2246. # Handle locale-dependent format groups by mapping them out of the format
  2247. # string. Note that the order of the [string map] operations is
  2248. # significant because later formats can refer to later ones; for example
  2249. # %c can refer to %X, which in turn can refer to %T.
  2250. set list {
  2251. %% %%
  2252. %D %m/%d/%Y
  2253. %+ {%a %b %e %H:%M:%S %Z %Y}
  2254. }
  2255. lappend list %EY [string map $list [mc LOCALE_YEAR_FORMAT]]
  2256. lappend list %T [string map $list [mc TIME_FORMAT_24_SECS]]
  2257. lappend list %R [string map $list [mc TIME_FORMAT_24]]
  2258. lappend list %r [string map $list [mc TIME_FORMAT_12]]
  2259. lappend list %X [string map $list [mc TIME_FORMAT]]
  2260. lappend list %EX [string map $list [mc LOCALE_TIME_FORMAT]]
  2261. lappend list %x [string map $list [mc DATE_FORMAT]]
  2262. lappend list %Ex [string map $list [mc LOCALE_DATE_FORMAT]]
  2263. lappend list %c [string map $list [mc DATE_TIME_FORMAT]]
  2264. lappend list %Ec [string map $list [mc LOCALE_DATE_TIME_FORMAT]]
  2265. set format [string map $list $format]
  2266. ::msgcat::mcset $locale $key $format
  2267. return $format
  2268. }
  2269. #----------------------------------------------------------------------
  2270. #
  2271. # FormatNumericTimeZone --
  2272. #
  2273. # Formats a time zone as +hhmmss
  2274. #
  2275. # Parameters:
  2276. # z - Time zone in seconds east of Greenwich
  2277. #
  2278. # Results:
  2279. # Returns the time zone formatted in a numeric form
  2280. #
  2281. # Side effects:
  2282. # None.
  2283. #
  2284. #----------------------------------------------------------------------
  2285. proc ::tcl::clock::FormatNumericTimeZone { z } {
  2286. if { $z < 0 } {
  2287. set z [expr { - $z }]
  2288. set retval -
  2289. } else {
  2290. set retval +
  2291. }
  2292. append retval [::format %02d [expr { $z / 3600 }]]
  2293. set z [expr { $z % 3600 }]
  2294. append retval [::format %02d [expr { $z / 60 }]]
  2295. set z [expr { $z % 60 }]
  2296. if { $z != 0 } {
  2297. append retval [::format %02d $z]
  2298. }
  2299. return $retval
  2300. }
  2301. #----------------------------------------------------------------------
  2302. #
  2303. # FormatStarDate --
  2304. #
  2305. # Formats a date as a StarDate.
  2306. #
  2307. # Parameters:
  2308. # date - Dictionary containing 'year', 'dayOfYear', and
  2309. # 'localSeconds' fields.
  2310. #
  2311. # Results:
  2312. # Returns the given date formatted as a StarDate.
  2313. #
  2314. # Side effects:
  2315. # None.
  2316. #
  2317. # Jeff Hobbs put this in to support an atrocious pun about Tcl being
  2318. # "Enterprise ready." Now we're stuck with it.
  2319. #
  2320. #----------------------------------------------------------------------
  2321. proc ::tcl::clock::FormatStarDate { date } {
  2322. variable Roddenberry
  2323. # Get day of year, zero based
  2324. set doy [expr { [dict get $date dayOfYear] - 1 }]
  2325. # Determine whether the year is a leap year
  2326. set lp [IsGregorianLeapYear $date]
  2327. # Convert day of year to a fractional year
  2328. if { $lp } {
  2329. set fractYear [expr { 1000 * $doy / 366 }]
  2330. } else {
  2331. set fractYear [expr { 1000 * $doy / 365 }]
  2332. }
  2333. # Put together the StarDate
  2334. return [::format "Stardate %02d%03d.%1d" \
  2335. [expr { [dict get $date year] - $Roddenberry }] \
  2336. $fractYear \
  2337. [expr { [dict get $date localSeconds] % 86400
  2338. / ( 86400 / 10 ) }]]
  2339. }
  2340. #----------------------------------------------------------------------
  2341. #
  2342. # ParseStarDate --
  2343. #
  2344. # Parses a StarDate
  2345. #
  2346. # Parameters:
  2347. # year - Year from the Roddenberry epoch
  2348. # fractYear - Fraction of a year specifiying the day of year.
  2349. # fractDay - Fraction of a day
  2350. #
  2351. # Results:
  2352. # Returns a count of seconds from the Posix epoch.
  2353. #
  2354. # Side effects:
  2355. # None.
  2356. #
  2357. # Jeff Hobbs put this in to support an atrocious pun about Tcl being
  2358. # "Enterprise ready." Now we're stuck with it.
  2359. #
  2360. #----------------------------------------------------------------------
  2361. proc ::tcl::clock::ParseStarDate { year fractYear fractDay } {
  2362. variable Roddenberry
  2363. # Build a tentative date from year and fraction.
  2364. set date [dict create \
  2365. gregorian 1 \
  2366. era CE \
  2367. year [expr { $year + $Roddenberry }] \
  2368. dayOfYear [expr { $fractYear * 365 / 1000 + 1 }]]
  2369. set date [GetJulianDayFromGregorianEraYearDay $date[set date {}]]
  2370. # Determine whether the given year is a leap year
  2371. set lp [IsGregorianLeapYear $date]
  2372. # Reconvert the fractional year according to whether the given year is a
  2373. # leap year
  2374. if { $lp } {
  2375. dict set date dayOfYear \
  2376. [expr { $fractYear * 366 / 1000 + 1 }]
  2377. } else {
  2378. dict set date dayOfYear \
  2379. [expr { $fractYear * 365 / 1000 + 1 }]
  2380. }
  2381. dict unset date julianDay
  2382. dict unset date gregorian
  2383. set date [GetJulianDayFromGregorianEraYearDay $date[set date {}]]
  2384. return [expr {
  2385. 86400 * [dict get $date julianDay]
  2386. - 210866803200
  2387. + ( 86400 / 10 ) * $fractDay
  2388. }]
  2389. }
  2390. #----------------------------------------------------------------------
  2391. #
  2392. # ScanWide --
  2393. #
  2394. # Scans a wide integer from an input
  2395. #
  2396. # Parameters:
  2397. # str - String containing a decimal wide integer
  2398. #
  2399. # Results:
  2400. # Returns the string as a pure wide integer. Throws an error if the
  2401. # string is misformatted or out of range.
  2402. #
  2403. #----------------------------------------------------------------------
  2404. proc ::tcl::clock::ScanWide { str } {
  2405. set count [::scan $str {%ld %c} result junk]
  2406. if { $count != 1 } {
  2407. return -code error -errorcode [list CLOCK notAnInteger $str] \
  2408. "\"$str\" is not an integer"
  2409. }
  2410. if { [incr result 0] != $str } {
  2411. return -code error -errorcode [list CLOCK integervalueTooLarge] \
  2412. "integer value too large to represent"
  2413. }
  2414. return $result
  2415. }
  2416. #----------------------------------------------------------------------
  2417. #
  2418. # InterpretTwoDigitYear --
  2419. #
  2420. # Given a date that contains only the year of the century, determines
  2421. # the target value of a two-digit year.
  2422. #
  2423. # Parameters:
  2424. # date - Dictionary containing fields of the date.
  2425. # baseTime - Base time relative to which the date is expressed.
  2426. # twoDigitField - Name of the field that stores the two-digit year.
  2427. # Default is 'yearOfCentury'
  2428. # fourDigitField - Name of the field that will receive the four-digit
  2429. # year. Default is 'year'
  2430. #
  2431. # Results:
  2432. # Returns the dictionary augmented with the four-digit year, stored in
  2433. # the given key.
  2434. #
  2435. # Side effects:
  2436. # None.
  2437. #
  2438. # The current rule for interpreting a two-digit year is that the year shall be
  2439. # between 1937 and 2037, thus staying within the range of a 32-bit signed
  2440. # value for time. This rule may change to a sliding window in future
  2441. # versions, so the 'baseTime' parameter (which is currently ignored) is
  2442. # provided in the procedure signature.
  2443. #
  2444. #----------------------------------------------------------------------
  2445. proc ::tcl::clock::InterpretTwoDigitYear { date baseTime
  2446. { twoDigitField yearOfCentury }
  2447. { fourDigitField year } } {
  2448. set yr [dict get $date $twoDigitField]
  2449. if { $yr <= 37 } {
  2450. dict set date $fourDigitField [expr { $yr + 2000 }]
  2451. } else {
  2452. dict set date $fourDigitField [expr { $yr + 1900 }]
  2453. }
  2454. return $date
  2455. }
  2456. #----------------------------------------------------------------------
  2457. #
  2458. # AssignBaseYear --
  2459. #
  2460. # Places the number of the current year into a dictionary.
  2461. #
  2462. # Parameters:
  2463. # date - Dictionary value to update
  2464. # baseTime - Base time from which to extract the year, expressed
  2465. # in seconds from the Posix epoch
  2466. # timezone - the time zone in which the date is being scanned
  2467. # changeover - the Julian Day on which the Gregorian calendar
  2468. # was adopted in the target locale.
  2469. #
  2470. # Results:
  2471. # Returns the dictionary with the current year assigned.
  2472. #
  2473. # Side effects:
  2474. # None.
  2475. #
  2476. #----------------------------------------------------------------------
  2477. proc ::tcl::clock::AssignBaseYear { date baseTime timezone changeover } {
  2478. variable TZData
  2479. # Find the Julian Day Number corresponding to the base time, and
  2480. # find the Gregorian year corresponding to that Julian Day.
  2481. set date2 [GetDateFields $baseTime $TZData($timezone) $changeover]
  2482. # Store the converted year
  2483. dict set date era [dict get $date2 era]
  2484. dict set date year [dict get $date2 year]
  2485. return $date
  2486. }
  2487. #----------------------------------------------------------------------
  2488. #
  2489. # AssignBaseIso8601Year --
  2490. #
  2491. # Determines the base year in the ISO8601 fiscal calendar.
  2492. #
  2493. # Parameters:
  2494. # date - Dictionary containing the fields of the date that
  2495. # is to be augmented with the base year.
  2496. # baseTime - Base time expressed in seconds from the Posix epoch.
  2497. # timeZone - Target time zone
  2498. # changeover - Julian Day of adoption of the Gregorian calendar in
  2499. # the target locale.
  2500. #
  2501. # Results:
  2502. # Returns the given date with "iso8601Year" set to the
  2503. # base year.
  2504. #
  2505. # Side effects:
  2506. # None.
  2507. #
  2508. #----------------------------------------------------------------------
  2509. proc ::tcl::clock::AssignBaseIso8601Year {date baseTime timeZone changeover} {
  2510. variable TZData
  2511. # Find the Julian Day Number corresponding to the base time
  2512. set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover]
  2513. # Calculate the ISO8601 date and transfer the year
  2514. dict set date era CE
  2515. dict set date iso8601Year [dict get $date2 iso8601Year]
  2516. return $date
  2517. }
  2518. #----------------------------------------------------------------------
  2519. #
  2520. # AssignBaseMonth --
  2521. #
  2522. # Places the number of the current year and month into a
  2523. # dictionary.
  2524. #
  2525. # Parameters:
  2526. # date - Dictionary value to update
  2527. # baseTime - Time from which the year and month are to be
  2528. # obtained, expressed in seconds from the Posix epoch.
  2529. # timezone - Name of the desired time zone
  2530. # changeover - Julian Day on which the Gregorian calendar was adopted.
  2531. #
  2532. # Results:
  2533. # Returns the dictionary with the base year and month assigned.
  2534. #
  2535. # Side effects:
  2536. # None.
  2537. #
  2538. #----------------------------------------------------------------------
  2539. proc ::tcl::clock::AssignBaseMonth {date baseTime timezone changeover} {
  2540. variable TZData
  2541. # Find the year and month corresponding to the base time
  2542. set date2 [GetDateFields $baseTime $TZData($timezone) $changeover]
  2543. dict set date era [dict get $date2 era]
  2544. dict set date year [dict get $date2 year]
  2545. dict set date month [dict get $date2 month]
  2546. return $date
  2547. }
  2548. #----------------------------------------------------------------------
  2549. #
  2550. # AssignBaseWeek --
  2551. #
  2552. # Determines the base year and week in the ISO8601 fiscal calendar.
  2553. #
  2554. # Parameters:
  2555. # date - Dictionary containing the fields of the date that
  2556. # is to be augmented with the base year and week.
  2557. # baseTime - Base time expressed in seconds from the Posix epoch.
  2558. # changeover - Julian Day on which the Gregorian calendar was adopted
  2559. # in the target locale.
  2560. #
  2561. # Results:
  2562. # Returns the given date with "iso8601Year" set to the
  2563. # base year and "iso8601Week" to the week number.
  2564. #
  2565. # Side effects:
  2566. # None.
  2567. #
  2568. #----------------------------------------------------------------------
  2569. proc ::tcl::clock::AssignBaseWeek {date baseTime timeZone changeover} {
  2570. variable TZData
  2571. # Find the Julian Day Number corresponding to the base time
  2572. set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover]
  2573. # Calculate the ISO8601 date and transfer the year
  2574. dict set date era CE
  2575. dict set date iso8601Year [dict get $date2 iso8601Year]
  2576. dict set date iso8601Week [dict get $date2 iso8601Week]
  2577. return $date
  2578. }
  2579. #----------------------------------------------------------------------
  2580. #
  2581. # AssignBaseJulianDay --
  2582. #
  2583. # Determines the base day for a time-of-day conversion.
  2584. #
  2585. # Parameters:
  2586. # date - Dictionary that is to get the base day
  2587. # baseTime - Base time expressed in seconds from the Posix epoch
  2588. # changeover - Julian day on which the Gregorian calendar was
  2589. # adpoted in the target locale.
  2590. #
  2591. # Results:
  2592. # Returns the given dictionary augmented with a 'julianDay' field
  2593. # that contains the base day.
  2594. #
  2595. # Side effects:
  2596. # None.
  2597. #
  2598. #----------------------------------------------------------------------
  2599. proc ::tcl::clock::AssignBaseJulianDay { date baseTime timeZone changeover } {
  2600. variable TZData
  2601. # Find the Julian Day Number corresponding to the base time
  2602. set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover]
  2603. dict set date julianDay [dict get $date2 julianDay]
  2604. return $date
  2605. }
  2606. #----------------------------------------------------------------------
  2607. #
  2608. # InterpretHMSP --
  2609. #
  2610. # Interprets a time in the form "hh:mm:ss am".
  2611. #
  2612. # Parameters:
  2613. # date -- Dictionary containing "hourAMPM", "minute", "second"
  2614. # and "amPmIndicator" fields.
  2615. #
  2616. # Results:
  2617. # Returns the number of seconds from local midnight.
  2618. #
  2619. # Side effects:
  2620. # None.
  2621. #
  2622. #----------------------------------------------------------------------
  2623. proc ::tcl::clock::InterpretHMSP { date } {
  2624. set hr [dict get $date hourAMPM]
  2625. if { $hr == 12 } {
  2626. set hr 0
  2627. }
  2628. if { [dict get $date amPmIndicator] } {
  2629. incr hr 12
  2630. }
  2631. dict set date hour $hr
  2632. return [InterpretHMS $date[set date {}]]
  2633. }
  2634. #----------------------------------------------------------------------
  2635. #
  2636. # InterpretHMS --
  2637. #
  2638. # Interprets a 24-hour time "hh:mm:ss"
  2639. #
  2640. # Parameters:
  2641. # date -- Dictionary containing the "hour", "minute" and "second"
  2642. # fields.
  2643. #
  2644. # Results:
  2645. # Returns the given dictionary augmented with a "secondOfDay"
  2646. # field containing the number of seconds from local midnight.
  2647. #
  2648. # Side effects:
  2649. # None.
  2650. #
  2651. #----------------------------------------------------------------------
  2652. proc ::tcl::clock::InterpretHMS { date } {
  2653. return [expr {
  2654. ( [dict get $date hour] * 60
  2655. + [dict get $date minute] ) * 60
  2656. + [dict get $date second]
  2657. }]
  2658. }
  2659. #----------------------------------------------------------------------
  2660. #
  2661. # GetSystemTimeZone --
  2662. #
  2663. # Determines the system time zone, which is the default for the
  2664. # 'clock' command if no other zone is supplied.
  2665. #
  2666. # Parameters:
  2667. # None.
  2668. #
  2669. # Results:
  2670. # Returns the system time zone.
  2671. #
  2672. # Side effects:
  2673. # Stores the sustem time zone in the 'CachedSystemTimeZone'
  2674. # variable, since determining it may be an expensive process.
  2675. #
  2676. #----------------------------------------------------------------------
  2677. proc ::tcl::clock::GetSystemTimeZone {} {
  2678. variable CachedSystemTimeZone
  2679. variable TimeZoneBad
  2680. if {[set result [getenv TCL_TZ]] ne {}} {
  2681. set timezone $result
  2682. } elseif {[set result [getenv TZ]] ne {}} {
  2683. set timezone $result
  2684. }
  2685. if {![info exists timezone]} {
  2686. # Cache the time zone only if it was detected by one of the
  2687. # expensive methods.
  2688. if { [info exists CachedSystemTimeZone] } {
  2689. set timezone $CachedSystemTimeZone
  2690. } elseif { $::tcl_platform(platform) eq {windows} } {
  2691. set timezone [GuessWindowsTimeZone]
  2692. } elseif { [file exists /etc/localtime]
  2693. && ![catch {ReadZoneinfoFile \
  2694. Tcl/Localtime /etc/localtime}] } {
  2695. set timezone :Tcl/Localtime
  2696. } else {
  2697. set timezone :localtime
  2698. }
  2699. set CachedSystemTimeZone $timezone
  2700. }
  2701. if { ![dict exists $TimeZoneBad $timezone] } {
  2702. dict set TimeZoneBad $timezone [catch {SetupTimeZone $timezone}]
  2703. }
  2704. if { [dict get $TimeZoneBad $timezone] } {
  2705. return :localtime
  2706. } else {
  2707. return $timezone
  2708. }
  2709. }
  2710. #----------------------------------------------------------------------
  2711. #
  2712. # ConvertLegacyTimeZone --
  2713. #
  2714. # Given an alphanumeric time zone identifier and the system time zone,
  2715. # convert the alphanumeric identifier to an unambiguous time zone.
  2716. #
  2717. # Parameters:
  2718. # tzname - Name of the time zone to convert
  2719. #
  2720. # Results:
  2721. # Returns a time zone name corresponding to tzname, but in an
  2722. # unambiguous form, generally +hhmm.
  2723. #
  2724. # This procedure is implemented primarily to allow the parsing of RFC822
  2725. # date/time strings. Processing a time zone name on input is not recommended
  2726. # practice, because there is considerable room for ambiguity; for instance, is
  2727. # BST Brazilian Standard Time, or British Summer Time?
  2728. #
  2729. #----------------------------------------------------------------------
  2730. proc ::tcl::clock::ConvertLegacyTimeZone { tzname } {
  2731. variable LegacyTimeZone
  2732. set tzname [string tolower $tzname]
  2733. if { ![dict exists $LegacyTimeZone $tzname] } {
  2734. return -code error -errorcode [list CLOCK badTZName $tzname] \
  2735. "time zone \"$tzname\" not found"
  2736. }
  2737. return [dict get $LegacyTimeZone $tzname]
  2738. }
  2739. #----------------------------------------------------------------------
  2740. #
  2741. # SetupTimeZone --
  2742. #
  2743. # Given the name or specification of a time zone, sets up its in-memory
  2744. # data.
  2745. #
  2746. # Parameters:
  2747. # tzname - Name of a time zone
  2748. #
  2749. # Results:
  2750. # Unless the time zone is ':localtime', sets the TZData array to contain
  2751. # the lookup table for local<->UTC conversion. Returns an error if the
  2752. # time zone cannot be parsed.
  2753. #
  2754. #----------------------------------------------------------------------
  2755. proc ::tcl::clock::SetupTimeZone { timezone } {
  2756. variable TZData
  2757. if {! [info exists TZData($timezone)] } {
  2758. variable MINWIDE
  2759. if { $timezone eq {:localtime} } {
  2760. # Nothing to do, we'll convert using the localtime function
  2761. } elseif {
  2762. [regexp {^([-+])(\d\d)(?::?(\d\d)(?::?(\d\d))?)?} $timezone \
  2763. -> s hh mm ss]
  2764. } then {
  2765. # Make a fixed offset
  2766. ::scan $hh %d hh
  2767. if { $mm eq {} } {
  2768. set mm 0
  2769. } else {
  2770. ::scan $mm %d mm
  2771. }
  2772. if { $ss eq {} } {
  2773. set ss 0
  2774. } else {
  2775. ::scan $ss %d ss
  2776. }
  2777. set offset [expr { ( $hh * 60 + $mm ) * 60 + $ss }]
  2778. if { $s eq {-} } {
  2779. set offset [expr { - $offset }]
  2780. }
  2781. set TZData($timezone) [list [list $MINWIDE $offset -1 $timezone]]
  2782. } elseif { [string index $timezone 0] eq {:} } {
  2783. # Convert using a time zone file
  2784. if {
  2785. [catch {
  2786. LoadTimeZoneFile [string range $timezone 1 end]
  2787. }] && [catch {
  2788. LoadZoneinfoFile [string range $timezone 1 end]
  2789. }]
  2790. } then {
  2791. return -code error \
  2792. -errorcode [list CLOCK badTimeZone $timezone] \
  2793. "time zone \"$timezone\" not found"
  2794. }
  2795. } elseif { ![catch {ParsePosixTimeZone $timezone} tzfields] } {
  2796. # This looks like a POSIX time zone - try to process it
  2797. if { [catch {ProcessPosixTimeZone $tzfields} data opts] } {
  2798. if { [lindex [dict get $opts -errorcode] 0] eq {CLOCK} } {
  2799. dict unset opts -errorinfo
  2800. }
  2801. return -options $opts $data
  2802. } else {
  2803. set TZData($timezone) $data
  2804. }
  2805. } else {
  2806. # We couldn't parse this as a POSIX time zone. Try again with a
  2807. # time zone file - this time without a colon
  2808. if { [catch { LoadTimeZoneFile $timezone }]
  2809. && [catch { LoadZoneinfoFile $timezone } - opts] } {
  2810. dict unset opts -errorinfo
  2811. return -options $opts "time zone $timezone not found"
  2812. }
  2813. set TZData($timezone) $TZData(:$timezone)
  2814. }
  2815. }
  2816. return
  2817. }
  2818. #----------------------------------------------------------------------
  2819. #
  2820. # GuessWindowsTimeZone --
  2821. #
  2822. # Determines the system time zone on windows.
  2823. #
  2824. # Parameters:
  2825. # None.
  2826. #
  2827. # Results:
  2828. # Returns a time zone specifier that corresponds to the system time zone
  2829. # information found in the Registry.
  2830. #
  2831. # Bugs:
  2832. # Fixed dates for DST change are unimplemented at present, because no
  2833. # time zone information supplied with Windows actually uses them!
  2834. #
  2835. # On a Windows system where neither $env(TCL_TZ) nor $env(TZ) is specified,
  2836. # GuessWindowsTimeZone looks in the Registry for the system time zone
  2837. # information. It then attempts to find an entry in WinZoneInfo for a time
  2838. # zone that uses the same rules. If it finds one, it returns it; otherwise,
  2839. # it constructs a Posix-style time zone string and returns that.
  2840. #
  2841. #----------------------------------------------------------------------
  2842. proc ::tcl::clock::GuessWindowsTimeZone {} {
  2843. variable WinZoneInfo
  2844. variable NoRegistry
  2845. variable TimeZoneBad
  2846. if { [info exists NoRegistry] } {
  2847. return :localtime
  2848. }
  2849. # Dredge time zone information out of the registry
  2850. if { [catch {
  2851. set rpath HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\TimeZoneInformation
  2852. set data [list \
  2853. [expr { -60
  2854. * [registry get $rpath Bias] }] \
  2855. [expr { -60
  2856. * [registry get $rpath StandardBias] }] \
  2857. [expr { -60 \
  2858. * [registry get $rpath DaylightBias] }]]
  2859. set stdtzi [registry get $rpath StandardStart]
  2860. foreach ind {0 2 14 4 6 8 10 12} {
  2861. binary scan $stdtzi @${ind}s val
  2862. lappend data $val
  2863. }
  2864. set daytzi [registry get $rpath DaylightStart]
  2865. foreach ind {0 2 14 4 6 8 10 12} {
  2866. binary scan $daytzi @${ind}s val
  2867. lappend data $val
  2868. }
  2869. }] } {
  2870. # Missing values in the Registry - bail out
  2871. return :localtime
  2872. }
  2873. # Make up a Posix time zone specifier if we can't find one. Check here
  2874. # that the tzdata file exists, in case we're running in an environment
  2875. # (e.g. starpack) where tzdata is incomplete. (Bug 1237907)
  2876. if { [dict exists $WinZoneInfo $data] } {
  2877. set tzname [dict get $WinZoneInfo $data]
  2878. if { ! [dict exists $TimeZoneBad $tzname] } {
  2879. dict set TimeZoneBad $tzname [catch {SetupTimeZone $tzname}]
  2880. }
  2881. } else {
  2882. set tzname {}
  2883. }
  2884. if { $tzname eq {} || [dict get $TimeZoneBad $tzname] } {
  2885. lassign $data \
  2886. bias stdBias dstBias \
  2887. stdYear stdMonth stdDayOfWeek stdDayOfMonth \
  2888. stdHour stdMinute stdSecond stdMillisec \
  2889. dstYear dstMonth dstDayOfWeek dstDayOfMonth \
  2890. dstHour dstMinute dstSecond dstMillisec
  2891. set stdDelta [expr { $bias + $stdBias }]
  2892. set dstDelta [expr { $bias + $dstBias }]
  2893. if { $stdDelta <= 0 } {
  2894. set stdSignum +
  2895. set stdDelta [expr { - $stdDelta }]
  2896. set dispStdSignum -
  2897. } else {
  2898. set stdSignum -
  2899. set dispStdSignum +
  2900. }
  2901. set hh [::format %02d [expr { $stdDelta / 3600 }]]
  2902. set mm [::format %02d [expr { ($stdDelta / 60 ) % 60 }]]
  2903. set ss [::format %02d [expr { $stdDelta % 60 }]]
  2904. set tzname {}
  2905. append tzname < $dispStdSignum $hh $mm > $stdSignum $hh : $mm : $ss
  2906. if { $stdMonth >= 0 } {
  2907. if { $dstDelta <= 0 } {
  2908. set dstSignum +
  2909. set dstDelta [expr { - $dstDelta }]
  2910. set dispDstSignum -
  2911. } else {
  2912. set dstSignum -
  2913. set dispDstSignum +
  2914. }
  2915. set hh [::format %02d [expr { $dstDelta / 3600 }]]
  2916. set mm [::format %02d [expr { ($dstDelta / 60 ) % 60 }]]
  2917. set ss [::format %02d [expr { $dstDelta % 60 }]]
  2918. append tzname < $dispDstSignum $hh $mm > $dstSignum $hh : $mm : $ss
  2919. if { $dstYear == 0 } {
  2920. append tzname ,M $dstMonth . $dstDayOfMonth . $dstDayOfWeek
  2921. } else {
  2922. # I have not been able to find any locale on which Windows
  2923. # converts time zone on a fixed day of the year, hence don't
  2924. # know how to interpret the fields. If someone can inform me,
  2925. # I'd be glad to code it up. For right now, we bail out in
  2926. # such a case.
  2927. return :localtime
  2928. }
  2929. append tzname / [::format %02d $dstHour] \
  2930. : [::format %02d $dstMinute] \
  2931. : [::format %02d $dstSecond]
  2932. if { $stdYear == 0 } {
  2933. append tzname ,M $stdMonth . $stdDayOfMonth . $stdDayOfWeek
  2934. } else {
  2935. # I have not been able to find any locale on which Windows
  2936. # converts time zone on a fixed day of the year, hence don't
  2937. # know how to interpret the fields. If someone can inform me,
  2938. # I'd be glad to code it up. For right now, we bail out in
  2939. # such a case.
  2940. return :localtime
  2941. }
  2942. append tzname / [::format %02d $stdHour] \
  2943. : [::format %02d $stdMinute] \
  2944. : [::format %02d $stdSecond]
  2945. }
  2946. dict set WinZoneInfo $data $tzname
  2947. }
  2948. return [dict get $WinZoneInfo $data]
  2949. }
  2950. #----------------------------------------------------------------------
  2951. #
  2952. # LoadTimeZoneFile --
  2953. #
  2954. # Load the data file that specifies the conversion between a
  2955. # given time zone and Greenwich.
  2956. #
  2957. # Parameters:
  2958. # fileName -- Name of the file to load
  2959. #
  2960. # Results:
  2961. # None.
  2962. #
  2963. # Side effects:
  2964. # TZData(:fileName) contains the time zone data
  2965. #
  2966. #----------------------------------------------------------------------
  2967. proc ::tcl::clock::LoadTimeZoneFile { fileName } {
  2968. variable DataDir
  2969. variable TZData
  2970. if { [info exists TZData($fileName)] } {
  2971. return
  2972. }
  2973. # Since an unsafe interp uses the [clock] command in the master, this code
  2974. # is security sensitive. Make sure that the path name cannot escape the
  2975. # given directory.
  2976. if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } {
  2977. return -code error \
  2978. -errorcode [list CLOCK badTimeZone $:fileName] \
  2979. "time zone \":$fileName\" not valid"
  2980. }
  2981. try {
  2982. source -encoding utf-8 [file join $DataDir $fileName]
  2983. } on error {} {
  2984. return -code error \
  2985. -errorcode [list CLOCK badTimeZone :$fileName] \
  2986. "time zone \":$fileName\" not found"
  2987. }
  2988. return
  2989. }
  2990. #----------------------------------------------------------------------
  2991. #
  2992. # LoadZoneinfoFile --
  2993. #
  2994. # Loads a binary time zone information file in Olson format.
  2995. #
  2996. # Parameters:
  2997. # fileName - Relative path name of the file to load.
  2998. #
  2999. # Results:
  3000. # Returns an empty result normally; returns an error if no Olson file
  3001. # was found or the file was malformed in some way.
  3002. #
  3003. # Side effects:
  3004. # TZData(:fileName) contains the time zone data
  3005. #
  3006. #----------------------------------------------------------------------
  3007. proc ::tcl::clock::LoadZoneinfoFile { fileName } {
  3008. variable ZoneinfoPaths
  3009. # Since an unsafe interp uses the [clock] command in the master, this code
  3010. # is security sensitive. Make sure that the path name cannot escape the
  3011. # given directory.
  3012. if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } {
  3013. return -code error \
  3014. -errorcode [list CLOCK badTimeZone $:fileName] \
  3015. "time zone \":$fileName\" not valid"
  3016. }
  3017. foreach d $ZoneinfoPaths {
  3018. set fname [file join $d $fileName]
  3019. if { [file readable $fname] && [file isfile $fname] } {
  3020. break
  3021. }
  3022. unset fname
  3023. }
  3024. ReadZoneinfoFile $fileName $fname
  3025. }
  3026. #----------------------------------------------------------------------
  3027. #
  3028. # ReadZoneinfoFile --
  3029. #
  3030. # Loads a binary time zone information file in Olson format.
  3031. #
  3032. # Parameters:
  3033. # fileName - Name of the time zone (relative path name of the
  3034. # file).
  3035. # fname - Absolute path name of the file.
  3036. #
  3037. # Results:
  3038. # Returns an empty result normally; returns an error if no Olson file
  3039. # was found or the file was malformed in some way.
  3040. #
  3041. # Side effects:
  3042. # TZData(:fileName) contains the time zone data
  3043. #
  3044. #----------------------------------------------------------------------
  3045. proc ::tcl::clock::ReadZoneinfoFile {fileName fname} {
  3046. variable MINWIDE
  3047. variable TZData
  3048. if { ![file exists $fname] } {
  3049. return -code error "$fileName not found"
  3050. }
  3051. if { [file size $fname] > 262144 } {
  3052. return -code error "$fileName too big"
  3053. }
  3054. # Suck in all the data from the file
  3055. set f [open $fname r]
  3056. fconfigure $f -translation binary
  3057. set d [read $f]
  3058. close $f
  3059. # The file begins with a magic number, sixteen reserved bytes, and then
  3060. # six 4-byte integers giving counts of fileds in the file.
  3061. binary scan $d a4a1x15IIIIII \
  3062. magic version nIsGMT nIsStd nLeap nTime nType nChar
  3063. set seek 44
  3064. set ilen 4
  3065. set iformat I
  3066. if { $magic != {TZif} } {
  3067. return -code error "$fileName not a time zone information file"
  3068. }
  3069. if { $nType > 255 } {
  3070. return -code error "$fileName contains too many time types"
  3071. }
  3072. # Accept only Posix-style zoneinfo. Sorry, 'leaps' bigots.
  3073. if { $nLeap != 0 } {
  3074. return -code error "$fileName contains leap seconds"
  3075. }
  3076. # In a version 2 file, we use the second part of the file, which contains
  3077. # 64-bit transition times.
  3078. if {$version eq "2"} {
  3079. set seek [expr {
  3080. 44
  3081. + 5 * $nTime
  3082. + 6 * $nType
  3083. + 4 * $nLeap
  3084. + $nIsStd
  3085. + $nIsGMT
  3086. + $nChar
  3087. }]
  3088. binary scan $d @${seek}a4a1x15IIIIII \
  3089. magic version nIsGMT nIsStd nLeap nTime nType nChar
  3090. if {$magic ne {TZif}} {
  3091. return -code error "seek address $seek miscomputed, magic = $magic"
  3092. }
  3093. set iformat W
  3094. set ilen 8
  3095. incr seek 44
  3096. }
  3097. # Next come ${nTime} transition times, followed by ${nTime} time type
  3098. # codes. The type codes are unsigned 1-byte quantities. We insert an
  3099. # arbitrary start time in front of the transitions.
  3100. binary scan $d @${seek}${iformat}${nTime}c${nTime} times tempCodes
  3101. incr seek [expr { ($ilen + 1) * $nTime }]
  3102. set times [linsert $times 0 $MINWIDE]
  3103. set codes {}
  3104. foreach c $tempCodes {
  3105. lappend codes [expr { $c & 0xff }]
  3106. }
  3107. set codes [linsert $codes 0 0]
  3108. # Next come ${nType} time type descriptions, each of which has an offset
  3109. # (seconds east of GMT), a DST indicator, and an index into the
  3110. # abbreviation text.
  3111. for { set i 0 } { $i < $nType } { incr i } {
  3112. binary scan $d @${seek}Icc gmtOff isDst abbrInd
  3113. lappend types [list $gmtOff $isDst $abbrInd]
  3114. incr seek 6
  3115. }
  3116. # Next come $nChar characters of time zone name abbreviations, which are
  3117. # null-terminated.
  3118. # We build them up into a dictionary indexed by character index, because
  3119. # that's what's in the indices above.
  3120. binary scan $d @${seek}a${nChar} abbrs
  3121. incr seek ${nChar}
  3122. set abbrList [split $abbrs \0]
  3123. set i 0
  3124. set abbrevs {}
  3125. foreach a $abbrList {
  3126. for {set j 0} {$j <= [string length $a]} {incr j} {
  3127. dict set abbrevs $i [string range $a $j end]
  3128. incr i
  3129. }
  3130. }
  3131. # Package up a list of tuples, each of which contains transition time,
  3132. # seconds east of Greenwich, DST flag and time zone abbreviation.
  3133. set r {}
  3134. set lastTime $MINWIDE
  3135. foreach t $times c $codes {
  3136. if { $t < $lastTime } {
  3137. return -code error "$fileName has times out of order"
  3138. }
  3139. set lastTime $t
  3140. lassign [lindex $types $c] gmtoff isDst abbrInd
  3141. set abbrev [dict get $abbrevs $abbrInd]
  3142. lappend r [list $t $gmtoff $isDst $abbrev]
  3143. }
  3144. # In a version 2 file, there is also a POSIX-style time zone description
  3145. # at the very end of the file. To get to it, skip over nLeap leap second
  3146. # values (8 bytes each),
  3147. # nIsStd standard/DST indicators and nIsGMT UTC/local indicators.
  3148. if {$version eq {2}} {
  3149. set seek [expr {$seek + 8 * $nLeap + $nIsStd + $nIsGMT + 1}]
  3150. set last [string first \n $d $seek]
  3151. set posix [string range $d $seek [expr {$last-1}]]
  3152. if {[llength $posix] > 0} {
  3153. set posixFields [ParsePosixTimeZone $posix]
  3154. foreach tuple [ProcessPosixTimeZone $posixFields] {
  3155. lassign $tuple t gmtoff isDst abbrev
  3156. if {$t > $lastTime} {
  3157. lappend r $tuple
  3158. }
  3159. }
  3160. }
  3161. }
  3162. set TZData(:$fileName) $r
  3163. return
  3164. }
  3165. #----------------------------------------------------------------------
  3166. #
  3167. # ParsePosixTimeZone --
  3168. #
  3169. # Parses the TZ environment variable in Posix form
  3170. #
  3171. # Parameters:
  3172. # tz Time zone specifier to be interpreted
  3173. #
  3174. # Results:
  3175. # Returns a dictionary whose values contain the various pieces of the
  3176. # time zone specification.
  3177. #
  3178. # Side effects:
  3179. # None.
  3180. #
  3181. # Errors:
  3182. # Throws an error if the syntax of the time zone is incorrect.
  3183. #
  3184. # The following keys are present in the dictionary:
  3185. # stdName - Name of the time zone when Daylight Saving Time
  3186. # is not in effect.
  3187. # stdSignum - Sign (+, -, or empty) of the offset from Greenwich
  3188. # to the given (non-DST) time zone. + and the empty
  3189. # string denote zones west of Greenwich, - denotes east
  3190. # of Greenwich; this is contrary to the ISO convention
  3191. # but follows Posix.
  3192. # stdHours - Hours part of the offset from Greenwich to the given
  3193. # (non-DST) time zone.
  3194. # stdMinutes - Minutes part of the offset from Greenwich to the
  3195. # given (non-DST) time zone. Empty denotes zero.
  3196. # stdSeconds - Seconds part of the offset from Greenwich to the
  3197. # given (non-DST) time zone. Empty denotes zero.
  3198. # dstName - Name of the time zone when DST is in effect, or the
  3199. # empty string if the time zone does not observe Daylight
  3200. # Saving Time.
  3201. # dstSignum, dstHours, dstMinutes, dstSeconds -
  3202. # Fields corresponding to stdSignum, stdHours, stdMinutes,
  3203. # stdSeconds for the Daylight Saving Time version of the
  3204. # time zone. If dstHours is empty, it is presumed to be 1.
  3205. # startDayOfYear - The ordinal number of the day of the year on which
  3206. # Daylight Saving Time begins. If this field is
  3207. # empty, then DST begins on a given month-week-day,
  3208. # as below.
  3209. # startJ - The letter J, or an empty string. If a J is present in
  3210. # this field, then startDayOfYear does not count February 29
  3211. # even in leap years.
  3212. # startMonth - The number of the month in which Daylight Saving Time
  3213. # begins, supplied if startDayOfYear is empty. If both
  3214. # startDayOfYear and startMonth are empty, then US rules
  3215. # are presumed.
  3216. # startWeekOfMonth - The number of the week in the month in which
  3217. # Daylight Saving Time begins, in the range 1-5.
  3218. # 5 denotes the last week of the month even in a
  3219. # 4-week month.
  3220. # startDayOfWeek - The number of the day of the week (Sunday=0,
  3221. # Saturday=6) on which Daylight Saving Time begins.
  3222. # startHours - The hours part of the time of day at which Daylight
  3223. # Saving Time begins. An empty string is presumed to be 2.
  3224. # startMinutes - The minutes part of the time of day at which DST begins.
  3225. # An empty string is presumed zero.
  3226. # startSeconds - The seconds part of the time of day at which DST begins.
  3227. # An empty string is presumed zero.
  3228. # endDayOfYear, endJ, endMonth, endWeekOfMonth, endDayOfWeek,
  3229. # endHours, endMinutes, endSeconds -
  3230. # Specify the end of DST in the same way that the start* fields
  3231. # specify the beginning of DST.
  3232. #
  3233. # This procedure serves only to break the time specifier into fields. No
  3234. # attempt is made to canonicalize the fields or supply default values.
  3235. #
  3236. #----------------------------------------------------------------------
  3237. proc ::tcl::clock::ParsePosixTimeZone { tz } {
  3238. if {[regexp -expanded -nocase -- {
  3239. ^
  3240. # 1 - Standard time zone name
  3241. ([[:alpha:]]+ | <[-+[:alnum:]]+>)
  3242. # 2 - Standard time zone offset, signum
  3243. ([-+]?)
  3244. # 3 - Standard time zone offset, hours
  3245. ([[:digit:]]{1,2})
  3246. (?:
  3247. # 4 - Standard time zone offset, minutes
  3248. : ([[:digit:]]{1,2})
  3249. (?:
  3250. # 5 - Standard time zone offset, seconds
  3251. : ([[:digit:]]{1,2} )
  3252. )?
  3253. )?
  3254. (?:
  3255. # 6 - DST time zone name
  3256. ([[:alpha:]]+ | <[-+[:alnum:]]+>)
  3257. (?:
  3258. (?:
  3259. # 7 - DST time zone offset, signum
  3260. ([-+]?)
  3261. # 8 - DST time zone offset, hours
  3262. ([[:digit:]]{1,2})
  3263. (?:
  3264. # 9 - DST time zone offset, minutes
  3265. : ([[:digit:]]{1,2})
  3266. (?:
  3267. # 10 - DST time zone offset, seconds
  3268. : ([[:digit:]]{1,2})
  3269. )?
  3270. )?
  3271. )?
  3272. (?:
  3273. ,
  3274. (?:
  3275. # 11 - Optional J in n and Jn form 12 - Day of year
  3276. ( J ? ) ( [[:digit:]]+ )
  3277. | M
  3278. # 13 - Month number 14 - Week of month 15 - Day of week
  3279. ( [[:digit:]] + )
  3280. [.] ( [[:digit:]] + )
  3281. [.] ( [[:digit:]] + )
  3282. )
  3283. (?:
  3284. # 16 - Start time of DST - hours
  3285. / ( [[:digit:]]{1,2} )
  3286. (?:
  3287. # 17 - Start time of DST - minutes
  3288. : ( [[:digit:]]{1,2} )
  3289. (?:
  3290. # 18 - Start time of DST - seconds
  3291. : ( [[:digit:]]{1,2} )
  3292. )?
  3293. )?
  3294. )?
  3295. ,
  3296. (?:
  3297. # 19 - Optional J in n and Jn form 20 - Day of year
  3298. ( J ? ) ( [[:digit:]]+ )
  3299. | M
  3300. # 21 - Month number 22 - Week of month 23 - Day of week
  3301. ( [[:digit:]] + )
  3302. [.] ( [[:digit:]] + )
  3303. [.] ( [[:digit:]] + )
  3304. )
  3305. (?:
  3306. # 24 - End time of DST - hours
  3307. / ( [[:digit:]]{1,2} )
  3308. (?:
  3309. # 25 - End time of DST - minutes
  3310. : ( [[:digit:]]{1,2} )
  3311. (?:
  3312. # 26 - End time of DST - seconds
  3313. : ( [[:digit:]]{1,2} )
  3314. )?
  3315. )?
  3316. )?
  3317. )?
  3318. )?
  3319. )?
  3320. $
  3321. } $tz -> x(stdName) x(stdSignum) x(stdHours) x(stdMinutes) x(stdSeconds) \
  3322. x(dstName) x(dstSignum) x(dstHours) x(dstMinutes) x(dstSeconds) \
  3323. x(startJ) x(startDayOfYear) \
  3324. x(startMonth) x(startWeekOfMonth) x(startDayOfWeek) \
  3325. x(startHours) x(startMinutes) x(startSeconds) \
  3326. x(endJ) x(endDayOfYear) \
  3327. x(endMonth) x(endWeekOfMonth) x(endDayOfWeek) \
  3328. x(endHours) x(endMinutes) x(endSeconds)] } {
  3329. # it's a good timezone
  3330. return [array get x]
  3331. }
  3332. return -code error\
  3333. -errorcode [list CLOCK badTimeZone $tz] \
  3334. "unable to parse time zone specification \"$tz\""
  3335. }
  3336. #----------------------------------------------------------------------
  3337. #
  3338. # ProcessPosixTimeZone --
  3339. #
  3340. # Handle a Posix time zone after it's been broken out into fields.
  3341. #
  3342. # Parameters:
  3343. # z - Dictionary returned from 'ParsePosixTimeZone'
  3344. #
  3345. # Results:
  3346. # Returns time zone information for the 'TZData' array.
  3347. #
  3348. # Side effects:
  3349. # None.
  3350. #
  3351. #----------------------------------------------------------------------
  3352. proc ::tcl::clock::ProcessPosixTimeZone { z } {
  3353. variable MINWIDE
  3354. variable TZData
  3355. # Determine the standard time zone name and seconds east of Greenwich
  3356. set stdName [dict get $z stdName]
  3357. if { [string index $stdName 0] eq {<} } {
  3358. set stdName [string range $stdName 1 end-1]
  3359. }
  3360. if { [dict get $z stdSignum] eq {-} } {
  3361. set stdSignum +1
  3362. } else {
  3363. set stdSignum -1
  3364. }
  3365. set stdHours [lindex [::scan [dict get $z stdHours] %d] 0]
  3366. if { [dict get $z stdMinutes] ne {} } {
  3367. set stdMinutes [lindex [::scan [dict get $z stdMinutes] %d] 0]
  3368. } else {
  3369. set stdMinutes 0
  3370. }
  3371. if { [dict get $z stdSeconds] ne {} } {
  3372. set stdSeconds [lindex [::scan [dict get $z stdSeconds] %d] 0]
  3373. } else {
  3374. set stdSeconds 0
  3375. }
  3376. set stdOffset [expr {
  3377. (($stdHours * 60 + $stdMinutes) * 60 + $stdSeconds) * $stdSignum
  3378. }]
  3379. set data [list [list $MINWIDE $stdOffset 0 $stdName]]
  3380. # If there's no daylight zone, we're done
  3381. set dstName [dict get $z dstName]
  3382. if { $dstName eq {} } {
  3383. return $data
  3384. }
  3385. if { [string index $dstName 0] eq {<} } {
  3386. set dstName [string range $dstName 1 end-1]
  3387. }
  3388. # Determine the daylight name
  3389. if { [dict get $z dstSignum] eq {-} } {
  3390. set dstSignum +1
  3391. } else {
  3392. set dstSignum -1
  3393. }
  3394. if { [dict get $z dstHours] eq {} } {
  3395. set dstOffset [expr { 3600 + $stdOffset }]
  3396. } else {
  3397. set dstHours [lindex [::scan [dict get $z dstHours] %d] 0]
  3398. if { [dict get $z dstMinutes] ne {} } {
  3399. set dstMinutes [lindex [::scan [dict get $z dstMinutes] %d] 0]
  3400. } else {
  3401. set dstMinutes 0
  3402. }
  3403. if { [dict get $z dstSeconds] ne {} } {
  3404. set dstSeconds [lindex [::scan [dict get $z dstSeconds] %d] 0]
  3405. } else {
  3406. set dstSeconds 0
  3407. }
  3408. set dstOffset [expr {
  3409. (($dstHours*60 + $dstMinutes) * 60 + $dstSeconds) * $dstSignum
  3410. }]
  3411. }
  3412. # Fill in defaults for European or US DST rules
  3413. # US start time is the second Sunday in March
  3414. # EU start time is the last Sunday in March
  3415. # US end time is the first Sunday in November.
  3416. # EU end time is the last Sunday in October
  3417. if {
  3418. [dict get $z startDayOfYear] eq {}
  3419. && [dict get $z startMonth] eq {}
  3420. } then {
  3421. if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} {
  3422. # EU
  3423. dict set z startWeekOfMonth 5
  3424. if {$stdHours>2} {
  3425. dict set z startHours 2
  3426. } else {
  3427. dict set z startHours [expr {$stdHours+1}]
  3428. }
  3429. } else {
  3430. # US
  3431. dict set z startWeekOfMonth 2
  3432. dict set z startHours 2
  3433. }
  3434. dict set z startMonth 3
  3435. dict set z startDayOfWeek 0
  3436. dict set z startMinutes 0
  3437. dict set z startSeconds 0
  3438. }
  3439. if {
  3440. [dict get $z endDayOfYear] eq {}
  3441. && [dict get $z endMonth] eq {}
  3442. } then {
  3443. if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} {
  3444. # EU
  3445. dict set z endMonth 10
  3446. dict set z endWeekOfMonth 5
  3447. if {$stdHours>2} {
  3448. dict set z endHours 3
  3449. } else {
  3450. dict set z endHours [expr {$stdHours+2}]
  3451. }
  3452. } else {
  3453. # US
  3454. dict set z endMonth 11
  3455. dict set z endWeekOfMonth 1
  3456. dict set z endHours 2
  3457. }
  3458. dict set z endDayOfWeek 0
  3459. dict set z endMinutes 0
  3460. dict set z endSeconds 0
  3461. }
  3462. # Put DST in effect in all years from 1916 to 2099.
  3463. for { set y 1916 } { $y < 2100 } { incr y } {
  3464. set startTime [DeterminePosixDSTTime $z start $y]
  3465. incr startTime [expr { - wide($stdOffset) }]
  3466. set endTime [DeterminePosixDSTTime $z end $y]
  3467. incr endTime [expr { - wide($dstOffset) }]
  3468. if { $startTime < $endTime } {
  3469. lappend data \
  3470. [list $startTime $dstOffset 1 $dstName] \
  3471. [list $endTime $stdOffset 0 $stdName]
  3472. } else {
  3473. lappend data \
  3474. [list $endTime $stdOffset 0 $stdName] \
  3475. [list $startTime $dstOffset 1 $dstName]
  3476. }
  3477. }
  3478. return $data
  3479. }
  3480. #----------------------------------------------------------------------
  3481. #
  3482. # DeterminePosixDSTTime --
  3483. #
  3484. # Determines the time that Daylight Saving Time starts or ends from a
  3485. # Posix time zone specification.
  3486. #
  3487. # Parameters:
  3488. # z - Time zone data returned from ParsePosixTimeZone.
  3489. # Missing fields are expected to be filled in with
  3490. # default values.
  3491. # bound - The word 'start' or 'end'
  3492. # y - The year for which the transition time is to be determined.
  3493. #
  3494. # Results:
  3495. # Returns the transition time as a count of seconds from the epoch. The
  3496. # time is relative to the wall clock, not UTC.
  3497. #
  3498. #----------------------------------------------------------------------
  3499. proc ::tcl::clock::DeterminePosixDSTTime { z bound y } {
  3500. variable FEB_28
  3501. # Determine the start or end day of DST
  3502. set date [dict create era CE year $y]
  3503. set doy [dict get $z ${bound}DayOfYear]
  3504. if { $doy ne {} } {
  3505. # Time was specified as a day of the year
  3506. if { [dict get $z ${bound}J] ne {}
  3507. && [IsGregorianLeapYear $y]
  3508. && ( $doy > $FEB_28 ) } {
  3509. incr doy
  3510. }
  3511. dict set date dayOfYear $doy
  3512. set date [GetJulianDayFromEraYearDay $date[set date {}] 2361222]
  3513. } else {
  3514. # Time was specified as a day of the week within a month
  3515. dict set date month [dict get $z ${bound}Month]
  3516. dict set date dayOfWeek [dict get $z ${bound}DayOfWeek]
  3517. set dowim [dict get $z ${bound}WeekOfMonth]
  3518. if { $dowim >= 5 } {
  3519. set dowim -1
  3520. }
  3521. dict set date dayOfWeekInMonth $dowim
  3522. set date [GetJulianDayFromEraYearMonthWeekDay $date[set date {}] 2361222]
  3523. }
  3524. set jd [dict get $date julianDay]
  3525. set seconds [expr {
  3526. wide($jd) * wide(86400) - wide(210866803200)
  3527. }]
  3528. set h [dict get $z ${bound}Hours]
  3529. if { $h eq {} } {
  3530. set h 2
  3531. } else {
  3532. set h [lindex [::scan $h %d] 0]
  3533. }
  3534. set m [dict get $z ${bound}Minutes]
  3535. if { $m eq {} } {
  3536. set m 0
  3537. } else {
  3538. set m [lindex [::scan $m %d] 0]
  3539. }
  3540. set s [dict get $z ${bound}Seconds]
  3541. if { $s eq {} } {
  3542. set s 0
  3543. } else {
  3544. set s [lindex [::scan $s %d] 0]
  3545. }
  3546. set tod [expr { ( $h * 60 + $m ) * 60 + $s }]
  3547. return [expr { $seconds + $tod }]
  3548. }
  3549. #----------------------------------------------------------------------
  3550. #
  3551. # GetLocaleEra --
  3552. #
  3553. # Given local time expressed in seconds from the Posix epoch,
  3554. # determine localized era and year within the era.
  3555. #
  3556. # Parameters:
  3557. # date - Dictionary that must contain the keys, 'localSeconds',
  3558. # whose value is expressed as the appropriate local time;
  3559. # and 'year', whose value is the Gregorian year.
  3560. # etable - Value of the LOCALE_ERAS key in the message catalogue
  3561. # for the target locale.
  3562. #
  3563. # Results:
  3564. # Returns the dictionary, augmented with the keys, 'localeEra' and
  3565. # 'localeYear'.
  3566. #
  3567. #----------------------------------------------------------------------
  3568. proc ::tcl::clock::GetLocaleEra { date etable } {
  3569. set index [BSearch $etable [dict get $date localSeconds]]
  3570. if { $index < 0} {
  3571. dict set date localeEra \
  3572. [::format %02d [expr { [dict get $date year] / 100 }]]
  3573. dict set date localeYear [expr {
  3574. [dict get $date year] % 100
  3575. }]
  3576. } else {
  3577. dict set date localeEra [lindex $etable $index 1]
  3578. dict set date localeYear [expr {
  3579. [dict get $date year] - [lindex $etable $index 2]
  3580. }]
  3581. }
  3582. return $date
  3583. }
  3584. #----------------------------------------------------------------------
  3585. #
  3586. # GetJulianDayFromEraYearDay --
  3587. #
  3588. # Given a year, month and day on the Gregorian calendar, determines
  3589. # the Julian Day Number beginning at noon on that date.
  3590. #
  3591. # Parameters:
  3592. # date -- A dictionary in which the 'era', 'year', and
  3593. # 'dayOfYear' slots are populated. The calendar in use
  3594. # is determined by the date itself relative to:
  3595. # changeover -- Julian day on which the Gregorian calendar was
  3596. # adopted in the current locale.
  3597. #
  3598. # Results:
  3599. # Returns the given dictionary augmented with a 'julianDay' key whose
  3600. # value is the desired Julian Day Number, and a 'gregorian' key that
  3601. # specifies whether the calendar is Gregorian (1) or Julian (0).
  3602. #
  3603. # Side effects:
  3604. # None.
  3605. #
  3606. # Bugs:
  3607. # This code needs to be moved to the C layer.
  3608. #
  3609. #----------------------------------------------------------------------
  3610. proc ::tcl::clock::GetJulianDayFromEraYearDay {date changeover} {
  3611. # Get absolute year number from the civil year
  3612. switch -exact -- [dict get $date era] {
  3613. BCE {
  3614. set year [expr { 1 - [dict get $date year] }]
  3615. }
  3616. CE {
  3617. set year [dict get $date year]
  3618. }
  3619. }
  3620. set ym1 [expr { $year - 1 }]
  3621. # Try the Gregorian calendar first.
  3622. dict set date gregorian 1
  3623. set jd [expr {
  3624. 1721425
  3625. + [dict get $date dayOfYear]
  3626. + ( 365 * $ym1 )
  3627. + ( $ym1 / 4 )
  3628. - ( $ym1 / 100 )
  3629. + ( $ym1 / 400 )
  3630. }]
  3631. # If the date is before the Gregorian change, use the Julian calendar.
  3632. if { $jd < $changeover } {
  3633. dict set date gregorian 0
  3634. set jd [expr {
  3635. 1721423
  3636. + [dict get $date dayOfYear]
  3637. + ( 365 * $ym1 )
  3638. + ( $ym1 / 4 )
  3639. }]
  3640. }
  3641. dict set date julianDay $jd
  3642. return $date
  3643. }
  3644. #----------------------------------------------------------------------
  3645. #
  3646. # GetJulianDayFromEraYearMonthWeekDay --
  3647. #
  3648. # Determines the Julian Day number corresponding to the nth given
  3649. # day-of-the-week in a given month.
  3650. #
  3651. # Parameters:
  3652. # date - Dictionary containing the keys, 'era', 'year', 'month'
  3653. # 'weekOfMonth', 'dayOfWeek', and 'dayOfWeekInMonth'.
  3654. # changeover - Julian Day of adoption of the Gregorian calendar
  3655. #
  3656. # Results:
  3657. # Returns the given dictionary, augmented with a 'julianDay' key.
  3658. #
  3659. # Side effects:
  3660. # None.
  3661. #
  3662. # Bugs:
  3663. # This code needs to be moved to the C layer.
  3664. #
  3665. #----------------------------------------------------------------------
  3666. proc ::tcl::clock::GetJulianDayFromEraYearMonthWeekDay {date changeover} {
  3667. # Come up with a reference day; either the zeroeth day of the given month
  3668. # (dayOfWeekInMonth >= 0) or the seventh day of the following month
  3669. # (dayOfWeekInMonth < 0)
  3670. set date2 $date
  3671. set week [dict get $date dayOfWeekInMonth]
  3672. if { $week >= 0 } {
  3673. dict set date2 dayOfMonth 0
  3674. } else {
  3675. dict incr date2 month
  3676. dict set date2 dayOfMonth 7
  3677. }
  3678. set date2 [GetJulianDayFromEraYearMonthDay $date2[set date2 {}] \
  3679. $changeover]
  3680. set wd0 [WeekdayOnOrBefore [dict get $date dayOfWeek] \
  3681. [dict get $date2 julianDay]]
  3682. dict set date julianDay [expr { $wd0 + 7 * $week }]
  3683. return $date
  3684. }
  3685. #----------------------------------------------------------------------
  3686. #
  3687. # IsGregorianLeapYear --
  3688. #
  3689. # Determines whether a given date represents a leap year in the
  3690. # Gregorian calendar.
  3691. #
  3692. # Parameters:
  3693. # date -- The date to test. The fields, 'era', 'year' and 'gregorian'
  3694. # must be set.
  3695. #
  3696. # Results:
  3697. # Returns 1 if the year is a leap year, 0 otherwise.
  3698. #
  3699. # Side effects:
  3700. # None.
  3701. #
  3702. #----------------------------------------------------------------------
  3703. proc ::tcl::clock::IsGregorianLeapYear { date } {
  3704. switch -exact -- [dict get $date era] {
  3705. BCE {
  3706. set year [expr { 1 - [dict get $date year]}]
  3707. }
  3708. CE {
  3709. set year [dict get $date year]
  3710. }
  3711. }
  3712. if { $year % 4 != 0 } {
  3713. return 0
  3714. } elseif { ![dict get $date gregorian] } {
  3715. return 1
  3716. } elseif { $year % 400 == 0 } {
  3717. return 1
  3718. } elseif { $year % 100 == 0 } {
  3719. return 0
  3720. } else {
  3721. return 1
  3722. }
  3723. }
  3724. #----------------------------------------------------------------------
  3725. #
  3726. # WeekdayOnOrBefore --
  3727. #
  3728. # Determine the nearest day of week (given by the 'weekday' parameter,
  3729. # Sunday==0) on or before a given Julian Day.
  3730. #
  3731. # Parameters:
  3732. # weekday -- Day of the week
  3733. # j -- Julian Day number
  3734. #
  3735. # Results:
  3736. # Returns the Julian Day Number of the desired date.
  3737. #
  3738. # Side effects:
  3739. # None.
  3740. #
  3741. #----------------------------------------------------------------------
  3742. proc ::tcl::clock::WeekdayOnOrBefore { weekday j } {
  3743. set k [expr { ( $weekday + 6 ) % 7 }]
  3744. return [expr { $j - ( $j - $k ) % 7 }]
  3745. }
  3746. #----------------------------------------------------------------------
  3747. #
  3748. # BSearch --
  3749. #
  3750. # Service procedure that does binary search in several places inside the
  3751. # 'clock' command.
  3752. #
  3753. # Parameters:
  3754. # list - List of lists, sorted in ascending order by the
  3755. # first elements
  3756. # key - Value to search for
  3757. #
  3758. # Results:
  3759. # Returns the index of the greatest element in $list that is less than
  3760. # or equal to $key.
  3761. #
  3762. # Side effects:
  3763. # None.
  3764. #
  3765. #----------------------------------------------------------------------
  3766. proc ::tcl::clock::BSearch { list key } {
  3767. if {[llength $list] == 0} {
  3768. return -1
  3769. }
  3770. if { $key < [lindex $list 0 0] } {
  3771. return -1
  3772. }
  3773. set l 0
  3774. set u [expr { [llength $list] - 1 }]
  3775. while { $l < $u } {
  3776. # At this point, we know that
  3777. # $k >= [lindex $list $l 0]
  3778. # Either $u == [llength $list] or else $k < [lindex $list $u+1 0]
  3779. # We find the midpoint of the interval {l,u} rounded UP, compare
  3780. # against it, and set l or u to maintain the invariant. Note that the
  3781. # interval shrinks at each step, guaranteeing convergence.
  3782. set m [expr { ( $l + $u + 1 ) / 2 }]
  3783. if { $key >= [lindex $list $m 0] } {
  3784. set l $m
  3785. } else {
  3786. set u [expr { $m - 1 }]
  3787. }
  3788. }
  3789. return $l
  3790. }
  3791. #----------------------------------------------------------------------
  3792. #
  3793. # clock add --
  3794. #
  3795. # Adds an offset to a given time.
  3796. #
  3797. # Syntax:
  3798. # clock add clockval ?count unit?... ?-option value?
  3799. #
  3800. # Parameters:
  3801. # clockval -- Starting time value
  3802. # count -- Amount of a unit of time to add
  3803. # unit -- Unit of time to add, must be one of:
  3804. # years year months month weeks week
  3805. # days day hours hour minutes minute
  3806. # seconds second
  3807. #
  3808. # Options:
  3809. # -gmt BOOLEAN
  3810. # (Deprecated) Flag synonymous with '-timezone :GMT'
  3811. # -timezone ZONE
  3812. # Name of the time zone in which calculations are to be done.
  3813. # -locale NAME
  3814. # Name of the locale in which calculations are to be done.
  3815. # Used to determine the Gregorian change date.
  3816. #
  3817. # Results:
  3818. # Returns the given time adjusted by the given offset(s) in
  3819. # order.
  3820. #
  3821. # Notes:
  3822. # It is possible that adding a number of months or years will adjust the
  3823. # day of the month as well. For instance, the time at one month after
  3824. # 31 January is either 28 or 29 February, because February has fewer
  3825. # than 31 days.
  3826. #
  3827. #----------------------------------------------------------------------
  3828. proc ::tcl::clock::add { clockval args } {
  3829. if { [llength $args] % 2 != 0 } {
  3830. set cmdName "clock add"
  3831. return -code error \
  3832. -errorcode [list CLOCK wrongNumArgs] \
  3833. "wrong \# args: should be\
  3834. \"$cmdName clockval ?number units?...\
  3835. ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?\""
  3836. }
  3837. if { [catch { expr {wide($clockval)} } result] } {
  3838. return -code error $result
  3839. }
  3840. set offsets {}
  3841. set gmt 0
  3842. set locale c
  3843. set timezone [GetSystemTimeZone]
  3844. foreach { a b } $args {
  3845. if { [string is integer -strict $a] } {
  3846. lappend offsets $a $b
  3847. } else {
  3848. switch -exact -- $a {
  3849. -g - -gm - -gmt {
  3850. set gmt $b
  3851. }
  3852. -l - -lo - -loc - -loca - -local - -locale {
  3853. set locale [string tolower $b]
  3854. }
  3855. -t - -ti - -tim - -time - -timez - -timezo - -timezon -
  3856. -timezone {
  3857. set timezone $b
  3858. }
  3859. default {
  3860. throw [list CLOCK badOption $a] \
  3861. "bad option \"$a\",\
  3862. must be -gmt, -locale or -timezone"
  3863. }
  3864. }
  3865. }
  3866. }
  3867. # Check options for validity
  3868. if { [info exists saw(-gmt)] && [info exists saw(-timezone)] } {
  3869. return -code error \
  3870. -errorcode [list CLOCK gmtWithTimezone] \
  3871. "cannot use -gmt and -timezone in same call"
  3872. }
  3873. if { [catch { expr { wide($clockval) } } result] } {
  3874. return -code error "expected integer but got \"$clockval\""
  3875. }
  3876. if { ![string is boolean -strict $gmt] } {
  3877. return -code error "expected boolean value but got \"$gmt\""
  3878. } elseif { $gmt } {
  3879. set timezone :GMT
  3880. }
  3881. EnterLocale $locale
  3882. set changeover [mc GREGORIAN_CHANGE_DATE]
  3883. if {[catch {SetupTimeZone $timezone} retval opts]} {
  3884. dict unset opts -errorinfo
  3885. return -options $opts $retval
  3886. }
  3887. try {
  3888. foreach { quantity unit } $offsets {
  3889. switch -exact -- $unit {
  3890. years - year {
  3891. set clockval [AddMonths [expr { 12 * $quantity }] \
  3892. $clockval $timezone $changeover]
  3893. }
  3894. months - month {
  3895. set clockval [AddMonths $quantity $clockval $timezone \
  3896. $changeover]
  3897. }
  3898. weeks - week {
  3899. set clockval [AddDays [expr { 7 * $quantity }] \
  3900. $clockval $timezone $changeover]
  3901. }
  3902. days - day {
  3903. set clockval [AddDays $quantity $clockval $timezone \
  3904. $changeover]
  3905. }
  3906. hours - hour {
  3907. set clockval [expr { 3600 * $quantity + $clockval }]
  3908. }
  3909. minutes - minute {
  3910. set clockval [expr { 60 * $quantity + $clockval }]
  3911. }
  3912. seconds - second {
  3913. set clockval [expr { $quantity + $clockval }]
  3914. }
  3915. default {
  3916. throw [list CLOCK badUnit $unit] \
  3917. "unknown unit \"$unit\", must be \
  3918. years, months, weeks, days, hours, minutes or seconds"
  3919. }
  3920. }
  3921. }
  3922. return $clockval
  3923. } trap CLOCK {result opts} {
  3924. # Conceal the innards of [clock] when it's an expected error
  3925. dict unset opts -errorinfo
  3926. return -options $opts $result
  3927. }
  3928. }
  3929. #----------------------------------------------------------------------
  3930. #
  3931. # AddMonths --
  3932. #
  3933. # Add a given number of months to a given clock value in a given
  3934. # time zone.
  3935. #
  3936. # Parameters:
  3937. # months - Number of months to add (may be negative)
  3938. # clockval - Seconds since the epoch before the operation
  3939. # timezone - Time zone in which the operation is to be performed
  3940. #
  3941. # Results:
  3942. # Returns the new clock value as a number of seconds since
  3943. # the epoch.
  3944. #
  3945. # Side effects:
  3946. # None.
  3947. #
  3948. #----------------------------------------------------------------------
  3949. proc ::tcl::clock::AddMonths { months clockval timezone changeover } {
  3950. variable DaysInRomanMonthInCommonYear
  3951. variable DaysInRomanMonthInLeapYear
  3952. variable TZData
  3953. # Convert the time to year, month, day, and fraction of day.
  3954. set date [GetDateFields $clockval $TZData($timezone) $changeover]
  3955. dict set date secondOfDay [expr {
  3956. [dict get $date localSeconds] % 86400
  3957. }]
  3958. dict set date tzName $timezone
  3959. # Add the requisite number of months
  3960. set m [dict get $date month]
  3961. incr m $months
  3962. incr m -1
  3963. set delta [expr { $m / 12 }]
  3964. set mm [expr { $m % 12 }]
  3965. dict set date month [expr { $mm + 1 }]
  3966. dict incr date year $delta
  3967. # If the date doesn't exist in the current month, repair it
  3968. if { [IsGregorianLeapYear $date] } {
  3969. set hath [lindex $DaysInRomanMonthInLeapYear $mm]
  3970. } else {
  3971. set hath [lindex $DaysInRomanMonthInCommonYear $mm]
  3972. }
  3973. if { [dict get $date dayOfMonth] > $hath } {
  3974. dict set date dayOfMonth $hath
  3975. }
  3976. # Reconvert to a number of seconds
  3977. set date [GetJulianDayFromEraYearMonthDay \
  3978. $date[set date {}]\
  3979. $changeover]
  3980. dict set date localSeconds [expr {
  3981. -210866803200
  3982. + ( 86400 * wide([dict get $date julianDay]) )
  3983. + [dict get $date secondOfDay]
  3984. }]
  3985. set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \
  3986. $changeover]
  3987. return [dict get $date seconds]
  3988. }
  3989. #----------------------------------------------------------------------
  3990. #
  3991. # AddDays --
  3992. #
  3993. # Add a given number of days to a given clock value in a given time
  3994. # zone.
  3995. #
  3996. # Parameters:
  3997. # days - Number of days to add (may be negative)
  3998. # clockval - Seconds since the epoch before the operation
  3999. # timezone - Time zone in which the operation is to be performed
  4000. # changeover - Julian Day on which the Gregorian calendar was adopted
  4001. # in the target locale.
  4002. #
  4003. # Results:
  4004. # Returns the new clock value as a number of seconds since the epoch.
  4005. #
  4006. # Side effects:
  4007. # None.
  4008. #
  4009. #----------------------------------------------------------------------
  4010. proc ::tcl::clock::AddDays { days clockval timezone changeover } {
  4011. variable TZData
  4012. # Convert the time to Julian Day
  4013. set date [GetDateFields $clockval $TZData($timezone) $changeover]
  4014. dict set date secondOfDay [expr {
  4015. [dict get $date localSeconds] % 86400
  4016. }]
  4017. dict set date tzName $timezone
  4018. # Add the requisite number of days
  4019. dict incr date julianDay $days
  4020. # Reconvert to a number of seconds
  4021. dict set date localSeconds [expr {
  4022. -210866803200
  4023. + ( 86400 * wide([dict get $date julianDay]) )
  4024. + [dict get $date secondOfDay]
  4025. }]
  4026. set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \
  4027. $changeover]
  4028. return [dict get $date seconds]
  4029. }
  4030. #----------------------------------------------------------------------
  4031. #
  4032. # ChangeCurrentLocale --
  4033. #
  4034. # The global locale was changed within msgcat.
  4035. # Clears the buffered parse functions of the current locale.
  4036. #
  4037. # Parameters:
  4038. # loclist (ignored)
  4039. #
  4040. # Results:
  4041. # None.
  4042. #
  4043. # Side effects:
  4044. # Buffered parse functions are cleared.
  4045. #
  4046. #----------------------------------------------------------------------
  4047. proc ::tcl::clock::ChangeCurrentLocale {args} {
  4048. variable FormatProc
  4049. variable LocaleNumeralCache
  4050. variable CachedSystemTimeZone
  4051. variable TimeZoneBad
  4052. foreach p [info procs [namespace current]::scanproc'*'current] {
  4053. rename $p {}
  4054. }
  4055. foreach p [info procs [namespace current]::formatproc'*'current] {
  4056. rename $p {}
  4057. }
  4058. catch {array unset FormatProc *'current}
  4059. set LocaleNumeralCache {}
  4060. }
  4061. #----------------------------------------------------------------------
  4062. #
  4063. # ClearCaches --
  4064. #
  4065. # Clears all caches to reclaim the memory used in [clock]
  4066. #
  4067. # Parameters:
  4068. # None.
  4069. #
  4070. # Results:
  4071. # None.
  4072. #
  4073. # Side effects:
  4074. # Caches are cleared.
  4075. #
  4076. #----------------------------------------------------------------------
  4077. proc ::tcl::clock::ClearCaches {} {
  4078. variable FormatProc
  4079. variable LocaleNumeralCache
  4080. variable CachedSystemTimeZone
  4081. variable TimeZoneBad
  4082. foreach p [info procs [namespace current]::scanproc'*] {
  4083. rename $p {}
  4084. }
  4085. foreach p [info procs [namespace current]::formatproc'*] {
  4086. rename $p {}
  4087. }
  4088. catch {unset FormatProc}
  4089. set LocaleNumeralCache {}
  4090. catch {unset CachedSystemTimeZone}
  4091. set TimeZoneBad {}
  4092. InitTZData
  4093. }