clock.tcl 127 KB

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