clock.tcl 125 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551
  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-2007 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 earlier - 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 scan' 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. switch -exact -- $flag {
  1091. -b - -ba - -bas - -base {
  1092. set base $value
  1093. }
  1094. -f - -fo - -for - -form - -forma - -format {
  1095. set saw(-format) {}
  1096. set format $value
  1097. }
  1098. -g - -gm - -gmt {
  1099. set saw(-gmt) {}
  1100. set gmt $value
  1101. }
  1102. -l - -lo - -loc - -loca - -local - -locale {
  1103. set saw(-locale) {}
  1104. set locale [string tolower $value]
  1105. }
  1106. -t - -ti - -tim - -time - -timez - -timezo - -timezon - -timezone {
  1107. set saw(-timezone) {}
  1108. set timezone $value
  1109. }
  1110. default {
  1111. return -code error \
  1112. -errorcode [list CLOCK badOption $flag] \
  1113. "bad option \"$flag\",\
  1114. must be -base, -format, -gmt, -locale or -timezone"
  1115. }
  1116. }
  1117. }
  1118. # Check options for validity
  1119. if { [info exists saw(-gmt)] && [info exists saw(-timezone)] } {
  1120. return -code error \
  1121. -errorcode [list CLOCK gmtWithTimezone] \
  1122. "cannot use -gmt and -timezone in same call"
  1123. }
  1124. if { [catch { expr { wide($base) } } result] } {
  1125. return -code error "expected integer but got \"$base\""
  1126. }
  1127. if { ![string is boolean -strict $gmt] } {
  1128. return -code error "expected boolean value but got \"$gmt\""
  1129. } elseif { $gmt } {
  1130. set timezone :GMT
  1131. }
  1132. if { ![info exists saw(-format)] } {
  1133. # Perhaps someday we'll localize the legacy code. Right now, it's not
  1134. # localized.
  1135. if { [info exists saw(-locale)] } {
  1136. return -code error \
  1137. -errorcode [list CLOCK flagWithLegacyFormat] \
  1138. "legacy \[clock scan\] does not support -locale"
  1139. }
  1140. return [FreeScan $string $base $timezone $locale]
  1141. }
  1142. # Change locale if a fresh locale has been given on the command line.
  1143. EnterLocale $locale
  1144. try {
  1145. # Map away the locale-dependent composite format groups
  1146. set scanner [ParseClockScanFormat $format $locale]
  1147. return [$scanner $string $base $timezone]
  1148. } trap CLOCK {result opts} {
  1149. # Conceal location of generation of expected errors
  1150. dict unset opts -errorinfo
  1151. return -options $opts $result
  1152. }
  1153. }
  1154. #----------------------------------------------------------------------
  1155. #
  1156. # FreeScan --
  1157. #
  1158. # Scans a time in free format
  1159. #
  1160. # Parameters:
  1161. # string - String containing the time to scan
  1162. # base - Base time, expressed in seconds from the Epoch
  1163. # timezone - Default time zone in which the time will be expressed
  1164. # locale - (Unused) Name of the locale where the time will be scanned.
  1165. #
  1166. # Results:
  1167. # Returns the date and time extracted from the string in seconds from
  1168. # the epoch
  1169. #
  1170. #----------------------------------------------------------------------
  1171. proc ::tcl::clock::FreeScan { string base timezone locale } {
  1172. variable TZData
  1173. # Get the data for time changes in the given zone
  1174. try {
  1175. SetupTimeZone $timezone
  1176. } on error {retval opts} {
  1177. dict unset opts -errorinfo
  1178. return -options $opts $retval
  1179. }
  1180. # Extract year, month and day from the base time for the parser to use as
  1181. # defaults
  1182. set date [GetDateFields $base $TZData($timezone) 2361222]
  1183. dict set date secondOfDay [expr {
  1184. [dict get $date localSeconds] % 86400
  1185. }]
  1186. # Parse the date. The parser will return a list comprising date, time,
  1187. # time zone, relative month/day/seconds, relative weekday, ordinal month.
  1188. try {
  1189. set scanned [Oldscan $string \
  1190. [dict get $date year] \
  1191. [dict get $date month] \
  1192. [dict get $date dayOfMonth]]
  1193. lassign $scanned \
  1194. parseDate parseTime parseZone parseRel \
  1195. parseWeekday parseOrdinalMonth
  1196. } on error message {
  1197. return -code error \
  1198. "unable to convert date-time string \"$string\": $message"
  1199. }
  1200. # If the caller supplied a date in the string, update the 'date' dict with
  1201. # the value. If the caller didn't specify a time with the date, default to
  1202. # midnight.
  1203. if { [llength $parseDate] > 0 } {
  1204. lassign $parseDate y m d
  1205. if { $y < 100 } {
  1206. if { $y >= 39 } {
  1207. incr y 1900
  1208. } else {
  1209. incr y 2000
  1210. }
  1211. }
  1212. dict set date era CE
  1213. dict set date year $y
  1214. dict set date month $m
  1215. dict set date dayOfMonth $d
  1216. if { $parseTime eq {} } {
  1217. set parseTime 0
  1218. }
  1219. }
  1220. # If the caller supplied a time zone in the string, it comes back as a
  1221. # two-element list; the first element is the number of minutes east of
  1222. # Greenwich, and the second is a Daylight Saving Time indicator (1 == yes,
  1223. # 0 == no, -1 == unknown). We make it into a time zone indicator of
  1224. # +-hhmm.
  1225. if { [llength $parseZone] > 0 } {
  1226. lassign $parseZone minEast dstFlag
  1227. set timezone [FormatNumericTimeZone \
  1228. [expr { 60 * $minEast + 3600 * $dstFlag }]]
  1229. SetupTimeZone $timezone
  1230. }
  1231. dict set date tzName $timezone
  1232. # Assemble date, time, zone into seconds-from-epoch
  1233. set date [GetJulianDayFromEraYearMonthDay $date[set date {}] 2361222]
  1234. if { $parseTime ne {} } {
  1235. dict set date secondOfDay $parseTime
  1236. } elseif { [llength $parseWeekday] != 0
  1237. || [llength $parseOrdinalMonth] != 0
  1238. || ( [llength $parseRel] != 0
  1239. && ( [lindex $parseRel 0] != 0
  1240. || [lindex $parseRel 1] != 0 ) ) } {
  1241. dict set date secondOfDay 0
  1242. }
  1243. dict set date localSeconds [expr {
  1244. -210866803200
  1245. + ( 86400 * wide([dict get $date julianDay]) )
  1246. + [dict get $date secondOfDay]
  1247. }]
  1248. dict set date tzName $timezone
  1249. set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) 2361222]
  1250. set seconds [dict get $date seconds]
  1251. # Do relative times
  1252. if { [llength $parseRel] > 0 } {
  1253. lassign $parseRel relMonth relDay relSecond
  1254. set seconds [add $seconds \
  1255. $relMonth months $relDay days $relSecond seconds \
  1256. -timezone $timezone -locale $locale]
  1257. }
  1258. # Do relative weekday
  1259. if { [llength $parseWeekday] > 0 } {
  1260. lassign $parseWeekday dayOrdinal dayOfWeek
  1261. set date2 [GetDateFields $seconds $TZData($timezone) 2361222]
  1262. dict set date2 era CE
  1263. set jdwkday [WeekdayOnOrBefore $dayOfWeek [expr {
  1264. [dict get $date2 julianDay] + 6
  1265. }]]
  1266. incr jdwkday [expr { 7 * $dayOrdinal }]
  1267. if { $dayOrdinal > 0 } {
  1268. incr jdwkday -7
  1269. }
  1270. dict set date2 secondOfDay \
  1271. [expr { [dict get $date2 localSeconds] % 86400 }]
  1272. dict set date2 julianDay $jdwkday
  1273. dict set date2 localSeconds [expr {
  1274. -210866803200
  1275. + ( 86400 * wide([dict get $date2 julianDay]) )
  1276. + [dict get $date secondOfDay]
  1277. }]
  1278. dict set date2 tzName $timezone
  1279. set date2 [ConvertLocalToUTC $date2[set date2 {}] $TZData($timezone) \
  1280. 2361222]
  1281. set seconds [dict get $date2 seconds]
  1282. }
  1283. # Do relative month
  1284. if { [llength $parseOrdinalMonth] > 0 } {
  1285. lassign $parseOrdinalMonth monthOrdinal monthNumber
  1286. if { $monthOrdinal > 0 } {
  1287. set monthDiff [expr { $monthNumber - [dict get $date month] }]
  1288. if { $monthDiff <= 0 } {
  1289. incr monthDiff 12
  1290. }
  1291. incr monthOrdinal -1
  1292. } else {
  1293. set monthDiff [expr { [dict get $date month] - $monthNumber }]
  1294. if { $monthDiff >= 0 } {
  1295. incr monthDiff -12
  1296. }
  1297. incr monthOrdinal
  1298. }
  1299. set seconds [add $seconds $monthOrdinal years $monthDiff months \
  1300. -timezone $timezone -locale $locale]
  1301. }
  1302. return $seconds
  1303. }
  1304. #----------------------------------------------------------------------
  1305. #
  1306. # ParseClockScanFormat --
  1307. #
  1308. # Parses a format string given to [clock scan -format]
  1309. #
  1310. # Parameters:
  1311. # formatString - The format being parsed
  1312. # locale - The current locale
  1313. #
  1314. # Results:
  1315. # Constructs and returns a procedure that accepts the string being
  1316. # scanned, the base time, and the time zone. The procedure will either
  1317. # return the scanned time or else throw an error that should be rethrown
  1318. # to the caller of [clock scan]
  1319. #
  1320. # Side effects:
  1321. # The given procedure is defined in the ::tcl::clock namespace. Scan
  1322. # procedures are not deleted once installed.
  1323. #
  1324. # Why do we parse dates by defining a procedure to parse them? The reason is
  1325. # that by doing so, we have one convenient place to cache all the information:
  1326. # the regular expressions that match the patterns (which will be compiled),
  1327. # the code that assembles the date information, everything lands in one place.
  1328. # In this way, when a given format is reused at run time, all the information
  1329. # of how to apply it is available in a single place.
  1330. #
  1331. #----------------------------------------------------------------------
  1332. proc ::tcl::clock::ParseClockScanFormat {formatString locale} {
  1333. # Check whether the format has been parsed previously, and return the
  1334. # existing recognizer if it has.
  1335. set procName scanproc'$formatString'$locale
  1336. set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName]
  1337. if { [namespace which $procName] != {} } {
  1338. return $procName
  1339. }
  1340. variable DateParseActions
  1341. variable TimeParseActions
  1342. # Localize the %x, %X, etc. groups
  1343. set formatString [LocalizeFormat $locale $formatString]
  1344. # Condense whitespace
  1345. regsub -all {[[:space:]]+} $formatString { } formatString
  1346. # Walk through the groups of the format string. In this loop, we
  1347. # accumulate:
  1348. # - a regular expression that matches the string,
  1349. # - the count of capturing brackets in the regexp
  1350. # - a set of code that post-processes the fields captured by the regexp,
  1351. # - a dictionary whose keys are the names of fields that are present
  1352. # in the format string.
  1353. set re {^[[:space:]]*}
  1354. set captureCount 0
  1355. set postcode {}
  1356. set fieldSet [dict create]
  1357. set fieldCount 0
  1358. set postSep {}
  1359. set state {}
  1360. foreach c [split $formatString {}] {
  1361. switch -exact -- $state {
  1362. {} {
  1363. if { $c eq "%" } {
  1364. set state %
  1365. } elseif { $c eq " " } {
  1366. append re {[[:space:]]+}
  1367. } else {
  1368. if { ! [string is alnum $c] } {
  1369. append re "\\"
  1370. }
  1371. append re $c
  1372. }
  1373. }
  1374. % {
  1375. set state {}
  1376. switch -exact -- $c {
  1377. % {
  1378. append re %
  1379. }
  1380. { } {
  1381. append re "\[\[:space:\]\]*"
  1382. }
  1383. a - A { # Day of week, in words
  1384. set l {}
  1385. foreach \
  1386. i {7 1 2 3 4 5 6} \
  1387. abr [mc DAYS_OF_WEEK_ABBREV] \
  1388. full [mc DAYS_OF_WEEK_FULL] {
  1389. dict set l [string tolower $abr] $i
  1390. dict set l [string tolower $full] $i
  1391. incr i
  1392. }
  1393. lassign [UniquePrefixRegexp $l] regex lookup
  1394. append re ( $regex )
  1395. dict set fieldSet dayOfWeek [incr fieldCount]
  1396. append postcode "dict set date dayOfWeek \[" \
  1397. "dict get " [list $lookup] " " \
  1398. \[ {string tolower $field} [incr captureCount] \] \
  1399. "\]\n"
  1400. }
  1401. b - B - h { # Name of month
  1402. set i 0
  1403. set l {}
  1404. foreach \
  1405. abr [mc MONTHS_ABBREV] \
  1406. full [mc MONTHS_FULL] {
  1407. incr i
  1408. dict set l [string tolower $abr] $i
  1409. dict set l [string tolower $full] $i
  1410. }
  1411. lassign [UniquePrefixRegexp $l] regex lookup
  1412. append re ( $regex )
  1413. dict set fieldSet month [incr fieldCount]
  1414. append postcode "dict set date month \[" \
  1415. "dict get " [list $lookup] \
  1416. " " \[ {string tolower $field} \
  1417. [incr captureCount] \] \
  1418. "\]\n"
  1419. }
  1420. C { # Gregorian century
  1421. append re \\s*(\\d\\d?)
  1422. dict set fieldSet century [incr fieldCount]
  1423. append postcode "dict set date century \[" \
  1424. "::scan \$field" [incr captureCount] " %d" \
  1425. "\]\n"
  1426. }
  1427. d - e { # Day of month
  1428. append re \\s*(\\d\\d?)
  1429. dict set fieldSet dayOfMonth [incr fieldCount]
  1430. append postcode "dict set date dayOfMonth \[" \
  1431. "::scan \$field" [incr captureCount] " %d" \
  1432. "\]\n"
  1433. }
  1434. E { # Prefix for locale-specific codes
  1435. set state %E
  1436. }
  1437. g { # ISO8601 2-digit year
  1438. append re \\s*(\\d\\d)
  1439. dict set fieldSet iso8601YearOfCentury \
  1440. [incr fieldCount]
  1441. append postcode \
  1442. "dict set date iso8601YearOfCentury \[" \
  1443. "::scan \$field" [incr captureCount] " %d" \
  1444. "\]\n"
  1445. }
  1446. G { # ISO8601 4-digit year
  1447. append re \\s*(\\d\\d)(\\d\\d)
  1448. dict set fieldSet iso8601Century [incr fieldCount]
  1449. dict set fieldSet iso8601YearOfCentury \
  1450. [incr fieldCount]
  1451. append postcode \
  1452. "dict set date iso8601Century \[" \
  1453. "::scan \$field" [incr captureCount] " %d" \
  1454. "\]\n" \
  1455. "dict set date iso8601YearOfCentury \[" \
  1456. "::scan \$field" [incr captureCount] " %d" \
  1457. "\]\n"
  1458. }
  1459. H - k { # Hour of day
  1460. append re \\s*(\\d\\d?)
  1461. dict set fieldSet hour [incr fieldCount]
  1462. append postcode "dict set date hour \[" \
  1463. "::scan \$field" [incr captureCount] " %d" \
  1464. "\]\n"
  1465. }
  1466. I - l { # Hour, AM/PM
  1467. append re \\s*(\\d\\d?)
  1468. dict set fieldSet hourAMPM [incr fieldCount]
  1469. append postcode "dict set date hourAMPM \[" \
  1470. "::scan \$field" [incr captureCount] " %d" \
  1471. "\]\n"
  1472. }
  1473. j { # Day of year
  1474. append re \\s*(\\d\\d?\\d?)
  1475. dict set fieldSet dayOfYear [incr fieldCount]
  1476. append postcode "dict set date dayOfYear \[" \
  1477. "::scan \$field" [incr captureCount] " %d" \
  1478. "\]\n"
  1479. }
  1480. J { # Julian Day Number
  1481. append re \\s*(\\d+)
  1482. dict set fieldSet julianDay [incr fieldCount]
  1483. append postcode "dict set date julianDay \[" \
  1484. "::scan \$field" [incr captureCount] " %ld" \
  1485. "\]\n"
  1486. }
  1487. m - N { # Month number
  1488. append re \\s*(\\d\\d?)
  1489. dict set fieldSet month [incr fieldCount]
  1490. append postcode "dict set date month \[" \
  1491. "::scan \$field" [incr captureCount] " %d" \
  1492. "\]\n"
  1493. }
  1494. M { # Minute
  1495. append re \\s*(\\d\\d?)
  1496. dict set fieldSet minute [incr fieldCount]
  1497. append postcode "dict set date minute \[" \
  1498. "::scan \$field" [incr captureCount] " %d" \
  1499. "\]\n"
  1500. }
  1501. n { # Literal newline
  1502. append re \\n
  1503. }
  1504. O { # Prefix for locale numerics
  1505. set state %O
  1506. }
  1507. p - P { # AM/PM indicator
  1508. set l [list [string tolower [mc AM]] 0 \
  1509. [string tolower [mc PM]] 1]
  1510. lassign [UniquePrefixRegexp $l] regex lookup
  1511. append re ( $regex )
  1512. dict set fieldSet amPmIndicator [incr fieldCount]
  1513. append postcode "dict set date amPmIndicator \[" \
  1514. "dict get " [list $lookup] " \[string tolower " \
  1515. "\$field" \
  1516. [incr captureCount] \
  1517. "\]\]\n"
  1518. }
  1519. Q { # Hi, Jeff!
  1520. append re {Stardate\s+([-+]?\d+)(\d\d\d)[.](\d)}
  1521. incr captureCount
  1522. dict set fieldSet seconds [incr fieldCount]
  1523. append postcode {dict set date seconds } \[ \
  1524. {ParseStarDate $field} [incr captureCount] \
  1525. { $field} [incr captureCount] \
  1526. { $field} [incr captureCount] \
  1527. \] \n
  1528. }
  1529. s { # Seconds from Posix Epoch
  1530. # This next case is insanely difficult, because it's
  1531. # problematic to determine whether the field is
  1532. # actually within the range of a wide integer.
  1533. append re {\s*([-+]?\d+)}
  1534. dict set fieldSet seconds [incr fieldCount]
  1535. append postcode {dict set date seconds } \[ \
  1536. {ScanWide $field} [incr captureCount] \] \n
  1537. }
  1538. S { # Second
  1539. append re \\s*(\\d\\d?)
  1540. dict set fieldSet second [incr fieldCount]
  1541. append postcode "dict set date second \[" \
  1542. "::scan \$field" [incr captureCount] " %d" \
  1543. "\]\n"
  1544. }
  1545. t { # Literal tab character
  1546. append re \\t
  1547. }
  1548. u - w { # Day number within week, 0 or 7 == Sun
  1549. # 1=Mon, 6=Sat
  1550. append re \\s*(\\d)
  1551. dict set fieldSet dayOfWeek [incr fieldCount]
  1552. append postcode {::scan $field} [incr captureCount] \
  1553. { %d dow} \n \
  1554. {
  1555. if { $dow == 0 } {
  1556. set dow 7
  1557. } elseif { $dow > 7 } {
  1558. return -code error \
  1559. -errorcode [list CLOCK badDayOfWeek] \
  1560. "day of week is greater than 7"
  1561. }
  1562. dict set date dayOfWeek $dow
  1563. }
  1564. }
  1565. U { # Week of year. The first Sunday of
  1566. # the year is the first day of week
  1567. # 01. No scan rule uses this group.
  1568. append re \\s*\\d\\d?
  1569. }
  1570. V { # Week of ISO8601 year
  1571. append re \\s*(\\d\\d?)
  1572. dict set fieldSet iso8601Week [incr fieldCount]
  1573. append postcode "dict set date iso8601Week \[" \
  1574. "::scan \$field" [incr captureCount] " %d" \
  1575. "\]\n"
  1576. }
  1577. W { # Week of the year (00-53). The first
  1578. # Monday of the year is the first day
  1579. # of week 01. No scan rule uses this
  1580. # group.
  1581. append re \\s*\\d\\d?
  1582. }
  1583. y { # Two-digit Gregorian year
  1584. append re \\s*(\\d\\d?)
  1585. dict set fieldSet yearOfCentury [incr fieldCount]
  1586. append postcode "dict set date yearOfCentury \[" \
  1587. "::scan \$field" [incr captureCount] " %d" \
  1588. "\]\n"
  1589. }
  1590. Y { # 4-digit Gregorian year
  1591. append re \\s*(\\d\\d)(\\d\\d)
  1592. dict set fieldSet century [incr fieldCount]
  1593. dict set fieldSet yearOfCentury [incr fieldCount]
  1594. append postcode \
  1595. "dict set date century \[" \
  1596. "::scan \$field" [incr captureCount] " %d" \
  1597. "\]\n" \
  1598. "dict set date yearOfCentury \[" \
  1599. "::scan \$field" [incr captureCount] " %d" \
  1600. "\]\n"
  1601. }
  1602. z - Z { # Time zone name
  1603. append re {(?:([-+]\d\d(?::?\d\d(?::?\d\d)?)?)|([[:alnum:]]{1,4}))}
  1604. dict set fieldSet tzName [incr fieldCount]
  1605. append postcode \
  1606. {if } \{ { $field} [incr captureCount] \
  1607. { ne "" } \} { } \{ \n \
  1608. {dict set date tzName $field} \
  1609. $captureCount \n \
  1610. \} { else } \{ \n \
  1611. {dict set date tzName } \[ \
  1612. {ConvertLegacyTimeZone $field} \
  1613. [incr captureCount] \] \n \
  1614. \} \n \
  1615. }
  1616. % { # Literal percent character
  1617. append re %
  1618. }
  1619. default {
  1620. append re %
  1621. if { ! [string is alnum $c] } {
  1622. append re \\
  1623. }
  1624. append re $c
  1625. }
  1626. }
  1627. }
  1628. %E {
  1629. switch -exact -- $c {
  1630. C { # Locale-dependent era
  1631. set d {}
  1632. foreach triple [mc LOCALE_ERAS] {
  1633. lassign $triple t symbol year
  1634. dict set d [string tolower $symbol] $year
  1635. }
  1636. lassign [UniquePrefixRegexp $d] regex lookup
  1637. append re (?: $regex )
  1638. }
  1639. E {
  1640. set l {}
  1641. dict set l [string tolower [mc BCE]] BCE
  1642. dict set l [string tolower [mc CE]] CE
  1643. dict set l b.c.e. BCE
  1644. dict set l c.e. CE
  1645. dict set l b.c. BCE
  1646. dict set l a.d. CE
  1647. lassign [UniquePrefixRegexp $l] regex lookup
  1648. append re ( $regex )
  1649. dict set fieldSet era [incr fieldCount]
  1650. append postcode "dict set date era \["\
  1651. "dict get " [list $lookup] \
  1652. { } \[ {string tolower $field} \
  1653. [incr captureCount] \] \
  1654. "\]\n"
  1655. }
  1656. y { # Locale-dependent year of the era
  1657. lassign [LocaleNumeralMatcher $locale] regex lookup
  1658. append re $regex
  1659. incr captureCount
  1660. }
  1661. default {
  1662. append re %E
  1663. if { ! [string is alnum $c] } {
  1664. append re \\
  1665. }
  1666. append re $c
  1667. }
  1668. }
  1669. set state {}
  1670. }
  1671. %O {
  1672. switch -exact -- $c {
  1673. d - e {
  1674. lassign [LocaleNumeralMatcher $locale] regex lookup
  1675. append re $regex
  1676. dict set fieldSet dayOfMonth [incr fieldCount]
  1677. append postcode "dict set date dayOfMonth \[" \
  1678. "dict get " [list $lookup] " \$field" \
  1679. [incr captureCount] \
  1680. "\]\n"
  1681. }
  1682. H - k {
  1683. lassign [LocaleNumeralMatcher $locale] regex lookup
  1684. append re $regex
  1685. dict set fieldSet hour [incr fieldCount]
  1686. append postcode "dict set date hour \[" \
  1687. "dict get " [list $lookup] " \$field" \
  1688. [incr captureCount] \
  1689. "\]\n"
  1690. }
  1691. I - l {
  1692. lassign [LocaleNumeralMatcher $locale] regex lookup
  1693. append re $regex
  1694. dict set fieldSet hourAMPM [incr fieldCount]
  1695. append postcode "dict set date hourAMPM \[" \
  1696. "dict get " [list $lookup] " \$field" \
  1697. [incr captureCount] \
  1698. "\]\n"
  1699. }
  1700. m {
  1701. lassign [LocaleNumeralMatcher $locale] regex lookup
  1702. append re $regex
  1703. dict set fieldSet month [incr fieldCount]
  1704. append postcode "dict set date month \[" \
  1705. "dict get " [list $lookup] " \$field" \
  1706. [incr captureCount] \
  1707. "\]\n"
  1708. }
  1709. M {
  1710. lassign [LocaleNumeralMatcher $locale] regex lookup
  1711. append re $regex
  1712. dict set fieldSet minute [incr fieldCount]
  1713. append postcode "dict set date minute \[" \
  1714. "dict get " [list $lookup] " \$field" \
  1715. [incr captureCount] \
  1716. "\]\n"
  1717. }
  1718. S {
  1719. lassign [LocaleNumeralMatcher $locale] regex lookup
  1720. append re $regex
  1721. dict set fieldSet second [incr fieldCount]
  1722. append postcode "dict set date second \[" \
  1723. "dict get " [list $lookup] " \$field" \
  1724. [incr captureCount] \
  1725. "\]\n"
  1726. }
  1727. u - w {
  1728. lassign [LocaleNumeralMatcher $locale] regex lookup
  1729. append re $regex
  1730. dict set fieldSet dayOfWeek [incr fieldCount]
  1731. append postcode "set dow \[dict get " [list $lookup] \
  1732. { $field} [incr captureCount] \] \n \
  1733. {
  1734. if { $dow == 0 } {
  1735. set dow 7
  1736. } elseif { $dow > 7 } {
  1737. return -code error \
  1738. -errorcode [list CLOCK badDayOfWeek] \
  1739. "day of week is greater than 7"
  1740. }
  1741. dict set date dayOfWeek $dow
  1742. }
  1743. }
  1744. y {
  1745. lassign [LocaleNumeralMatcher $locale] regex lookup
  1746. append re $regex
  1747. dict set fieldSet yearOfCentury [incr fieldCount]
  1748. append postcode {dict set date yearOfCentury } \[ \
  1749. {dict get } [list $lookup] { $field} \
  1750. [incr captureCount] \] \n
  1751. }
  1752. default {
  1753. append re %O
  1754. if { ! [string is alnum $c] } {
  1755. append re \\
  1756. }
  1757. append re $c
  1758. }
  1759. }
  1760. set state {}
  1761. }
  1762. }
  1763. }
  1764. # Clean up any unfinished format groups
  1765. append re $state \\s*\$
  1766. # Build the procedure
  1767. set procBody {}
  1768. append procBody "variable ::tcl::clock::TZData" \n
  1769. append procBody "if \{ !\[ regexp -nocase [list $re] \$string ->"
  1770. for { set i 1 } { $i <= $captureCount } { incr i } {
  1771. append procBody " " field $i
  1772. }
  1773. append procBody "\] \} \{" \n
  1774. append procBody {
  1775. return -code error -errorcode [list CLOCK badInputString] \
  1776. {input string does not match supplied format}
  1777. }
  1778. append procBody \}\n
  1779. append procBody "set date \[dict create\]" \n
  1780. append procBody {dict set date tzName $timeZone} \n
  1781. append procBody $postcode
  1782. append procBody [list set changeover [mc GREGORIAN_CHANGE_DATE]] \n
  1783. # Set up the time zone before doing anything with a default base date
  1784. # that might need a timezone to interpret it.
  1785. if { ![dict exists $fieldSet seconds]
  1786. && ![dict exists $fieldSet starDate] } {
  1787. if { [dict exists $fieldSet tzName] } {
  1788. append procBody {
  1789. set timeZone [dict get $date tzName]
  1790. }
  1791. }
  1792. append procBody {
  1793. ::tcl::clock::SetupTimeZone $timeZone
  1794. }
  1795. }
  1796. # Add code that gets Julian Day Number from the fields.
  1797. append procBody [MakeParseCodeFromFields $fieldSet $DateParseActions]
  1798. # Get time of day
  1799. append procBody [MakeParseCodeFromFields $fieldSet $TimeParseActions]
  1800. # Assemble seconds from the Julian day and second of the day.
  1801. # Convert to local time unless epoch seconds or stardate are
  1802. # being processed - they're always absolute
  1803. if { ![dict exists $fieldSet seconds]
  1804. && ![dict exists $fieldSet starDate] } {
  1805. append procBody {
  1806. if { [dict get $date julianDay] > 5373484 } {
  1807. return -code error -errorcode [list CLOCK dateTooLarge] \
  1808. "requested date too large to represent"
  1809. }
  1810. dict set date localSeconds [expr {
  1811. -210866803200
  1812. + ( 86400 * wide([dict get $date julianDay]) )
  1813. + [dict get $date secondOfDay]
  1814. }]
  1815. }
  1816. # Finally, convert the date to local time
  1817. append procBody {
  1818. set date [::tcl::clock::ConvertLocalToUTC $date[set date {}] \
  1819. $TZData($timeZone) $changeover]
  1820. }
  1821. }
  1822. # Return result
  1823. append procBody {return [dict get $date seconds]} \n
  1824. proc $procName { string baseTime timeZone } $procBody
  1825. # puts [list proc $procName [list string baseTime timeZone] $procBody]
  1826. return $procName
  1827. }
  1828. #----------------------------------------------------------------------
  1829. #
  1830. # LocaleNumeralMatcher --
  1831. #
  1832. # Composes a regexp that captures the numerals in the given locale, and
  1833. # a dictionary to map them to conventional numerals.
  1834. #
  1835. # Parameters:
  1836. # locale - Name of the current locale
  1837. #
  1838. # Results:
  1839. # Returns a two-element list comprising the regexp and the dictionary.
  1840. #
  1841. # Side effects:
  1842. # Caches the result.
  1843. #
  1844. #----------------------------------------------------------------------
  1845. proc ::tcl::clock::LocaleNumeralMatcher {l} {
  1846. variable LocaleNumeralCache
  1847. if { ![dict exists $LocaleNumeralCache $l] } {
  1848. set d {}
  1849. set i 0
  1850. set sep \(
  1851. foreach n [mc LOCALE_NUMERALS] {
  1852. dict set d $n $i
  1853. regsub -all {[^[:alnum:]]} $n \\\\& subex
  1854. append re $sep $subex
  1855. set sep |
  1856. incr i
  1857. }
  1858. append re \)
  1859. dict set LocaleNumeralCache $l [list $re $d]
  1860. }
  1861. return [dict get $LocaleNumeralCache $l]
  1862. }
  1863. #----------------------------------------------------------------------
  1864. #
  1865. # UniquePrefixRegexp --
  1866. #
  1867. # Composes a regexp that performs unique-prefix matching. The RE
  1868. # matches one of a supplied set of strings, or any unique prefix
  1869. # thereof.
  1870. #
  1871. # Parameters:
  1872. # data - List of alternating match-strings and values.
  1873. # Match-strings with distinct values are considered
  1874. # distinct.
  1875. #
  1876. # Results:
  1877. # Returns a two-element list. The first is a regexp that matches any
  1878. # unique prefix of any of the strings. The second is a dictionary whose
  1879. # keys are match values from the regexp and whose values are the
  1880. # corresponding values from 'data'.
  1881. #
  1882. # Side effects:
  1883. # None.
  1884. #
  1885. #----------------------------------------------------------------------
  1886. proc ::tcl::clock::UniquePrefixRegexp { data } {
  1887. # The 'successors' dictionary will contain, for each string that is a
  1888. # prefix of any key, all characters that may follow that prefix. The
  1889. # 'prefixMapping' dictionary will have keys that are prefixes of keys and
  1890. # values that correspond to the keys.
  1891. set prefixMapping [dict create]
  1892. set successors [dict create {} {}]
  1893. # Walk the key-value pairs
  1894. foreach { key value } $data {
  1895. # Construct all prefixes of the key;
  1896. set prefix {}
  1897. foreach char [split $key {}] {
  1898. set oldPrefix $prefix
  1899. dict set successors $oldPrefix $char {}
  1900. append prefix $char
  1901. # Put the prefixes in the 'prefixMapping' and 'successors'
  1902. # dictionaries
  1903. dict lappend prefixMapping $prefix $value
  1904. if { ![dict exists $successors $prefix] } {
  1905. dict set successors $prefix {}
  1906. }
  1907. }
  1908. }
  1909. # Identify those prefixes that designate unique values, and those that are
  1910. # the full keys
  1911. set uniquePrefixMapping {}
  1912. dict for { key valueList } $prefixMapping {
  1913. if { [llength $valueList] == 1 } {
  1914. dict set uniquePrefixMapping $key [lindex $valueList 0]
  1915. }
  1916. }
  1917. foreach { key value } $data {
  1918. dict set uniquePrefixMapping $key $value
  1919. }
  1920. # Construct the re.
  1921. return [list \
  1922. [MakeUniquePrefixRegexp $successors $uniquePrefixMapping {}] \
  1923. $uniquePrefixMapping]
  1924. }
  1925. #----------------------------------------------------------------------
  1926. #
  1927. # MakeUniquePrefixRegexp --
  1928. #
  1929. # Service procedure for 'UniquePrefixRegexp' that constructs a regular
  1930. # expresison that matches the unique prefixes.
  1931. #
  1932. # Parameters:
  1933. # successors - Dictionary whose keys are all prefixes
  1934. # of keys passed to 'UniquePrefixRegexp' and whose
  1935. # values are dictionaries whose keys are the characters
  1936. # that may follow those prefixes.
  1937. # uniquePrefixMapping - Dictionary whose keys are the unique
  1938. # prefixes and whose values are not examined.
  1939. # prefixString - Current prefix being processed.
  1940. #
  1941. # Results:
  1942. # Returns a constructed regular expression that matches the set of
  1943. # unique prefixes beginning with the 'prefixString'.
  1944. #
  1945. # Side effects:
  1946. # None.
  1947. #
  1948. #----------------------------------------------------------------------
  1949. proc ::tcl::clock::MakeUniquePrefixRegexp { successors
  1950. uniquePrefixMapping
  1951. prefixString } {
  1952. # Get the characters that may follow the current prefix string
  1953. set schars [lsort -ascii [dict keys [dict get $successors $prefixString]]]
  1954. if { [llength $schars] == 0 } {
  1955. return {}
  1956. }
  1957. # If there is more than one successor character, or if the current prefix
  1958. # is a unique prefix, surround the generated re with non-capturing
  1959. # parentheses.
  1960. set re {}
  1961. if {
  1962. [dict exists $uniquePrefixMapping $prefixString]
  1963. || [llength $schars] > 1
  1964. } then {
  1965. append re "(?:"
  1966. }
  1967. # Generate a regexp that matches the successors.
  1968. set sep ""
  1969. foreach { c } $schars {
  1970. set nextPrefix $prefixString$c
  1971. regsub -all {[^[:alnum:]]} $c \\\\& rechar
  1972. append re $sep $rechar \
  1973. [MakeUniquePrefixRegexp \
  1974. $successors $uniquePrefixMapping $nextPrefix]
  1975. set sep |
  1976. }
  1977. # If the current prefix is a unique prefix, make all following text
  1978. # optional. Otherwise, if there is more than one successor character,
  1979. # close the non-capturing parentheses.
  1980. if { [dict exists $uniquePrefixMapping $prefixString] } {
  1981. append re ")?"
  1982. } elseif { [llength $schars] > 1 } {
  1983. append re ")"
  1984. }
  1985. return $re
  1986. }
  1987. #----------------------------------------------------------------------
  1988. #
  1989. # MakeParseCodeFromFields --
  1990. #
  1991. # Composes Tcl code to extract the Julian Day Number from a dictionary
  1992. # containing date fields.
  1993. #
  1994. # Parameters:
  1995. # dateFields -- Dictionary whose keys are fields of the date,
  1996. # and whose values are the rightmost positions
  1997. # at which those fields appear.
  1998. # parseActions -- List of triples: field set, priority, and
  1999. # code to emit. Smaller priorities are better, and
  2000. # the list must be in ascending order by priority
  2001. #
  2002. # Results:
  2003. # Returns a burst of code that extracts the day number from the given
  2004. # date.
  2005. #
  2006. # Side effects:
  2007. # None.
  2008. #
  2009. #----------------------------------------------------------------------
  2010. proc ::tcl::clock::MakeParseCodeFromFields { dateFields parseActions } {
  2011. set currPrio 999
  2012. set currFieldPos [list]
  2013. set currCodeBurst {
  2014. error "in ::tcl::clock::MakeParseCodeFromFields: can't happen"
  2015. }
  2016. foreach { fieldSet prio parseAction } $parseActions {
  2017. # If we've found an answer that's better than any that follow, quit
  2018. # now.
  2019. if { $prio > $currPrio } {
  2020. break
  2021. }
  2022. # Accumulate the field positions that are used in the current field
  2023. # grouping.
  2024. set fieldPos [list]
  2025. set ok true
  2026. foreach field $fieldSet {
  2027. if { ! [dict exists $dateFields $field] } {
  2028. set ok 0
  2029. break
  2030. }
  2031. lappend fieldPos [dict get $dateFields $field]
  2032. }
  2033. # Quit if we don't have a complete set of fields
  2034. if { !$ok } {
  2035. continue
  2036. }
  2037. # Determine whether the current answer is better than the last.
  2038. set fPos [lsort -integer -decreasing $fieldPos]
  2039. if { $prio == $currPrio } {
  2040. foreach currPos $currFieldPos newPos $fPos {
  2041. if {
  2042. ![string is integer $newPos]
  2043. || ![string is integer $currPos]
  2044. || $newPos > $currPos
  2045. } then {
  2046. break
  2047. }
  2048. if { $newPos < $currPos } {
  2049. set ok 0
  2050. break
  2051. }
  2052. }
  2053. }
  2054. if { !$ok } {
  2055. continue
  2056. }
  2057. # Remember the best possibility for extracting date information
  2058. set currPrio $prio
  2059. set currFieldPos $fPos
  2060. set currCodeBurst $parseAction
  2061. }
  2062. return $currCodeBurst
  2063. }
  2064. #----------------------------------------------------------------------
  2065. #
  2066. # EnterLocale --
  2067. #
  2068. # Switch [mclocale] to a given locale if necessary
  2069. #
  2070. # Parameters:
  2071. # locale -- Desired locale
  2072. #
  2073. # Results:
  2074. # Returns the locale that was previously current.
  2075. #
  2076. # Side effects:
  2077. # Does [mclocale]. If necessary, loads the designated locale's files.
  2078. #
  2079. #----------------------------------------------------------------------
  2080. proc ::tcl::clock::EnterLocale { locale } {
  2081. if { $locale eq {system} } {
  2082. if { $::tcl_platform(platform) ne {windows} } {
  2083. # On a non-windows platform, the 'system' locale is the same as
  2084. # the 'current' locale
  2085. set locale current
  2086. } else {
  2087. # On a windows platform, the 'system' locale is adapted from the
  2088. # 'current' locale by applying the date and time formats from the
  2089. # Control Panel. First, load the 'current' locale if it's not yet
  2090. # loaded
  2091. mcpackagelocale set [mclocale]
  2092. # Make a new locale string for the system locale, and get the
  2093. # Control Panel information
  2094. set locale [mclocale]_windows
  2095. if { ! [mcpackagelocale present $locale] } {
  2096. LoadWindowsDateTimeFormats $locale
  2097. }
  2098. }
  2099. }
  2100. if { $locale eq {current}} {
  2101. set locale [mclocale]
  2102. }
  2103. # Eventually load the locale
  2104. mcpackagelocale set $locale
  2105. }
  2106. #----------------------------------------------------------------------
  2107. #
  2108. # LoadWindowsDateTimeFormats --
  2109. #
  2110. # Load the date/time formats from the Control Panel in Windows and
  2111. # convert them so that they're usable by Tcl.
  2112. #
  2113. # Parameters:
  2114. # locale - Name of the locale in whose message catalog
  2115. # the converted formats are to be stored.
  2116. #
  2117. # Results:
  2118. # None.
  2119. #
  2120. # Side effects:
  2121. # Updates the given message catalog with the locale strings.
  2122. #
  2123. # Presumes that on entry, [mclocale] is set to the current locale, so that
  2124. # default strings can be obtained if the Registry query fails.
  2125. #
  2126. #----------------------------------------------------------------------
  2127. proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } {
  2128. # Bail out if we can't find the Registry
  2129. variable NoRegistry
  2130. if { [info exists NoRegistry] } return
  2131. if { ![catch {
  2132. registry get "HKEY_CURRENT_USER\\Control Panel\\International" \
  2133. sShortDate
  2134. } string] } {
  2135. set quote {}
  2136. set datefmt {}
  2137. foreach { unquoted quoted } [split $string '] {
  2138. append datefmt $quote [string map {
  2139. dddd %A
  2140. ddd %a
  2141. dd %d
  2142. d %e
  2143. MMMM %B
  2144. MMM %b
  2145. MM %m
  2146. M %N
  2147. yyyy %Y
  2148. yy %y
  2149. y %y
  2150. gg {}
  2151. } $unquoted]
  2152. if { $quoted eq {} } {
  2153. set quote '
  2154. } else {
  2155. set quote $quoted
  2156. }
  2157. }
  2158. ::msgcat::mcset $locale DATE_FORMAT $datefmt
  2159. }
  2160. if { ![catch {
  2161. registry get "HKEY_CURRENT_USER\\Control Panel\\International" \
  2162. sLongDate
  2163. } string] } {
  2164. set quote {}
  2165. set ldatefmt {}
  2166. foreach { unquoted quoted } [split $string '] {
  2167. append ldatefmt $quote [string map {
  2168. dddd %A
  2169. ddd %a
  2170. dd %d
  2171. d %e
  2172. MMMM %B
  2173. MMM %b
  2174. MM %m
  2175. M %N
  2176. yyyy %Y
  2177. yy %y
  2178. y %y
  2179. gg {}
  2180. } $unquoted]
  2181. if { $quoted eq {} } {
  2182. set quote '
  2183. } else {
  2184. set quote $quoted
  2185. }
  2186. }
  2187. ::msgcat::mcset $locale LOCALE_DATE_FORMAT $ldatefmt
  2188. }
  2189. if { ![catch {
  2190. registry get "HKEY_CURRENT_USER\\Control Panel\\International" \
  2191. sTimeFormat
  2192. } string] } {
  2193. set quote {}
  2194. set timefmt {}
  2195. foreach { unquoted quoted } [split $string '] {
  2196. append timefmt $quote [string map {
  2197. HH %H
  2198. H %k
  2199. hh %I
  2200. h %l
  2201. mm %M
  2202. m %M
  2203. ss %S
  2204. s %S
  2205. tt %p
  2206. t %p
  2207. } $unquoted]
  2208. if { $quoted eq {} } {
  2209. set quote '
  2210. } else {
  2211. set quote $quoted
  2212. }
  2213. }
  2214. ::msgcat::mcset $locale TIME_FORMAT $timefmt
  2215. }
  2216. catch {
  2217. ::msgcat::mcset $locale DATE_TIME_FORMAT "$datefmt $timefmt"
  2218. }
  2219. catch {
  2220. ::msgcat::mcset $locale LOCALE_DATE_TIME_FORMAT "$ldatefmt $timefmt"
  2221. }
  2222. return
  2223. }
  2224. #----------------------------------------------------------------------
  2225. #
  2226. # LocalizeFormat --
  2227. #
  2228. # Map away locale-dependent format groups in a clock format.
  2229. #
  2230. # Parameters:
  2231. # locale -- Current [mclocale] locale, supplied to avoid
  2232. # an extra call
  2233. # format -- Format supplied to [clock scan] or [clock format]
  2234. #
  2235. # Results:
  2236. # Returns the string with locale-dependent composite format groups
  2237. # substituted out.
  2238. #
  2239. # Side effects:
  2240. # None.
  2241. #
  2242. #----------------------------------------------------------------------
  2243. proc ::tcl::clock::LocalizeFormat { locale format } {
  2244. # message catalog key to cache this format
  2245. set key FORMAT_$format
  2246. if { [::msgcat::mcexists -exactlocale -exactnamespace $key] } {
  2247. return [mc $key]
  2248. }
  2249. # Handle locale-dependent format groups by mapping them out of the format
  2250. # string. Note that the order of the [string map] operations is
  2251. # significant because later formats can refer to later ones; for example
  2252. # %c can refer to %X, which in turn can refer to %T.
  2253. set list {
  2254. %% %%
  2255. %D %m/%d/%Y
  2256. %+ {%a %b %e %H:%M:%S %Z %Y}
  2257. }
  2258. lappend list %EY [string map $list [mc LOCALE_YEAR_FORMAT]]
  2259. lappend list %T [string map $list [mc TIME_FORMAT_24_SECS]]
  2260. lappend list %R [string map $list [mc TIME_FORMAT_24]]
  2261. lappend list %r [string map $list [mc TIME_FORMAT_12]]
  2262. lappend list %X [string map $list [mc TIME_FORMAT]]
  2263. lappend list %EX [string map $list [mc LOCALE_TIME_FORMAT]]
  2264. lappend list %x [string map $list [mc DATE_FORMAT]]
  2265. lappend list %Ex [string map $list [mc LOCALE_DATE_FORMAT]]
  2266. lappend list %c [string map $list [mc DATE_TIME_FORMAT]]
  2267. lappend list %Ec [string map $list [mc LOCALE_DATE_TIME_FORMAT]]
  2268. set format [string map $list $format]
  2269. ::msgcat::mcset $locale $key $format
  2270. return $format
  2271. }
  2272. #----------------------------------------------------------------------
  2273. #
  2274. # FormatNumericTimeZone --
  2275. #
  2276. # Formats a time zone as +hhmmss
  2277. #
  2278. # Parameters:
  2279. # z - Time zone in seconds east of Greenwich
  2280. #
  2281. # Results:
  2282. # Returns the time zone formatted in a numeric form
  2283. #
  2284. # Side effects:
  2285. # None.
  2286. #
  2287. #----------------------------------------------------------------------
  2288. proc ::tcl::clock::FormatNumericTimeZone { z } {
  2289. if { $z < 0 } {
  2290. set z [expr { - $z }]
  2291. set retval -
  2292. } else {
  2293. set retval +
  2294. }
  2295. append retval [::format %02d [expr { $z / 3600 }]]
  2296. set z [expr { $z % 3600 }]
  2297. append retval [::format %02d [expr { $z / 60 }]]
  2298. set z [expr { $z % 60 }]
  2299. if { $z != 0 } {
  2300. append retval [::format %02d $z]
  2301. }
  2302. return $retval
  2303. }
  2304. #----------------------------------------------------------------------
  2305. #
  2306. # FormatStarDate --
  2307. #
  2308. # Formats a date as a StarDate.
  2309. #
  2310. # Parameters:
  2311. # date - Dictionary containing 'year', 'dayOfYear', and
  2312. # 'localSeconds' fields.
  2313. #
  2314. # Results:
  2315. # Returns the given date formatted as a StarDate.
  2316. #
  2317. # Side effects:
  2318. # None.
  2319. #
  2320. # Jeff Hobbs put this in to support an atrocious pun about Tcl being
  2321. # "Enterprise ready." Now we're stuck with it.
  2322. #
  2323. #----------------------------------------------------------------------
  2324. proc ::tcl::clock::FormatStarDate { date } {
  2325. variable Roddenberry
  2326. # Get day of year, zero based
  2327. set doy [expr { [dict get $date dayOfYear] - 1 }]
  2328. # Determine whether the year is a leap year
  2329. set lp [IsGregorianLeapYear $date]
  2330. # Convert day of year to a fractional year
  2331. if { $lp } {
  2332. set fractYear [expr { 1000 * $doy / 366 }]
  2333. } else {
  2334. set fractYear [expr { 1000 * $doy / 365 }]
  2335. }
  2336. # Put together the StarDate
  2337. return [::format "Stardate %02d%03d.%1d" \
  2338. [expr { [dict get $date year] - $Roddenberry }] \
  2339. $fractYear \
  2340. [expr { [dict get $date localSeconds] % 86400
  2341. / ( 86400 / 10 ) }]]
  2342. }
  2343. #----------------------------------------------------------------------
  2344. #
  2345. # ParseStarDate --
  2346. #
  2347. # Parses a StarDate
  2348. #
  2349. # Parameters:
  2350. # year - Year from the Roddenberry epoch
  2351. # fractYear - Fraction of a year specifying the day of year.
  2352. # fractDay - Fraction of a day
  2353. #
  2354. # Results:
  2355. # Returns a count of seconds from the Posix epoch.
  2356. #
  2357. # Side effects:
  2358. # None.
  2359. #
  2360. # Jeff Hobbs put this in to support an atrocious pun about Tcl being
  2361. # "Enterprise ready." Now we're stuck with it.
  2362. #
  2363. #----------------------------------------------------------------------
  2364. proc ::tcl::clock::ParseStarDate { year fractYear fractDay } {
  2365. variable Roddenberry
  2366. # Build a tentative date from year and fraction.
  2367. set date [dict create \
  2368. gregorian 1 \
  2369. era CE \
  2370. year [expr { $year + $Roddenberry }] \
  2371. dayOfYear [expr { $fractYear * 365 / 1000 + 1 }]]
  2372. set date [GetJulianDayFromGregorianEraYearDay $date[set date {}]]
  2373. # Determine whether the given year is a leap year
  2374. set lp [IsGregorianLeapYear $date]
  2375. # Reconvert the fractional year according to whether the given year is a
  2376. # leap year
  2377. if { $lp } {
  2378. dict set date dayOfYear \
  2379. [expr { $fractYear * 366 / 1000 + 1 }]
  2380. } else {
  2381. dict set date dayOfYear \
  2382. [expr { $fractYear * 365 / 1000 + 1 }]
  2383. }
  2384. dict unset date julianDay
  2385. dict unset date gregorian
  2386. set date [GetJulianDayFromGregorianEraYearDay $date[set date {}]]
  2387. return [expr {
  2388. 86400 * [dict get $date julianDay]
  2389. - 210866803200
  2390. + ( 86400 / 10 ) * $fractDay
  2391. }]
  2392. }
  2393. #----------------------------------------------------------------------
  2394. #
  2395. # ScanWide --
  2396. #
  2397. # Scans a wide integer from an input
  2398. #
  2399. # Parameters:
  2400. # str - String containing a decimal wide integer
  2401. #
  2402. # Results:
  2403. # Returns the string as a pure wide integer. Throws an error if the
  2404. # string is misformatted or out of range.
  2405. #
  2406. #----------------------------------------------------------------------
  2407. proc ::tcl::clock::ScanWide { str } {
  2408. set count [::scan $str {%ld %c} result junk]
  2409. if { $count != 1 } {
  2410. return -code error -errorcode [list CLOCK notAnInteger $str] \
  2411. "\"$str\" is not an integer"
  2412. }
  2413. if { [incr result 0] != $str } {
  2414. return -code error -errorcode [list CLOCK integervalueTooLarge] \
  2415. "integer value too large to represent"
  2416. }
  2417. return $result
  2418. }
  2419. #----------------------------------------------------------------------
  2420. #
  2421. # InterpretTwoDigitYear --
  2422. #
  2423. # Given a date that contains only the year of the century, determines
  2424. # the target value of a two-digit year.
  2425. #
  2426. # Parameters:
  2427. # date - Dictionary containing fields of the date.
  2428. # baseTime - Base time relative to which the date is expressed.
  2429. # twoDigitField - Name of the field that stores the two-digit year.
  2430. # Default is 'yearOfCentury'
  2431. # fourDigitField - Name of the field that will receive the four-digit
  2432. # year. Default is 'year'
  2433. #
  2434. # Results:
  2435. # Returns the dictionary augmented with the four-digit year, stored in
  2436. # the given key.
  2437. #
  2438. # Side effects:
  2439. # None.
  2440. #
  2441. # The current rule for interpreting a two-digit year is that the year shall be
  2442. # between 1937 and 2037, thus staying within the range of a 32-bit signed
  2443. # value for time. This rule may change to a sliding window in future
  2444. # versions, so the 'baseTime' parameter (which is currently ignored) is
  2445. # provided in the procedure signature.
  2446. #
  2447. #----------------------------------------------------------------------
  2448. proc ::tcl::clock::InterpretTwoDigitYear { date baseTime
  2449. { twoDigitField yearOfCentury }
  2450. { fourDigitField year } } {
  2451. set yr [dict get $date $twoDigitField]
  2452. if { $yr <= 37 } {
  2453. dict set date $fourDigitField [expr { $yr + 2000 }]
  2454. } else {
  2455. dict set date $fourDigitField [expr { $yr + 1900 }]
  2456. }
  2457. return $date
  2458. }
  2459. #----------------------------------------------------------------------
  2460. #
  2461. # AssignBaseYear --
  2462. #
  2463. # Places the number of the current year into a dictionary.
  2464. #
  2465. # Parameters:
  2466. # date - Dictionary value to update
  2467. # baseTime - Base time from which to extract the year, expressed
  2468. # in seconds from the Posix epoch
  2469. # timezone - the time zone in which the date is being scanned
  2470. # changeover - the Julian Day on which the Gregorian calendar
  2471. # was adopted in the target locale.
  2472. #
  2473. # Results:
  2474. # Returns the dictionary with the current year assigned.
  2475. #
  2476. # Side effects:
  2477. # None.
  2478. #
  2479. #----------------------------------------------------------------------
  2480. proc ::tcl::clock::AssignBaseYear { date baseTime timezone changeover } {
  2481. variable TZData
  2482. # Find the Julian Day Number corresponding to the base time, and
  2483. # find the Gregorian year corresponding to that Julian Day.
  2484. set date2 [GetDateFields $baseTime $TZData($timezone) $changeover]
  2485. # Store the converted year
  2486. dict set date era [dict get $date2 era]
  2487. dict set date year [dict get $date2 year]
  2488. return $date
  2489. }
  2490. #----------------------------------------------------------------------
  2491. #
  2492. # AssignBaseIso8601Year --
  2493. #
  2494. # Determines the base year in the ISO8601 fiscal calendar.
  2495. #
  2496. # Parameters:
  2497. # date - Dictionary containing the fields of the date that
  2498. # is to be augmented with the base year.
  2499. # baseTime - Base time expressed in seconds from the Posix epoch.
  2500. # timeZone - Target time zone
  2501. # changeover - Julian Day of adoption of the Gregorian calendar in
  2502. # the target locale.
  2503. #
  2504. # Results:
  2505. # Returns the given date with "iso8601Year" set to the
  2506. # base year.
  2507. #
  2508. # Side effects:
  2509. # None.
  2510. #
  2511. #----------------------------------------------------------------------
  2512. proc ::tcl::clock::AssignBaseIso8601Year {date baseTime timeZone changeover} {
  2513. variable TZData
  2514. # Find the Julian Day Number corresponding to the base time
  2515. set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover]
  2516. # Calculate the ISO8601 date and transfer the year
  2517. dict set date era CE
  2518. dict set date iso8601Year [dict get $date2 iso8601Year]
  2519. return $date
  2520. }
  2521. #----------------------------------------------------------------------
  2522. #
  2523. # AssignBaseMonth --
  2524. #
  2525. # Places the number of the current year and month into a
  2526. # dictionary.
  2527. #
  2528. # Parameters:
  2529. # date - Dictionary value to update
  2530. # baseTime - Time from which the year and month are to be
  2531. # obtained, expressed in seconds from the Posix epoch.
  2532. # timezone - Name of the desired time zone
  2533. # changeover - Julian Day on which the Gregorian calendar was adopted.
  2534. #
  2535. # Results:
  2536. # Returns the dictionary with the base year and month assigned.
  2537. #
  2538. # Side effects:
  2539. # None.
  2540. #
  2541. #----------------------------------------------------------------------
  2542. proc ::tcl::clock::AssignBaseMonth {date baseTime timezone changeover} {
  2543. variable TZData
  2544. # Find the year and month corresponding to the base time
  2545. set date2 [GetDateFields $baseTime $TZData($timezone) $changeover]
  2546. dict set date era [dict get $date2 era]
  2547. dict set date year [dict get $date2 year]
  2548. dict set date month [dict get $date2 month]
  2549. return $date
  2550. }
  2551. #----------------------------------------------------------------------
  2552. #
  2553. # AssignBaseWeek --
  2554. #
  2555. # Determines the base year and week in the ISO8601 fiscal calendar.
  2556. #
  2557. # Parameters:
  2558. # date - Dictionary containing the fields of the date that
  2559. # is to be augmented with the base year and week.
  2560. # baseTime - Base time expressed in seconds from the Posix epoch.
  2561. # changeover - Julian Day on which the Gregorian calendar was adopted
  2562. # in the target locale.
  2563. #
  2564. # Results:
  2565. # Returns the given date with "iso8601Year" set to the
  2566. # base year and "iso8601Week" to the week number.
  2567. #
  2568. # Side effects:
  2569. # None.
  2570. #
  2571. #----------------------------------------------------------------------
  2572. proc ::tcl::clock::AssignBaseWeek {date baseTime timeZone changeover} {
  2573. variable TZData
  2574. # Find the Julian Day Number corresponding to the base time
  2575. set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover]
  2576. # Calculate the ISO8601 date and transfer the year
  2577. dict set date era CE
  2578. dict set date iso8601Year [dict get $date2 iso8601Year]
  2579. dict set date iso8601Week [dict get $date2 iso8601Week]
  2580. return $date
  2581. }
  2582. #----------------------------------------------------------------------
  2583. #
  2584. # AssignBaseJulianDay --
  2585. #
  2586. # Determines the base day for a time-of-day conversion.
  2587. #
  2588. # Parameters:
  2589. # date - Dictionary that is to get the base day
  2590. # baseTime - Base time expressed in seconds from the Posix epoch
  2591. # changeover - Julian day on which the Gregorian calendar was
  2592. # adpoted in the target locale.
  2593. #
  2594. # Results:
  2595. # Returns the given dictionary augmented with a 'julianDay' field
  2596. # that contains the base day.
  2597. #
  2598. # Side effects:
  2599. # None.
  2600. #
  2601. #----------------------------------------------------------------------
  2602. proc ::tcl::clock::AssignBaseJulianDay { date baseTime timeZone changeover } {
  2603. variable TZData
  2604. # Find the Julian Day Number corresponding to the base time
  2605. set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover]
  2606. dict set date julianDay [dict get $date2 julianDay]
  2607. return $date
  2608. }
  2609. #----------------------------------------------------------------------
  2610. #
  2611. # InterpretHMSP --
  2612. #
  2613. # Interprets a time in the form "hh:mm:ss am".
  2614. #
  2615. # Parameters:
  2616. # date -- Dictionary containing "hourAMPM", "minute", "second"
  2617. # and "amPmIndicator" fields.
  2618. #
  2619. # Results:
  2620. # Returns the number of seconds from local midnight.
  2621. #
  2622. # Side effects:
  2623. # None.
  2624. #
  2625. #----------------------------------------------------------------------
  2626. proc ::tcl::clock::InterpretHMSP { date } {
  2627. set hr [dict get $date hourAMPM]
  2628. if { $hr == 12 } {
  2629. set hr 0
  2630. }
  2631. if { [dict get $date amPmIndicator] } {
  2632. incr hr 12
  2633. }
  2634. dict set date hour $hr
  2635. return [InterpretHMS $date[set date {}]]
  2636. }
  2637. #----------------------------------------------------------------------
  2638. #
  2639. # InterpretHMS --
  2640. #
  2641. # Interprets a 24-hour time "hh:mm:ss"
  2642. #
  2643. # Parameters:
  2644. # date -- Dictionary containing the "hour", "minute" and "second"
  2645. # fields.
  2646. #
  2647. # Results:
  2648. # Returns the given dictionary augmented with a "secondOfDay"
  2649. # field containing the number of seconds from local midnight.
  2650. #
  2651. # Side effects:
  2652. # None.
  2653. #
  2654. #----------------------------------------------------------------------
  2655. proc ::tcl::clock::InterpretHMS { date } {
  2656. return [expr {
  2657. ( [dict get $date hour] * 60
  2658. + [dict get $date minute] ) * 60
  2659. + [dict get $date second]
  2660. }]
  2661. }
  2662. #----------------------------------------------------------------------
  2663. #
  2664. # GetSystemTimeZone --
  2665. #
  2666. # Determines the system time zone, which is the default for the
  2667. # 'clock' command if no other zone is supplied.
  2668. #
  2669. # Parameters:
  2670. # None.
  2671. #
  2672. # Results:
  2673. # Returns the system time zone.
  2674. #
  2675. # Side effects:
  2676. # Stores the system time zone in the 'CachedSystemTimeZone'
  2677. # variable, since determining it may be an expensive process.
  2678. #
  2679. #----------------------------------------------------------------------
  2680. proc ::tcl::clock::GetSystemTimeZone {} {
  2681. variable CachedSystemTimeZone
  2682. variable TimeZoneBad
  2683. if {[set result [getenv TCL_TZ]] ne {}} {
  2684. set timezone $result
  2685. } elseif {[set result [getenv TZ]] ne {}} {
  2686. set timezone $result
  2687. } else {
  2688. # Cache the time zone only if it was detected by one of the
  2689. # expensive methods.
  2690. if { [info exists CachedSystemTimeZone] } {
  2691. set timezone $CachedSystemTimeZone
  2692. } elseif { $::tcl_platform(platform) eq {windows} } {
  2693. set timezone [GuessWindowsTimeZone]
  2694. } elseif { [file exists /etc/localtime]
  2695. && ![catch {ReadZoneinfoFile \
  2696. Tcl/Localtime /etc/localtime}] } {
  2697. set timezone :Tcl/Localtime
  2698. } else {
  2699. set timezone :localtime
  2700. }
  2701. set CachedSystemTimeZone $timezone
  2702. }
  2703. if { ![dict exists $TimeZoneBad $timezone] } {
  2704. dict set TimeZoneBad $timezone [catch {SetupTimeZone $timezone}]
  2705. }
  2706. if { [dict get $TimeZoneBad $timezone] } {
  2707. return :localtime
  2708. } else {
  2709. return $timezone
  2710. }
  2711. }
  2712. #----------------------------------------------------------------------
  2713. #
  2714. # ConvertLegacyTimeZone --
  2715. #
  2716. # Given an alphanumeric time zone identifier and the system time zone,
  2717. # convert the alphanumeric identifier to an unambiguous time zone.
  2718. #
  2719. # Parameters:
  2720. # tzname - Name of the time zone to convert
  2721. #
  2722. # Results:
  2723. # Returns a time zone name corresponding to tzname, but in an
  2724. # unambiguous form, generally +hhmm.
  2725. #
  2726. # This procedure is implemented primarily to allow the parsing of RFC822
  2727. # date/time strings. Processing a time zone name on input is not recommended
  2728. # practice, because there is considerable room for ambiguity; for instance, is
  2729. # BST Brazilian Standard Time, or British Summer Time?
  2730. #
  2731. #----------------------------------------------------------------------
  2732. proc ::tcl::clock::ConvertLegacyTimeZone { tzname } {
  2733. variable LegacyTimeZone
  2734. set tzname [string tolower $tzname]
  2735. if { ![dict exists $LegacyTimeZone $tzname] } {
  2736. return -code error -errorcode [list CLOCK badTZName $tzname] \
  2737. "time zone \"$tzname\" not found"
  2738. }
  2739. return [dict get $LegacyTimeZone $tzname]
  2740. }
  2741. #----------------------------------------------------------------------
  2742. #
  2743. # SetupTimeZone --
  2744. #
  2745. # Given the name or specification of a time zone, sets up its in-memory
  2746. # data.
  2747. #
  2748. # Parameters:
  2749. # tzname - Name of a time zone
  2750. #
  2751. # Results:
  2752. # Unless the time zone is ':localtime', sets the TZData array to contain
  2753. # the lookup table for local<->UTC conversion. Returns an error if the
  2754. # time zone cannot be parsed.
  2755. #
  2756. #----------------------------------------------------------------------
  2757. proc ::tcl::clock::SetupTimeZone { timezone } {
  2758. variable TZData
  2759. if {! [info exists TZData($timezone)] } {
  2760. variable MINWIDE
  2761. if { $timezone eq {:localtime} } {
  2762. # Nothing to do, we'll convert using the localtime function
  2763. } elseif {
  2764. [regexp {^([-+])(\d\d)(?::?(\d\d)(?::?(\d\d))?)?} $timezone \
  2765. -> s hh mm ss]
  2766. } then {
  2767. # Make a fixed offset
  2768. ::scan $hh %d hh
  2769. if { $mm eq {} } {
  2770. set mm 0
  2771. } else {
  2772. ::scan $mm %d mm
  2773. }
  2774. if { $ss eq {} } {
  2775. set ss 0
  2776. } else {
  2777. ::scan $ss %d ss
  2778. }
  2779. set offset [expr { ( $hh * 60 + $mm ) * 60 + $ss }]
  2780. if { $s eq {-} } {
  2781. set offset [expr { - $offset }]
  2782. }
  2783. set TZData($timezone) [list [list $MINWIDE $offset -1 $timezone]]
  2784. } elseif { [string index $timezone 0] eq {:} } {
  2785. # Convert using a time zone file
  2786. if {
  2787. [catch {
  2788. LoadTimeZoneFile [string range $timezone 1 end]
  2789. }] && [catch {
  2790. LoadZoneinfoFile [string range $timezone 1 end]
  2791. }]
  2792. } then {
  2793. return -code error \
  2794. -errorcode [list CLOCK badTimeZone $timezone] \
  2795. "time zone \"$timezone\" not found"
  2796. }
  2797. } elseif { ![catch {ParsePosixTimeZone $timezone} tzfields] } {
  2798. # This looks like a POSIX time zone - try to process it
  2799. if { [catch {ProcessPosixTimeZone $tzfields} data opts] } {
  2800. if { [lindex [dict get $opts -errorcode] 0] eq {CLOCK} } {
  2801. dict unset opts -errorinfo
  2802. }
  2803. return -options $opts $data
  2804. } else {
  2805. set TZData($timezone) $data
  2806. }
  2807. } else {
  2808. # We couldn't parse this as a POSIX time zone. Try again with a
  2809. # time zone file - this time without a colon
  2810. if { [catch { LoadTimeZoneFile $timezone }]
  2811. && [catch { LoadZoneinfoFile $timezone } - opts] } {
  2812. dict unset opts -errorinfo
  2813. return -options $opts "time zone $timezone not found"
  2814. }
  2815. set TZData($timezone) $TZData(:$timezone)
  2816. }
  2817. }
  2818. return
  2819. }
  2820. #----------------------------------------------------------------------
  2821. #
  2822. # GuessWindowsTimeZone --
  2823. #
  2824. # Determines the system time zone on windows.
  2825. #
  2826. # Parameters:
  2827. # None.
  2828. #
  2829. # Results:
  2830. # Returns a time zone specifier that corresponds to the system time zone
  2831. # information found in the Registry.
  2832. #
  2833. # Bugs:
  2834. # Fixed dates for DST change are unimplemented at present, because no
  2835. # time zone information supplied with Windows actually uses them!
  2836. #
  2837. # On a Windows system where neither $env(TCL_TZ) nor $env(TZ) is specified,
  2838. # GuessWindowsTimeZone looks in the Registry for the system time zone
  2839. # information. It then attempts to find an entry in WinZoneInfo for a time
  2840. # zone that uses the same rules. If it finds one, it returns it; otherwise,
  2841. # it constructs a Posix-style time zone string and returns that.
  2842. #
  2843. #----------------------------------------------------------------------
  2844. proc ::tcl::clock::GuessWindowsTimeZone {} {
  2845. variable WinZoneInfo
  2846. variable NoRegistry
  2847. variable TimeZoneBad
  2848. if { [info exists NoRegistry] } {
  2849. return :localtime
  2850. }
  2851. # Dredge time zone information out of the registry
  2852. if { [catch {
  2853. set rpath HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\TimeZoneInformation
  2854. set data [list \
  2855. [expr { -60
  2856. * [registry get $rpath Bias] }] \
  2857. [expr { -60
  2858. * [registry get $rpath StandardBias] }] \
  2859. [expr { -60 \
  2860. * [registry get $rpath DaylightBias] }]]
  2861. set stdtzi [registry get $rpath StandardStart]
  2862. foreach ind {0 2 14 4 6 8 10 12} {
  2863. binary scan $stdtzi @${ind}s val
  2864. lappend data $val
  2865. }
  2866. set daytzi [registry get $rpath DaylightStart]
  2867. foreach ind {0 2 14 4 6 8 10 12} {
  2868. binary scan $daytzi @${ind}s val
  2869. lappend data $val
  2870. }
  2871. }] } {
  2872. # Missing values in the Registry - bail out
  2873. return :localtime
  2874. }
  2875. # Make up a Posix time zone specifier if we can't find one. Check here
  2876. # that the tzdata file exists, in case we're running in an environment
  2877. # (e.g. starpack) where tzdata is incomplete. (Bug 1237907)
  2878. if { [dict exists $WinZoneInfo $data] } {
  2879. set tzname [dict get $WinZoneInfo $data]
  2880. if { ! [dict exists $TimeZoneBad $tzname] } {
  2881. dict set TimeZoneBad $tzname [catch {SetupTimeZone $tzname}]
  2882. }
  2883. } else {
  2884. set tzname {}
  2885. }
  2886. if { $tzname eq {} || [dict get $TimeZoneBad $tzname] } {
  2887. lassign $data \
  2888. bias stdBias dstBias \
  2889. stdYear stdMonth stdDayOfWeek stdDayOfMonth \
  2890. stdHour stdMinute stdSecond stdMillisec \
  2891. dstYear dstMonth dstDayOfWeek dstDayOfMonth \
  2892. dstHour dstMinute dstSecond dstMillisec
  2893. set stdDelta [expr { $bias + $stdBias }]
  2894. set dstDelta [expr { $bias + $dstBias }]
  2895. if { $stdDelta <= 0 } {
  2896. set stdSignum +
  2897. set stdDelta [expr { - $stdDelta }]
  2898. set dispStdSignum -
  2899. } else {
  2900. set stdSignum -
  2901. set dispStdSignum +
  2902. }
  2903. set hh [::format %02d [expr { $stdDelta / 3600 }]]
  2904. set mm [::format %02d [expr { ($stdDelta / 60 ) % 60 }]]
  2905. set ss [::format %02d [expr { $stdDelta % 60 }]]
  2906. set tzname {}
  2907. append tzname < $dispStdSignum $hh $mm > $stdSignum $hh : $mm : $ss
  2908. if { $stdMonth >= 0 } {
  2909. if { $dstDelta <= 0 } {
  2910. set dstSignum +
  2911. set dstDelta [expr { - $dstDelta }]
  2912. set dispDstSignum -
  2913. } else {
  2914. set dstSignum -
  2915. set dispDstSignum +
  2916. }
  2917. set hh [::format %02d [expr { $dstDelta / 3600 }]]
  2918. set mm [::format %02d [expr { ($dstDelta / 60 ) % 60 }]]
  2919. set ss [::format %02d [expr { $dstDelta % 60 }]]
  2920. append tzname < $dispDstSignum $hh $mm > $dstSignum $hh : $mm : $ss
  2921. if { $dstYear == 0 } {
  2922. append tzname ,M $dstMonth . $dstDayOfMonth . $dstDayOfWeek
  2923. } else {
  2924. # I have not been able to find any locale on which Windows
  2925. # converts time zone on a fixed day of the year, hence don't
  2926. # know how to interpret the fields. If someone can inform me,
  2927. # I'd be glad to code it up. For right now, we bail out in
  2928. # such a case.
  2929. return :localtime
  2930. }
  2931. append tzname / [::format %02d $dstHour] \
  2932. : [::format %02d $dstMinute] \
  2933. : [::format %02d $dstSecond]
  2934. if { $stdYear == 0 } {
  2935. append tzname ,M $stdMonth . $stdDayOfMonth . $stdDayOfWeek
  2936. } else {
  2937. # I have not been able to find any locale on which Windows
  2938. # converts time zone on a fixed day of the year, hence don't
  2939. # know how to interpret the fields. If someone can inform me,
  2940. # I'd be glad to code it up. For right now, we bail out in
  2941. # such a case.
  2942. return :localtime
  2943. }
  2944. append tzname / [::format %02d $stdHour] \
  2945. : [::format %02d $stdMinute] \
  2946. : [::format %02d $stdSecond]
  2947. }
  2948. dict set WinZoneInfo $data $tzname
  2949. }
  2950. return [dict get $WinZoneInfo $data]
  2951. }
  2952. #----------------------------------------------------------------------
  2953. #
  2954. # LoadTimeZoneFile --
  2955. #
  2956. # Load the data file that specifies the conversion between a
  2957. # given time zone and Greenwich.
  2958. #
  2959. # Parameters:
  2960. # fileName -- Name of the file to load
  2961. #
  2962. # Results:
  2963. # None.
  2964. #
  2965. # Side effects:
  2966. # TZData(:fileName) contains the time zone data
  2967. #
  2968. #----------------------------------------------------------------------
  2969. proc ::tcl::clock::LoadTimeZoneFile { fileName } {
  2970. variable DataDir
  2971. variable TZData
  2972. if { [info exists TZData($fileName)] } {
  2973. return
  2974. }
  2975. # Since an unsafe interp uses the [clock] command in the parent, this code
  2976. # is security sensitive. Make sure that the path name cannot escape the
  2977. # given directory.
  2978. if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } {
  2979. return -code error \
  2980. -errorcode [list CLOCK badTimeZone $:fileName] \
  2981. "time zone \":$fileName\" not valid"
  2982. }
  2983. try {
  2984. source -encoding utf-8 [file join $DataDir $fileName]
  2985. } on error {} {
  2986. return -code error \
  2987. -errorcode [list CLOCK badTimeZone :$fileName] \
  2988. "time zone \":$fileName\" not found"
  2989. }
  2990. return
  2991. }
  2992. #----------------------------------------------------------------------
  2993. #
  2994. # LoadZoneinfoFile --
  2995. #
  2996. # Loads a binary time zone information file in Olson format.
  2997. #
  2998. # Parameters:
  2999. # fileName - Relative path name of the file to load.
  3000. #
  3001. # Results:
  3002. # Returns an empty result normally; returns an error if no Olson file
  3003. # was found or the file was malformed in some way.
  3004. #
  3005. # Side effects:
  3006. # TZData(:fileName) contains the time zone data
  3007. #
  3008. #----------------------------------------------------------------------
  3009. proc ::tcl::clock::LoadZoneinfoFile { fileName } {
  3010. variable ZoneinfoPaths
  3011. # Since an unsafe interp uses the [clock] command in the parent, this code
  3012. # is security sensitive. Make sure that the path name cannot escape the
  3013. # given directory.
  3014. if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } {
  3015. return -code error \
  3016. -errorcode [list CLOCK badTimeZone $:fileName] \
  3017. "time zone \":$fileName\" not valid"
  3018. }
  3019. foreach d $ZoneinfoPaths {
  3020. set fname [file join $d $fileName]
  3021. if { [file readable $fname] && [file isfile $fname] } {
  3022. break
  3023. }
  3024. unset fname
  3025. }
  3026. ReadZoneinfoFile $fileName $fname
  3027. }
  3028. #----------------------------------------------------------------------
  3029. #
  3030. # ReadZoneinfoFile --
  3031. #
  3032. # Loads a binary time zone information file in Olson format.
  3033. #
  3034. # Parameters:
  3035. # fileName - Name of the time zone (relative path name of the
  3036. # file).
  3037. # fname - Absolute path name of the file.
  3038. #
  3039. # Results:
  3040. # Returns an empty result normally; returns an error if no Olson file
  3041. # was found or the file was malformed in some way.
  3042. #
  3043. # Side effects:
  3044. # TZData(:fileName) contains the time zone data
  3045. #
  3046. #----------------------------------------------------------------------
  3047. proc ::tcl::clock::ReadZoneinfoFile {fileName fname} {
  3048. variable MINWIDE
  3049. variable TZData
  3050. if { ![file exists $fname] } {
  3051. return -code error "$fileName not found"
  3052. }
  3053. if { [file size $fname] > 262144 } {
  3054. return -code error "$fileName too big"
  3055. }
  3056. # Suck in all the data from the file
  3057. set f [open $fname r]
  3058. fconfigure $f -translation binary
  3059. set d [read $f]
  3060. close $f
  3061. # The file begins with a magic number, sixteen reserved bytes, and then
  3062. # six 4-byte integers giving counts of fields in the file.
  3063. binary scan $d a4a1x15IIIIII \
  3064. magic version nIsGMT nIsStd nLeap nTime nType nChar
  3065. set seek 44
  3066. set ilen 4
  3067. set iformat I
  3068. if { $magic != {TZif} } {
  3069. return -code error "$fileName not a time zone information file"
  3070. }
  3071. if { $nType > 255 } {
  3072. return -code error "$fileName contains too many time types"
  3073. }
  3074. # Accept only Posix-style zoneinfo. Sorry, 'leaps' bigots.
  3075. if { $nLeap != 0 } {
  3076. return -code error "$fileName contains leap seconds"
  3077. }
  3078. # In a version 2 file, we use the second part of the file, which contains
  3079. # 64-bit transition times.
  3080. if {$version eq "2"} {
  3081. set seek [expr {
  3082. 44
  3083. + 5 * $nTime
  3084. + 6 * $nType
  3085. + 4 * $nLeap
  3086. + $nIsStd
  3087. + $nIsGMT
  3088. + $nChar
  3089. }]
  3090. binary scan $d @${seek}a4a1x15IIIIII \
  3091. magic version nIsGMT nIsStd nLeap nTime nType nChar
  3092. if {$magic ne {TZif}} {
  3093. return -code error "seek address $seek miscomputed, magic = $magic"
  3094. }
  3095. set iformat W
  3096. set ilen 8
  3097. incr seek 44
  3098. }
  3099. # Next come ${nTime} transition times, followed by ${nTime} time type
  3100. # codes. The type codes are unsigned 1-byte quantities. We insert an
  3101. # arbitrary start time in front of the transitions.
  3102. binary scan $d @${seek}${iformat}${nTime}c${nTime} times tempCodes
  3103. incr seek [expr { ($ilen + 1) * $nTime }]
  3104. set times [linsert $times 0 $MINWIDE]
  3105. set codes {}
  3106. foreach c $tempCodes {
  3107. lappend codes [expr { $c & 0xFF }]
  3108. }
  3109. set codes [linsert $codes 0 0]
  3110. # Next come ${nType} time type descriptions, each of which has an offset
  3111. # (seconds east of GMT), a DST indicator, and an index into the
  3112. # abbreviation text.
  3113. for { set i 0 } { $i < $nType } { incr i } {
  3114. binary scan $d @${seek}Icc gmtOff isDst abbrInd
  3115. lappend types [list $gmtOff $isDst $abbrInd]
  3116. incr seek 6
  3117. }
  3118. # Next come $nChar characters of time zone name abbreviations, which are
  3119. # null-terminated.
  3120. # We build them up into a dictionary indexed by character index, because
  3121. # that's what's in the indices above.
  3122. binary scan $d @${seek}a${nChar} abbrs
  3123. incr seek ${nChar}
  3124. set abbrList [split $abbrs \0]
  3125. set i 0
  3126. set abbrevs {}
  3127. foreach a $abbrList {
  3128. for {set j 0} {$j <= [string length $a]} {incr j} {
  3129. dict set abbrevs $i [string range $a $j end]
  3130. incr i
  3131. }
  3132. }
  3133. # Package up a list of tuples, each of which contains transition time,
  3134. # seconds east of Greenwich, DST flag and time zone abbreviation.
  3135. set r {}
  3136. set lastTime $MINWIDE
  3137. foreach t $times c $codes {
  3138. if { $t < $lastTime } {
  3139. return -code error "$fileName has times out of order"
  3140. }
  3141. set lastTime $t
  3142. lassign [lindex $types $c] gmtoff isDst abbrInd
  3143. set abbrev [dict get $abbrevs $abbrInd]
  3144. lappend r [list $t $gmtoff $isDst $abbrev]
  3145. }
  3146. # In a version 2 file, there is also a POSIX-style time zone description
  3147. # at the very end of the file. To get to it, skip over nLeap leap second
  3148. # values (8 bytes each),
  3149. # nIsStd standard/DST indicators and nIsGMT UTC/local indicators.
  3150. if {$version eq {2}} {
  3151. set seek [expr {$seek + 8 * $nLeap + $nIsStd + $nIsGMT + 1}]
  3152. set last [string first \n $d $seek]
  3153. set posix [string range $d $seek [expr {$last-1}]]
  3154. if {[llength $posix] > 0} {
  3155. set posixFields [ParsePosixTimeZone $posix]
  3156. foreach tuple [ProcessPosixTimeZone $posixFields] {
  3157. lassign $tuple t gmtoff isDst abbrev
  3158. if {$t > $lastTime} {
  3159. lappend r $tuple
  3160. }
  3161. }
  3162. }
  3163. }
  3164. set TZData(:$fileName) $r
  3165. return
  3166. }
  3167. #----------------------------------------------------------------------
  3168. #
  3169. # ParsePosixTimeZone --
  3170. #
  3171. # Parses the TZ environment variable in Posix form
  3172. #
  3173. # Parameters:
  3174. # tz Time zone specifier to be interpreted
  3175. #
  3176. # Results:
  3177. # Returns a dictionary whose values contain the various pieces of the
  3178. # time zone specification.
  3179. #
  3180. # Side effects:
  3181. # None.
  3182. #
  3183. # Errors:
  3184. # Throws an error if the syntax of the time zone is incorrect.
  3185. #
  3186. # The following keys are present in the dictionary:
  3187. # stdName - Name of the time zone when Daylight Saving Time
  3188. # is not in effect.
  3189. # stdSignum - Sign (+, -, or empty) of the offset from Greenwich
  3190. # to the given (non-DST) time zone. + and the empty
  3191. # string denote zones west of Greenwich, - denotes east
  3192. # of Greenwich; this is contrary to the ISO convention
  3193. # but follows Posix.
  3194. # stdHours - Hours part of the offset from Greenwich to the given
  3195. # (non-DST) time zone.
  3196. # stdMinutes - Minutes part of the offset from Greenwich to the
  3197. # given (non-DST) time zone. Empty denotes zero.
  3198. # stdSeconds - Seconds part of the offset from Greenwich to the
  3199. # given (non-DST) time zone. Empty denotes zero.
  3200. # dstName - Name of the time zone when DST is in effect, or the
  3201. # empty string if the time zone does not observe Daylight
  3202. # Saving Time.
  3203. # dstSignum, dstHours, dstMinutes, dstSeconds -
  3204. # Fields corresponding to stdSignum, stdHours, stdMinutes,
  3205. # stdSeconds for the Daylight Saving Time version of the
  3206. # time zone. If dstHours is empty, it is presumed to be 1.
  3207. # startDayOfYear - The ordinal number of the day of the year on which
  3208. # Daylight Saving Time begins. If this field is
  3209. # empty, then DST begins on a given month-week-day,
  3210. # as below.
  3211. # startJ - The letter J, or an empty string. If a J is present in
  3212. # this field, then startDayOfYear does not count February 29
  3213. # even in leap years.
  3214. # startMonth - The number of the month in which Daylight Saving Time
  3215. # begins, supplied if startDayOfYear is empty. If both
  3216. # startDayOfYear and startMonth are empty, then US rules
  3217. # are presumed.
  3218. # startWeekOfMonth - The number of the week in the month in which
  3219. # Daylight Saving Time begins, in the range 1-5.
  3220. # 5 denotes the last week of the month even in a
  3221. # 4-week month.
  3222. # startDayOfWeek - The number of the day of the week (Sunday=0,
  3223. # Saturday=6) on which Daylight Saving Time begins.
  3224. # startHours - The hours part of the time of day at which Daylight
  3225. # Saving Time begins. An empty string is presumed to be 2.
  3226. # startMinutes - The minutes part of the time of day at which DST begins.
  3227. # An empty string is presumed zero.
  3228. # startSeconds - The seconds part of the time of day at which DST begins.
  3229. # An empty string is presumed zero.
  3230. # endDayOfYear, endJ, endMonth, endWeekOfMonth, endDayOfWeek,
  3231. # endHours, endMinutes, endSeconds -
  3232. # Specify the end of DST in the same way that the start* fields
  3233. # specify the beginning of DST.
  3234. #
  3235. # This procedure serves only to break the time specifier into fields. No
  3236. # attempt is made to canonicalize the fields or supply default values.
  3237. #
  3238. #----------------------------------------------------------------------
  3239. proc ::tcl::clock::ParsePosixTimeZone { tz } {
  3240. if {[regexp -expanded -nocase -- {
  3241. ^
  3242. # 1 - Standard time zone name
  3243. ([[:alpha:]]+ | <[-+[:alnum:]]+>)
  3244. # 2 - Standard time zone offset, signum
  3245. ([-+]?)
  3246. # 3 - Standard time zone offset, hours
  3247. ([[:digit:]]{1,2})
  3248. (?:
  3249. # 4 - Standard time zone offset, minutes
  3250. : ([[:digit:]]{1,2})
  3251. (?:
  3252. # 5 - Standard time zone offset, seconds
  3253. : ([[:digit:]]{1,2} )
  3254. )?
  3255. )?
  3256. (?:
  3257. # 6 - DST time zone name
  3258. ([[:alpha:]]+ | <[-+[:alnum:]]+>)
  3259. (?:
  3260. (?:
  3261. # 7 - DST time zone offset, signum
  3262. ([-+]?)
  3263. # 8 - DST time zone offset, hours
  3264. ([[:digit:]]{1,2})
  3265. (?:
  3266. # 9 - DST time zone offset, minutes
  3267. : ([[:digit:]]{1,2})
  3268. (?:
  3269. # 10 - DST time zone offset, seconds
  3270. : ([[:digit:]]{1,2})
  3271. )?
  3272. )?
  3273. )?
  3274. (?:
  3275. ,
  3276. (?:
  3277. # 11 - Optional J in n and Jn form 12 - Day of year
  3278. ( J ? ) ( [[:digit:]]+ )
  3279. | M
  3280. # 13 - Month number 14 - Week of month 15 - Day of week
  3281. ( [[:digit:]] + )
  3282. [.] ( [[:digit:]] + )
  3283. [.] ( [[:digit:]] + )
  3284. )
  3285. (?:
  3286. # 16 - Start time of DST - hours
  3287. / ( [[:digit:]]{1,2} )
  3288. (?:
  3289. # 17 - Start time of DST - minutes
  3290. : ( [[:digit:]]{1,2} )
  3291. (?:
  3292. # 18 - Start time of DST - seconds
  3293. : ( [[:digit:]]{1,2} )
  3294. )?
  3295. )?
  3296. )?
  3297. ,
  3298. (?:
  3299. # 19 - Optional J in n and Jn form 20 - Day of year
  3300. ( J ? ) ( [[:digit:]]+ )
  3301. | M
  3302. # 21 - Month number 22 - Week of month 23 - Day of week
  3303. ( [[:digit:]] + )
  3304. [.] ( [[:digit:]] + )
  3305. [.] ( [[:digit:]] + )
  3306. )
  3307. (?:
  3308. # 24 - End time of DST - hours
  3309. / ( [[:digit:]]{1,2} )
  3310. (?:
  3311. # 25 - End time of DST - minutes
  3312. : ( [[:digit:]]{1,2} )
  3313. (?:
  3314. # 26 - End time of DST - seconds
  3315. : ( [[:digit:]]{1,2} )
  3316. )?
  3317. )?
  3318. )?
  3319. )?
  3320. )?
  3321. )?
  3322. $
  3323. } $tz -> x(stdName) x(stdSignum) x(stdHours) x(stdMinutes) x(stdSeconds) \
  3324. x(dstName) x(dstSignum) x(dstHours) x(dstMinutes) x(dstSeconds) \
  3325. x(startJ) x(startDayOfYear) \
  3326. x(startMonth) x(startWeekOfMonth) x(startDayOfWeek) \
  3327. x(startHours) x(startMinutes) x(startSeconds) \
  3328. x(endJ) x(endDayOfYear) \
  3329. x(endMonth) x(endWeekOfMonth) x(endDayOfWeek) \
  3330. x(endHours) x(endMinutes) x(endSeconds)] } {
  3331. # it's a good timezone
  3332. return [array get x]
  3333. }
  3334. return -code error\
  3335. -errorcode [list CLOCK badTimeZone $tz] \
  3336. "unable to parse time zone specification \"$tz\""
  3337. }
  3338. #----------------------------------------------------------------------
  3339. #
  3340. # ProcessPosixTimeZone --
  3341. #
  3342. # Handle a Posix time zone after it's been broken out into fields.
  3343. #
  3344. # Parameters:
  3345. # z - Dictionary returned from 'ParsePosixTimeZone'
  3346. #
  3347. # Results:
  3348. # Returns time zone information for the 'TZData' array.
  3349. #
  3350. # Side effects:
  3351. # None.
  3352. #
  3353. #----------------------------------------------------------------------
  3354. proc ::tcl::clock::ProcessPosixTimeZone { z } {
  3355. variable MINWIDE
  3356. variable TZData
  3357. # Determine the standard time zone name and seconds east of Greenwich
  3358. set stdName [dict get $z stdName]
  3359. if { [string index $stdName 0] eq {<} } {
  3360. set stdName [string range $stdName 1 end-1]
  3361. }
  3362. if { [dict get $z stdSignum] eq {-} } {
  3363. set stdSignum +1
  3364. } else {
  3365. set stdSignum -1
  3366. }
  3367. set stdHours [lindex [::scan [dict get $z stdHours] %d] 0]
  3368. if { [dict get $z stdMinutes] ne {} } {
  3369. set stdMinutes [lindex [::scan [dict get $z stdMinutes] %d] 0]
  3370. } else {
  3371. set stdMinutes 0
  3372. }
  3373. if { [dict get $z stdSeconds] ne {} } {
  3374. set stdSeconds [lindex [::scan [dict get $z stdSeconds] %d] 0]
  3375. } else {
  3376. set stdSeconds 0
  3377. }
  3378. set stdOffset [expr {
  3379. (($stdHours * 60 + $stdMinutes) * 60 + $stdSeconds) * $stdSignum
  3380. }]
  3381. set data [list [list $MINWIDE $stdOffset 0 $stdName]]
  3382. # If there's no daylight zone, we're done
  3383. set dstName [dict get $z dstName]
  3384. if { $dstName eq {} } {
  3385. return $data
  3386. }
  3387. if { [string index $dstName 0] eq {<} } {
  3388. set dstName [string range $dstName 1 end-1]
  3389. }
  3390. # Determine the daylight name
  3391. if { [dict get $z dstSignum] eq {-} } {
  3392. set dstSignum +1
  3393. } else {
  3394. set dstSignum -1
  3395. }
  3396. if { [dict get $z dstHours] eq {} } {
  3397. set dstOffset [expr { 3600 + $stdOffset }]
  3398. } else {
  3399. set dstHours [lindex [::scan [dict get $z dstHours] %d] 0]
  3400. if { [dict get $z dstMinutes] ne {} } {
  3401. set dstMinutes [lindex [::scan [dict get $z dstMinutes] %d] 0]
  3402. } else {
  3403. set dstMinutes 0
  3404. }
  3405. if { [dict get $z dstSeconds] ne {} } {
  3406. set dstSeconds [lindex [::scan [dict get $z dstSeconds] %d] 0]
  3407. } else {
  3408. set dstSeconds 0
  3409. }
  3410. set dstOffset [expr {
  3411. (($dstHours*60 + $dstMinutes) * 60 + $dstSeconds) * $dstSignum
  3412. }]
  3413. }
  3414. # Fill in defaults for European or US DST rules
  3415. # US start time is the second Sunday in March
  3416. # EU start time is the last Sunday in March
  3417. # US end time is the first Sunday in November.
  3418. # EU end time is the last Sunday in October
  3419. if {
  3420. [dict get $z startDayOfYear] eq {}
  3421. && [dict get $z startMonth] eq {}
  3422. } then {
  3423. if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} {
  3424. # EU
  3425. dict set z startWeekOfMonth 5
  3426. if {$stdHours>2} {
  3427. dict set z startHours 2
  3428. } else {
  3429. dict set z startHours [expr {$stdHours+1}]
  3430. }
  3431. } else {
  3432. # US
  3433. dict set z startWeekOfMonth 2
  3434. dict set z startHours 2
  3435. }
  3436. dict set z startMonth 3
  3437. dict set z startDayOfWeek 0
  3438. dict set z startMinutes 0
  3439. dict set z startSeconds 0
  3440. }
  3441. if {
  3442. [dict get $z endDayOfYear] eq {}
  3443. && [dict get $z endMonth] eq {}
  3444. } then {
  3445. if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} {
  3446. # EU
  3447. dict set z endMonth 10
  3448. dict set z endWeekOfMonth 5
  3449. if {$stdHours>2} {
  3450. dict set z endHours 3
  3451. } else {
  3452. dict set z endHours [expr {$stdHours+2}]
  3453. }
  3454. } else {
  3455. # US
  3456. dict set z endMonth 11
  3457. dict set z endWeekOfMonth 1
  3458. dict set z endHours 2
  3459. }
  3460. dict set z endDayOfWeek 0
  3461. dict set z endMinutes 0
  3462. dict set z endSeconds 0
  3463. }
  3464. # Put DST in effect in all years from 1916 to 2099.
  3465. for { set y 1916 } { $y < 2100 } { incr y } {
  3466. set startTime [DeterminePosixDSTTime $z start $y]
  3467. incr startTime [expr { - wide($stdOffset) }]
  3468. set endTime [DeterminePosixDSTTime $z end $y]
  3469. incr endTime [expr { - wide($dstOffset) }]
  3470. if { $startTime < $endTime } {
  3471. lappend data \
  3472. [list $startTime $dstOffset 1 $dstName] \
  3473. [list $endTime $stdOffset 0 $stdName]
  3474. } else {
  3475. lappend data \
  3476. [list $endTime $stdOffset 0 $stdName] \
  3477. [list $startTime $dstOffset 1 $dstName]
  3478. }
  3479. }
  3480. return $data
  3481. }
  3482. #----------------------------------------------------------------------
  3483. #
  3484. # DeterminePosixDSTTime --
  3485. #
  3486. # Determines the time that Daylight Saving Time starts or ends from a
  3487. # Posix time zone specification.
  3488. #
  3489. # Parameters:
  3490. # z - Time zone data returned from ParsePosixTimeZone.
  3491. # Missing fields are expected to be filled in with
  3492. # default values.
  3493. # bound - The word 'start' or 'end'
  3494. # y - The year for which the transition time is to be determined.
  3495. #
  3496. # Results:
  3497. # Returns the transition time as a count of seconds from the epoch. The
  3498. # time is relative to the wall clock, not UTC.
  3499. #
  3500. #----------------------------------------------------------------------
  3501. proc ::tcl::clock::DeterminePosixDSTTime { z bound y } {
  3502. variable FEB_28
  3503. # Determine the start or end day of DST
  3504. set date [dict create era CE year $y]
  3505. set doy [dict get $z ${bound}DayOfYear]
  3506. if { $doy ne {} } {
  3507. # Time was specified as a day of the year
  3508. if { [dict get $z ${bound}J] ne {}
  3509. && [IsGregorianLeapYear $y]
  3510. && ( $doy > $FEB_28 ) } {
  3511. incr doy
  3512. }
  3513. dict set date dayOfYear $doy
  3514. set date [GetJulianDayFromEraYearDay $date[set date {}] 2361222]
  3515. } else {
  3516. # Time was specified as a day of the week within a month
  3517. dict set date month [dict get $z ${bound}Month]
  3518. dict set date dayOfWeek [dict get $z ${bound}DayOfWeek]
  3519. set dowim [dict get $z ${bound}WeekOfMonth]
  3520. if { $dowim >= 5 } {
  3521. set dowim -1
  3522. }
  3523. dict set date dayOfWeekInMonth $dowim
  3524. set date [GetJulianDayFromEraYearMonthWeekDay $date[set date {}] 2361222]
  3525. }
  3526. set jd [dict get $date julianDay]
  3527. set seconds [expr {
  3528. wide($jd) * wide(86400) - wide(210866803200)
  3529. }]
  3530. set h [dict get $z ${bound}Hours]
  3531. if { $h eq {} } {
  3532. set h 2
  3533. } else {
  3534. set h [lindex [::scan $h %d] 0]
  3535. }
  3536. set m [dict get $z ${bound}Minutes]
  3537. if { $m eq {} } {
  3538. set m 0
  3539. } else {
  3540. set m [lindex [::scan $m %d] 0]
  3541. }
  3542. set s [dict get $z ${bound}Seconds]
  3543. if { $s eq {} } {
  3544. set s 0
  3545. } else {
  3546. set s [lindex [::scan $s %d] 0]
  3547. }
  3548. set tod [expr { ( $h * 60 + $m ) * 60 + $s }]
  3549. return [expr { $seconds + $tod }]
  3550. }
  3551. #----------------------------------------------------------------------
  3552. #
  3553. # GetLocaleEra --
  3554. #
  3555. # Given local time expressed in seconds from the Posix epoch,
  3556. # determine localized era and year within the era.
  3557. #
  3558. # Parameters:
  3559. # date - Dictionary that must contain the keys, 'localSeconds',
  3560. # whose value is expressed as the appropriate local time;
  3561. # and 'year', whose value is the Gregorian year.
  3562. # etable - Value of the LOCALE_ERAS key in the message catalogue
  3563. # for the target locale.
  3564. #
  3565. # Results:
  3566. # Returns the dictionary, augmented with the keys, 'localeEra' and
  3567. # 'localeYear'.
  3568. #
  3569. #----------------------------------------------------------------------
  3570. proc ::tcl::clock::GetLocaleEra { date etable } {
  3571. set index [BSearch $etable [dict get $date localSeconds]]
  3572. if { $index < 0} {
  3573. dict set date localeEra \
  3574. [::format %02d [expr { [dict get $date year] / 100 }]]
  3575. dict set date localeYear [expr {
  3576. [dict get $date year] % 100
  3577. }]
  3578. } else {
  3579. dict set date localeEra [lindex $etable $index 1]
  3580. dict set date localeYear [expr {
  3581. [dict get $date year] - [lindex $etable $index 2]
  3582. }]
  3583. }
  3584. return $date
  3585. }
  3586. #----------------------------------------------------------------------
  3587. #
  3588. # GetJulianDayFromEraYearDay --
  3589. #
  3590. # Given a year, month and day on the Gregorian calendar, determines
  3591. # the Julian Day Number beginning at noon on that date.
  3592. #
  3593. # Parameters:
  3594. # date -- A dictionary in which the 'era', 'year', and
  3595. # 'dayOfYear' slots are populated. The calendar in use
  3596. # is determined by the date itself relative to:
  3597. # changeover -- Julian day on which the Gregorian calendar was
  3598. # adopted in the current locale.
  3599. #
  3600. # Results:
  3601. # Returns the given dictionary augmented with a 'julianDay' key whose
  3602. # value is the desired Julian Day Number, and a 'gregorian' key that
  3603. # specifies whether the calendar is Gregorian (1) or Julian (0).
  3604. #
  3605. # Side effects:
  3606. # None.
  3607. #
  3608. # Bugs:
  3609. # This code needs to be moved to the C layer.
  3610. #
  3611. #----------------------------------------------------------------------
  3612. proc ::tcl::clock::GetJulianDayFromEraYearDay {date changeover} {
  3613. # Get absolute year number from the civil year
  3614. switch -exact -- [dict get $date era] {
  3615. BCE {
  3616. set year [expr { 1 - [dict get $date year] }]
  3617. }
  3618. CE {
  3619. set year [dict get $date year]
  3620. }
  3621. }
  3622. set ym1 [expr { $year - 1 }]
  3623. # Try the Gregorian calendar first.
  3624. dict set date gregorian 1
  3625. set jd [expr {
  3626. 1721425
  3627. + [dict get $date dayOfYear]
  3628. + ( 365 * $ym1 )
  3629. + ( $ym1 / 4 )
  3630. - ( $ym1 / 100 )
  3631. + ( $ym1 / 400 )
  3632. }]
  3633. # If the date is before the Gregorian change, use the Julian calendar.
  3634. if { $jd < $changeover } {
  3635. dict set date gregorian 0
  3636. set jd [expr {
  3637. 1721423
  3638. + [dict get $date dayOfYear]
  3639. + ( 365 * $ym1 )
  3640. + ( $ym1 / 4 )
  3641. }]
  3642. }
  3643. dict set date julianDay $jd
  3644. return $date
  3645. }
  3646. #----------------------------------------------------------------------
  3647. #
  3648. # GetJulianDayFromEraYearMonthWeekDay --
  3649. #
  3650. # Determines the Julian Day number corresponding to the nth given
  3651. # day-of-the-week in a given month.
  3652. #
  3653. # Parameters:
  3654. # date - Dictionary containing the keys, 'era', 'year', 'month'
  3655. # 'weekOfMonth', 'dayOfWeek', and 'dayOfWeekInMonth'.
  3656. # changeover - Julian Day of adoption of the Gregorian calendar
  3657. #
  3658. # Results:
  3659. # Returns the given dictionary, augmented with a 'julianDay' key.
  3660. #
  3661. # Side effects:
  3662. # None.
  3663. #
  3664. # Bugs:
  3665. # This code needs to be moved to the C layer.
  3666. #
  3667. #----------------------------------------------------------------------
  3668. proc ::tcl::clock::GetJulianDayFromEraYearMonthWeekDay {date changeover} {
  3669. # Come up with a reference day; either the zeroeth day of the given month
  3670. # (dayOfWeekInMonth >= 0) or the seventh day of the following month
  3671. # (dayOfWeekInMonth < 0)
  3672. set date2 $date
  3673. set week [dict get $date dayOfWeekInMonth]
  3674. if { $week >= 0 } {
  3675. dict set date2 dayOfMonth 0
  3676. } else {
  3677. dict incr date2 month
  3678. dict set date2 dayOfMonth 7
  3679. }
  3680. set date2 [GetJulianDayFromEraYearMonthDay $date2[set date2 {}] \
  3681. $changeover]
  3682. set wd0 [WeekdayOnOrBefore [dict get $date dayOfWeek] \
  3683. [dict get $date2 julianDay]]
  3684. dict set date julianDay [expr { $wd0 + 7 * $week }]
  3685. return $date
  3686. }
  3687. #----------------------------------------------------------------------
  3688. #
  3689. # IsGregorianLeapYear --
  3690. #
  3691. # Determines whether a given date represents a leap year in the
  3692. # Gregorian calendar.
  3693. #
  3694. # Parameters:
  3695. # date -- The date to test. The fields, 'era', 'year' and 'gregorian'
  3696. # must be set.
  3697. #
  3698. # Results:
  3699. # Returns 1 if the year is a leap year, 0 otherwise.
  3700. #
  3701. # Side effects:
  3702. # None.
  3703. #
  3704. #----------------------------------------------------------------------
  3705. proc ::tcl::clock::IsGregorianLeapYear { date } {
  3706. switch -exact -- [dict get $date era] {
  3707. BCE {
  3708. set year [expr { 1 - [dict get $date year]}]
  3709. }
  3710. CE {
  3711. set year [dict get $date year]
  3712. }
  3713. }
  3714. if { $year % 4 != 0 } {
  3715. return 0
  3716. } elseif { ![dict get $date gregorian] } {
  3717. return 1
  3718. } elseif { $year % 400 == 0 } {
  3719. return 1
  3720. } elseif { $year % 100 == 0 } {
  3721. return 0
  3722. } else {
  3723. return 1
  3724. }
  3725. }
  3726. #----------------------------------------------------------------------
  3727. #
  3728. # WeekdayOnOrBefore --
  3729. #
  3730. # Determine the nearest day of week (given by the 'weekday' parameter,
  3731. # Sunday==0) on or before a given Julian Day.
  3732. #
  3733. # Parameters:
  3734. # weekday -- Day of the week
  3735. # j -- Julian Day number
  3736. #
  3737. # Results:
  3738. # Returns the Julian Day Number of the desired date.
  3739. #
  3740. # Side effects:
  3741. # None.
  3742. #
  3743. #----------------------------------------------------------------------
  3744. proc ::tcl::clock::WeekdayOnOrBefore { weekday j } {
  3745. set k [expr { ( $weekday + 6 ) % 7 }]
  3746. return [expr { $j - ( $j - $k ) % 7 }]
  3747. }
  3748. #----------------------------------------------------------------------
  3749. #
  3750. # BSearch --
  3751. #
  3752. # Service procedure that does binary search in several places inside the
  3753. # 'clock' command.
  3754. #
  3755. # Parameters:
  3756. # list - List of lists, sorted in ascending order by the
  3757. # first elements
  3758. # key - Value to search for
  3759. #
  3760. # Results:
  3761. # Returns the index of the greatest element in $list that is less than
  3762. # or equal to $key.
  3763. #
  3764. # Side effects:
  3765. # None.
  3766. #
  3767. #----------------------------------------------------------------------
  3768. proc ::tcl::clock::BSearch { list key } {
  3769. if {[llength $list] == 0} {
  3770. return -1
  3771. }
  3772. if { $key < [lindex $list 0 0] } {
  3773. return -1
  3774. }
  3775. set l 0
  3776. set u [expr { [llength $list] - 1 }]
  3777. while { $l < $u } {
  3778. # At this point, we know that
  3779. # $k >= [lindex $list $l 0]
  3780. # Either $u == [llength $list] or else $k < [lindex $list $u+1 0]
  3781. # We find the midpoint of the interval {l,u} rounded UP, compare
  3782. # against it, and set l or u to maintain the invariant. Note that the
  3783. # interval shrinks at each step, guaranteeing convergence.
  3784. set m [expr { ( $l + $u + 1 ) / 2 }]
  3785. if { $key >= [lindex $list $m 0] } {
  3786. set l $m
  3787. } else {
  3788. set u [expr { $m - 1 }]
  3789. }
  3790. }
  3791. return $l
  3792. }
  3793. #----------------------------------------------------------------------
  3794. #
  3795. # clock add --
  3796. #
  3797. # Adds an offset to a given time.
  3798. #
  3799. # Syntax:
  3800. # clock add clockval ?count unit?... ?-option value?
  3801. #
  3802. # Parameters:
  3803. # clockval -- Starting time value
  3804. # count -- Amount of a unit of time to add
  3805. # unit -- Unit of time to add, must be one of:
  3806. # years year months month weeks week
  3807. # days day hours hour minutes minute
  3808. # seconds second
  3809. #
  3810. # Options:
  3811. # -gmt BOOLEAN
  3812. # (Deprecated) Flag synonymous with '-timezone :GMT'
  3813. # -timezone ZONE
  3814. # Name of the time zone in which calculations are to be done.
  3815. # -locale NAME
  3816. # Name of the locale in which calculations are to be done.
  3817. # Used to determine the Gregorian change date.
  3818. #
  3819. # Results:
  3820. # Returns the given time adjusted by the given offset(s) in
  3821. # order.
  3822. #
  3823. # Notes:
  3824. # It is possible that adding a number of months or years will adjust the
  3825. # day of the month as well. For instance, the time at one month after
  3826. # 31 January is either 28 or 29 February, because February has fewer
  3827. # than 31 days.
  3828. #
  3829. #----------------------------------------------------------------------
  3830. proc ::tcl::clock::add { clockval args } {
  3831. if { [llength $args] % 2 != 0 } {
  3832. set cmdName "clock add"
  3833. return -code error \
  3834. -errorcode [list CLOCK wrongNumArgs] \
  3835. "wrong \# args: should be\
  3836. \"$cmdName clockval ?number units?...\
  3837. ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?\""
  3838. }
  3839. if { [catch { expr {wide($clockval)} } result] } {
  3840. return -code error $result
  3841. }
  3842. set offsets {}
  3843. set gmt 0
  3844. set locale c
  3845. set timezone [GetSystemTimeZone]
  3846. foreach { a b } $args {
  3847. if { [string is integer -strict $a] } {
  3848. lappend offsets $a $b
  3849. } else {
  3850. switch -exact -- $a {
  3851. -g - -gm - -gmt {
  3852. set saw(-gmt) {}
  3853. set gmt $b
  3854. }
  3855. -l - -lo - -loc - -loca - -local - -locale {
  3856. set locale [string tolower $b]
  3857. }
  3858. -t - -ti - -tim - -time - -timez - -timezo - -timezon -
  3859. -timezone {
  3860. set saw(-timezone) {}
  3861. set timezone $b
  3862. }
  3863. default {
  3864. throw [list CLOCK badOption $a] \
  3865. "bad option \"$a\",\
  3866. must be -gmt, -locale or -timezone"
  3867. }
  3868. }
  3869. }
  3870. }
  3871. # Check options for validity
  3872. if { [info exists saw(-gmt)] && [info exists saw(-timezone)] } {
  3873. return -code error \
  3874. -errorcode [list CLOCK gmtWithTimezone] \
  3875. "cannot use -gmt and -timezone in same call"
  3876. }
  3877. if { [catch { expr { wide($clockval) } } result] } {
  3878. return -code error "expected integer but got \"$clockval\""
  3879. }
  3880. if { ![string is boolean -strict $gmt] } {
  3881. return -code error "expected boolean value but got \"$gmt\""
  3882. } elseif { $gmt } {
  3883. set timezone :GMT
  3884. }
  3885. EnterLocale $locale
  3886. set changeover [mc GREGORIAN_CHANGE_DATE]
  3887. if {[catch {SetupTimeZone $timezone} retval opts]} {
  3888. dict unset opts -errorinfo
  3889. return -options $opts $retval
  3890. }
  3891. try {
  3892. foreach { quantity unit } $offsets {
  3893. switch -exact -- $unit {
  3894. years - year {
  3895. set clockval [AddMonths [expr { 12 * $quantity }] \
  3896. $clockval $timezone $changeover]
  3897. }
  3898. months - month {
  3899. set clockval [AddMonths $quantity $clockval $timezone \
  3900. $changeover]
  3901. }
  3902. weeks - week {
  3903. set clockval [AddDays [expr { 7 * $quantity }] \
  3904. $clockval $timezone $changeover]
  3905. }
  3906. days - day {
  3907. set clockval [AddDays $quantity $clockval $timezone \
  3908. $changeover]
  3909. }
  3910. hours - hour {
  3911. set clockval [expr { 3600 * $quantity + $clockval }]
  3912. }
  3913. minutes - minute {
  3914. set clockval [expr { 60 * $quantity + $clockval }]
  3915. }
  3916. seconds - second {
  3917. set clockval [expr { $quantity + $clockval }]
  3918. }
  3919. default {
  3920. throw [list CLOCK badUnit $unit] \
  3921. "unknown unit \"$unit\", must be \
  3922. years, months, weeks, days, hours, minutes or seconds"
  3923. }
  3924. }
  3925. }
  3926. return $clockval
  3927. } trap CLOCK {result opts} {
  3928. # Conceal the innards of [clock] when it's an expected error
  3929. dict unset opts -errorinfo
  3930. return -options $opts $result
  3931. }
  3932. }
  3933. #----------------------------------------------------------------------
  3934. #
  3935. # AddMonths --
  3936. #
  3937. # Add a given number of months to a given clock value in a given
  3938. # time zone.
  3939. #
  3940. # Parameters:
  3941. # months - Number of months to add (may be negative)
  3942. # clockval - Seconds since the epoch before the operation
  3943. # timezone - Time zone in which the operation is to be performed
  3944. #
  3945. # Results:
  3946. # Returns the new clock value as a number of seconds since
  3947. # the epoch.
  3948. #
  3949. # Side effects:
  3950. # None.
  3951. #
  3952. #----------------------------------------------------------------------
  3953. proc ::tcl::clock::AddMonths { months clockval timezone changeover } {
  3954. variable DaysInRomanMonthInCommonYear
  3955. variable DaysInRomanMonthInLeapYear
  3956. variable TZData
  3957. # Convert the time to year, month, day, and fraction of day.
  3958. set date [GetDateFields $clockval $TZData($timezone) $changeover]
  3959. dict set date secondOfDay [expr {
  3960. [dict get $date localSeconds] % 86400
  3961. }]
  3962. dict set date tzName $timezone
  3963. # Add the requisite number of months
  3964. set m [dict get $date month]
  3965. incr m $months
  3966. incr m -1
  3967. set delta [expr { $m / 12 }]
  3968. set mm [expr { $m % 12 }]
  3969. dict set date month [expr { $mm + 1 }]
  3970. dict incr date year $delta
  3971. # If the date doesn't exist in the current month, repair it
  3972. if { [IsGregorianLeapYear $date] } {
  3973. set hath [lindex $DaysInRomanMonthInLeapYear $mm]
  3974. } else {
  3975. set hath [lindex $DaysInRomanMonthInCommonYear $mm]
  3976. }
  3977. if { [dict get $date dayOfMonth] > $hath } {
  3978. dict set date dayOfMonth $hath
  3979. }
  3980. # Reconvert to a number of seconds
  3981. set date [GetJulianDayFromEraYearMonthDay \
  3982. $date[set date {}]\
  3983. $changeover]
  3984. dict set date localSeconds [expr {
  3985. -210866803200
  3986. + ( 86400 * wide([dict get $date julianDay]) )
  3987. + [dict get $date secondOfDay]
  3988. }]
  3989. set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \
  3990. $changeover]
  3991. return [dict get $date seconds]
  3992. }
  3993. #----------------------------------------------------------------------
  3994. #
  3995. # AddDays --
  3996. #
  3997. # Add a given number of days to a given clock value in a given time
  3998. # zone.
  3999. #
  4000. # Parameters:
  4001. # days - Number of days to add (may be negative)
  4002. # clockval - Seconds since the epoch before the operation
  4003. # timezone - Time zone in which the operation is to be performed
  4004. # changeover - Julian Day on which the Gregorian calendar was adopted
  4005. # in the target locale.
  4006. #
  4007. # Results:
  4008. # Returns the new clock value as a number of seconds since the epoch.
  4009. #
  4010. # Side effects:
  4011. # None.
  4012. #
  4013. #----------------------------------------------------------------------
  4014. proc ::tcl::clock::AddDays { days clockval timezone changeover } {
  4015. variable TZData
  4016. # Convert the time to Julian Day
  4017. set date [GetDateFields $clockval $TZData($timezone) $changeover]
  4018. dict set date secondOfDay [expr {
  4019. [dict get $date localSeconds] % 86400
  4020. }]
  4021. dict set date tzName $timezone
  4022. # Add the requisite number of days
  4023. dict incr date julianDay $days
  4024. # Reconvert to a number of seconds
  4025. dict set date localSeconds [expr {
  4026. -210866803200
  4027. + ( 86400 * wide([dict get $date julianDay]) )
  4028. + [dict get $date secondOfDay]
  4029. }]
  4030. set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \
  4031. $changeover]
  4032. return [dict get $date seconds]
  4033. }
  4034. #----------------------------------------------------------------------
  4035. #
  4036. # ChangeCurrentLocale --
  4037. #
  4038. # The global locale was changed within msgcat.
  4039. # Clears the buffered parse functions of the current locale.
  4040. #
  4041. # Parameters:
  4042. # loclist (ignored)
  4043. #
  4044. # Results:
  4045. # None.
  4046. #
  4047. # Side effects:
  4048. # Buffered parse functions are cleared.
  4049. #
  4050. #----------------------------------------------------------------------
  4051. proc ::tcl::clock::ChangeCurrentLocale {args} {
  4052. variable FormatProc
  4053. variable LocaleNumeralCache
  4054. variable CachedSystemTimeZone
  4055. variable TimeZoneBad
  4056. foreach p [info procs [namespace current]::scanproc'*'current] {
  4057. rename $p {}
  4058. }
  4059. foreach p [info procs [namespace current]::formatproc'*'current] {
  4060. rename $p {}
  4061. }
  4062. catch {array unset FormatProc *'current}
  4063. set LocaleNumeralCache {}
  4064. }
  4065. #----------------------------------------------------------------------
  4066. #
  4067. # ClearCaches --
  4068. #
  4069. # Clears all caches to reclaim the memory used in [clock]
  4070. #
  4071. # Parameters:
  4072. # None.
  4073. #
  4074. # Results:
  4075. # None.
  4076. #
  4077. # Side effects:
  4078. # Caches are cleared.
  4079. #
  4080. #----------------------------------------------------------------------
  4081. proc ::tcl::clock::ClearCaches {} {
  4082. variable FormatProc
  4083. variable LocaleNumeralCache
  4084. variable CachedSystemTimeZone
  4085. variable TimeZoneBad
  4086. foreach p [info procs [namespace current]::scanproc'*] {
  4087. rename $p {}
  4088. }
  4089. foreach p [info procs [namespace current]::formatproc'*] {
  4090. rename $p {}
  4091. }
  4092. catch {unset FormatProc}
  4093. set LocaleNumeralCache {}
  4094. catch {unset CachedSystemTimeZone}
  4095. set TimeZoneBad {}
  4096. InitTZData
  4097. }