1 #----------------------------------------------------------------------
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.
10 #----------------------------------------------------------------------
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.
16 # RCS: @(#) $Id: clock.tcl,v 1.47.2.9 2009/10/29 01:17:03 kennykb Exp $
18 #----------------------------------------------------------------------
20 # We must have message catalogs that support the root locale, and
21 # we need access to the Registry on Windows systems.
24 package require
msgcat 1.4
25 if { $::tcl_platform(platform
) eq
{windows
} } {
26 if { [catch { package require
registry 1.1 }] } {
27 namespace eval ::tcl::clock [list variable NoRegistry
{}]
32 # Put the library directory into the namespace for the ensemble
33 # so that the library code can find message catalogs and time zone
36 namespace eval ::tcl::clock \
37 [list variable LibDir
[file dirname
[info script
]]]
39 #----------------------------------------------------------------------
45 # The 'clock' command manipulates time. Refer to the user documentation
46 # for the available subcommands and what they do.
48 #----------------------------------------------------------------------
50 namespace eval ::tcl::clock {
52 # Export the subcommands
54 namespace export
format
55 namespace export clicks
56 namespace export microseconds
57 namespace export milliseconds
59 namespace export seconds
62 # Import the message catalog commands that we use.
64 namespace import
::msgcat::mcload
65 namespace import
::msgcat::mclocale
69 #----------------------------------------------------------------------
71 # ::tcl::clock::Initialize --
73 # Finish initializing the 'clock' subsystem
79 # Namespace variable in the 'clock' subsystem are initialized.
81 # The '::tcl::clock::Initialize' procedure initializes the namespace
82 # variables and root locale message catalog for the 'clock' subsystem.
83 # It is broken into a procedure rather than simply evaluated as a script
84 # so that it will be able to use local variables, avoiding the dangers
85 # of 'creative writing' as in Bug 1185933.
87 #----------------------------------------------------------------------
89 proc ::tcl::clock::Initialize {} {
91 rename ::tcl::clock::Initialize {}
95 # Define the Greenwich time zone
100 set TZData
(:Etc
/GMT
) {
101 {-9223372036854775808 0 0 GMT
}
103 set TZData
(:GMT
) $TZData(:Etc
/GMT
)
104 set TZData
(:Etc
/UTC
) {
105 {-9223372036854775808 0 0 UTC
}
107 set TZData
(:UTC
) $TZData(:Etc
/UTC
)
108 set TZData
(:localtime
) {}
112 # Define the message catalog for the root locale.
114 ::msgcat::mcmset {} {
118 DATE_FORMAT
{%m
/%d
/%Y
}
119 DATE_TIME_FORMAT
{%a
%b
%e
%H
:%M
:%S
%Y
}
120 DAYS_OF_WEEK_ABBREV
{
121 Sun Mon Tue Wed Thu Fri Sat
124 Sunday Monday Tuesday Wednesday Thursday Friday Saturday
126 GREGORIAN_CHANGE_DATE
2299161
127 LOCALE_DATE_FORMAT
{%m
/%d
/%Y
}
128 LOCALE_DATE_TIME_FORMAT
{%a
%b
%e
%H
:%M
:%S
%Y
}
131 00 01 02 03 04 05 06 07 08 09
132 10 11 12 13 14 15 16 17 18 19
133 20 21 22 23 24 25 26 27 28 29
134 30 31 32 33 34 35 36 37 38 39
135 40 41 42 43 44 45 46 47 48 49
136 50 51 52 53 54 55 56 57 58 59
137 60 61 62 63 64 65 66 67 68 69
138 70 71 72 73 74 75 76 77 78 79
139 80 81 82 83 84 85 86 87 88 89
140 90 91 92 93 94 95 96 97 98 99
142 LOCALE_TIME_FORMAT
{%H
:%M
:%S
}
143 LOCALE_YEAR_FORMAT
{%EC
%Ey
}
145 Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
148 January February March
150 July August September
151 October November December
154 TIME_FORMAT
{%H
:%M
:%S
}
155 TIME_FORMAT_12
{%I
:%M
:%S
%P
}
156 TIME_FORMAT_24
{%H
:%M
}
157 TIME_FORMAT_24_SECS
{%H
:%M
:%S
}
160 # Define a few Gregorian change dates for other locales. In most cases
161 # the change date follows a language, because a nation's colonies changed
162 # at the same time as the nation itself. In many cases, different
163 # national boundaries existed; the dominating rule is to follow the
166 # Italy, Spain, Portugal, Poland
168 ::msgcat::mcset it GREGORIAN_CHANGE_DATE
2299161
169 ::msgcat::mcset es GREGORIAN_CHANGE_DATE
2299161
170 ::msgcat::mcset pt GREGORIAN_CHANGE_DATE
2299161
171 ::msgcat::mcset pl GREGORIAN_CHANGE_DATE
2299161
175 ::msgcat::mcset fr GREGORIAN_CHANGE_DATE
2299227
177 # For Belgium, we follow Southern Netherlands; Liege Diocese
178 # changed several weeks later.
180 ::msgcat::mcset fr_BE GREGORIAN_CHANGE_DATE
2299238
181 ::msgcat::mcset nl_BE GREGORIAN_CHANGE_DATE
2299238
185 ::msgcat::mcset de_AT GREGORIAN_CHANGE_DATE
2299527
189 ::msgcat::mcset hu GREGORIAN_CHANGE_DATE
2301004
191 # Germany, Norway, Denmark (Catholic Germany changed earlier)
193 ::msgcat::mcset de_DE GREGORIAN_CHANGE_DATE
2342032
194 ::msgcat::mcset nb GREGORIAN_CHANGE_DATE
2342032
195 ::msgcat::mcset nn GREGORIAN_CHANGE_DATE
2342032
196 ::msgcat::mcset no GREGORIAN_CHANGE_DATE
2342032
197 ::msgcat::mcset da GREGORIAN_CHANGE_DATE
2342032
199 # Holland (Brabant, Gelderland, Flanders, Friesland, etc. changed
202 ::msgcat::mcset nl GREGORIAN_CHANGE_DATE
2342165
204 # Protestant Switzerland (Catholic cantons changed earlier)
206 ::msgcat::mcset fr_CH GREGORIAN_CHANGE_DATE
2361342
207 ::msgcat::mcset it_CH GREGORIAN_CHANGE_DATE
2361342
208 ::msgcat::mcset de_CH GREGORIAN_CHANGE_DATE
2361342
210 # English speaking countries
212 ::msgcat::mcset en GREGORIAN_CHANGE_DATE
2361222
214 # Sweden (had several changes onto and off of the Gregorian calendar)
216 ::msgcat::mcset sv GREGORIAN_CHANGE_DATE
2361390
220 ::msgcat::mcset ru GREGORIAN_CHANGE_DATE
2421639
222 # Romania (Transylvania changed earler - perhaps de_RO should show
225 ::msgcat::mcset ro GREGORIAN_CHANGE_DATE
2422063
229 ::msgcat::mcset el GREGORIAN_CHANGE_DATE
2423480
231 #------------------------------------------------------------------
235 #------------------------------------------------------------------
237 # Paths at which binary time zone data for the Olson libraries
238 # are known to reside on various operating systems
240 variable ZoneinfoPaths
{}
243 /usr
/share
/lib
/zoneinfo
245 /usr
/local
/etc
/zoneinfo
247 if { [file isdirectory
$path] } {
248 lappend ZoneinfoPaths
$path
252 # Define the directories for time zone data and message catalogs.
254 variable DataDir
[file join $LibDir tzdata
]
255 variable MsgDir
[file join $LibDir msgs
]
257 # Number of days in the months, in common years and leap years.
259 variable DaysInRomanMonthInCommonYear
\
260 { 31 28 31 30 31 30 31 31 30 31 30 31 }
261 variable DaysInRomanMonthInLeapYear
\
262 { 31 29 31 30 31 30 31 31 30 31 30 31 }
263 variable DaysInPriorMonthsInCommonYear
[list 0]
264 variable DaysInPriorMonthsInLeapYear
[list 0]
266 foreach j
$DaysInRomanMonthInCommonYear {
267 lappend DaysInPriorMonthsInCommonYear
[incr i
$j]
270 foreach j
$DaysInRomanMonthInLeapYear {
271 lappend DaysInPriorMonthsInLeapYear
[incr i
$j]
274 # Another epoch (Hi, Jeff!)
276 variable Roddenberry
1946
280 variable MINWIDE
-9223372036854775808
281 variable MAXWIDE
9223372036854775807
283 # Day before Leap Day
287 # Translation table to map Windows TZI onto cities, so that
288 # the Olson rules can apply. In some cases the mapping is ambiguous,
289 # so it's wise to specify $::env(TCL_TZ) rather than simply depending
290 # on the system time zone.
292 # The keys are long lists of values obtained from the time zone
293 # information in the Registry. In order, the list elements are:
294 # Bias StandardBias DaylightBias
295 # StandardDate.wYear StandardDate.wMonth StandardDate.wDayOfWeek
296 # StandardDate.wDay StandardDate.wHour StandardDate.wMinute
297 # StandardDate.wSecond StandardDate.wMilliseconds
298 # DaylightDate.wYear DaylightDate.wMonth DaylightDate.wDayOfWeek
299 # DaylightDate.wDay DaylightDate.wHour DaylightDate.wMinute
300 # DaylightDate.wSecond DaylightDate.wMilliseconds
301 # The values are the names of time zones where those rules apply.
302 # There is considerable ambiguity in certain zones; an attempt has
303 # been made to make a reasonable guess, but this table needs to be
304 # taken with a grain of salt.
306 variable WinZoneInfo
[dict create
{*}{
307 {-43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific
/Kwajalein
308 {-39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific
/Midway
309 {-36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific
/Honolulu
310 {-32400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America
/Anchorage
311 {-28800 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America
/Los_Angeles
312 {-28800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America
/Tijuana
313 {-25200 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America
/Denver
314 {-25200 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America
/Chihuahua
315 {-25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America
/Phoenix
316 {-21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America
/Regina
317 {-21600 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America
/Chicago
318 {-21600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America
/Mexico_City
319 {-18000 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America
/New_York
320 {-18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America
/Indianapolis
321 {-14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America
/Caracas
322 {-14400 0 3600 0 3 6 2 23 59 59 999 0 10 6 2 23 59 59 999}
324 {-14400 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America
/Manaus
325 {-14400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America
/Halifax
326 {-12600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America
/St_Johns
327 {-10800 0 3600 0 2 0 2 2 0 0 0 0 10 0 3 2 0 0 0} :America
/Sao_Paulo
328 {-10800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America
/Godthab
329 {-10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America
/Buenos_Aires
330 {-10800 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America
/Brasilia
331 {-10800 0 3600 0 3 0 2 2 0 0 0 0 10 0 1 2 0 0 0} :America
/Montevideo
332 {-7200 0 3600 0 9 0 5 2 0 0 0 0 3 0 5 2 0 0 0} :America
/Noronha
333 {-3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Atlantic
/Azores
334 {-3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Atlantic
/Cape_Verde
335 {0 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :UTC
336 {0 0 3600 0 10 0 5 2 0 0 0 0 3 0 5 1 0 0 0} :Europe
/London
337 {3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa
/Kinshasa
338 {3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :CET
339 {7200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa
/Harare
340 {7200 0 3600 0 9 4 5 23 59 59 0 0 4 4 5 23 59 59 0}
342 {7200 0 3600 0 10 0 5 4 0 0 0 0 3 0 5 3 0 0 0} :Europe
/Helsinki
343 {7200 0 3600 0 9 0 3 2 0 0 0 0 3 5 5 2 0 0 0} :Asia
/Jerusalem
344 {7200 0 3600 0 9 0 5 1 0 0 0 0 3 0 5 0 0 0 0} :Europe
/Bucharest
345 {7200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe
/Athens
346 {7200 0 3600 0 9 5 5 1 0 0 0 0 3 4 5 0 0 0 0} :Asia
/Amman
347 {7200 0 3600 0 10 6 5 23 59 59 999 0 3 0 5 0 0 0 0}
349 {7200 0 -3600 0 4 0 1 2 0 0 0 0 9 0 1 2 0 0 0} :Africa
/Windhoek
350 {10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Riyadh
351 {10800 0 3600 0 10 0 1 4 0 0 0 0 4 0 1 3 0 0 0} :Asia
/Baghdad
352 {10800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe
/Moscow
353 {12600 0 3600 0 9 2 4 2 0 0 0 0 3 0 1 2 0 0 0} :Asia
/Tehran
354 {14400 0 3600 0 10 0 5 5 0 0 0 0 3 0 5 4 0 0 0} :Asia
/Baku
355 {14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Muscat
356 {14400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia
/Tbilisi
357 {16200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Kabul
358 {18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Karachi
359 {18000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia
/Yekaterinburg
360 {19800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Calcutta
361 {20700 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Katmandu
362 {21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Dhaka
363 {21600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia
/Novosibirsk
364 {23400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Rangoon
365 {25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Bangkok
366 {25200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia
/Krasnoyarsk
367 {28800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Chongqing
368 {28800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia
/Irkutsk
369 {32400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia
/Tokyo
370 {32400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia
/Yakutsk
371 {34200 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia
/Adelaide
372 {34200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia
/Darwin
373 {36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia
/Brisbane
374 {36000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia
/Vladivostok
375 {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 1 2 0 0 0} :Australia
/Hobart
376 {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia
/Sydney
377 {39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific
/Noumea
378 {43200 0 3600 0 3 0 3 3 0 0 0 0 10 0 1 2 0 0 0} :Pacific
/Auckland
379 {43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific
/Fiji
380 {46800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific
/Tongatapu
383 # Groups of fields that specify the date, priorities, and
384 # code bursts that determine Julian Day Number given those groups.
385 # The code in [clock scan] will choose the highest priority
386 # (lowest numbered) set of fields that determines the date.
388 variable DateParseActions
{
394 { era century yearOfCentury month dayOfMonth
} 2 {
395 dict
set date year
[expr { 100 * [dict get
$date century
]
396 + [dict get
$date yearOfCentury
] }]
397 set date
[GetJulianDayFromEraYearMonthDay
$date[set date
{}] \
400 { era century yearOfCentury dayOfYear
} 2 {
401 dict
set date year
[expr { 100 * [dict get
$date century
]
402 + [dict get
$date yearOfCentury
] }]
403 set date
[GetJulianDayFromEraYearDay
$date[set date
{}] \
407 { century yearOfCentury month dayOfMonth
} 3 {
409 dict
set date year
[expr { 100 * [dict get
$date century
]
410 + [dict get
$date yearOfCentury
] }]
411 set date
[GetJulianDayFromEraYearMonthDay
$date[set date
{}] \
414 { century yearOfCentury dayOfYear
} 3 {
416 dict
set date year
[expr { 100 * [dict get
$date century
]
417 + [dict get
$date yearOfCentury
] }]
418 set date
[GetJulianDayFromEraYearDay
$date[set date
{}] \
421 { iso8601Century iso8601YearOfCentury iso8601Week dayOfWeek
} 3 {
423 dict
set date iso8601Year
\
424 [expr { 100 * [dict get
$date iso8601Century
]
425 + [dict get
$date iso8601YearOfCentury
] }]
426 set date
[GetJulianDayFromEraYearWeekDay
$date[set date
{}] \
430 { yearOfCentury month dayOfMonth
} 4 {
431 set date
[InterpretTwoDigitYear
$date[set date
{}] $baseTime]
433 set date
[GetJulianDayFromEraYearMonthDay
$date[set date
{}] \
436 { yearOfCentury dayOfYear
} 4 {
437 set date
[InterpretTwoDigitYear
$date[set date
{}] $baseTime]
439 set date
[GetJulianDayFromEraYearDay
$date[set date
{}] \
442 { iso8601YearOfCentury iso8601Week dayOfWeek
} 4 {
443 set date
[InterpretTwoDigitYear
\
444 $date[set date
{}] $baseTime \
445 iso8601YearOfCentury iso8601Year
]
447 set date
[GetJulianDayFromEraYearWeekDay
$date[set date
{}] \
451 { month dayOfMonth
} 5 {
452 set date
[AssignBaseYear
$date[set date
{}] \
453 $baseTime $timeZone $changeover]
454 set date
[GetJulianDayFromEraYearMonthDay
$date[set date
{}] \
458 set date
[AssignBaseYear
$date[set date
{}] \
459 $baseTime $timeZone $changeover]
460 set date
[GetJulianDayFromEraYearDay
$date[set date
{}] \
463 { iso8601Week dayOfWeek
} 5 {
464 set date
[AssignBaseIso8601Year
$date[set date
{}] \
465 $baseTime $timeZone $changeover]
466 set date
[GetJulianDayFromEraYearWeekDay
$date[set date
{}] \
471 set date
[AssignBaseMonth
$date[set date
{}] \
472 $baseTime $timeZone $changeover]
473 set date
[GetJulianDayFromEraYearMonthDay
$date[set date
{}] \
478 set date
[AssignBaseWeek
$date[set date
{}] \
479 $baseTime $timeZone $changeover]
480 set date
[GetJulianDayFromEraYearWeekDay
$date[set date
{}] \
485 set date
[AssignBaseJulianDay
$date[set date
{}] \
486 $baseTime $timeZone $changeover]
490 # Groups of fields that specify time of day, priorities,
491 # and code that processes them
493 variable TimeParseActions
{
497 { hourAMPM minute second amPmIndicator
} 2 {
498 dict
set date secondOfDay
[InterpretHMSP
$date]
500 { hour minute second
} 2 {
501 dict
set date secondOfDay
[InterpretHMS
$date]
504 { hourAMPM minute amPmIndicator
} 3 {
505 dict
set date second
0
506 dict
set date secondOfDay
[InterpretHMSP
$date]
509 dict
set date second
0
510 dict
set date secondOfDay
[InterpretHMS
$date]
513 { hourAMPM amPmIndicator
} 4 {
514 dict
set date minute
0
515 dict
set date second
0
516 dict
set date secondOfDay
[InterpretHMSP
$date]
519 dict
set date minute
0
520 dict
set date second
0
521 dict
set date secondOfDay
[InterpretHMS
$date]
525 dict
set date secondOfDay
0
529 # Legacy time zones, used primarily for parsing RFC822 dates.
531 variable LegacyTimeZone
[dict create
\
623 variable LocaleNumeralCache
{}; # Dictionary whose keys are locale
624 # names and whose values are pairs
625 # comprising regexes matching numerals
626 # in the given locales and dictionaries
627 # mapping the numerals to their numeric
629 variable McLoaded
{}; # Dictionary whose keys are locales
630 # in which [mcload] has been executed
631 # and whose values are second-level
632 # dictionaries indexed by message
633 # name and giving message text.
634 # variable CachedSystemTimeZone; # If 'CachedSystemTimeZone' exists,
635 # it contains the value of the
636 # system time zone, as determined from
638 variable TimeZoneBad
{}; # Dictionary whose keys are time zone
639 # names and whose values are 1 if
640 # the time zone is unknown and 0
642 variable TZData
; # Array whose keys are time zone names
643 # and whose values are lists of quads
644 # comprising start time, UTC offset,
645 # Daylight Saving Time indicator, and
646 # time zone abbreviation.
647 variable FormatProc
; # Array mapping format group
648 # and locale to the name of a procedure
649 # that renders the given format
651 ::tcl::clock::Initialize
653 #----------------------------------------------------------------------
657 # Formats a count of seconds since the Posix Epoch as a time
660 # The 'clock format' command formats times of day for output.
661 # Refer to the user documentation to see what it does.
663 #----------------------------------------------------------------------
665 proc ::tcl::clock::format { args
} {
670 lassign
[ParseFormatArgs
{*}$args] format locale timezone
671 set locale
[string tolower
$locale]
672 set clockval
[lindex $args 0]
674 # Get the data for time changes in the given zone
676 if {$timezone eq
""} {
677 set timezone
[GetSystemTimeZone
]
679 if {![info exists TZData
($timezone)]} {
680 if {[catch {SetupTimeZone
$timezone} retval opts
]} {
681 dict
unset opts
-errorinfo
682 return -options $opts $retval
686 # Build a procedure to format the result. Cache the built procedure's
687 # name in the 'FormatProc' array to avoid losing its internal
688 # representation, which contains the name resolution.
690 set procName formatproc'
$format'
$locale
691 set procName
[namespace current
]::[string map
{: {\:} \\ {\\}} $procName]
692 if {[info exists FormatProc
($procName)]} {
693 set procName
$FormatProc($procName)
695 set FormatProc
($procName) \
696 [ParseClockFormatFormat
$procName $format $locale]
699 return [$procName $clockval $timezone]
703 #----------------------------------------------------------------------
705 # ParseClockFormatFormat --
707 # Builds and caches a procedure that formats a time value.
710 # format -- Format string to use
711 # locale -- Locale in which the format string is to be interpreted
714 # Returns the name of the newly-built procedure.
716 #----------------------------------------------------------------------
718 proc ::tcl::clock::ParseClockFormatFormat {procName
format locale
} {
720 if {[namespace which
$procName] ne
{}} {
724 # Map away the locale-dependent composite format groups
726 EnterLocale
$locale oldLocale
728 # Change locale if a fresh locale has been given on the command line.
732 ParseClockFormatFormat2
$format $locale $procName
738 if { [info exists oldLocale
] } {
742 # Return either the error or the proc name
744 if { $status == 1 } {
745 if { [lindex [dict get
$opts -errorcode] 0] eq
{clock} } {
746 return -code error $result
748 return -options $opts $result
756 proc ::tcl::clock::ParseClockFormatFormat2 {format locale procName
} {
759 set didLocaleNumerals
0
761 [string map
[list @GREGORIAN_CHANGE_DATE
@ \
762 [mc GREGORIAN_CHANGE_DATE
]] \
765 set date
[GetDateFields
$clockval \
767 @GREGORIAN_CHANGE_DATE
@]
773 set format [LocalizeFormat
$locale $format]
775 foreach char
[split $format {}] {
776 switch -exact -- $state {
778 if { [string equal
% $char] } {
781 append formatString
$char
784 percent
{ # Character following a '%' character
786 switch -exact -- $char {
787 % { # A literal character, '%'
788 append formatString
%%
790 a
{ # Day of week, abbreviated
791 append formatString
%s
792 append substituents
\
794 [list @DAYS_OF_WEEK_ABBREV
@ \
795 [list [mc DAYS_OF_WEEK_ABBREV
]]] \
796 { [lindex @DAYS_OF_WEEK_ABBREV
@ \
797 [expr {[dict get
$date dayOfWeek
] \
800 A
{ # Day of week, spelt out.
801 append formatString
%s
802 append substituents
\
804 [list @DAYS_OF_WEEK_FULL
@ \
805 [list [mc DAYS_OF_WEEK_FULL
]]] \
806 { [lindex @DAYS_OF_WEEK_FULL
@ \
807 [expr {[dict get
$date dayOfWeek
] \
810 b
- h
{ # Name of month, abbreviated.
811 append formatString
%s
812 append substituents
\
814 [list @MONTHS_ABBREV
@ \
815 [list [mc MONTHS_ABBREV
]]] \
816 { [lindex @MONTHS_ABBREV
@ \
817 [expr {[dict get
$date month
]-1}]]}]
819 B
{ # Name of month, spelt out
820 append formatString
%s
821 append substituents
\
823 [list @MONTHS_FULL
@ \
824 [list [mc MONTHS_FULL
]]] \
825 { [lindex @MONTHS_FULL
@ \
826 [expr {[dict get
$date month
]-1}]]}]
829 append formatString
%02d
830 append substituents
\
831 { [expr {[dict get
$date year
] / 100}]}
833 d
{ # Day of month, with leading zero
834 append formatString
%02d
835 append substituents
{ [dict get
$date dayOfMonth
]}
837 e
{ # Day of month, without leading zero
838 append formatString
%2d
839 append substituents
{ [dict get
$date dayOfMonth
]}
841 E
{ # Format group in a locale-dependent
844 if {!$didLocaleEra} {
845 append preFormatCode
\
847 [list @LOCALE_ERAS
@ \
848 [list [mc LOCALE_ERAS
]]] \
850 set date
[GetLocaleEra
\
855 if {!$didLocaleNumerals} {
856 append preFormatCode
\
857 [list set localeNumerals
\
858 [mc LOCALE_NUMERALS
]] \n
859 set didLocaleNumerals
1
862 g
{ # Two-digit year relative to ISO8601
864 append formatString
%02d
865 append substituents
\
866 { [expr { [dict get
$date iso8601Year
] % 100 }]}
868 G
{ # Four-digit year relative to ISO8601
870 append formatString
%02d
871 append substituents
{ [dict get
$date iso8601Year
]}
873 H
{ # Hour in the 24-hour day, leading zero
874 append formatString
%02d
875 append substituents
\
876 { [expr { [dict get
$date localSeconds
] \
879 I
{ # Hour AM/PM, with leading zero
880 append formatString
%02d
881 append substituents
\
882 { [expr { ( ( ( [dict get
$date localSeconds
] \
889 j
{ # Day of year (001-366)
890 append formatString
%03d
891 append substituents
{ [dict get
$date dayOfYear
]}
893 J
{ # Julian Day Number
894 append formatString
%07ld
895 append substituents
{ [dict get
$date julianDay
]}
897 k
{ # Hour (0-23), no leading zero
898 append formatString
%2d
899 append substituents
\
900 { [expr { [dict get
$date localSeconds
]
904 l
{ # Hour (12-11), no leading zero
905 append formatString
%2d
906 append substituents
\
907 { [expr { ( ( ( [dict get
$date localSeconds
]
914 m
{ # Month number, leading zero
915 append formatString
%02d
916 append substituents
{ [dict get
$date month
]}
918 M
{ # Minute of the hour, leading zero
919 append formatString
%02d
920 append substituents
\
921 { [expr { [dict get
$date localSeconds
]
925 n
{ # A literal newline
926 append formatString
\n
928 N
{ # Month number, no leading zero
929 append formatString
%2d
930 append substituents
{ [dict get
$date month
]}
932 O
{ # A format group in the locale's
933 # alternative numerals
935 if {!$didLocaleNumerals} {
936 append preFormatCode
\
937 [list set localeNumerals
\
938 [mc LOCALE_NUMERALS
]] \n
939 set didLocaleNumerals
1
942 p
{ # Localized 'AM' or 'PM' indicator
943 # converted to uppercase
944 append formatString
%s
945 append preFormatCode
\
946 [list set AM
[string toupper
[mc AM
]]] \n \
947 [list set PM
[string toupper
[mc PM
]]] \n
948 append substituents
\
949 { [expr {(([dict get
$date localSeconds
]
953 P
{ # Localized 'AM' or 'PM' indicator
954 append formatString
%s
955 append preFormatCode
\
956 [list set am
[mc AM
]] \n \
957 [list set pm
[mc PM
]] \n
958 append substituents
\
959 { [expr {(([dict get
$date localSeconds
]
965 append formatString
%s
966 append substituents
{ [FormatStarDate
$date]}
968 s
{ # Seconds from the Posix Epoch
969 append formatString
%s
970 append substituents
{ [dict get
$date seconds
]}
972 S
{ # Second of the minute, with
974 append formatString
%02d
975 append substituents
\
976 { [expr { [dict get
$date localSeconds
]
979 t
{ # A literal tab character
980 append formatString
\t
982 u
{ # Day of the week (1-Monday, 7-Sunday)
983 append formatString
%1d
984 append substituents
{ [dict get
$date dayOfWeek
]}
986 U
{ # Week of the year (00-53). The
987 # first Sunday of the year is the
988 # first day of week 01
989 append formatString
%02d
990 append preFormatCode
{
991 set dow
[dict get
$date dayOfWeek
]
997 [expr { ( [dict get
$date dayOfYear
]
1001 append substituents
{ $UweekNumber}
1003 V
{ # The ISO8601 week number
1004 append formatString
%02d
1005 append substituents
{ [dict get
$date iso8601Week
]}
1007 w
{ # Day of the week (0-Sunday,
1009 append formatString
%1d
1010 append substituents
\
1011 { [expr { [dict get
$date dayOfWeek
] % 7 }]}
1013 W
{ # Week of the year (00-53). The first
1014 # Monday of the year is the first day
1016 append preFormatCode
{
1018 [expr { ( [dict get
$date dayOfYear
]
1019 - [dict get
$date dayOfWeek
]
1023 append formatString
%02d
1024 append substituents
{ $WweekNumber}
1026 y
{ # The two-digit year of the century
1027 append formatString
%02d
1028 append substituents
\
1029 { [expr { [dict get
$date year
] % 100 }]}
1031 Y
{ # The four-digit year
1032 append formatString
%04d
1033 append substituents
{ [dict get
$date year
]}
1035 z
{ # The time zone as hours and minutes
1036 # east (+) or west (-) of Greenwich
1037 append formatString
%s
1038 append substituents
{ [FormatNumericTimeZone
\
1039 [dict get
$date tzOffset
]]}
1041 Z
{ # The name of the time zone
1042 append formatString
%s
1043 append substituents
{ [dict get
$date tzName
]}
1045 % { # A literal percent character
1046 append formatString
%%
1048 default { # An unknown escape sequence
1049 append formatString
%% $char
1053 percentE
{ # Character following %E
1055 switch -exact -- $char {
1057 append formatString
%s
1058 append substituents
{ } \
1060 [list @BCE
@ [list [mc BCE
]] \
1061 @CE
@ [list [mc CE
]]] \
1062 {[dict get
{BCE
@BCE
@ CE
@CE
@} \
1063 [dict get
$date era
]]}]
1065 C
{ # Locale-dependent era
1066 append formatString
%s
1067 append substituents
{ [dict get
$date localeEra
]}
1069 y
{ # Locale-dependent year of the era
1070 append preFormatCode
{
1071 set y
[dict get
$date localeYear
]
1072 if { $y >= 0 && $y < 100 } {
1073 set Eyear
[lindex $localeNumerals $y]
1078 append formatString
%s
1079 append substituents
{ $Eyear}
1081 default { # Unknown %E format group
1082 append formatString
%%E
$char
1086 percentO
{ # Character following %O
1088 switch -exact -- $char {
1089 d
- e
{ # Day of the month in alternative
1091 append formatString
%s
1092 append substituents
\
1093 { [lindex $localeNumerals \
1094 [dict get
$date dayOfMonth
]]}
1096 H
- k
{ # Hour of the day in alternative
1098 append formatString
%s
1099 append substituents
\
1100 { [lindex $localeNumerals \
1101 [expr { [dict get
$date localSeconds
]
1105 I
- l
{ # Hour (12-11) AM/PM in alternative
1107 append formatString
%s
1108 append substituents
\
1109 { [lindex $localeNumerals \
1110 [expr { ( ( ( [dict get
$date localSeconds
]
1117 m
{ # Month number in alternative numerals
1118 append formatString
%s
1119 append substituents
\
1120 { [lindex $localeNumerals [dict get
$date month
]]}
1122 M
{ # Minute of the hour in alternative
1124 append formatString
%s
1125 append substituents
\
1126 { [lindex $localeNumerals \
1127 [expr { [dict get
$date localSeconds
]
1131 S
{ # Second of the minute in alternative
1133 append formatString
%s
1134 append substituents
\
1135 { [lindex $localeNumerals \
1136 [expr { [dict get
$date localSeconds
]
1139 u
{ # Day of the week (Monday=1,Sunday=7)
1140 # in alternative numerals
1141 append formatString
%s
1142 append substituents
\
1143 { [lindex $localeNumerals \
1144 [dict get
$date dayOfWeek
]]}
1146 w
{ # Day of the week (Sunday=0,Saturday=6)
1147 # in alternative numerals
1148 append formatString
%s
1149 append substituents
\
1150 { [lindex $localeNumerals \
1151 [expr { [dict get
$date dayOfWeek
] % 7 }]]}
1153 y
{ # Year of the century in alternative
1155 append formatString
%s
1156 append substituents
\
1157 { [lindex $localeNumerals \
1158 [expr { [dict get
$date year
] % 100 }]]}
1160 default { # Unknown format group
1161 append formatString
%%O
$char
1168 # Clean up any improperly terminated groups
1170 switch -exact -- $state {
1172 append formatString
%%
1182 proc $procName {clockval timezone
} "
1184 return \[::format [list $formatString] $substituents\]
1187 # puts [list $procName [info args $procName] [info body $procName]]
1192 #----------------------------------------------------------------------
1196 # Inputs a count of seconds since the Posix Epoch as a time
1199 # The 'clock format' command scans times of day on input.
1200 # Refer to the user documentation to see what it does.
1202 #----------------------------------------------------------------------
1204 proc ::tcl::clock::scan { args
} {
1208 # Check the count of args
1210 if { [llength $args] < 1 ||
[llength $args] % 2 != 1 } {
1211 set cmdName
"clock scan"
1212 return -code error \
1213 -errorcode [list CLOCK wrongNumArgs
] \
1214 "wrong \# args: should be\
1217 ?-format string? ?-gmt boolean?\
1218 ?-locale LOCALE? ?-timezone ZONE?\""
1223 set base
[clock seconds
]
1224 set string [lindex $args 0]
1228 set timezone
[GetSystemTimeZone
]
1230 # Pick up command line options.
1232 foreach { flag value
} [lreplace $args 0 0] {
1234 switch -exact -- $flag {
1235 -b - -ba - -bas - -base {
1238 -f - -fo - -for - -form - -forma - -format {
1244 -l - -lo - -loc - -loca - -local - -locale {
1245 set locale
[string tolower
$value]
1247 -t - -ti - -tim - -time - -timez - -timezo - -timezon - -timezone {
1251 return -code error \
1252 -errorcode [list CLOCK badSwitch
$flag] \
1253 "bad switch \"$flag\",\
1254 must be -base, -format, -gmt, -locale or -timezone"
1259 # Check options for validity
1261 if { [info exists saw
(-gmt)] && [info exists saw
(-timezone)] } {
1262 return -code error \
1263 -errorcode [list CLOCK gmtWithTimezone
] \
1264 "cannot use -gmt and -timezone in same call"
1266 if { [catch { expr { wide
($base) } } result
] } {
1267 return -code error \
1268 "expected integer but got \"$base\""
1270 if { ![string is boolean
$gmt] } {
1271 return -code error \
1272 "expected boolean value but got \"$gmt\""
1279 if { ![info exists saw
(-format)] } {
1280 # Perhaps someday we'll localize the legacy code. Right now,
1281 # it's not localized.
1282 if { [info exists saw
(-locale)] } {
1283 return -code error \
1284 -errorcode [list CLOCK flagWithLegacyFormat
] \
1285 "legacy \[clock scan\] does not support -locale"
1288 return [FreeScan
$string $base $timezone $locale]
1291 # Change locale if a fresh locale has been given on the command line.
1293 EnterLocale
$locale oldLocale
1297 # Map away the locale-dependent composite format groups
1299 set scanner
[ParseClockScanFormat
$format $locale]
1300 $scanner $string $base $timezone
1304 # Restore the locale
1306 if { [info exists oldLocale
] } {
1310 if { $status == 1 } {
1311 if { [lindex [dict get
$opts -errorcode] 0] eq
{clock} } {
1312 return -code error $result
1314 return -options $opts $result
1322 #----------------------------------------------------------------------
1326 # Scans a time in free format
1329 # string - String containing the time to scan
1330 # base - Base time, expressed in seconds from the Epoch
1331 # timezone - Default time zone in which the time will be expressed
1332 # locale - (Unused) Name of the locale where the time will be scanned.
1335 # Returns the date and time extracted from the string in seconds
1338 #----------------------------------------------------------------------
1340 proc ::tcl::clock::FreeScan { string base timezone locale
} {
1344 # Get the data for time changes in the given zone
1346 if {[catch {SetupTimeZone
$timezone} retval opts
]} {
1347 dict
unset opts
-errorinfo
1348 return -options $opts $retval
1351 # Extract year, month and day from the base time for the
1352 # parser to use as defaults
1354 set date
[GetDateFields
\
1356 $TZData($timezone) \
1358 dict
set date secondOfDay
[expr { [dict get
$date localSeconds
]
1361 # Parse the date. The parser will return a list comprising
1362 # date, time, time zone, relative month/day/seconds, relative
1363 # weekday, ordinal month.
1367 [dict get
$date year
] \
1368 [dict get
$date month
] \
1369 [dict get
$date dayOfMonth
]
1371 if { $status != 0 } {
1372 return -code error "unable to convert date-time string \"$string\": $result"
1375 lassign
$result parseDate parseTime parseZone parseRel
\
1376 parseWeekday parseOrdinalMonth
1378 # If the caller supplied a date in the string, update the 'date' dict
1379 # with the value. If the caller didn't specify a time with the date,
1380 # default to midnight.
1382 if { [llength $parseDate] > 0 } {
1383 lassign
$parseDate y m d
1391 dict
set date era CE
1392 dict
set date year
$y
1393 dict
set date month
$m
1394 dict
set date dayOfMonth
$d
1395 if { $parseTime eq
{} } {
1400 # If the caller supplied a time zone in the string, it comes back
1401 # as a two-element list; the first element is the number of minutes
1402 # east of Greenwich, and the second is a Daylight Saving Time
1403 # indicator ( 1 == yes, 0 == no, -1 == unknown ). We make it into
1404 # a time zone indicator of +-hhmm.
1406 if { [llength $parseZone] > 0 } {
1407 lassign
$parseZone minEast dstFlag
1408 set timezone
[FormatNumericTimeZone
\
1409 [expr { 60 * $minEast + 3600 * $dstFlag }]]
1410 SetupTimeZone
$timezone
1412 dict
set date tzName
$timezone
1414 # Assemble date, time, zone into seconds-from-epoch
1416 set date
[GetJulianDayFromEraYearMonthDay
$date[set date
{}] 2361222]
1417 if { $parseTime ne
{} } {
1418 dict
set date secondOfDay
$parseTime
1419 } elseif
{ [llength $parseWeekday] != 0
1420 ||
[llength $parseOrdinalMonth] != 0
1421 ||
( [llength $parseRel] != 0
1422 && ( [lindex $parseRel 0] != 0
1423 ||
[lindex $parseRel 1] != 0 ) ) } {
1424 dict
set date secondOfDay
0
1427 dict
set date localSeconds
\
1428 [expr { -210866803200
1429 + ( 86400 * wide
([dict get
$date julianDay
]) )
1430 + [dict get
$date secondOfDay
] }]
1431 dict
set date tzName
$timezone
1432 set date
[ConvertLocalToUTC
$date[set date
{}] $TZData($timezone) 2361222]
1433 set seconds
[dict get
$date seconds
]
1437 if { [llength $parseRel] > 0 } {
1438 lassign
$parseRel relMonth relDay relSecond
1439 set seconds
[add
$seconds \
1440 $relMonth months
$relDay days
$relSecond seconds
\
1441 -timezone $timezone -locale $locale]
1444 # Do relative weekday
1446 if { [llength $parseWeekday] > 0 } {
1448 lassign
$parseWeekday dayOrdinal dayOfWeek
1449 set date2
[GetDateFields
$seconds $TZData($timezone) 2361222]
1450 dict
set date2 era CE
1451 set jdwkday
[WeekdayOnOrBefore
$dayOfWeek \
1452 [expr { [dict get
$date2 julianDay
]
1454 incr jdwkday
[expr { 7 * $dayOrdinal }]
1455 if { $dayOrdinal > 0 } {
1458 dict
set date2 secondOfDay
\
1459 [expr { [dict get
$date2 localSeconds
] % 86400 }]
1460 dict
set date2 julianDay
$jdwkday
1461 dict
set date2 localSeconds
\
1462 [expr { -210866803200
1463 + ( 86400 * wide
([dict get
$date2 julianDay
]) )
1464 + [dict get
$date secondOfDay
] }]
1465 dict
set date2 tzName
$timezone
1466 set date2
[ConvertLocalToUTC
$date2[set date2
{}] $TZData($timezone) \
1468 set seconds
[dict get
$date2 seconds
]
1474 if { [llength $parseOrdinalMonth] > 0 } {
1476 lassign
$parseOrdinalMonth monthOrdinal monthNumber
1477 if { $monthOrdinal > 0 } {
1478 set monthDiff
[expr { $monthNumber - [dict get
$date month
] }]
1479 if { $monthDiff <= 0 } {
1482 incr monthOrdinal
-1
1484 set monthDiff
[expr { [dict get
$date month
] - $monthNumber }]
1485 if { $monthDiff >= 0 } {
1490 set seconds
[add
$seconds $monthOrdinal years
$monthDiff months
\
1491 -timezone $timezone -locale $locale]
1499 #----------------------------------------------------------------------
1501 # ParseClockScanFormat --
1503 # Parses a format string given to [clock scan -format]
1506 # formatString - The format being parsed
1507 # locale - The current locale
1510 # Constructs and returns a procedure that accepts the
1511 # string being scanned, the base time, and the time zone.
1512 # The procedure will either return the scanned time or
1513 # else throw an error that should be rethrown to the caller
1517 # The given procedure is defined in the ::tcl::clock
1518 # namespace. Scan procedures are not deleted once installed.
1520 # Why do we parse dates by defining a procedure to parse them?
1521 # The reason is that by doing so, we have one convenient place to
1522 # cache all the information: the regular expressions that match the
1523 # patterns (which will be compiled), the code that assembles the
1524 # date information, everything lands in one place. In this way,
1525 # when a given format is reused at run time, all the information
1526 # of how to apply it is available in a single place.
1528 #----------------------------------------------------------------------
1530 proc ::tcl::clock::ParseClockScanFormat {formatString locale
} {
1532 # Check whether the format has been parsed previously, and return
1533 # the existing recognizer if it has.
1535 set procName scanproc'
$formatString'
$locale
1536 set procName
[namespace current
]::[string map
{: {\:} \\ {\\}} $procName]
1537 if { [namespace which
$procName] != {} } {
1541 variable DateParseActions
1542 variable TimeParseActions
1544 # Localize the %x, %X, etc. groups
1546 set formatString
[LocalizeFormat
$locale $formatString]
1548 # Condense whitespace
1550 regsub -all {[[:space
:]]+} $formatString { } formatString
1552 # Walk through the groups of the format string. In this loop, we
1554 # - a regular expression that matches the string,
1555 # - the count of capturing brackets in the regexp
1556 # - a set of code that post-processes the fields captured by the regexp,
1557 # - a dictionary whose keys are the names of fields that are present
1558 # in the format string.
1560 set re
{^
[[:space
:]]*}
1563 set fieldSet
[dict create
]
1568 foreach c
[split $formatString {}] {
1569 switch -exact -- $state {
1573 } elseif
{ $c eq
" " } {
1574 append re
{[[:space
:]]+}
1576 if { ! [string is alnum
$c] } {
1584 switch -exact -- $c {
1589 append re
"\[\[:space:\]\]*"
1591 a
- A
{ # Day of week, in words
1595 abr
[mc DAYS_OF_WEEK_ABBREV
] \
1596 full
[mc DAYS_OF_WEEK_FULL
] {
1597 dict
set l
[string tolower
$abr] $i
1598 dict
set l
[string tolower
$full] $i
1601 lassign
[UniquePrefixRegexp
$l] regex lookup
1602 append re
( $regex )
1603 dict
set fieldSet dayOfWeek
[incr fieldCount
]
1604 append postcode
"dict set date dayOfWeek \[" \
1605 "dict get " [list $lookup] " " \
1606 \[ {string tolower
$field} [incr captureCount
] \] \
1609 b
- B
- h
{ # Name of month
1613 abr
[mc MONTHS_ABBREV
] \
1614 full
[mc MONTHS_FULL
] {
1616 dict
set l
[string tolower
$abr] $i
1617 dict
set l
[string tolower
$full] $i
1619 lassign
[UniquePrefixRegexp
$l] regex lookup
1620 append re
( $regex )
1621 dict
set fieldSet month
[incr fieldCount
]
1622 append postcode
"dict set date month \[" \
1623 "dict get " [list $lookup] \
1624 " " \[ {string tolower
$field} \
1625 [incr captureCount
] \] \
1628 C
{ # Gregorian century
1629 append re
\\s
*(\\d
\\d?
)
1630 dict
set fieldSet century
[incr fieldCount
]
1631 append postcode
"dict set date century \[" \
1632 "::scan \$field" [incr captureCount
] " %d" \
1635 d
- e
{ # Day of month
1636 append re
\\s
*(\\d
\\d?
)
1637 dict
set fieldSet dayOfMonth
[incr fieldCount
]
1638 append postcode
"dict set date dayOfMonth \[" \
1639 "::scan \$field" [incr captureCount
] " %d" \
1642 E
{ # Prefix for locale-specific codes
1645 g
{ # ISO8601 2-digit year
1646 append re
\\s
*(\\d
\\d
)
1647 dict
set fieldSet iso8601YearOfCentury
\
1650 "dict set date iso8601YearOfCentury \[" \
1651 "::scan \$field" [incr captureCount
] " %d" \
1654 G
{ # ISO8601 4-digit year
1655 append re
\\s
*(\\d
\\d
)(\\d
\\d
)
1656 dict
set fieldSet iso8601Century
[incr fieldCount
]
1657 dict
set fieldSet iso8601YearOfCentury
\
1660 "dict set date iso8601Century \[" \
1661 "::scan \$field" [incr captureCount
] " %d" \
1663 "dict set date iso8601YearOfCentury \[" \
1664 "::scan \$field" [incr captureCount
] " %d" \
1667 H
- k
{ # Hour of day
1668 append re
\\s
*(\\d
\\d?
)
1669 dict
set fieldSet hour
[incr fieldCount
]
1670 append postcode
"dict set date hour \[" \
1671 "::scan \$field" [incr captureCount
] " %d" \
1674 I
- l
{ # Hour, AM/PM
1675 append re
\\s
*(\\d
\\d?
)
1676 dict
set fieldSet hourAMPM
[incr fieldCount
]
1677 append postcode
"dict set date hourAMPM \[" \
1678 "::scan \$field" [incr captureCount
] " %d" \
1682 append re
\\s
*(\\d
\\d?
\\d?
)
1683 dict
set fieldSet dayOfYear
[incr fieldCount
]
1684 append postcode
"dict set date dayOfYear \[" \
1685 "::scan \$field" [incr captureCount
] " %d" \
1688 J
{ # Julian Day Number
1689 append re
\\s
*(\\d
+)
1690 dict
set fieldSet julianDay
[incr fieldCount
]
1691 append postcode
"dict set date julianDay \[" \
1692 "::scan \$field" [incr captureCount
] " %ld" \
1695 m
- N
{ # Month number
1696 append re
\\s
*(\\d
\\d?
)
1697 dict
set fieldSet month
[incr fieldCount
]
1698 append postcode
"dict set date month \[" \
1699 "::scan \$field" [incr captureCount
] " %d" \
1703 append re
\\s
*(\\d
\\d?
)
1704 dict
set fieldSet minute
[incr fieldCount
]
1705 append postcode
"dict set date minute \[" \
1706 "::scan \$field" [incr captureCount
] " %d" \
1709 n
{ # Literal newline
1712 O
{ # Prefix for locale numerics
1715 p
- P
{ # AM/PM indicator
1716 set l
[list [string tolower
[mc AM
]] 0 \
1717 [string tolower
[mc PM
]] 1]
1718 lassign
[UniquePrefixRegexp
$l] regex lookup
1719 append re
( $regex )
1720 dict
set fieldSet amPmIndicator
[incr fieldCount
]
1721 append postcode
"dict set date amPmIndicator \[" \
1722 "dict get " [list $lookup] " \[string tolower " \
1724 [incr captureCount
] \
1728 append re
{Stardate
\s
+([-+]?
\d
+)(\d
\d
\d
)[.
](\d
)}
1730 dict
set fieldSet seconds
[incr fieldCount
]
1731 append postcode
{dict
set date seconds
} \[ \
1732 {ParseStarDate
$field} [incr captureCount
] \
1733 { $field} [incr captureCount
] \
1734 { $field} [incr captureCount
] \
1737 s
{ # Seconds from Posix Epoch
1738 # This next case is insanely difficult,
1739 # because it's problematic to determine
1740 # whether the field is actually within
1741 # the range of a wide integer.
1742 append re
{\s
*([-+]?
\d
+)}
1743 dict
set fieldSet seconds
[incr fieldCount
]
1744 append postcode
{dict
set date seconds
} \[ \
1745 {ScanWide
$field} [incr captureCount
] \] \n
1748 append re
\\s
*(\\d
\\d?
)
1749 dict
set fieldSet second
[incr fieldCount
]
1750 append postcode
"dict set date second \[" \
1751 "::scan \$field" [incr captureCount
] " %d" \
1754 t
{ # Literal tab character
1757 u
- w
{ # Day number within week, 0 or 7 == Sun
1760 dict
set fieldSet dayOfWeek
[incr fieldCount
]
1761 append postcode
{::scan $field} [incr captureCount
] \
1766 } elseif
{ $dow > 7 } {
1767 return -code error \
1768 -errorcode [list CLOCK badDayOfWeek
] \
1769 "day of week is greater than 7"
1771 dict
set date dayOfWeek
$dow
1774 U
{ # Week of year. The
1775 # first Sunday of the year is the
1776 # first day of week 01. No scan rule
1778 append re
\\s
*\\d
\\d?
1780 V
{ # Week of ISO8601 year
1782 append re
\\s
*(\\d
\\d?
)
1783 dict
set fieldSet iso8601Week
[incr fieldCount
]
1784 append postcode
"dict set date iso8601Week \[" \
1785 "::scan \$field" [incr captureCount
] " %d" \
1788 W
{ # Week of the year (00-53). The first
1789 # Monday of the year is the first day
1790 # of week 01. No scan rule uses this
1792 append re
\\s
*\\d
\\d?
1794 y
{ # Two-digit Gregorian year
1795 append re
\\s
*(\\d
\\d?
)
1796 dict
set fieldSet yearOfCentury
[incr fieldCount
]
1797 append postcode
"dict set date yearOfCentury \[" \
1798 "::scan \$field" [incr captureCount
] " %d" \
1801 Y
{ # 4-digit Gregorian year
1802 append re
\\s
*(\\d
\\d
)(\\d
\\d
)
1803 dict
set fieldSet century
[incr fieldCount
]
1804 dict
set fieldSet yearOfCentury
[incr fieldCount
]
1806 "dict set date century \[" \
1807 "::scan \$field" [incr captureCount
] " %d" \
1809 "dict set date yearOfCentury \[" \
1810 "::scan \$field" [incr captureCount
] " %d" \
1813 z
- Z
{ # Time zone name
1814 append re
{(?
:([-+]\d
\d
(?
::?
\d
\d
(?
::?
\d
\d
)?
)?
)|
([[:alnum
:]]{1,4}))}
1815 dict
set fieldSet tzName
[incr fieldCount
]
1817 {if } \{ { $field} [incr captureCount
] \
1818 { ne
"" } \} { } \{ \n \
1819 {dict
set date tzName
$field} \
1822 {dict
set date tzName
} \[ \
1823 {ConvertLegacyTimeZone
$field} \
1824 [incr captureCount
] \] \n \
1827 % { # Literal percent character
1832 if { ! [string is alnum
$c] } {
1840 switch -exact -- $c {
1841 C
{ # Locale-dependent era
1843 foreach triple
[mc LOCALE_ERAS
] {
1844 lassign
$triple t symbol year
1845 dict
set d
[string tolower
$symbol] $year
1847 lassign
[UniquePrefixRegexp
$d] regex lookup
1848 append re
(?
: $regex )
1852 dict
set l
[string tolower
[mc BCE
]] BCE
1853 dict
set l
[string tolower
[mc CE
]] CE
1854 dict
set l b.c.e. BCE
1858 lassign
[UniquePrefixRegexp
$l] regex lookup
1859 append re
( $regex )
1860 dict
set fieldSet era
[incr fieldCount
]
1861 append postcode
"dict set date era \["\
1862 "dict get " [list $lookup] \
1863 { } \[ {string tolower
$field} \
1864 [incr captureCount
] \] \
1867 y
{ # Locale-dependent year of the era
1868 lassign
[LocaleNumeralMatcher
$locale] regex lookup
1874 if { ! [string is alnum
$c] } {
1883 switch -exact -- $c {
1885 lassign
[LocaleNumeralMatcher
$locale] regex lookup
1887 dict
set fieldSet dayOfMonth
[incr fieldCount
]
1888 append postcode
"dict set date dayOfMonth \[" \
1889 "dict get " [list $lookup] " \$field" \
1890 [incr captureCount
] \
1894 lassign
[LocaleNumeralMatcher
$locale] regex lookup
1896 dict
set fieldSet hour
[incr fieldCount
]
1897 append postcode
"dict set date hour \[" \
1898 "dict get " [list $lookup] " \$field" \
1899 [incr captureCount
] \
1903 lassign
[LocaleNumeralMatcher
$locale] regex lookup
1905 dict
set fieldSet hourAMPM
[incr fieldCount
]
1906 append postcode
"dict set date hourAMPM \[" \
1907 "dict get " [list $lookup] " \$field" \
1908 [incr captureCount
] \
1912 lassign
[LocaleNumeralMatcher
$locale] regex lookup
1914 dict
set fieldSet month
[incr fieldCount
]
1915 append postcode
"dict set date month \[" \
1916 "dict get " [list $lookup] " \$field" \
1917 [incr captureCount
] \
1921 lassign
[LocaleNumeralMatcher
$locale] regex lookup
1923 dict
set fieldSet minute
[incr fieldCount
]
1924 append postcode
"dict set date minute \[" \
1925 "dict get " [list $lookup] " \$field" \
1926 [incr captureCount
] \
1930 lassign
[LocaleNumeralMatcher
$locale] regex lookup
1932 dict
set fieldSet second
[incr fieldCount
]
1933 append postcode
"dict set date second \[" \
1934 "dict get " [list $lookup] " \$field" \
1935 [incr captureCount
] \
1939 lassign
[LocaleNumeralMatcher
$locale] regex lookup
1941 dict
set fieldSet dayOfWeek
[incr fieldCount
]
1942 append postcode
"set dow \[dict get " [list $lookup] \
1943 { $field} [incr captureCount
] \] \n \
1947 } elseif
{ $dow > 7 } {
1948 return -code error \
1949 -errorcode [list CLOCK badDayOfWeek
] \
1950 "day of week is greater than 7"
1952 dict
set date dayOfWeek
$dow
1956 lassign
[LocaleNumeralMatcher
$locale] regex lookup
1958 dict
set fieldSet yearOfCentury
[incr fieldCount
]
1959 append postcode
{dict
set date yearOfCentury
} \[ \
1960 {dict get
} [list $lookup] { $field} \
1961 [incr captureCount
] \] \n
1965 if { ! [string is alnum
$c] } {
1976 # Clean up any unfinished format groups
1978 append re
$state \\s
*\$
1980 # Build the procedure
1983 append procBody
"variable ::tcl::clock::TZData" \n
1984 append procBody
"if \{ !\[ regexp -nocase [list $re] \$string ->"
1985 for { set i
1 } { $i <= $captureCount } { incr i
} {
1986 append procBody
" " field
$i
1988 append procBody
"\] \} \{" \n
1990 return -code error -errorcode [list CLOCK badInputString
] \
1991 {input
string does not match supplied
format}
1993 append procBody
\}\n
1994 append procBody
"set date \[dict create\]" \n
1995 append procBody
{dict
set date tzName
$timeZone} \n
1996 append procBody
$postcode
1997 append procBody
[list set changeover
[mc GREGORIAN_CHANGE_DATE
]] \n
1999 # Get time zone if needed
2001 if { ![dict exists
$fieldSet seconds
]
2002 && ![dict exists
$fieldSet starDate
] } {
2003 if { [dict exists
$fieldSet tzName
] } {
2005 set timeZone
[dict get
$date tzName
]
2009 ::tcl::clock::SetupTimeZone $timeZone
2013 # Add code that gets Julian Day Number from the fields.
2015 append procBody
[MakeParseCodeFromFields
$fieldSet $DateParseActions]
2019 append procBody
[MakeParseCodeFromFields
$fieldSet $TimeParseActions]
2021 # Assemble seconds, and convert local nominal time to UTC.
2023 if { ![dict exists
$fieldSet seconds
]
2024 && ![dict exists
$fieldSet starDate
] } {
2026 if { [dict get
$date julianDay
] > 5373484 } {
2027 return -code error -errorcode [list CLOCK dateTooLarge
] \
2028 "requested date too large to represent"
2030 dict
set date localSeconds
\
2031 [expr { -210866803200
2032 + ( 86400 * wide
([dict get
$date julianDay
]) )
2033 + [dict get
$date secondOfDay
] }]
2036 set date
[::tcl::clock::ConvertLocalToUTC $date[set date
{}] \
2037 $TZData($timeZone) \
2044 append procBody
{return [dict get
$date seconds
]} \n
2046 proc $procName { string baseTime timeZone
} $procBody
2048 # puts [list proc $procName [list string baseTime timeZone] $procBody]
2053 #----------------------------------------------------------------------
2055 # LocaleNumeralMatcher --
2057 # Composes a regexp that captures the numerals in the given
2058 # locale, and a dictionary to map them to conventional numerals.
2061 # locale - Name of the current locale
2064 # Returns a two-element list comprising the regexp and the
2068 # Caches the result.
2070 #----------------------------------------------------------------------
2072 proc ::tcl::clock::LocaleNumeralMatcher {l
} {
2074 variable LocaleNumeralCache
2076 if { ![dict exists
$LocaleNumeralCache $l] } {
2080 foreach n
[mc LOCALE_NUMERALS
] {
2082 regsub -all {[^
[:alnum
:]]} $n \\\\& subex
2083 append re
$sep $subex
2088 dict
set LocaleNumeralCache
$l [list $re $d]
2090 return [dict get
$LocaleNumeralCache $l]
2095 #----------------------------------------------------------------------
2097 # UniquePrefixRegexp --
2099 # Composes a regexp that performs unique-prefix matching. The
2100 # RE matches one of a supplied set of strings, or any unique
2104 # data - List of alternating match-strings and values.
2105 # Match-strings with distinct values are considered
2109 # Returns a two-element list. The first is a regexp that
2110 # matches any unique prefix of any of the strings. The second
2111 # is a dictionary whose keys are match values from the regexp
2112 # and whose values are the corresponding values from 'data'.
2117 #----------------------------------------------------------------------
2119 proc ::tcl::clock::UniquePrefixRegexp { data
} {
2121 # The 'successors' dictionary will contain, for each string that
2122 # is a prefix of any key, all characters that may follow that
2123 # prefix. The 'prefixMapping' dictionary will have keys that
2124 # are prefixes of keys and values that correspond to the keys.
2126 set prefixMapping
[dict create
]
2127 set successors
[dict create
{} {}]
2129 # Walk the key-value pairs
2131 foreach { key value
} $data {
2133 # Construct all prefixes of the key;
2136 foreach char
[split $key {}] {
2137 set oldPrefix
$prefix
2138 dict
set successors
$oldPrefix $char {}
2141 # Put the prefixes in the 'prefixMapping' and 'successors'
2144 dict
lappend prefixMapping
$prefix $value
2145 if { ![dict exists
$successors $prefix] } {
2146 dict
set successors
$prefix {}
2151 # Identify those prefixes that designate unique values, and
2152 # those that are the full keys
2154 set uniquePrefixMapping
{}
2155 dict
for { key valueList
} $prefixMapping {
2156 if { [llength $valueList] == 1 } {
2157 dict
set uniquePrefixMapping
$key [lindex $valueList 0]
2160 foreach { key value
} $data {
2161 dict
set uniquePrefixMapping
$key $value
2167 [MakeUniquePrefixRegexp
$successors $uniquePrefixMapping {}] \
2168 $uniquePrefixMapping]
2171 #----------------------------------------------------------------------
2173 # MakeUniquePrefixRegexp --
2175 # Service procedure for 'UniquePrefixRegexp' that constructs
2176 # a regular expresison that matches the unique prefixes.
2179 # successors - Dictionary whose keys are all prefixes
2180 # of keys passed to 'UniquePrefixRegexp' and whose
2181 # values are dictionaries whose keys are the characters
2182 # that may follow those prefixes.
2183 # uniquePrefixMapping - Dictionary whose keys are the unique
2184 # prefixes and whose values are not examined.
2185 # prefixString - Current prefix being processed.
2188 # Returns a constructed regular expression that matches the set
2189 # of unique prefixes beginning with the 'prefixString'.
2194 #----------------------------------------------------------------------
2196 proc ::tcl::clock::MakeUniquePrefixRegexp { successors
2200 # Get the characters that may follow the current prefix string
2202 set schars
[lsort -ascii [dict keys
[dict get
$successors $prefixString]]]
2203 if { [llength $schars] == 0 } {
2207 # If there is more than one successor character, or if the current
2208 # prefix is a unique prefix, surround the generated re with non-capturing
2212 if { [dict exists
$uniquePrefixMapping $prefixString]
2213 ||
[llength $schars] > 1 } {
2217 # Generate a regexp that matches the successors.
2220 foreach { c
} $schars {
2221 set nextPrefix
$prefixString$c
2222 regsub -all {[^
[:alnum
:]]} $c \\\\& rechar
2223 append re
$sep $rechar \
2224 [MakeUniquePrefixRegexp
\
2225 $successors $uniquePrefixMapping $nextPrefix]
2229 # If the current prefix is a unique prefix, make all following text
2230 # optional. Otherwise, if there is more than one successor character,
2231 # close the non-capturing parentheses.
2233 if { [dict exists
$uniquePrefixMapping $prefixString] } {
2235 } elseif
{ [llength $schars] > 1 } {
2242 #----------------------------------------------------------------------
2244 # MakeParseCodeFromFields --
2246 # Composes Tcl code to extract the Julian Day Number from a
2247 # dictionary containing date fields.
2250 # dateFields -- Dictionary whose keys are fields of the date,
2251 # and whose values are the rightmost positions
2252 # at which those fields appear.
2253 # parseActions -- List of triples: field set, priority, and
2254 # code to emit. Smaller priorities are better, and
2255 # the list must be in ascending order by priority
2258 # Returns a burst of code that extracts the day number from the
2264 #----------------------------------------------------------------------
2266 proc ::tcl::clock::MakeParseCodeFromFields { dateFields parseActions
} {
2269 set currFieldPos
[list]
2271 error "in ::tcl::clock::MakeParseCodeFromFields: can't happen"
2274 foreach { fieldSet prio parseAction
} $parseActions {
2276 # If we've found an answer that's better than any that follow,
2279 if { $prio > $currPrio } {
2283 # Accumulate the field positions that are used in the current
2288 foreach field
$fieldSet {
2289 if { ! [dict exists
$dateFields $field] } {
2293 lappend fieldPos
[dict get
$dateFields $field]
2296 # Quit if we don't have a complete set of fields
2301 # Determine whether the current answer is better than the last.
2303 set fPos
[lsort -integer -decreasing $fieldPos]
2305 if { $prio == $currPrio } {
2306 foreach currPos
$currFieldPos newPos
$fPos {
2307 if { ![string is integer
$newPos]
2308 ||
![string is integer
$currPos]
2309 ||
$newPos > $currPos } {
2312 if { $newPos < $currPos } {
2322 # Remember the best possibility for extracting date information
2325 set currFieldPos
$fPos
2326 set currCodeBurst
$parseAction
2330 return $currCodeBurst
2334 #----------------------------------------------------------------------
2338 # Switch [mclocale] to a given locale if necessary
2341 # locale -- Desired locale
2342 # oldLocaleVar -- Name of a variable in caller's scope that
2343 # tracks the previous locale name.
2346 # Returns the locale that was previously current.
2349 # Does [mclocale]. If necessary, uses [mcload] to load the
2350 # designated locale's files, and tracks that it has done so
2351 # in the 'McLoaded' variable.
2353 #----------------------------------------------------------------------
2355 proc ::tcl::clock::EnterLocale { locale oldLocaleVar
} {
2357 upvar 1 $oldLocaleVar oldLocale
2362 set oldLocale
[mclocale
]
2363 if { $locale eq
{system
} } {
2365 if { $::tcl_platform(platform
) ne
{windows
} } {
2367 # On a non-windows platform, the 'system' locale is
2368 # the same as the 'current' locale
2373 # On a windows platform, the 'system' locale is
2374 # adapted from the 'current' locale by applying the
2375 # date and time formats from the Control Panel.
2376 # First, load the 'current' locale if it's not yet loaded
2378 if {![dict exists
$McLoaded $oldLocale] } {
2380 dict
set McLoaded
$oldLocale {}
2383 # Make a new locale string for the system locale, and
2384 # get the Control Panel information
2386 set locale
${oldLocale
}_windows
2387 if { ![dict exists
$McLoaded $locale] } {
2388 LoadWindowsDateTimeFormats
$locale
2389 dict
set McLoaded
$locale {}
2393 if { $locale eq
{current
}} {
2394 set locale
$oldLocale
2396 } elseif
{ $locale eq
$oldLocale } {
2401 if { ![dict exists
$McLoaded $locale] } {
2403 dict
set McLoaded
$locale {}
2408 #----------------------------------------------------------------------
2410 # LoadWindowsDateTimeFormats --
2412 # Load the date/time formats from the Control Panel in Windows
2413 # and convert them so that they're usable by Tcl.
2416 # locale - Name of the locale in whose message catalog
2417 # the converted formats are to be stored.
2423 # Updates the given message catalog with the locale strings.
2425 # Presumes that on entry, [mclocale] is set to the current locale,
2426 # so that default strings can be obtained if the Registry query
2429 #----------------------------------------------------------------------
2431 proc ::tcl::clock::LoadWindowsDateTimeFormats { locale
} {
2433 # Bail out if we can't find the Registry
2436 if { [info exists NoRegistry
] } return
2439 registry get
"HKEY_CURRENT_USER\\Control Panel\\International" \
2444 foreach { unquoted quoted
} [split $string '
] {
2445 append datefmt
$quote [string map
{
2459 if { $quoted eq
{} } {
2465 ::msgcat::mcset $locale DATE_FORMAT
$datefmt
2469 registry get
"HKEY_CURRENT_USER\\Control Panel\\International" \
2474 foreach { unquoted quoted
} [split $string '
] {
2475 append ldatefmt
$quote [string map
{
2489 if { $quoted eq
{} } {
2495 ::msgcat::mcset $locale LOCALE_DATE_FORMAT
$ldatefmt
2499 registry get
"HKEY_CURRENT_USER\\Control Panel\\International" \
2504 foreach { unquoted quoted
} [split $string '
] {
2505 append timefmt
$quote [string map
{
2517 if { $quoted eq
{} } {
2523 ::msgcat::mcset $locale TIME_FORMAT
$timefmt
2527 ::msgcat::mcset $locale DATE_TIME_FORMAT
"$datefmt $timefmt"
2530 ::msgcat::mcset $locale LOCALE_DATE_TIME_FORMAT
"$ldatefmt $timefmt"
2537 #----------------------------------------------------------------------
2541 # Map away locale-dependent format groups in a clock format.
2544 # locale -- Current [mclocale] locale, supplied to avoid
2546 # format -- Format supplied to [clock scan] or [clock format]
2549 # Returns the string with locale-dependent composite format
2550 # groups substituted out.
2555 #----------------------------------------------------------------------
2557 proc ::tcl::clock::LocalizeFormat { locale
format } {
2561 if { [dict exists
$McLoaded $locale FORMAT
$format] } {
2562 return [dict get
$McLoaded $locale FORMAT
$format]
2564 set inFormat
$format
2566 # Handle locale-dependent format groups by mapping them out of the format
2567 # string. Note that the order of the [string map] operations is
2568 # significant because later formats can refer to later ones; for example
2569 # %c can refer to %X, which in turn can refer to %T.
2574 %+ {%a
%b
%e
%H
:%M
:%S
%Z
%Y
}
2576 lappend list %EY
[string map
$list [mc LOCALE_YEAR_FORMAT
]]
2577 lappend list %T
[string map
$list [mc TIME_FORMAT_24_SECS
]]
2578 lappend list %R
[string map
$list [mc TIME_FORMAT_24
]]
2579 lappend list %r
[string map
$list [mc TIME_FORMAT_12
]]
2580 lappend list %X
[string map
$list [mc TIME_FORMAT
]]
2581 lappend list %EX
[string map
$list [mc LOCALE_TIME_FORMAT
]]
2582 lappend list %x
[string map
$list [mc DATE_FORMAT
]]
2583 lappend list %Ex
[string map
$list [mc LOCALE_DATE_FORMAT
]]
2584 lappend list %c
[string map
$list [mc DATE_TIME_FORMAT
]]
2585 lappend list %Ec
[string map
$list [mc LOCALE_DATE_TIME_FORMAT
]]
2586 set format [string map
$list $format]
2588 dict
set McLoaded
$locale FORMAT
$inFormat $format
2592 #----------------------------------------------------------------------
2594 # FormatNumericTimeZone --
2596 # Formats a time zone as +hhmmss
2599 # z - Time zone in seconds east of Greenwich
2602 # Returns the time zone formatted in a numeric form
2607 #----------------------------------------------------------------------
2609 proc ::tcl::clock::FormatNumericTimeZone { z
} {
2612 set z
[expr { - $z }]
2617 append retval
[::format %02d
[expr { $z / 3600 }]]
2618 set z
[expr { $z % 3600 }]
2619 append retval
[::format %02d
[expr { $z / 60 }]]
2620 set z
[expr { $z % 60 }]
2622 append retval
[::format %02d
$z]
2628 #----------------------------------------------------------------------
2632 # Formats a date as a StarDate.
2635 # date - Dictionary containing 'year', 'dayOfYear', and
2636 # 'localSeconds' fields.
2639 # Returns the given date formatted as a StarDate.
2644 # Jeff Hobbs put this in to support an atrocious pun about Tcl being
2645 # "Enterprise ready." Now we're stuck with it.
2647 #----------------------------------------------------------------------
2649 proc ::tcl::clock::FormatStarDate { date
} {
2651 variable Roddenberry
2653 # Get day of year, zero based
2655 set doy
[expr { [dict get
$date dayOfYear
] - 1 }]
2657 # Determine whether the year is a leap year
2659 set lp
[IsGregorianLeapYear
$date]
2661 # Convert day of year to a fractional year
2664 set fractYear
[expr { 1000 * $doy / 366 }]
2666 set fractYear
[expr { 1000 * $doy / 365 }]
2669 # Put together the StarDate
2671 return [::format "Stardate %02d%03d.%1d" \
2672 [expr { [dict get
$date year
] - $Roddenberry }] \
2674 [expr { [dict get
$date localSeconds
] % 86400
2675 / ( 86400 / 10 ) }]]
2678 #----------------------------------------------------------------------
2685 # year - Year from the Roddenberry epoch
2686 # fractYear - Fraction of a year specifiying the day of year.
2687 # fractDay - Fraction of a day
2690 # Returns a count of seconds from the Posix epoch.
2695 # Jeff Hobbs put this in to support an atrocious pun about Tcl being
2696 # "Enterprise ready." Now we're stuck with it.
2698 #----------------------------------------------------------------------
2700 proc ::tcl::clock::ParseStarDate { year fractYear fractDay
} {
2702 variable Roddenberry
2704 # Build a tentative date from year and fraction.
2706 set date
[dict create
\
2709 year
[expr { $year + $Roddenberry }] \
2710 dayOfYear
[expr { $fractYear * 365 / 1000 + 1 }]]
2711 set date
[GetJulianDayFromGregorianEraYearDay
$date[set date
{}]]
2713 # Determine whether the given year is a leap year
2715 set lp
[IsGregorianLeapYear
$date]
2717 # Reconvert the fractional year according to whether the given
2718 # year is a leap year
2721 dict
set date dayOfYear
\
2722 [expr { $fractYear * 366 / 1000 + 1 }]
2724 dict
set date dayOfYear
\
2725 [expr { $fractYear * 365 / 1000 + 1 }]
2727 dict
unset date julianDay
2728 dict
unset date gregorian
2729 set date
[GetJulianDayFromGregorianEraYearDay
$date[set date
{}]]
2731 return [expr { 86400 * [dict get
$date julianDay
]
2733 + ( 86400 / 10 ) * $fractDay }]
2737 #----------------------------------------------------------------------
2741 # Scans a wide integer from an input
2744 # str - String containing a decimal wide integer
2747 # Returns the string as a pure wide integer. Throws an error if
2748 # the string is misformatted or out of range.
2750 #----------------------------------------------------------------------
2752 proc ::tcl::clock::ScanWide { str
} {
2753 set count
[::scan $str {%ld
%c
} result junk
]
2754 if { $count != 1 } {
2755 return -code error -errorcode [list CLOCK notAnInteger
$str] \
2756 "\"$str\" is not an integer"
2758 if { [incr result
0] != $str } {
2759 return -code error -errorcode [list CLOCK integervalueTooLarge
] \
2760 "integer value too large to represent"
2765 #----------------------------------------------------------------------
2767 # InterpretTwoDigitYear --
2769 # Given a date that contains only the year of the century,
2770 # determines the target value of a two-digit year.
2773 # date - Dictionary containing fields of the date.
2774 # baseTime - Base time relative to which the date is expressed.
2775 # twoDigitField - Name of the field that stores the two-digit year.
2776 # Default is 'yearOfCentury'
2777 # fourDigitField - Name of the field that will receive the four-digit
2778 # year. Default is 'year'
2781 # Returns the dictionary augmented with the four-digit year, stored in
2787 # The current rule for interpreting a two-digit year is that the year
2788 # shall be between 1937 and 2037, thus staying within the range of a
2789 # 32-bit signed value for time. This rule may change to a sliding
2790 # window in future versions, so the 'baseTime' parameter (which is
2791 # currently ignored) is provided in the procedure signature.
2793 #----------------------------------------------------------------------
2795 proc ::tcl::clock::InterpretTwoDigitYear { date baseTime
2796 { twoDigitField yearOfCentury
}
2797 { fourDigitField year
} } {
2799 set yr
[dict get
$date $twoDigitField]
2801 dict
set date
$fourDigitField [expr { $yr + 2000 }]
2803 dict
set date
$fourDigitField [expr { $yr + 1900 }]
2809 #----------------------------------------------------------------------
2813 # Places the number of the current year into a dictionary.
2816 # date - Dictionary value to update
2817 # baseTime - Base time from which to extract the year, expressed
2818 # in seconds from the Posix epoch
2819 # timezone - the time zone in which the date is being scanned
2820 # changeover - the Julian Day on which the Gregorian calendar
2821 # was adopted in the target locale.
2824 # Returns the dictionary with the current year assigned.
2829 #----------------------------------------------------------------------
2831 proc ::tcl::clock::AssignBaseYear { date baseTime timezone changeover
} {
2835 # Find the Julian Day Number corresponding to the base time, and
2836 # find the Gregorian year corresponding to that Julian Day.
2838 set date2
[GetDateFields
$baseTime $TZData($timezone) $changeover]
2840 # Store the converted year
2842 dict
set date era
[dict get
$date2 era
]
2843 dict
set date year
[dict get
$date2 year
]
2849 #----------------------------------------------------------------------
2851 # AssignBaseIso8601Year --
2853 # Determines the base year in the ISO8601 fiscal calendar.
2856 # date - Dictionary containing the fields of the date that
2857 # is to be augmented with the base year.
2858 # baseTime - Base time expressed in seconds from the Posix epoch.
2859 # timeZone - Target time zone
2860 # changeover - Julian Day of adoption of the Gregorian calendar in
2861 # the target locale.
2864 # Returns the given date with "iso8601Year" set to the
2870 #----------------------------------------------------------------------
2872 proc ::tcl::clock::AssignBaseIso8601Year {date baseTime timeZone changeover
} {
2876 # Find the Julian Day Number corresponding to the base time
2878 set date2
[GetDateFields
$baseTime $TZData($timeZone) $changeover]
2880 # Calculate the ISO8601 date and transfer the year
2882 dict
set date era CE
2883 dict
set date iso8601Year
[dict get
$date2 iso8601Year
]
2887 #----------------------------------------------------------------------
2889 # AssignBaseMonth --
2891 # Places the number of the current year and month into a
2895 # date - Dictionary value to update
2896 # baseTime - Time from which the year and month are to be
2897 # obtained, expressed in seconds from the Posix epoch.
2898 # timezone - Name of the desired time zone
2899 # changeover - Julian Day on which the Gregorian calendar was adopted.
2902 # Returns the dictionary with the base year and month assigned.
2907 #----------------------------------------------------------------------
2909 proc ::tcl::clock::AssignBaseMonth {date baseTime timezone changeover
} {
2913 # Find the year and month corresponding to the base time
2915 set date2
[GetDateFields
$baseTime $TZData($timezone) $changeover]
2916 dict
set date era
[dict get
$date2 era
]
2917 dict
set date year
[dict get
$date2 year
]
2918 dict
set date month
[dict get
$date2 month
]
2923 #----------------------------------------------------------------------
2927 # Determines the base year and week in the ISO8601 fiscal calendar.
2930 # date - Dictionary containing the fields of the date that
2931 # is to be augmented with the base year and week.
2932 # baseTime - Base time expressed in seconds from the Posix epoch.
2933 # changeover - Julian Day on which the Gregorian calendar was adopted
2934 # in the target locale.
2937 # Returns the given date with "iso8601Year" set to the
2938 # base year and "iso8601Week" to the week number.
2943 #----------------------------------------------------------------------
2945 proc ::tcl::clock::AssignBaseWeek {date baseTime timeZone changeover
} {
2949 # Find the Julian Day Number corresponding to the base time
2951 set date2
[GetDateFields
$baseTime $TZData($timeZone) $changeover]
2953 # Calculate the ISO8601 date and transfer the year
2955 dict
set date era CE
2956 dict
set date iso8601Year
[dict get
$date2 iso8601Year
]
2957 dict
set date iso8601Week
[dict get
$date2 iso8601Week
]
2961 #----------------------------------------------------------------------
2963 # AssignBaseJulianDay --
2965 # Determines the base day for a time-of-day conversion.
2968 # date - Dictionary that is to get the base day
2969 # baseTime - Base time expressed in seconds from the Posix epoch
2970 # changeover - Julian day on which the Gregorian calendar was
2971 # adpoted in the target locale.
2974 # Returns the given dictionary augmented with a 'julianDay' field
2975 # that contains the base day.
2980 #----------------------------------------------------------------------
2982 proc ::tcl::clock::AssignBaseJulianDay { date baseTime timeZone changeover
} {
2986 # Find the Julian Day Number corresponding to the base time
2988 set date2
[GetDateFields
$baseTime $TZData($timeZone) $changeover]
2989 dict
set date julianDay
[dict get
$date2 julianDay
]
2994 #----------------------------------------------------------------------
2998 # Interprets a time in the form "hh:mm:ss am".
3001 # date -- Dictionary containing "hourAMPM", "minute", "second"
3002 # and "amPmIndicator" fields.
3005 # Returns the number of seconds from local midnight.
3010 #----------------------------------------------------------------------
3012 proc ::tcl::clock::InterpretHMSP { date
} {
3014 set hr
[dict get
$date hourAMPM
]
3018 if { [dict get
$date amPmIndicator
] } {
3021 dict
set date hour
$hr
3022 return [InterpretHMS
$date[set date
{}]]
3026 #----------------------------------------------------------------------
3030 # Interprets a 24-hour time "hh:mm:ss"
3033 # date -- Dictionary containing the "hour", "minute" and "second"
3037 # Returns the given dictionary augmented with a "secondOfDay"
3038 # field containing the number of seconds from local midnight.
3043 #----------------------------------------------------------------------
3045 proc ::tcl::clock::InterpretHMS { date
} {
3047 return [expr { ( [dict get
$date hour
] * 60
3048 + [dict get
$date minute
] ) * 60
3049 + [dict get
$date second
] }]
3053 #----------------------------------------------------------------------
3055 # GetSystemTimeZone --
3057 # Determines the system time zone, which is the default for the
3058 # 'clock' command if no other zone is supplied.
3064 # Returns the system time zone.
3067 # Stores the sustem time zone in the 'CachedSystemTimeZone'
3068 # variable, since determining it may be an expensive process.
3070 #----------------------------------------------------------------------
3072 proc ::tcl::clock::GetSystemTimeZone {} {
3074 variable CachedSystemTimeZone
3075 variable TimeZoneBad
3077 if {[set result
[getenv TCL_TZ
]] ne
{}} {
3078 set timezone
$result
3079 } elseif
{[set result
[getenv TZ
]] ne
{}} {
3080 set timezone
$result
3081 } elseif
{ [info exists CachedSystemTimeZone
] } {
3082 set timezone
$CachedSystemTimeZone
3083 } elseif
{ $::tcl_platform(platform
) eq
{windows
} } {
3084 set timezone
[GuessWindowsTimeZone
]
3085 } elseif
{ [file exists
/etc
/localtime
]
3086 && ![catch {ReadZoneinfoFile
\
3087 Tcl/Localtime
/etc
/localtime
}] } {
3088 set timezone
:Tcl/Localtime
3090 set timezone
:localtime
3092 set CachedSystemTimeZone
$timezone
3093 if { ![dict exists
$TimeZoneBad $timezone] } {
3094 dict
set TimeZoneBad
$timezone [catch {SetupTimeZone
$timezone}]
3096 if { [dict get
$TimeZoneBad $timezone] } {
3104 #----------------------------------------------------------------------
3106 # ConvertLegacyTimeZone --
3108 # Given an alphanumeric time zone identifier and the system
3109 # time zone, convert the alphanumeric identifier to an
3110 # unambiguous time zone.
3113 # tzname - Name of the time zone to convert
3116 # Returns a time zone name corresponding to tzname, but
3117 # in an unambiguous form, generally +hhmm.
3119 # This procedure is implemented primarily to allow the parsing of
3120 # RFC822 date/time strings. Processing a time zone name on input
3121 # is not recommended practice, because there is considerable room
3122 # for ambiguity; for instance, is BST Brazilian Standard Time, or
3123 # British Summer Time?
3125 #----------------------------------------------------------------------
3127 proc ::tcl::clock::ConvertLegacyTimeZone { tzname
} {
3129 variable LegacyTimeZone
3131 set tzname
[string tolower
$tzname]
3132 if { ![dict exists
$LegacyTimeZone $tzname] } {
3133 return -code error -errorcode [list CLOCK badTZName
$tzname] \
3134 "time zone \"$tzname\" not found"
3136 return [dict get
$LegacyTimeZone $tzname]
3141 #----------------------------------------------------------------------
3145 # Given the name or specification of a time zone, sets up
3146 # its in-memory data.
3149 # tzname - Name of a time zone
3152 # Unless the time zone is ':localtime', sets the TZData array
3153 # to contain the lookup table for local<->UTC conversion.
3154 # Returns an error if the time zone cannot be parsed.
3156 #----------------------------------------------------------------------
3158 proc ::tcl::clock::SetupTimeZone { timezone
} {
3162 if {! [info exists TZData
($timezone)] } {
3164 if { $timezone eq
{:localtime
} } {
3166 # Nothing to do, we'll convert using the localtime function
3168 } elseif
{ [regexp {^
([-+])(\d
\d
)(?
::?
(\d
\d
)(?
::?
(\d
\d
))?
)?
} $timezone \
3171 # Make a fixed offset
3184 set offset
[expr { ( $hh * 60 + $mm ) * 60 + $ss }]
3186 set offset
[expr { - $offset }]
3188 set TZData
($timezone) [list [list $MINWIDE $offset -1 $timezone]]
3190 } elseif
{ [string index
$timezone 0] eq
{:} } {
3192 # Convert using a time zone file
3196 LoadTimeZoneFile
[string range
$timezone 1 end
]
3199 LoadZoneinfoFile
[string range
$timezone 1 end
]
3202 return -code error \
3203 -errorcode [list CLOCK badTimeZone
$timezone] \
3204 "time zone \"$timezone\" not found"
3207 } elseif
{ ![catch {ParsePosixTimeZone
$timezone} tzfields
] } {
3209 # This looks like a POSIX time zone - try to process it
3211 if { [catch {ProcessPosixTimeZone
$tzfields} data opts
] } {
3212 if { [lindex [dict get
$opts -errorcode] 0] eq
{CLOCK
} } {
3213 dict
unset opts
-errorinfo
3215 return -options $opts $data
3217 set TZData
($timezone) $data
3222 # We couldn't parse this as a POSIX time zone. Try
3223 # again with a time zone file - this time without a colon
3225 if { [catch { LoadTimeZoneFile
$timezone }]
3226 && [catch { LoadZoneinfoFile
$timezone } - opts
] } {
3227 dict
unset opts
-errorinfo
3228 return -options $opts "time zone $timezone not found"
3230 set TZData
($timezone) $TZData(:$timezone)
3237 #----------------------------------------------------------------------
3239 # GuessWindowsTimeZone --
3241 # Determines the system time zone on windows.
3247 # Returns a time zone specifier that corresponds to the system
3248 # time zone information found in the Registry.
3251 # Fixed dates for DST change are unimplemented at present, because
3252 # no time zone information supplied with Windows actually uses
3255 # On a Windows system where neither $env(TCL_TZ) nor $env(TZ) is
3256 # specified, GuessWindowsTimeZone looks in the Registry for the
3257 # system time zone information. It then attempts to find an entry
3258 # in WinZoneInfo for a time zone that uses the same rules. If
3259 # it finds one, it returns it; otherwise, it constructs a Posix-style
3260 # time zone string and returns that.
3262 #----------------------------------------------------------------------
3264 proc ::tcl::clock::GuessWindowsTimeZone {} {
3266 variable WinZoneInfo
3268 variable TimeZoneBad
3270 if { [info exists NoRegistry
] } {
3274 # Dredge time zone information out of the registry
3277 set rpath HKEY_LOCAL_MACHINE
\\System
\\CurrentControlSet
\\Control
\\TimeZoneInformation
3280 * [registry get
$rpath Bias
] }] \
3282 * [registry get
$rpath StandardBias
] }] \
3284 * [registry get
$rpath DaylightBias
] }]]
3285 set stdtzi
[registry get
$rpath StandardStart
]
3286 foreach ind
{0 2 14 4 6 8 10 12} {
3287 binary scan $stdtzi @${ind
}s val
3290 set daytzi
[registry get
$rpath DaylightStart
]
3291 foreach ind
{0 2 14 4 6 8 10 12} {
3292 binary scan $daytzi @${ind
}s val
3297 # Missing values in the Registry - bail out
3302 # Make up a Posix time zone specifier if we can't find one.
3303 # Check here that the tzdata file exists, in case we're running
3304 # in an environment (e.g. starpack) where tzdata is incomplete.
3307 if { [dict exists
$WinZoneInfo $data] } {
3308 set tzname
[dict get
$WinZoneInfo $data]
3309 if { ! [dict exists
$TimeZoneBad $tzname] } {
3310 dict
set TimeZoneBad
$tzname [catch {SetupTimeZone
$tzname}]
3315 if { $tzname eq
{} ||
[dict get
$TimeZoneBad $tzname] } {
3317 bias stdBias dstBias
\
3318 stdYear stdMonth stdDayOfWeek stdDayOfMonth
\
3319 stdHour stdMinute stdSecond stdMillisec
\
3320 dstYear dstMonth dstDayOfWeek dstDayOfMonth
\
3321 dstHour dstMinute dstSecond dstMillisec
3322 set stdDelta
[expr { $bias + $stdBias }]
3323 set dstDelta
[expr { $bias + $dstBias }]
3324 if { $stdDelta <= 0 } {
3326 set stdDelta
[expr { - $stdDelta }]
3332 set hh
[::format %02d
[expr { $stdDelta / 3600 }]]
3333 set mm
[::format %02d
[expr { ($stdDelta / 60 ) % 60 }]]
3334 set ss
[::format %02d
[expr { $stdDelta % 60 }]]
3336 append tzname
< $dispStdSignum $hh $mm > $stdSignum $hh : $mm : $ss
3337 if { $stdMonth >= 0 } {
3338 if { $dstDelta <= 0 } {
3340 set dstDelta
[expr { - $dstDelta }]
3346 set hh
[::format %02d
[expr { $dstDelta / 3600 }]]
3347 set mm
[::format %02d
[expr { ($dstDelta / 60 ) % 60 }]]
3348 set ss
[::format %02d
[expr { $dstDelta % 60 }]]
3349 append tzname
< $dispDstSignum $hh $mm > $dstSignum $hh : $mm : $ss
3350 if { $dstYear == 0 } {
3351 append tzname
,M
$dstMonth .
$dstDayOfMonth .
$dstDayOfWeek
3353 # I have not been able to find any locale on which
3354 # Windows converts time zone on a fixed day of the year,
3355 # hence don't know how to interpret the fields.
3356 # If someone can inform me, I'd be glad to code it up.
3357 # For right now, we bail out in such a case.
3360 append tzname
/ [::format %02d
$dstHour] \
3361 : [::format %02d
$dstMinute] \
3362 : [::format %02d
$dstSecond]
3363 if { $stdYear == 0 } {
3364 append tzname
,M
$stdMonth .
$stdDayOfMonth .
$stdDayOfWeek
3366 # I have not been able to find any locale on which
3367 # Windows converts time zone on a fixed day of the year,
3368 # hence don't know how to interpret the fields.
3369 # If someone can inform me, I'd be glad to code it up.
3370 # For right now, we bail out in such a case.
3373 append tzname
/ [::format %02d
$stdHour] \
3374 : [::format %02d
$stdMinute] \
3375 : [::format %02d
$stdSecond]
3377 dict
set WinZoneInfo
$data $tzname
3380 return [dict get
$WinZoneInfo $data]
3384 #----------------------------------------------------------------------
3386 # LoadTimeZoneFile --
3388 # Load the data file that specifies the conversion between a
3389 # given time zone and Greenwich.
3392 # fileName -- Name of the file to load
3398 # TZData(:fileName) contains the time zone data
3400 #----------------------------------------------------------------------
3402 proc ::tcl::clock::LoadTimeZoneFile { fileName
} {
3406 if { [info exists TZData
($fileName)] } {
3410 # Since an unsafe interp uses the [clock] command in the master,
3411 # this code is security sensitive. Make sure that the path name
3412 # cannot escape the given directory.
3414 if { ![regexp {^
[[.
-.
][:alpha
:]_
]+(?
:/[[.
-.
][:alpha
:]_
]+)*$} $fileName] } {
3415 return -code error \
3416 -errorcode [list CLOCK badTimeZone
$:fileName
] \
3417 "time zone \":$fileName\" not valid"
3420 source -encoding utf-8
[file join $DataDir $fileName]
3422 return -code error \
3423 -errorcode [list CLOCK badTimeZone
:$fileName] \
3424 "time zone \":$fileName\" not found"
3429 #----------------------------------------------------------------------
3431 # LoadZoneinfoFile --
3433 # Loads a binary time zone information file in Olson format.
3436 # fileName - Relative path name of the file to load.
3439 # Returns an empty result normally; returns an error if no
3440 # Olson file was found or the file was malformed in some way.
3443 # TZData(:fileName) contains the time zone data
3445 #----------------------------------------------------------------------
3447 proc ::tcl::clock::LoadZoneinfoFile { fileName
} {
3449 variable ZoneinfoPaths
3451 # Since an unsafe interp uses the [clock] command in the master,
3452 # this code is security sensitive. Make sure that the path name
3453 # cannot escape the given directory.
3455 if { ![regexp {^
[[.
-.
][:alpha
:]_
]+(?
:/[[.
-.
][:alpha
:]_
]+)*$} $fileName] } {
3456 return -code error \
3457 -errorcode [list CLOCK badTimeZone
$:fileName
] \
3458 "time zone \":$fileName\" not valid"
3460 foreach d
$ZoneinfoPaths {
3461 set fname
[file join $d $fileName]
3462 if { [file readable
$fname] && [file isfile
$fname] } {
3467 ReadZoneinfoFile
$fileName $fname
3470 #----------------------------------------------------------------------
3472 # ReadZoneinfoFile --
3474 # Loads a binary time zone information file in Olson format.
3477 # fileName - Name of the time zone (relative path name of the
3479 # fname - Absolute path name of the file.
3482 # Returns an empty result normally; returns an error if no
3483 # Olson file was found or the file was malformed in some way.
3486 # TZData(:fileName) contains the time zone data
3488 #----------------------------------------------------------------------
3491 proc ::tcl::clock::ReadZoneinfoFile {fileName fname
} {
3494 if { ![info exists fname
] } {
3495 return -code error "$fileName not found"
3498 if { [file size
$fname] > 262144 } {
3499 return -code error "$fileName too big"
3502 # Suck in all the data from the file
3504 set f
[open $fname r
]
3505 fconfigure $f -translation binary
3509 # The file begins with a magic number, sixteen reserved bytes,
3510 # and then six 4-byte integers giving counts of fileds in the file.
3512 binary scan $d a4a1x15IIIIII
\
3513 magic version nIsGMT nIsStd nLeap nTime nType nChar
3517 if { $magic != {TZif
} } {
3518 return -code error "$fileName not a time zone information file"
3520 if { $nType > 255 } {
3521 return -code error "$fileName contains too many time types"
3523 # Accept only Posix-style zoneinfo. Sorry, 'leaps' bigots.
3524 if { $nLeap != 0 } {
3525 return -code error "$fileName contains leap seconds"
3528 # In a version 2 file, we use the second part of the file, which
3529 # contains 64-bit transition times.
3531 if {$version eq
"2"} {
3540 binary scan $d @${seek}a4a1x15IIIIII
\
3541 magic version nIsGMT nIsStd nLeap nTime nType nChar
3542 if {$magic ne
{TZif
}} {
3543 return -code error "seek address $seek miscomputed, magic = $magic"
3550 # Next come ${nTime} transition times, followed by ${nTime} time type
3551 # codes. The type codes are unsigned 1-byte quantities. We insert an
3552 # arbitrary start time in front of the transitions.
3554 binary scan $d @${seek}${iformat
}${nTime
}c
${nTime
} times tempCodes
3555 incr seek [expr { ($ilen + 1) * $nTime }]
3556 set times
[linsert $times 0 $MINWIDE]
3558 foreach c
$tempCodes {
3559 lappend codes
[expr { $c & 0xff }]
3561 set codes
[linsert $codes 0 0]
3563 # Next come ${nType} time type descriptions, each of which has an
3564 # offset (seconds east of GMT), a DST indicator, and an index into
3565 # the abbreviation text.
3567 for { set i
0 } { $i < $nType } { incr i
} {
3568 binary scan $d @${seek}Icc gmtOff isDst abbrInd
3569 lappend types
[list $gmtOff $isDst $abbrInd]
3573 # Next come $nChar characters of time zone name abbreviations,
3574 # which are null-terminated.
3575 # We build them up into a dictionary indexed by character index,
3576 # because that's what's in the indices above.
3578 binary scan $d @${seek}a
${nChar
} abbrs
3580 set abbrList
[split $abbrs \0]
3583 foreach a
$abbrList {
3584 dict
set abbrevs
$i $a
3585 incr i
[expr { [string length
$a] + 1 }]
3588 # Package up a list of tuples, each of which contains transition time,
3589 # seconds east of Greenwich, DST flag and time zone abbreviation.
3592 set lastTime
$MINWIDE
3593 foreach t
$times c
$codes {
3594 if { $t < $lastTime } {
3595 return -code error "$fileName has times out of order"
3598 lassign
[lindex $types $c] gmtoff isDst abbrInd
3599 set abbrev
[dict get
$abbrevs $abbrInd]
3600 lappend r
[list $t $gmtoff $isDst $abbrev]
3603 # In a version 2 file, there is also a POSIX-style time zone description
3604 # at the very end of the file. To get to it, skip over
3605 # nLeap leap second values (8 bytes each),
3606 # nIsStd standard/DST indicators and nIsGMT UTC/local indicators.
3608 if {$version eq
{2}} {
3609 set seek [expr {$seek + 8 * $nLeap + $nIsStd + $nIsGMT + 1}]
3610 set last
[string first
\n $d $seek]
3611 set posix
[string range
$d $seek [expr {$last-1}]]
3612 if {[llength $posix] > 0} {
3613 set posixFields
[ParsePosixTimeZone
$posix]
3614 foreach tuple
[ProcessPosixTimeZone
$posixFields] {
3615 lassign
$tuple t gmtoff isDst abbrev
3616 if {$t > $lastTime} {
3623 set TZData
(:$fileName) $r
3628 #----------------------------------------------------------------------
3630 # ParsePosixTimeZone --
3632 # Parses the TZ environment variable in Posix form
3635 # tz Time zone specifier to be interpreted
3638 # Returns a dictionary whose values contain the various pieces of
3639 # the time zone specification.
3645 # Throws an error if the syntax of the time zone is incorrect.
3647 # The following keys are present in the dictionary:
3648 # stdName - Name of the time zone when Daylight Saving Time
3650 # stdSignum - Sign (+, -, or empty) of the offset from Greenwich
3651 # to the given (non-DST) time zone. + and the empty
3652 # string denote zones west of Greenwich, - denotes east
3653 # of Greenwich; this is contrary to the ISO convention
3654 # but follows Posix.
3655 # stdHours - Hours part of the offset from Greenwich to the given
3656 # (non-DST) time zone.
3657 # stdMinutes - Minutes part of the offset from Greenwich to the
3658 # given (non-DST) time zone. Empty denotes zero.
3659 # stdSeconds - Seconds part of the offset from Greenwich to the
3660 # given (non-DST) time zone. Empty denotes zero.
3661 # dstName - Name of the time zone when DST is in effect, or the
3662 # empty string if the time zone does not observe Daylight
3664 # dstSignum, dstHours, dstMinutes, dstSeconds -
3665 # Fields corresponding to stdSignum, stdHours, stdMinutes,
3666 # stdSeconds for the Daylight Saving Time version of the
3667 # time zone. If dstHours is empty, it is presumed to be 1.
3668 # startDayOfYear - The ordinal number of the day of the year on which
3669 # Daylight Saving Time begins. If this field is
3670 # empty, then DST begins on a given month-week-day,
3672 # startJ - The letter J, or an empty string. If a J is present in
3673 # this field, then startDayOfYear does not count February 29
3674 # even in leap years.
3675 # startMonth - The number of the month in which Daylight Saving Time
3676 # begins, supplied if startDayOfYear is empty. If both
3677 # startDayOfYear and startMonth are empty, then US rules
3679 # startWeekOfMonth - The number of the week in the month in which
3680 # Daylight Saving Time begins, in the range 1-5.
3681 # 5 denotes the last week of the month even in a
3683 # startDayOfWeek - The number of the day of the week (Sunday=0,
3684 # Saturday=6) on which Daylight Saving Time begins.
3685 # startHours - The hours part of the time of day at which Daylight
3686 # Saving Time begins. An empty string is presumed to be 2.
3687 # startMinutes - The minutes part of the time of day at which DST begins.
3688 # An empty string is presumed zero.
3689 # startSeconds - The seconds part of the time of day at which DST begins.
3690 # An empty string is presumed zero.
3691 # endDayOfYear, endJ, endMonth, endWeekOfMonth, endDayOfWeek,
3692 # endHours, endMinutes, endSeconds -
3693 # Specify the end of DST in the same way that the start* fields
3694 # specify the beginning of DST.
3696 # This procedure serves only to break the time specifier into fields.
3697 # No attempt is made to canonicalize the fields or supply default values.
3699 #----------------------------------------------------------------------
3701 proc ::tcl::clock::ParsePosixTimeZone { tz
} {
3703 if {[regexp -expanded -nocase -- {
3705 # 1 - Standard time zone name
3706 ([[:alpha
:]]+ |
<[-+[:alnum
:]]+>)
3707 # 2 - Standard time zone offset, signum
3709 # 3 - Standard time zone offset, hours
3712 # 4 - Standard time zone offset, minutes
3713 : ([[:digit
:]]{1,2})
3715 # 5 - Standard time zone offset, seconds
3716 : ([[:digit
:]]{1,2} )
3720 # 6 - DST time zone name
3721 ([[:alpha
:]]+ |
<[-+[:alnum
:]]+>)
3724 # 7 - DST time zone offset, signum
3726 # 8 - DST time zone offset, hours
3729 # 9 - DST time zone offset, minutes
3730 : ([[:digit
:]]{1,2})
3732 # 10 - DST time zone offset, seconds
3733 : ([[:digit
:]]{1,2})
3740 # 11 - Optional J in n and Jn form 12 - Day of year
3741 ( J ?
) ( [[:digit
:]]+ )
3743 # 13 - Month number 14 - Week of month 15 - Day of week
3745 [.
] ( [[:digit
:]] + )
3746 [.
] ( [[:digit
:]] + )
3749 # 16 - Start time of DST - hours
3750 / ( [[:digit
:]]{1,2} )
3752 # 17 - Start time of DST - minutes
3753 : ( [[:digit
:]]{1,2} )
3755 # 18 - Start time of DST - seconds
3756 : ( [[:digit
:]]{1,2} )
3762 # 19 - Optional J in n and Jn form 20 - Day of year
3763 ( J ?
) ( [[:digit
:]]+ )
3765 # 21 - Month number 22 - Week of month 23 - Day of week
3767 [.
] ( [[:digit
:]] + )
3768 [.
] ( [[:digit
:]] + )
3771 # 24 - End time of DST - hours
3772 / ( [[:digit
:]]{1,2} )
3774 # 25 - End time of DST - minutes
3775 : ( [[:digit
:]]{1,2} )
3777 # 26 - End time of DST - seconds
3778 : ( [[:digit
:]]{1,2} )
3786 } $tz -> x
(stdName
) x
(stdSignum
) x
(stdHours
) x
(stdMinutes
) x
(stdSeconds
) \
3787 x
(dstName
) x
(dstSignum
) x
(dstHours
) x
(dstMinutes
) x
(dstSeconds
) \
3788 x
(startJ
) x
(startDayOfYear
) \
3789 x
(startMonth
) x
(startWeekOfMonth
) x
(startDayOfWeek
) \
3790 x
(startHours
) x
(startMinutes
) x
(startSeconds
) \
3791 x
(endJ
) x
(endDayOfYear
) \
3792 x
(endMonth
) x
(endWeekOfMonth
) x
(endDayOfWeek
) \
3793 x
(endHours
) x
(endMinutes
) x
(endSeconds
)] } {
3795 # it's a good timezone
3797 return [array get x
]
3802 -errorcode [list CLOCK badTimeZone
$tz] \
3803 "unable to parse time zone specification \"$tz\""
3809 #----------------------------------------------------------------------
3811 # ProcessPosixTimeZone --
3813 # Handle a Posix time zone after it's been broken out into
3817 # z - Dictionary returned from 'ParsePosixTimeZone'
3820 # Returns time zone information for the 'TZData' array.
3825 #----------------------------------------------------------------------
3827 proc ::tcl::clock::ProcessPosixTimeZone { z
} {
3832 # Determine the standard time zone name and seconds east of Greenwich
3834 set stdName
[dict get
$z stdName
]
3835 if { [string index
$stdName 0] eq
{<} } {
3836 set stdName
[string range
$stdName 1 end-1
]
3838 if { [dict get
$z stdSignum
] eq
{-} } {
3843 set stdHours
[lindex [::scan [dict get
$z stdHours
] %d
] 0]
3844 if { [dict get
$z stdMinutes
] ne
{} } {
3845 set stdMinutes
[lindex [::scan [dict get
$z stdMinutes
] %d
] 0]
3849 if { [dict get
$z stdSeconds
] ne
{} } {
3850 set stdSeconds
[lindex [::scan [dict get
$z stdSeconds
] %d
] 0]
3854 set stdOffset
[expr { ( ( $stdHours * 60 + $stdMinutes )
3855 * 60 + $stdSeconds )
3857 set data
[list [list $MINWIDE $stdOffset 0 $stdName]]
3859 # If there's no daylight zone, we're done
3861 set dstName
[dict get
$z dstName
]
3862 if { $dstName eq
{} } {
3865 if { [string index
$dstName 0] eq
{<} } {
3866 set dstName
[string range
$dstName 1 end-1
]
3869 # Determine the daylight name
3871 if { [dict get
$z dstSignum
] eq
{-} } {
3876 if { [dict get
$z dstHours
] eq
{} } {
3877 set dstOffset
[expr { 3600 + $stdOffset }]
3879 set dstHours
[lindex [::scan [dict get
$z dstHours
] %d
] 0]
3880 if { [dict get
$z dstMinutes
] ne
{} } {
3881 set dstMinutes
[lindex [::scan [dict get
$z dstMinutes
] %d
] 0]
3885 if { [dict get
$z dstSeconds
] ne
{} } {
3886 set dstSeconds
[lindex [::scan [dict get
$z dstSeconds
] %d
] 0]
3890 set dstOffset
[expr { ( ( $dstHours * 60 + $dstMinutes )
3891 * 60 + $dstSeconds )
3895 # Fill in defaults for European or US DST rules
3896 # US start time is the second Sunday in March
3897 # EU start time is the last Sunday in March
3898 # US end time is the first Sunday in November.
3899 # EU end time is the last Sunday in October
3901 if { [dict get
$z startDayOfYear
] eq
{}
3902 && [dict get
$z startMonth
] eq
{} } {
3903 if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} {
3905 dict
set z startWeekOfMonth
5
3907 dict
set z startHours
2
3909 dict
set z startHours
[expr {$stdHours+1}]
3913 dict
set z startWeekOfMonth
2
3914 dict
set z startHours
2
3916 dict
set z startMonth
3
3917 dict
set z startDayOfWeek
0
3918 dict
set z startMinutes
0
3919 dict
set z startSeconds
0
3921 if { [dict get
$z endDayOfYear
] eq
{}
3922 && [dict get
$z endMonth
] eq
{} } {
3923 if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} {
3925 dict
set z endMonth
10
3926 dict
set z endWeekOfMonth
5
3928 dict
set z endHours
3
3930 dict
set z endHours
[expr {$stdHours+2}]
3934 dict
set z endMonth
11
3935 dict
set z endWeekOfMonth
1
3936 dict
set z endHours
2
3938 dict
set z endDayOfWeek
0
3939 dict
set z endMinutes
0
3940 dict
set z endSeconds
0
3943 # Put DST in effect in all years from 1916 to 2099.
3945 for { set y
1916 } { $y < 2099 } { incr y
} {
3946 set startTime
[DeterminePosixDSTTime
$z start
$y]
3947 incr startTime
[expr { - wide
($stdOffset) }]
3948 set endTime
[DeterminePosixDSTTime
$z end
$y]
3949 incr endTime
[expr { - wide
($dstOffset) }]
3950 if { $startTime < $endTime } {
3952 [list $startTime $dstOffset 1 $dstName] \
3953 [list $endTime $stdOffset 0 $stdName]
3956 [list $endTime $stdOffset 0 $stdName] \
3957 [list $startTime $dstOffset 1 $dstName]
3965 #----------------------------------------------------------------------
3967 # DeterminePosixDSTTime --
3969 # Determines the time that Daylight Saving Time starts or ends
3970 # from a Posix time zone specification.
3973 # z - Time zone data returned from ParsePosixTimeZone.
3974 # Missing fields are expected to be filled in with
3976 # bound - The word 'start' or 'end'
3977 # y - The year for which the transition time is to be determined.
3980 # Returns the transition time as a count of seconds from
3981 # the epoch. The time is relative to the wall clock, not UTC.
3983 #----------------------------------------------------------------------
3985 proc ::tcl::clock::DeterminePosixDSTTime { z bound y
} {
3989 # Determine the start or end day of DST
3991 set date
[dict create era CE year
$y]
3992 set doy
[dict get
$z ${bound
}DayOfYear
]
3995 # Time was specified as a day of the year
3997 if { [dict get
$z ${bound
}J
] ne
{}
3998 && [IsGregorianLeapYear
$y]
3999 && ( $doy > $FEB_28 ) } {
4002 dict
set date dayOfYear
$doy
4003 set date
[GetJulianDayFromEraYearDay
$date[set date
{}] 2361222]
4006 # Time was specified as a day of the week within a month
4008 dict
set date month
[dict get
$z ${bound
}Month
]
4009 dict
set date dayOfWeek
[dict get
$z ${bound
}DayOfWeek
]
4010 set dowim
[dict get
$z ${bound
}WeekOfMonth
]
4011 if { $dowim >= 5 } {
4014 dict
set date dayOfWeekInMonth
$dowim
4015 set date
[GetJulianDayFromEraYearMonthWeekDay
$date[set date
{}] 2361222]
4019 set jd
[dict get
$date julianDay
]
4020 set seconds
[expr { wide
($jd) * wide
(86400)
4021 - wide
(210866803200) }]
4023 set h
[dict get
$z ${bound
}Hours
]
4027 set h
[lindex [::scan $h %d
] 0]
4029 set m
[dict get
$z ${bound
}Minutes
]
4033 set m
[lindex [::scan $m %d
] 0]
4035 set s
[dict get
$z ${bound
}Seconds
]
4039 set s
[lindex [::scan $s %d
] 0]
4041 set tod
[expr { ( $h * 60 + $m ) * 60 + $s }]
4042 return [expr { $seconds + $tod }]
4046 #----------------------------------------------------------------------
4050 # Given local time expressed in seconds from the Posix epoch,
4051 # determine localized era and year within the era.
4054 # date - Dictionary that must contain the keys, 'localSeconds',
4055 # whose value is expressed as the appropriate local time;
4056 # and 'year', whose value is the Gregorian year.
4057 # etable - Value of the LOCALE_ERAS key in the message catalogue
4058 # for the target locale.
4061 # Returns the dictionary, augmented with the keys, 'localeEra'
4064 #----------------------------------------------------------------------
4066 proc ::tcl::clock::GetLocaleEra { date etable
} {
4068 set index
[BSearch
$etable [dict get
$date localSeconds
]]
4070 dict
set date localeEra
\
4071 [::format %02d
[expr { [dict get
$date year
] / 100 }]]
4072 dict
set date localeYear
\
4073 [expr { [dict get
$date year
] % 100 }]
4075 dict
set date localeEra
[lindex $etable $index 1]
4076 dict
set date localeYear
[expr { [dict get
$date year
]
4077 - [lindex $etable $index 2] }]
4083 #----------------------------------------------------------------------
4085 # GetJulianDayFromEraYearDay --
4087 # Given a year, month and day on the Gregorian calendar, determines
4088 # the Julian Day Number beginning at noon on that date.
4091 # date -- A dictionary in which the 'era', 'year', and
4092 # 'dayOfYear' slots are populated. The calendar in use
4093 # is determined by the date itself relative to:
4094 # changeover -- Julian day on which the Gregorian calendar was
4095 # adopted in the current locale.
4098 # Returns the given dictionary augmented with a 'julianDay' key
4099 # whose value is the desired Julian Day Number, and a 'gregorian'
4100 # key that specifies whether the calendar is Gregorian (1) or
4107 # This code needs to be moved to the C layer.
4109 #----------------------------------------------------------------------
4111 proc ::tcl::clock::GetJulianDayFromEraYearDay {date changeover
} {
4113 # Get absolute year number from the civil year
4115 switch -exact -- [dict get
$date era
] {
4117 set year
[expr { 1 - [dict get
$date year
] }]
4120 set year
[dict get
$date year
]
4123 set ym1
[expr { $year - 1 }]
4125 # Try the Gregorian calendar first.
4127 dict
set date gregorian
1
4128 set jd
[expr { 1721425
4129 + [dict get
$date dayOfYear
]
4135 # If the date is before the Gregorian change, use the Julian calendar.
4137 if { $jd < $changeover } {
4138 dict
set date gregorian
0
4139 set jd
[expr { 1721423
4140 + [dict get
$date dayOfYear
]
4145 dict
set date julianDay
$jd
4149 #----------------------------------------------------------------------
4151 # GetJulianDayFromEraYearMonthWeekDay --
4153 # Determines the Julian Day number corresponding to the nth
4154 # given day-of-the-week in a given month.
4157 # date - Dictionary containing the keys, 'era', 'year', 'month'
4158 # 'weekOfMonth', 'dayOfWeek', and 'dayOfWeekInMonth'.
4159 # changeover - Julian Day of adoption of the Gregorian calendar
4162 # Returns the given dictionary, augmented with a 'julianDay' key.
4168 # This code needs to be moved to the C layer.
4170 #----------------------------------------------------------------------
4172 proc ::tcl::clock::GetJulianDayFromEraYearMonthWeekDay {date changeover
} {
4174 # Come up with a reference day; either the zeroeth day of the
4175 # given month (dayOfWeekInMonth >= 0) or the seventh day of the
4176 # following month (dayOfWeekInMonth < 0)
4179 set week
[dict get
$date dayOfWeekInMonth
]
4181 dict
set date2 dayOfMonth
0
4183 dict
incr date2 month
4184 dict
set date2 dayOfMonth
7
4186 set date2
[GetJulianDayFromEraYearMonthDay
$date2[set date2
{}] \
4188 set wd0
[WeekdayOnOrBefore
[dict get
$date dayOfWeek
] \
4189 [dict get
$date2 julianDay
]]
4190 dict
set date julianDay
[expr { $wd0 + 7 * $week }]
4195 #----------------------------------------------------------------------
4197 # IsGregorianLeapYear --
4199 # Determines whether a given date represents a leap year in the
4200 # Gregorian calendar.
4203 # date -- The date to test. The fields, 'era', 'year' and 'gregorian'
4207 # Returns 1 if the year is a leap year, 0 otherwise.
4212 #----------------------------------------------------------------------
4214 proc ::tcl::clock::IsGregorianLeapYear { date
} {
4216 switch -exact -- [dict get
$date era
] {
4218 set year
[expr { 1 - [dict get
$date year
]}]
4221 set year
[dict get
$date year
]
4224 if { $year % 4 != 0 } {
4226 } elseif
{ ![dict get
$date gregorian
] } {
4228 } elseif
{ $year % 400 == 0 } {
4230 } elseif
{ $year % 100 == 0 } {
4238 #----------------------------------------------------------------------
4240 # WeekdayOnOrBefore --
4242 # Determine the nearest day of week (given by the 'weekday'
4243 # parameter, Sunday==0) on or before a given Julian Day.
4246 # weekday -- Day of the week
4247 # j -- Julian Day number
4250 # Returns the Julian Day Number of the desired date.
4255 #----------------------------------------------------------------------
4257 proc ::tcl::clock::WeekdayOnOrBefore { weekday j
} {
4259 set k
[expr { ( $weekday + 6 ) % 7 }]
4260 return [expr { $j - ( $j - $k ) % 7 }]
4264 #----------------------------------------------------------------------
4268 # Service procedure that does binary search in several places
4269 # inside the 'clock' command.
4272 # list - List of lists, sorted in ascending order by the
4274 # key - Value to search for
4277 # Returns the index of the greatest element in $list that is less
4278 # than or equal to $key.
4283 #----------------------------------------------------------------------
4285 proc ::tcl::clock::BSearch { list key
} {
4287 if {[llength $list] == 0} {
4290 if { $key < [lindex $list 0 0] } {
4295 set u
[expr { [llength $list] - 1 }]
4299 # At this point, we know that
4300 # $k >= [lindex $list $l 0]
4301 # Either $u == [llength $list] or else $k < [lindex $list $u+1 0]
4302 # We find the midpoint of the interval {l,u} rounded UP, compare
4303 # against it, and set l or u to maintain the invariant. Note
4304 # that the interval shrinks at each step, guaranteeing convergence.
4306 set m
[expr { ( $l + $u + 1 ) / 2 }]
4307 if { $key >= [lindex $list $m 0] } {
4310 set u
[expr { $m - 1 }]
4317 #----------------------------------------------------------------------
4321 # Adds an offset to a given time.
4324 # clock add clockval ?count unit?... ?-option value?
4327 # clockval -- Starting time value
4328 # count -- Amount of a unit of time to add
4329 # unit -- Unit of time to add, must be one of:
4330 # years year months month weeks week
4331 # days day hours hour minutes minute
4336 # (Deprecated) Flag synonymous with '-timezone :GMT'
4338 # Name of the time zone in which calculations are to be done.
4340 # Name of the locale in which calculations are to be done.
4341 # Used to determine the Gregorian change date.
4344 # Returns the given time adjusted by the given offset(s) in
4348 # It is possible that adding a number of months or years will adjust
4349 # the day of the month as well. For instance, the time at
4350 # one month after 31 January is either 28 or 29 February, because
4351 # February has fewer than 31 days.
4353 #----------------------------------------------------------------------
4355 proc ::tcl::clock::add { clockval args
} {
4357 if { [llength $args] % 2 != 0 } {
4358 set cmdName
"clock add"
4359 return -code error \
4360 -errorcode [list CLOCK wrongNumArgs
] \
4361 "wrong \# args: should be\
4362 \"$cmdName clockval ?number units?...\
4363 ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?\""
4365 if { [catch { expr {wide
($clockval)} } result
] } {
4366 return -code error $result
4372 set timezone
[GetSystemTimeZone
]
4374 foreach { a b
} $args {
4376 if { [string is integer
-strict $a] } {
4378 lappend offsets
$a $b
4382 switch -exact -- $a {
4387 -l - -lo - -loc - -loca - -local - -locale {
4388 set locale
[string tolower
$b]
4390 -t - -ti - -tim - -time - -timez - -timezo - -timezon -
4395 return -code error \
4396 -errorcode [list CLOCK badSwitch
$a] \
4397 "bad switch \"$a\",\
4398 must be -gmt, -locale or -timezone"
4404 # Check options for validity
4406 if { [info exists saw
(-gmt)] && [info exists saw
(-timezone)] } {
4407 return -code error \
4408 -errorcode [list CLOCK gmtWithTimezone
] \
4409 "cannot use -gmt and -timezone in same call"
4411 if { [catch { expr { wide
($clockval) } } result
] } {
4412 return -code error \
4413 "expected integer but got \"$clockval\""
4415 if { ![string is boolean
$gmt] } {
4416 return -code error \
4417 "expected boolean value but got \"$gmt\""
4424 EnterLocale
$locale oldLocale
4426 set changeover
[mc GREGORIAN_CHANGE_DATE
]
4428 if {[catch {SetupTimeZone
$timezone} retval opts
]} {
4429 dict
unset opts
-errorinfo
4430 return -options $opts $retval
4435 foreach { quantity unit
} $offsets {
4437 switch -exact -- $unit {
4441 [AddMonths
[expr { 12 * $quantity }] \
4442 $clockval $timezone $changeover]
4445 set clockval
[AddMonths
$quantity $clockval $timezone \
4450 set clockval
[AddDays
[expr { 7 * $quantity }] \
4451 $clockval $timezone $changeover]
4454 set clockval
[AddDays
$quantity $clockval $timezone \
4459 set clockval
[expr { 3600 * $quantity + $clockval }]
4462 set clockval
[expr { 60 * $quantity + $clockval }]
4465 set clockval
[expr { $quantity + $clockval }]
4469 error "unknown unit \"$unit\", must be \
4470 years, months, weeks, days, hours, minutes or seconds" \
4471 "unknown unit \"$unit\", must be \
4472 years, months, weeks, days, hours, minutes or seconds" \
4473 [list CLOCK badUnit
$unit]
4479 # Restore the locale
4481 if { [info exists oldLocale
] } {
4485 if { $status == 1 } {
4486 if { [lindex [dict get
$opts -errorcode] 0] eq
{CLOCK
} } {
4487 dict
unset opts
-errorinfo
4489 return -options $opts $result
4496 #----------------------------------------------------------------------
4500 # Add a given number of months to a given clock value in a given
4504 # months - Number of months to add (may be negative)
4505 # clockval - Seconds since the epoch before the operation
4506 # timezone - Time zone in which the operation is to be performed
4509 # Returns the new clock value as a number of seconds since
4515 #----------------------------------------------------------------------
4517 proc ::tcl::clock::AddMonths { months clockval timezone changeover
} {
4519 variable DaysInRomanMonthInCommonYear
4520 variable DaysInRomanMonthInLeapYear
4523 # Convert the time to year, month, day, and fraction of day.
4525 set date
[GetDateFields
$clockval $TZData($timezone) $changeover]
4526 dict
set date secondOfDay
[expr { [dict get
$date localSeconds
]
4528 dict
set date tzName
$timezone
4530 # Add the requisite number of months
4532 set m
[dict get
$date month
]
4535 set delta
[expr { $m / 12 }]
4536 set mm
[expr { $m % 12 }]
4537 dict
set date month
[expr { $mm + 1 }]
4538 dict
incr date year
$delta
4540 # If the date doesn't exist in the current month, repair it
4542 if { [IsGregorianLeapYear
$date] } {
4543 set hath
[lindex $DaysInRomanMonthInLeapYear $mm]
4545 set hath
[lindex $DaysInRomanMonthInCommonYear $mm]
4547 if { [dict get
$date dayOfMonth
] > $hath } {
4548 dict
set date dayOfMonth
$hath
4551 # Reconvert to a number of seconds
4553 set date
[GetJulianDayFromEraYearMonthDay
\
4556 dict
set date localSeconds
\
4557 [expr { -210866803200
4558 + ( 86400 * wide
([dict get
$date julianDay
]) )
4559 + [dict get
$date secondOfDay
] }]
4560 set date
[ConvertLocalToUTC
$date[set date
{}] $TZData($timezone) \
4563 return [dict get
$date seconds
]
4567 #----------------------------------------------------------------------
4571 # Add a given number of days to a given clock value in a given
4575 # days - Number of days to add (may be negative)
4576 # clockval - Seconds since the epoch before the operation
4577 # timezone - Time zone in which the operation is to be performed
4578 # changeover - Julian Day on which the Gregorian calendar was adopted
4579 # in the target locale.
4582 # Returns the new clock value as a number of seconds since
4588 #----------------------------------------------------------------------
4590 proc ::tcl::clock::AddDays { days clockval timezone changeover
} {
4594 # Convert the time to Julian Day
4596 set date
[GetDateFields
$clockval $TZData($timezone) $changeover]
4597 dict
set date secondOfDay
[expr { [dict get
$date localSeconds
]
4599 dict
set date tzName
$timezone
4601 # Add the requisite number of days
4603 dict
incr date julianDay
$days
4605 # Reconvert to a number of seconds
4607 dict
set date localSeconds
\
4608 [expr { -210866803200
4609 + ( 86400 * wide
([dict get
$date julianDay
]) )
4610 + [dict get
$date secondOfDay
] }]
4611 set date
[ConvertLocalToUTC
$date[set date
{}] $TZData($timezone) \
4614 return [dict get
$date seconds
]
4618 #----------------------------------------------------------------------
4622 # Wrapper around ::msgcat::mc that caches the result according
4626 # Accepts the name of the message to retrieve.
4629 # Returns the message text.
4632 # Caches the message text.
4635 # Only the single-argument version of [mc] is supported.
4637 #----------------------------------------------------------------------
4639 proc ::tcl::clock::mc { name
} {
4641 set Locale
[mclocale
]
4642 if { [dict exists
$McLoaded $Locale $name] } {
4643 return [dict get
$McLoaded $Locale $name]
4645 set val
[::msgcat::mc $name]
4646 dict
set McLoaded
$Locale $name $val
4651 #----------------------------------------------------------------------
4655 # Clears all caches to reclaim the memory used in [clock]
4664 # Caches are cleared.
4666 #----------------------------------------------------------------------
4668 proc ::tcl::clock::ClearCaches {} {
4671 variable LocaleNumeralCache
4673 variable CachedSystemTimeZone
4674 variable TimeZoneBad
4676 foreach p
[info procs
[namespace current
]::scanproc'
*] {
4679 foreach p
[info procs
[namespace current
]::formatproc'
*] {
4683 catch {unset FormatProc
}
4684 set LocaleNumeralCache
{}
4686 catch {unset CachedSystemTimeZone
}