Spaces:
Running
Running
| #---------------------------------------------------------------------- | |
| # | |
| # clock.tcl -- | |
| # | |
| # This file implements the portions of the [clock] ensemble that are | |
| # coded in Tcl. Refer to the users' manual to see the description of | |
| # the [clock] command and its subcommands. | |
| # | |
| # | |
| #---------------------------------------------------------------------- | |
| # | |
| # Copyright (c) 2004-2007 Kevin B. Kenny | |
| # See the file "license.terms" for information on usage and redistribution | |
| # of this file, and for a DISCLAIMER OF ALL WARRANTIES. | |
| # | |
| #---------------------------------------------------------------------- | |
| # We must have message catalogs that support the root locale, and we need | |
| # access to the Registry on Windows systems. | |
| uplevel \#0 { | |
| package require msgcat 1.6 | |
| if { $::tcl_platform(platform) eq {windows} } { | |
| if { [catch { package require registry 1.1 }] } { | |
| namespace eval ::tcl::clock [list variable NoRegistry {}] | |
| } | |
| } | |
| } | |
| # Put the library directory into the namespace for the ensemble so that the | |
| # library code can find message catalogs and time zone definition files. | |
| namespace eval ::tcl::clock \ | |
| [list variable LibDir [file dirname [info script]]] | |
| #---------------------------------------------------------------------- | |
| # | |
| # clock -- | |
| # | |
| # Manipulate times. | |
| # | |
| # The 'clock' command manipulates time. Refer to the user documentation for | |
| # the available subcommands and what they do. | |
| # | |
| #---------------------------------------------------------------------- | |
| namespace eval ::tcl::clock { | |
| # Export the subcommands | |
| namespace export format | |
| namespace export clicks | |
| namespace export microseconds | |
| namespace export milliseconds | |
| namespace export scan | |
| namespace export seconds | |
| namespace export add | |
| # Import the message catalog commands that we use. | |
| namespace import ::msgcat::mcload | |
| namespace import ::msgcat::mclocale | |
| namespace import ::msgcat::mc | |
| namespace import ::msgcat::mcpackagelocale | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ::tcl::clock::Initialize -- | |
| # | |
| # Finish initializing the 'clock' subsystem | |
| # | |
| # Results: | |
| # None. | |
| # | |
| # Side effects: | |
| # Namespace variable in the 'clock' subsystem are initialized. | |
| # | |
| # The '::tcl::clock::Initialize' procedure initializes the namespace variables | |
| # and root locale message catalog for the 'clock' subsystem. It is broken | |
| # into a procedure rather than simply evaluated as a script so that it will be | |
| # able to use local variables, avoiding the dangers of 'creative writing' as | |
| # in Bug 1185933. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::Initialize {} { | |
| rename ::tcl::clock::Initialize {} | |
| variable LibDir | |
| # Define the Greenwich time zone | |
| proc InitTZData {} { | |
| variable TZData | |
| array unset TZData | |
| set TZData(:Etc/GMT) { | |
| {-9223372036854775808 0 0 GMT} | |
| } | |
| set TZData(:GMT) $TZData(:Etc/GMT) | |
| set TZData(:Etc/UTC) { | |
| {-9223372036854775808 0 0 UTC} | |
| } | |
| set TZData(:UTC) $TZData(:Etc/UTC) | |
| set TZData(:localtime) {} | |
| } | |
| InitTZData | |
| mcpackagelocale set {} | |
| ::msgcat::mcpackageconfig set mcfolder [file join $LibDir msgs] | |
| ::msgcat::mcpackageconfig set unknowncmd "" | |
| ::msgcat::mcpackageconfig set changecmd ChangeCurrentLocale | |
| # Define the message catalog for the root locale. | |
| ::msgcat::mcmset {} { | |
| AM {am} | |
| BCE {B.C.E.} | |
| CE {C.E.} | |
| DATE_FORMAT {%m/%d/%Y} | |
| DATE_TIME_FORMAT {%a %b %e %H:%M:%S %Y} | |
| DAYS_OF_WEEK_ABBREV { | |
| Sun Mon Tue Wed Thu Fri Sat | |
| } | |
| DAYS_OF_WEEK_FULL { | |
| Sunday Monday Tuesday Wednesday Thursday Friday Saturday | |
| } | |
| GREGORIAN_CHANGE_DATE 2299161 | |
| LOCALE_DATE_FORMAT {%m/%d/%Y} | |
| LOCALE_DATE_TIME_FORMAT {%a %b %e %H:%M:%S %Y} | |
| LOCALE_ERAS {} | |
| LOCALE_NUMERALS { | |
| 00 01 02 03 04 05 06 07 08 09 | |
| 10 11 12 13 14 15 16 17 18 19 | |
| 20 21 22 23 24 25 26 27 28 29 | |
| 30 31 32 33 34 35 36 37 38 39 | |
| 40 41 42 43 44 45 46 47 48 49 | |
| 50 51 52 53 54 55 56 57 58 59 | |
| 60 61 62 63 64 65 66 67 68 69 | |
| 70 71 72 73 74 75 76 77 78 79 | |
| 80 81 82 83 84 85 86 87 88 89 | |
| 90 91 92 93 94 95 96 97 98 99 | |
| } | |
| LOCALE_TIME_FORMAT {%H:%M:%S} | |
| LOCALE_YEAR_FORMAT {%EC%Ey} | |
| MONTHS_ABBREV { | |
| Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec | |
| } | |
| MONTHS_FULL { | |
| January February March | |
| April May June | |
| July August September | |
| October November December | |
| } | |
| PM {pm} | |
| TIME_FORMAT {%H:%M:%S} | |
| TIME_FORMAT_12 {%I:%M:%S %P} | |
| TIME_FORMAT_24 {%H:%M} | |
| TIME_FORMAT_24_SECS {%H:%M:%S} | |
| } | |
| # Define a few Gregorian change dates for other locales. In most cases | |
| # the change date follows a language, because a nation's colonies changed | |
| # at the same time as the nation itself. In many cases, different | |
| # national boundaries existed; the dominating rule is to follow the | |
| # nation's capital. | |
| # Italy, Spain, Portugal, Poland | |
| ::msgcat::mcset it GREGORIAN_CHANGE_DATE 2299161 | |
| ::msgcat::mcset es GREGORIAN_CHANGE_DATE 2299161 | |
| ::msgcat::mcset pt GREGORIAN_CHANGE_DATE 2299161 | |
| ::msgcat::mcset pl GREGORIAN_CHANGE_DATE 2299161 | |
| # France, Austria | |
| ::msgcat::mcset fr GREGORIAN_CHANGE_DATE 2299227 | |
| # For Belgium, we follow Southern Netherlands; Liege Diocese changed | |
| # several weeks later. | |
| ::msgcat::mcset fr_BE GREGORIAN_CHANGE_DATE 2299238 | |
| ::msgcat::mcset nl_BE GREGORIAN_CHANGE_DATE 2299238 | |
| # Austria | |
| ::msgcat::mcset de_AT GREGORIAN_CHANGE_DATE 2299527 | |
| # Hungary | |
| ::msgcat::mcset hu GREGORIAN_CHANGE_DATE 2301004 | |
| # Germany, Norway, Denmark (Catholic Germany changed earlier) | |
| ::msgcat::mcset de_DE GREGORIAN_CHANGE_DATE 2342032 | |
| ::msgcat::mcset nb GREGORIAN_CHANGE_DATE 2342032 | |
| ::msgcat::mcset nn GREGORIAN_CHANGE_DATE 2342032 | |
| ::msgcat::mcset no GREGORIAN_CHANGE_DATE 2342032 | |
| ::msgcat::mcset da GREGORIAN_CHANGE_DATE 2342032 | |
| # Holland (Brabant, Gelderland, Flanders, Friesland, etc. changed at | |
| # various times) | |
| ::msgcat::mcset nl GREGORIAN_CHANGE_DATE 2342165 | |
| # Protestant Switzerland (Catholic cantons changed earlier) | |
| ::msgcat::mcset fr_CH GREGORIAN_CHANGE_DATE 2361342 | |
| ::msgcat::mcset it_CH GREGORIAN_CHANGE_DATE 2361342 | |
| ::msgcat::mcset de_CH GREGORIAN_CHANGE_DATE 2361342 | |
| # English speaking countries | |
| ::msgcat::mcset en GREGORIAN_CHANGE_DATE 2361222 | |
| # Sweden (had several changes onto and off of the Gregorian calendar) | |
| ::msgcat::mcset sv GREGORIAN_CHANGE_DATE 2361390 | |
| # Russia | |
| ::msgcat::mcset ru GREGORIAN_CHANGE_DATE 2421639 | |
| # Romania (Transylvania changed earler - perhaps de_RO should show the | |
| # earlier date?) | |
| ::msgcat::mcset ro GREGORIAN_CHANGE_DATE 2422063 | |
| # Greece | |
| ::msgcat::mcset el GREGORIAN_CHANGE_DATE 2423480 | |
| #------------------------------------------------------------------ | |
| # | |
| # CONSTANTS | |
| # | |
| #------------------------------------------------------------------ | |
| # Paths at which binary time zone data for the Olson libraries are known | |
| # to reside on various operating systems | |
| variable ZoneinfoPaths {} | |
| foreach path { | |
| /usr/share/zoneinfo | |
| /usr/share/lib/zoneinfo | |
| /usr/lib/zoneinfo | |
| /usr/local/etc/zoneinfo | |
| } { | |
| if { [file isdirectory $path] } { | |
| lappend ZoneinfoPaths $path | |
| } | |
| } | |
| # Define the directories for time zone data and message catalogs. | |
| variable DataDir [file join $LibDir tzdata] | |
| # Number of days in the months, in common years and leap years. | |
| variable DaysInRomanMonthInCommonYear \ | |
| { 31 28 31 30 31 30 31 31 30 31 30 31 } | |
| variable DaysInRomanMonthInLeapYear \ | |
| { 31 29 31 30 31 30 31 31 30 31 30 31 } | |
| variable DaysInPriorMonthsInCommonYear [list 0] | |
| variable DaysInPriorMonthsInLeapYear [list 0] | |
| set i 0 | |
| foreach j $DaysInRomanMonthInCommonYear { | |
| lappend DaysInPriorMonthsInCommonYear [incr i $j] | |
| } | |
| set i 0 | |
| foreach j $DaysInRomanMonthInLeapYear { | |
| lappend DaysInPriorMonthsInLeapYear [incr i $j] | |
| } | |
| # Another epoch (Hi, Jeff!) | |
| variable Roddenberry 1946 | |
| # Integer ranges | |
| variable MINWIDE -9223372036854775808 | |
| variable MAXWIDE 9223372036854775807 | |
| # Day before Leap Day | |
| variable FEB_28 58 | |
| # Translation table to map Windows TZI onto cities, so that the Olson | |
| # rules can apply. In some cases the mapping is ambiguous, so it's wise | |
| # to specify $::env(TCL_TZ) rather than simply depending on the system | |
| # time zone. | |
| # The keys are long lists of values obtained from the time zone | |
| # information in the Registry. In order, the list elements are: | |
| # Bias StandardBias DaylightBias | |
| # StandardDate.wYear StandardDate.wMonth StandardDate.wDayOfWeek | |
| # StandardDate.wDay StandardDate.wHour StandardDate.wMinute | |
| # StandardDate.wSecond StandardDate.wMilliseconds | |
| # DaylightDate.wYear DaylightDate.wMonth DaylightDate.wDayOfWeek | |
| # DaylightDate.wDay DaylightDate.wHour DaylightDate.wMinute | |
| # DaylightDate.wSecond DaylightDate.wMilliseconds | |
| # The values are the names of time zones where those rules apply. There | |
| # is considerable ambiguity in certain zones; an attempt has been made to | |
| # make a reasonable guess, but this table needs to be taken with a grain | |
| # of salt. | |
| variable WinZoneInfo [dict create {*}{ | |
| {-43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Kwajalein | |
| {-39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Midway | |
| {-36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Honolulu | |
| {-32400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Anchorage | |
| {-28800 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Los_Angeles | |
| {-28800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Tijuana | |
| {-25200 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Denver | |
| {-25200 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Chihuahua | |
| {-25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Phoenix | |
| {-21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Regina | |
| {-21600 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Chicago | |
| {-21600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Mexico_City | |
| {-18000 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/New_York | |
| {-18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Indianapolis | |
| {-14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Caracas | |
| {-14400 0 3600 0 3 6 2 23 59 59 999 0 10 6 2 23 59 59 999} | |
| :America/Santiago | |
| {-14400 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Manaus | |
| {-14400 0 3600 0 11 0 1 2 0 0 0 0 3 0 2 2 0 0 0} :America/Halifax | |
| {-12600 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/St_Johns | |
| {-10800 0 3600 0 2 0 2 2 0 0 0 0 10 0 3 2 0 0 0} :America/Sao_Paulo | |
| {-10800 0 3600 0 10 0 5 2 0 0 0 0 4 0 1 2 0 0 0} :America/Godthab | |
| {-10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :America/Buenos_Aires | |
| {-10800 0 3600 0 2 0 5 2 0 0 0 0 11 0 1 2 0 0 0} :America/Bahia | |
| {-10800 0 3600 0 3 0 2 2 0 0 0 0 10 0 1 2 0 0 0} :America/Montevideo | |
| {-7200 0 3600 0 9 0 5 2 0 0 0 0 3 0 5 2 0 0 0} :America/Noronha | |
| {-3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Atlantic/Azores | |
| {-3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Atlantic/Cape_Verde | |
| {0 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :UTC | |
| {0 0 3600 0 10 0 5 2 0 0 0 0 3 0 5 1 0 0 0} :Europe/London | |
| {3600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Kinshasa | |
| {3600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :CET | |
| {7200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Africa/Harare | |
| {7200 0 3600 0 9 4 5 23 59 59 0 0 4 4 5 23 59 59 0} | |
| :Africa/Cairo | |
| {7200 0 3600 0 10 0 5 4 0 0 0 0 3 0 5 3 0 0 0} :Europe/Helsinki | |
| {7200 0 3600 0 9 0 3 2 0 0 0 0 3 5 5 2 0 0 0} :Asia/Jerusalem | |
| {7200 0 3600 0 9 0 5 1 0 0 0 0 3 0 5 0 0 0 0} :Europe/Bucharest | |
| {7200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Athens | |
| {7200 0 3600 0 9 5 5 1 0 0 0 0 3 4 5 0 0 0 0} :Asia/Amman | |
| {7200 0 3600 0 10 6 5 23 59 59 999 0 3 0 5 0 0 0 0} | |
| :Asia/Beirut | |
| {7200 0 -3600 0 4 0 1 2 0 0 0 0 9 0 1 2 0 0 0} :Africa/Windhoek | |
| {10800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Riyadh | |
| {10800 0 3600 0 10 0 1 4 0 0 0 0 4 0 1 3 0 0 0} :Asia/Baghdad | |
| {10800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Europe/Moscow | |
| {12600 0 3600 0 9 2 4 2 0 0 0 0 3 0 1 2 0 0 0} :Asia/Tehran | |
| {14400 0 3600 0 10 0 5 5 0 0 0 0 3 0 5 4 0 0 0} :Asia/Baku | |
| {14400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Muscat | |
| {14400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Tbilisi | |
| {16200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Kabul | |
| {18000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Karachi | |
| {18000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yekaterinburg | |
| {19800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Calcutta | |
| {20700 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Katmandu | |
| {21600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Dhaka | |
| {21600 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Novosibirsk | |
| {23400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Rangoon | |
| {25200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Bangkok | |
| {25200 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Krasnoyarsk | |
| {28800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Chongqing | |
| {28800 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Irkutsk | |
| {32400 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Asia/Tokyo | |
| {32400 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Yakutsk | |
| {34200 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia/Adelaide | |
| {34200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia/Darwin | |
| {36000 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Australia/Brisbane | |
| {36000 0 3600 0 10 0 5 3 0 0 0 0 3 0 5 2 0 0 0} :Asia/Vladivostok | |
| {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 1 2 0 0 0} :Australia/Hobart | |
| {36000 0 3600 0 3 0 5 3 0 0 0 0 10 0 5 2 0 0 0} :Australia/Sydney | |
| {39600 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Noumea | |
| {43200 0 3600 0 3 0 3 3 0 0 0 0 10 0 1 2 0 0 0} :Pacific/Auckland | |
| {43200 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Fiji | |
| {46800 0 3600 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0} :Pacific/Tongatapu | |
| }] | |
| # Groups of fields that specify the date, priorities, and code bursts that | |
| # determine Julian Day Number given those groups. The code in [clock | |
| # scan] will choose the highest priority (lowest numbered) set of fields | |
| # that determines the date. | |
| variable DateParseActions { | |
| { seconds } 0 {} | |
| { julianDay } 1 {} | |
| { era century yearOfCentury month dayOfMonth } 2 { | |
| dict set date year [expr { 100 * [dict get $date century] | |
| + [dict get $date yearOfCentury] }] | |
| set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { era century yearOfCentury dayOfYear } 2 { | |
| dict set date year [expr { 100 * [dict get $date century] | |
| + [dict get $date yearOfCentury] }] | |
| set date [GetJulianDayFromEraYearDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { century yearOfCentury month dayOfMonth } 3 { | |
| dict set date era CE | |
| dict set date year [expr { 100 * [dict get $date century] | |
| + [dict get $date yearOfCentury] }] | |
| set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { century yearOfCentury dayOfYear } 3 { | |
| dict set date era CE | |
| dict set date year [expr { 100 * [dict get $date century] | |
| + [dict get $date yearOfCentury] }] | |
| set date [GetJulianDayFromEraYearDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { iso8601Century iso8601YearOfCentury iso8601Week dayOfWeek } 3 { | |
| dict set date era CE | |
| dict set date iso8601Year \ | |
| [expr { 100 * [dict get $date iso8601Century] | |
| + [dict get $date iso8601YearOfCentury] }] | |
| set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { yearOfCentury month dayOfMonth } 4 { | |
| set date [InterpretTwoDigitYear $date[set date {}] $baseTime] | |
| dict set date era CE | |
| set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { yearOfCentury dayOfYear } 4 { | |
| set date [InterpretTwoDigitYear $date[set date {}] $baseTime] | |
| dict set date era CE | |
| set date [GetJulianDayFromEraYearDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { iso8601YearOfCentury iso8601Week dayOfWeek } 4 { | |
| set date [InterpretTwoDigitYear \ | |
| $date[set date {}] $baseTime \ | |
| iso8601YearOfCentury iso8601Year] | |
| dict set date era CE | |
| set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { month dayOfMonth } 5 { | |
| set date [AssignBaseYear $date[set date {}] \ | |
| $baseTime $timeZone $changeover] | |
| set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { dayOfYear } 5 { | |
| set date [AssignBaseYear $date[set date {}] \ | |
| $baseTime $timeZone $changeover] | |
| set date [GetJulianDayFromEraYearDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { iso8601Week dayOfWeek } 5 { | |
| set date [AssignBaseIso8601Year $date[set date {}] \ | |
| $baseTime $timeZone $changeover] | |
| set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { dayOfMonth } 6 { | |
| set date [AssignBaseMonth $date[set date {}] \ | |
| $baseTime $timeZone $changeover] | |
| set date [GetJulianDayFromEraYearMonthDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| { dayOfWeek } 7 { | |
| set date [AssignBaseWeek $date[set date {}] \ | |
| $baseTime $timeZone $changeover] | |
| set date [GetJulianDayFromEraYearWeekDay $date[set date {}] \ | |
| $changeover] | |
| } | |
| {} 8 { | |
| set date [AssignBaseJulianDay $date[set date {}] \ | |
| $baseTime $timeZone $changeover] | |
| } | |
| } | |
| # Groups of fields that specify time of day, priorities, and code that | |
| # processes them | |
| variable TimeParseActions { | |
| seconds 1 {} | |
| { hourAMPM minute second amPmIndicator } 2 { | |
| dict set date secondOfDay [InterpretHMSP $date] | |
| } | |
| { hour minute second } 2 { | |
| dict set date secondOfDay [InterpretHMS $date] | |
| } | |
| { hourAMPM minute amPmIndicator } 3 { | |
| dict set date second 0 | |
| dict set date secondOfDay [InterpretHMSP $date] | |
| } | |
| { hour minute } 3 { | |
| dict set date second 0 | |
| dict set date secondOfDay [InterpretHMS $date] | |
| } | |
| { hourAMPM amPmIndicator } 4 { | |
| dict set date minute 0 | |
| dict set date second 0 | |
| dict set date secondOfDay [InterpretHMSP $date] | |
| } | |
| { hour } 4 { | |
| dict set date minute 0 | |
| dict set date second 0 | |
| dict set date secondOfDay [InterpretHMS $date] | |
| } | |
| { } 5 { | |
| dict set date secondOfDay 0 | |
| } | |
| } | |
| # Legacy time zones, used primarily for parsing RFC822 dates. | |
| variable LegacyTimeZone [dict create \ | |
| gmt +0000 \ | |
| ut +0000 \ | |
| utc +0000 \ | |
| bst +0100 \ | |
| wet +0000 \ | |
| wat -0100 \ | |
| at -0200 \ | |
| nft -0330 \ | |
| nst -0330 \ | |
| ndt -0230 \ | |
| ast -0400 \ | |
| adt -0300 \ | |
| est -0500 \ | |
| edt -0400 \ | |
| cst -0600 \ | |
| cdt -0500 \ | |
| mst -0700 \ | |
| mdt -0600 \ | |
| pst -0800 \ | |
| pdt -0700 \ | |
| yst -0900 \ | |
| ydt -0800 \ | |
| hst -1000 \ | |
| hdt -0900 \ | |
| cat -1000 \ | |
| ahst -1000 \ | |
| nt -1100 \ | |
| idlw -1200 \ | |
| cet +0100 \ | |
| cest +0200 \ | |
| met +0100 \ | |
| mewt +0100 \ | |
| mest +0200 \ | |
| swt +0100 \ | |
| sst +0200 \ | |
| fwt +0100 \ | |
| fst +0200 \ | |
| eet +0200 \ | |
| eest +0300 \ | |
| bt +0300 \ | |
| it +0330 \ | |
| zp4 +0400 \ | |
| zp5 +0500 \ | |
| ist +0530 \ | |
| zp6 +0600 \ | |
| wast +0700 \ | |
| wadt +0800 \ | |
| jt +0730 \ | |
| cct +0800 \ | |
| jst +0900 \ | |
| kst +0900 \ | |
| cast +0930 \ | |
| jdt +1000 \ | |
| kdt +1000 \ | |
| cadt +1030 \ | |
| east +1000 \ | |
| eadt +1030 \ | |
| gst +1000 \ | |
| nzt +1200 \ | |
| nzst +1200 \ | |
| nzdt +1300 \ | |
| idle +1200 \ | |
| a +0100 \ | |
| b +0200 \ | |
| c +0300 \ | |
| d +0400 \ | |
| e +0500 \ | |
| f +0600 \ | |
| g +0700 \ | |
| h +0800 \ | |
| i +0900 \ | |
| k +1000 \ | |
| l +1100 \ | |
| m +1200 \ | |
| n -0100 \ | |
| o -0200 \ | |
| p -0300 \ | |
| q -0400 \ | |
| r -0500 \ | |
| s -0600 \ | |
| t -0700 \ | |
| u -0800 \ | |
| v -0900 \ | |
| w -1000 \ | |
| x -1100 \ | |
| y -1200 \ | |
| z +0000 \ | |
| ] | |
| # Caches | |
| variable LocaleNumeralCache {}; # Dictionary whose keys are locale | |
| # names and whose values are pairs | |
| # comprising regexes matching numerals | |
| # in the given locales and dictionaries | |
| # mapping the numerals to their numeric | |
| # values. | |
| # variable CachedSystemTimeZone; # If 'CachedSystemTimeZone' exists, | |
| # it contains the value of the | |
| # system time zone, as determined from | |
| # the environment. | |
| variable TimeZoneBad {}; # Dictionary whose keys are time zone | |
| # names and whose values are 1 if | |
| # the time zone is unknown and 0 | |
| # if it is known. | |
| variable TZData; # Array whose keys are time zone names | |
| # and whose values are lists of quads | |
| # comprising start time, UTC offset, | |
| # Daylight Saving Time indicator, and | |
| # time zone abbreviation. | |
| variable FormatProc; # Array mapping format group | |
| # and locale to the name of a procedure | |
| # that renders the given format | |
| } | |
| ::tcl::clock::Initialize | |
| #---------------------------------------------------------------------- | |
| # | |
| # clock format -- | |
| # | |
| # Formats a count of seconds since the Posix Epoch as a time of day. | |
| # | |
| # The 'clock format' command formats times of day for output. Refer to the | |
| # user documentation to see what it does. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::format { args } { | |
| variable FormatProc | |
| variable TZData | |
| lassign [ParseFormatArgs {*}$args] format locale timezone | |
| set locale [string tolower $locale] | |
| set clockval [lindex $args 0] | |
| # Get the data for time changes in the given zone | |
| if {$timezone eq ""} { | |
| set timezone [GetSystemTimeZone] | |
| } | |
| if {![info exists TZData($timezone)]} { | |
| if {[catch {SetupTimeZone $timezone} retval opts]} { | |
| dict unset opts -errorinfo | |
| return -options $opts $retval | |
| } | |
| } | |
| # Build a procedure to format the result. Cache the built procedure's name | |
| # in the 'FormatProc' array to avoid losing its internal representation, | |
| # which contains the name resolution. | |
| set procName formatproc'$format'$locale | |
| set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName] | |
| if {[info exists FormatProc($procName)]} { | |
| set procName $FormatProc($procName) | |
| } else { | |
| set FormatProc($procName) \ | |
| [ParseClockFormatFormat $procName $format $locale] | |
| } | |
| return [$procName $clockval $timezone] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ParseClockFormatFormat -- | |
| # | |
| # Builds and caches a procedure that formats a time value. | |
| # | |
| # Parameters: | |
| # format -- Format string to use | |
| # locale -- Locale in which the format string is to be interpreted | |
| # | |
| # Results: | |
| # Returns the name of the newly-built procedure. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::ParseClockFormatFormat {procName format locale} { | |
| if {[namespace which $procName] ne {}} { | |
| return $procName | |
| } | |
| # Map away the locale-dependent composite format groups | |
| EnterLocale $locale | |
| # Change locale if a fresh locale has been given on the command line. | |
| try { | |
| return [ParseClockFormatFormat2 $format $locale $procName] | |
| } trap CLOCK {result opts} { | |
| dict unset opts -errorinfo | |
| return -options $opts $result | |
| } | |
| } | |
| proc ::tcl::clock::ParseClockFormatFormat2 {format locale procName} { | |
| set didLocaleEra 0 | |
| set didLocaleNumerals 0 | |
| set preFormatCode \ | |
| [string map [list @GREGORIAN_CHANGE_DATE@ \ | |
| [mc GREGORIAN_CHANGE_DATE]] \ | |
| { | |
| variable TZData | |
| set date [GetDateFields $clockval \ | |
| $TZData($timezone) \ | |
| @GREGORIAN_CHANGE_DATE@] | |
| }] | |
| set formatString {} | |
| set substituents {} | |
| set state {} | |
| set format [LocalizeFormat $locale $format] | |
| foreach char [split $format {}] { | |
| switch -exact -- $state { | |
| {} { | |
| if { [string equal % $char] } { | |
| set state percent | |
| } else { | |
| append formatString $char | |
| } | |
| } | |
| percent { # Character following a '%' character | |
| set state {} | |
| switch -exact -- $char { | |
| % { # A literal character, '%' | |
| append formatString %% | |
| } | |
| a { # Day of week, abbreviated | |
| append formatString %s | |
| append substituents \ | |
| [string map \ | |
| [list @DAYS_OF_WEEK_ABBREV@ \ | |
| [list [mc DAYS_OF_WEEK_ABBREV]]] \ | |
| { [lindex @DAYS_OF_WEEK_ABBREV@ \ | |
| [expr {[dict get $date dayOfWeek] \ | |
| % 7}]]}] | |
| } | |
| A { # Day of week, spelt out. | |
| append formatString %s | |
| append substituents \ | |
| [string map \ | |
| [list @DAYS_OF_WEEK_FULL@ \ | |
| [list [mc DAYS_OF_WEEK_FULL]]] \ | |
| { [lindex @DAYS_OF_WEEK_FULL@ \ | |
| [expr {[dict get $date dayOfWeek] \ | |
| % 7}]]}] | |
| } | |
| b - h { # Name of month, abbreviated. | |
| append formatString %s | |
| append substituents \ | |
| [string map \ | |
| [list @MONTHS_ABBREV@ \ | |
| [list [mc MONTHS_ABBREV]]] \ | |
| { [lindex @MONTHS_ABBREV@ \ | |
| [expr {[dict get $date month]-1}]]}] | |
| } | |
| B { # Name of month, spelt out | |
| append formatString %s | |
| append substituents \ | |
| [string map \ | |
| [list @MONTHS_FULL@ \ | |
| [list [mc MONTHS_FULL]]] \ | |
| { [lindex @MONTHS_FULL@ \ | |
| [expr {[dict get $date month]-1}]]}] | |
| } | |
| C { # Century number | |
| append formatString %02d | |
| append substituents \ | |
| { [expr {[dict get $date year] / 100}]} | |
| } | |
| d { # Day of month, with leading zero | |
| append formatString %02d | |
| append substituents { [dict get $date dayOfMonth]} | |
| } | |
| e { # Day of month, without leading zero | |
| append formatString %2d | |
| append substituents { [dict get $date dayOfMonth]} | |
| } | |
| E { # Format group in a locale-dependent | |
| # alternative era | |
| set state percentE | |
| if {!$didLocaleEra} { | |
| append preFormatCode \ | |
| [string map \ | |
| [list @LOCALE_ERAS@ \ | |
| [list [mc LOCALE_ERAS]]] \ | |
| { | |
| set date [GetLocaleEra \ | |
| $date[set date {}] \ | |
| @LOCALE_ERAS@]}] \n | |
| set didLocaleEra 1 | |
| } | |
| if {!$didLocaleNumerals} { | |
| append preFormatCode \ | |
| [list set localeNumerals \ | |
| [mc LOCALE_NUMERALS]] \n | |
| set didLocaleNumerals 1 | |
| } | |
| } | |
| g { # Two-digit year relative to ISO8601 | |
| # week number | |
| append formatString %02d | |
| append substituents \ | |
| { [expr { [dict get $date iso8601Year] % 100 }]} | |
| } | |
| G { # Four-digit year relative to ISO8601 | |
| # week number | |
| append formatString %02d | |
| append substituents { [dict get $date iso8601Year]} | |
| } | |
| H { # Hour in the 24-hour day, leading zero | |
| append formatString %02d | |
| append substituents \ | |
| { [expr { [dict get $date localSeconds] \ | |
| / 3600 % 24}]} | |
| } | |
| I { # Hour AM/PM, with leading zero | |
| append formatString %02d | |
| append substituents \ | |
| { [expr { ( ( ( [dict get $date localSeconds] \ | |
| % 86400 ) \ | |
| + 86400 \ | |
| - 3600 ) \ | |
| / 3600 ) \ | |
| % 12 + 1 }] } | |
| } | |
| j { # Day of year (001-366) | |
| append formatString %03d | |
| append substituents { [dict get $date dayOfYear]} | |
| } | |
| J { # Julian Day Number | |
| append formatString %07ld | |
| append substituents { [dict get $date julianDay]} | |
| } | |
| k { # Hour (0-23), no leading zero | |
| append formatString %2d | |
| append substituents \ | |
| { [expr { [dict get $date localSeconds] | |
| / 3600 | |
| % 24 }]} | |
| } | |
| l { # Hour (12-11), no leading zero | |
| append formatString %2d | |
| append substituents \ | |
| { [expr { ( ( ( [dict get $date localSeconds] | |
| % 86400 ) | |
| + 86400 | |
| - 3600 ) | |
| / 3600 ) | |
| % 12 + 1 }]} | |
| } | |
| m { # Month number, leading zero | |
| append formatString %02d | |
| append substituents { [dict get $date month]} | |
| } | |
| M { # Minute of the hour, leading zero | |
| append formatString %02d | |
| append substituents \ | |
| { [expr { [dict get $date localSeconds] | |
| / 60 | |
| % 60 }]} | |
| } | |
| n { # A literal newline | |
| append formatString \n | |
| } | |
| N { # Month number, no leading zero | |
| append formatString %2d | |
| append substituents { [dict get $date month]} | |
| } | |
| O { # A format group in the locale's | |
| # alternative numerals | |
| set state percentO | |
| if {!$didLocaleNumerals} { | |
| append preFormatCode \ | |
| [list set localeNumerals \ | |
| [mc LOCALE_NUMERALS]] \n | |
| set didLocaleNumerals 1 | |
| } | |
| } | |
| p { # Localized 'AM' or 'PM' indicator | |
| # converted to uppercase | |
| append formatString %s | |
| append preFormatCode \ | |
| [list set AM [string toupper [mc AM]]] \n \ | |
| [list set PM [string toupper [mc PM]]] \n | |
| append substituents \ | |
| { [expr {(([dict get $date localSeconds] | |
| % 86400) < 43200) ? | |
| $AM : $PM}]} | |
| } | |
| P { # Localized 'AM' or 'PM' indicator | |
| append formatString %s | |
| append preFormatCode \ | |
| [list set am [mc AM]] \n \ | |
| [list set pm [mc PM]] \n | |
| append substituents \ | |
| { [expr {(([dict get $date localSeconds] | |
| % 86400) < 43200) ? | |
| $am : $pm}]} | |
| } | |
| Q { # Hi, Jeff! | |
| append formatString %s | |
| append substituents { [FormatStarDate $date]} | |
| } | |
| s { # Seconds from the Posix Epoch | |
| append formatString %s | |
| append substituents { [dict get $date seconds]} | |
| } | |
| S { # Second of the minute, with | |
| # leading zero | |
| append formatString %02d | |
| append substituents \ | |
| { [expr { [dict get $date localSeconds] | |
| % 60 }]} | |
| } | |
| t { # A literal tab character | |
| append formatString \t | |
| } | |
| u { # Day of the week (1-Monday, 7-Sunday) | |
| append formatString %1d | |
| append substituents { [dict get $date dayOfWeek]} | |
| } | |
| U { # Week of the year (00-53). The | |
| # first Sunday of the year is the | |
| # first day of week 01 | |
| append formatString %02d | |
| append preFormatCode { | |
| set dow [dict get $date dayOfWeek] | |
| if { $dow == 7 } { | |
| set dow 0 | |
| } | |
| incr dow | |
| set UweekNumber \ | |
| [expr { ( [dict get $date dayOfYear] | |
| - $dow + 7 ) | |
| / 7 }] | |
| } | |
| append substituents { $UweekNumber} | |
| } | |
| V { # The ISO8601 week number | |
| append formatString %02d | |
| append substituents { [dict get $date iso8601Week]} | |
| } | |
| w { # Day of the week (0-Sunday, | |
| # 6-Saturday) | |
| append formatString %1d | |
| append substituents \ | |
| { [expr { [dict get $date dayOfWeek] % 7 }]} | |
| } | |
| W { # Week of the year (00-53). The first | |
| # Monday of the year is the first day | |
| # of week 01. | |
| append preFormatCode { | |
| set WweekNumber \ | |
| [expr { ( [dict get $date dayOfYear] | |
| - [dict get $date dayOfWeek] | |
| + 7 ) | |
| / 7 }] | |
| } | |
| append formatString %02d | |
| append substituents { $WweekNumber} | |
| } | |
| y { # The two-digit year of the century | |
| append formatString %02d | |
| append substituents \ | |
| { [expr { [dict get $date year] % 100 }]} | |
| } | |
| Y { # The four-digit year | |
| append formatString %04d | |
| append substituents { [dict get $date year]} | |
| } | |
| z { # The time zone as hours and minutes | |
| # east (+) or west (-) of Greenwich | |
| append formatString %s | |
| append substituents { [FormatNumericTimeZone \ | |
| [dict get $date tzOffset]]} | |
| } | |
| Z { # The name of the time zone | |
| append formatString %s | |
| append substituents { [dict get $date tzName]} | |
| } | |
| % { # A literal percent character | |
| append formatString %% | |
| } | |
| default { # An unknown escape sequence | |
| append formatString %% $char | |
| } | |
| } | |
| } | |
| percentE { # Character following %E | |
| set state {} | |
| switch -exact -- $char { | |
| E { | |
| append formatString %s | |
| append substituents { } \ | |
| [string map \ | |
| [list @BCE@ [list [mc BCE]] \ | |
| @CE@ [list [mc CE]]] \ | |
| {[dict get {BCE @BCE@ CE @CE@} \ | |
| [dict get $date era]]}] | |
| } | |
| C { # Locale-dependent era | |
| append formatString %s | |
| append substituents { [dict get $date localeEra]} | |
| } | |
| y { # Locale-dependent year of the era | |
| append preFormatCode { | |
| set y [dict get $date localeYear] | |
| if { $y >= 0 && $y < 100 } { | |
| set Eyear [lindex $localeNumerals $y] | |
| } else { | |
| set Eyear $y | |
| } | |
| } | |
| append formatString %s | |
| append substituents { $Eyear} | |
| } | |
| default { # Unknown %E format group | |
| append formatString %%E $char | |
| } | |
| } | |
| } | |
| percentO { # Character following %O | |
| set state {} | |
| switch -exact -- $char { | |
| d - e { # Day of the month in alternative | |
| # numerals | |
| append formatString %s | |
| append substituents \ | |
| { [lindex $localeNumerals \ | |
| [dict get $date dayOfMonth]]} | |
| } | |
| H - k { # Hour of the day in alternative | |
| # numerals | |
| append formatString %s | |
| append substituents \ | |
| { [lindex $localeNumerals \ | |
| [expr { [dict get $date localSeconds] | |
| / 3600 | |
| % 24 }]]} | |
| } | |
| I - l { # Hour (12-11) AM/PM in alternative | |
| # numerals | |
| append formatString %s | |
| append substituents \ | |
| { [lindex $localeNumerals \ | |
| [expr { ( ( ( [dict get $date localSeconds] | |
| % 86400 ) | |
| + 86400 | |
| - 3600 ) | |
| / 3600 ) | |
| % 12 + 1 }]]} | |
| } | |
| m { # Month number in alternative numerals | |
| append formatString %s | |
| append substituents \ | |
| { [lindex $localeNumerals [dict get $date month]]} | |
| } | |
| M { # Minute of the hour in alternative | |
| # numerals | |
| append formatString %s | |
| append substituents \ | |
| { [lindex $localeNumerals \ | |
| [expr { [dict get $date localSeconds] | |
| / 60 | |
| % 60 }]]} | |
| } | |
| S { # Second of the minute in alternative | |
| # numerals | |
| append formatString %s | |
| append substituents \ | |
| { [lindex $localeNumerals \ | |
| [expr { [dict get $date localSeconds] | |
| % 60 }]]} | |
| } | |
| u { # Day of the week (Monday=1,Sunday=7) | |
| # in alternative numerals | |
| append formatString %s | |
| append substituents \ | |
| { [lindex $localeNumerals \ | |
| [dict get $date dayOfWeek]]} | |
| } | |
| w { # Day of the week (Sunday=0,Saturday=6) | |
| # in alternative numerals | |
| append formatString %s | |
| append substituents \ | |
| { [lindex $localeNumerals \ | |
| [expr { [dict get $date dayOfWeek] % 7 }]]} | |
| } | |
| y { # Year of the century in alternative | |
| # numerals | |
| append formatString %s | |
| append substituents \ | |
| { [lindex $localeNumerals \ | |
| [expr { [dict get $date year] % 100 }]]} | |
| } | |
| default { # Unknown format group | |
| append formatString %%O $char | |
| } | |
| } | |
| } | |
| } | |
| } | |
| # Clean up any improperly terminated groups | |
| switch -exact -- $state { | |
| percent { | |
| append formatString %% | |
| } | |
| percentE { | |
| append retval %%E | |
| } | |
| percentO { | |
| append retval %%O | |
| } | |
| } | |
| proc $procName {clockval timezone} " | |
| $preFormatCode | |
| return \[::format [list $formatString] $substituents\] | |
| " | |
| # puts [list $procName [info args $procName] [info body $procName]] | |
| return $procName | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # clock scan -- | |
| # | |
| # Inputs a count of seconds since the Posix Epoch as a time of day. | |
| # | |
| # The 'clock format' command scans times of day on input. Refer to the user | |
| # documentation to see what it does. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::scan { args } { | |
| set format {} | |
| # Check the count of args | |
| if { [llength $args] < 1 || [llength $args] % 2 != 1 } { | |
| set cmdName "clock scan" | |
| return -code error \ | |
| -errorcode [list CLOCK wrongNumArgs] \ | |
| "wrong \# args: should be\ | |
| \"$cmdName string\ | |
| ?-base seconds?\ | |
| ?-format string? ?-gmt boolean?\ | |
| ?-locale LOCALE? ?-timezone ZONE?\"" | |
| } | |
| # Set defaults | |
| set base [clock seconds] | |
| set string [lindex $args 0] | |
| set format {} | |
| set gmt 0 | |
| set locale c | |
| set timezone [GetSystemTimeZone] | |
| # Pick up command line options. | |
| foreach { flag value } [lreplace $args 0 0] { | |
| set saw($flag) {} | |
| switch -exact -- $flag { | |
| -b - -ba - -bas - -base { | |
| set base $value | |
| } | |
| -f - -fo - -for - -form - -forma - -format { | |
| set format $value | |
| } | |
| -g - -gm - -gmt { | |
| set gmt $value | |
| } | |
| -l - -lo - -loc - -loca - -local - -locale { | |
| set locale [string tolower $value] | |
| } | |
| -t - -ti - -tim - -time - -timez - -timezo - -timezon - -timezone { | |
| set timezone $value | |
| } | |
| default { | |
| return -code error \ | |
| -errorcode [list CLOCK badOption $flag] \ | |
| "bad option \"$flag\",\ | |
| must be -base, -format, -gmt, -locale or -timezone" | |
| } | |
| } | |
| } | |
| # Check options for validity | |
| if { [info exists saw(-gmt)] && [info exists saw(-timezone)] } { | |
| return -code error \ | |
| -errorcode [list CLOCK gmtWithTimezone] \ | |
| "cannot use -gmt and -timezone in same call" | |
| } | |
| if { [catch { expr { wide($base) } } result] } { | |
| return -code error "expected integer but got \"$base\"" | |
| } | |
| if { ![string is boolean -strict $gmt] } { | |
| return -code error "expected boolean value but got \"$gmt\"" | |
| } elseif { $gmt } { | |
| set timezone :GMT | |
| } | |
| if { ![info exists saw(-format)] } { | |
| # Perhaps someday we'll localize the legacy code. Right now, it's not | |
| # localized. | |
| if { [info exists saw(-locale)] } { | |
| return -code error \ | |
| -errorcode [list CLOCK flagWithLegacyFormat] \ | |
| "legacy \[clock scan\] does not support -locale" | |
| } | |
| return [FreeScan $string $base $timezone $locale] | |
| } | |
| # Change locale if a fresh locale has been given on the command line. | |
| EnterLocale $locale | |
| try { | |
| # Map away the locale-dependent composite format groups | |
| set scanner [ParseClockScanFormat $format $locale] | |
| return [$scanner $string $base $timezone] | |
| } trap CLOCK {result opts} { | |
| # Conceal location of generation of expected errors | |
| dict unset opts -errorinfo | |
| return -options $opts $result | |
| } | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # FreeScan -- | |
| # | |
| # Scans a time in free format | |
| # | |
| # Parameters: | |
| # string - String containing the time to scan | |
| # base - Base time, expressed in seconds from the Epoch | |
| # timezone - Default time zone in which the time will be expressed | |
| # locale - (Unused) Name of the locale where the time will be scanned. | |
| # | |
| # Results: | |
| # Returns the date and time extracted from the string in seconds from | |
| # the epoch | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::FreeScan { string base timezone locale } { | |
| variable TZData | |
| # Get the data for time changes in the given zone | |
| try { | |
| SetupTimeZone $timezone | |
| } on error {retval opts} { | |
| dict unset opts -errorinfo | |
| return -options $opts $retval | |
| } | |
| # Extract year, month and day from the base time for the parser to use as | |
| # defaults | |
| set date [GetDateFields $base $TZData($timezone) 2361222] | |
| dict set date secondOfDay [expr { | |
| [dict get $date localSeconds] % 86400 | |
| }] | |
| # Parse the date. The parser will return a list comprising date, time, | |
| # time zone, relative month/day/seconds, relative weekday, ordinal month. | |
| try { | |
| set scanned [Oldscan $string \ | |
| [dict get $date year] \ | |
| [dict get $date month] \ | |
| [dict get $date dayOfMonth]] | |
| lassign $scanned \ | |
| parseDate parseTime parseZone parseRel \ | |
| parseWeekday parseOrdinalMonth | |
| } on error message { | |
| return -code error \ | |
| "unable to convert date-time string \"$string\": $message" | |
| } | |
| # If the caller supplied a date in the string, update the 'date' dict with | |
| # the value. If the caller didn't specify a time with the date, default to | |
| # midnight. | |
| if { [llength $parseDate] > 0 } { | |
| lassign $parseDate y m d | |
| if { $y < 100 } { | |
| if { $y >= 39 } { | |
| incr y 1900 | |
| } else { | |
| incr y 2000 | |
| } | |
| } | |
| dict set date era CE | |
| dict set date year $y | |
| dict set date month $m | |
| dict set date dayOfMonth $d | |
| if { $parseTime eq {} } { | |
| set parseTime 0 | |
| } | |
| } | |
| # If the caller supplied a time zone in the string, it comes back as a | |
| # two-element list; the first element is the number of minutes east of | |
| # Greenwich, and the second is a Daylight Saving Time indicator (1 == yes, | |
| # 0 == no, -1 == unknown). We make it into a time zone indicator of | |
| # +-hhmm. | |
| if { [llength $parseZone] > 0 } { | |
| lassign $parseZone minEast dstFlag | |
| set timezone [FormatNumericTimeZone \ | |
| [expr { 60 * $minEast + 3600 * $dstFlag }]] | |
| SetupTimeZone $timezone | |
| } | |
| dict set date tzName $timezone | |
| # Assemble date, time, zone into seconds-from-epoch | |
| set date [GetJulianDayFromEraYearMonthDay $date[set date {}] 2361222] | |
| if { $parseTime ne {} } { | |
| dict set date secondOfDay $parseTime | |
| } elseif { [llength $parseWeekday] != 0 | |
| || [llength $parseOrdinalMonth] != 0 | |
| || ( [llength $parseRel] != 0 | |
| && ( [lindex $parseRel 0] != 0 | |
| || [lindex $parseRel 1] != 0 ) ) } { | |
| dict set date secondOfDay 0 | |
| } | |
| dict set date localSeconds [expr { | |
| -210866803200 | |
| + ( 86400 * wide([dict get $date julianDay]) ) | |
| + [dict get $date secondOfDay] | |
| }] | |
| dict set date tzName $timezone | |
| set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) 2361222] | |
| set seconds [dict get $date seconds] | |
| # Do relative times | |
| if { [llength $parseRel] > 0 } { | |
| lassign $parseRel relMonth relDay relSecond | |
| set seconds [add $seconds \ | |
| $relMonth months $relDay days $relSecond seconds \ | |
| -timezone $timezone -locale $locale] | |
| } | |
| # Do relative weekday | |
| if { [llength $parseWeekday] > 0 } { | |
| lassign $parseWeekday dayOrdinal dayOfWeek | |
| set date2 [GetDateFields $seconds $TZData($timezone) 2361222] | |
| dict set date2 era CE | |
| set jdwkday [WeekdayOnOrBefore $dayOfWeek [expr { | |
| [dict get $date2 julianDay] + 6 | |
| }]] | |
| incr jdwkday [expr { 7 * $dayOrdinal }] | |
| if { $dayOrdinal > 0 } { | |
| incr jdwkday -7 | |
| } | |
| dict set date2 secondOfDay \ | |
| [expr { [dict get $date2 localSeconds] % 86400 }] | |
| dict set date2 julianDay $jdwkday | |
| dict set date2 localSeconds [expr { | |
| -210866803200 | |
| + ( 86400 * wide([dict get $date2 julianDay]) ) | |
| + [dict get $date secondOfDay] | |
| }] | |
| dict set date2 tzName $timezone | |
| set date2 [ConvertLocalToUTC $date2[set date2 {}] $TZData($timezone) \ | |
| 2361222] | |
| set seconds [dict get $date2 seconds] | |
| } | |
| # Do relative month | |
| if { [llength $parseOrdinalMonth] > 0 } { | |
| lassign $parseOrdinalMonth monthOrdinal monthNumber | |
| if { $monthOrdinal > 0 } { | |
| set monthDiff [expr { $monthNumber - [dict get $date month] }] | |
| if { $monthDiff <= 0 } { | |
| incr monthDiff 12 | |
| } | |
| incr monthOrdinal -1 | |
| } else { | |
| set monthDiff [expr { [dict get $date month] - $monthNumber }] | |
| if { $monthDiff >= 0 } { | |
| incr monthDiff -12 | |
| } | |
| incr monthOrdinal | |
| } | |
| set seconds [add $seconds $monthOrdinal years $monthDiff months \ | |
| -timezone $timezone -locale $locale] | |
| } | |
| return $seconds | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ParseClockScanFormat -- | |
| # | |
| # Parses a format string given to [clock scan -format] | |
| # | |
| # Parameters: | |
| # formatString - The format being parsed | |
| # locale - The current locale | |
| # | |
| # Results: | |
| # Constructs and returns a procedure that accepts the string being | |
| # scanned, the base time, and the time zone. The procedure will either | |
| # return the scanned time or else throw an error that should be rethrown | |
| # to the caller of [clock scan] | |
| # | |
| # Side effects: | |
| # The given procedure is defined in the ::tcl::clock namespace. Scan | |
| # procedures are not deleted once installed. | |
| # | |
| # Why do we parse dates by defining a procedure to parse them? The reason is | |
| # that by doing so, we have one convenient place to cache all the information: | |
| # the regular expressions that match the patterns (which will be compiled), | |
| # the code that assembles the date information, everything lands in one place. | |
| # In this way, when a given format is reused at run time, all the information | |
| # of how to apply it is available in a single place. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::ParseClockScanFormat {formatString locale} { | |
| # Check whether the format has been parsed previously, and return the | |
| # existing recognizer if it has. | |
| set procName scanproc'$formatString'$locale | |
| set procName [namespace current]::[string map {: {\:} \\ {\\}} $procName] | |
| if { [namespace which $procName] != {} } { | |
| return $procName | |
| } | |
| variable DateParseActions | |
| variable TimeParseActions | |
| # Localize the %x, %X, etc. groups | |
| set formatString [LocalizeFormat $locale $formatString] | |
| # Condense whitespace | |
| regsub -all {[[:space:]]+} $formatString { } formatString | |
| # Walk through the groups of the format string. In this loop, we | |
| # accumulate: | |
| # - a regular expression that matches the string, | |
| # - the count of capturing brackets in the regexp | |
| # - a set of code that post-processes the fields captured by the regexp, | |
| # - a dictionary whose keys are the names of fields that are present | |
| # in the format string. | |
| set re {^[[:space:]]*} | |
| set captureCount 0 | |
| set postcode {} | |
| set fieldSet [dict create] | |
| set fieldCount 0 | |
| set postSep {} | |
| set state {} | |
| foreach c [split $formatString {}] { | |
| switch -exact -- $state { | |
| {} { | |
| if { $c eq "%" } { | |
| set state % | |
| } elseif { $c eq " " } { | |
| append re {[[:space:]]+} | |
| } else { | |
| if { ! [string is alnum $c] } { | |
| append re "\\" | |
| } | |
| append re $c | |
| } | |
| } | |
| % { | |
| set state {} | |
| switch -exact -- $c { | |
| % { | |
| append re % | |
| } | |
| { } { | |
| append re "\[\[:space:\]\]*" | |
| } | |
| a - A { # Day of week, in words | |
| set l {} | |
| foreach \ | |
| i {7 1 2 3 4 5 6} \ | |
| abr [mc DAYS_OF_WEEK_ABBREV] \ | |
| full [mc DAYS_OF_WEEK_FULL] { | |
| dict set l [string tolower $abr] $i | |
| dict set l [string tolower $full] $i | |
| incr i | |
| } | |
| lassign [UniquePrefixRegexp $l] regex lookup | |
| append re ( $regex ) | |
| dict set fieldSet dayOfWeek [incr fieldCount] | |
| append postcode "dict set date dayOfWeek \[" \ | |
| "dict get " [list $lookup] " " \ | |
| \[ {string tolower $field} [incr captureCount] \] \ | |
| "\]\n" | |
| } | |
| b - B - h { # Name of month | |
| set i 0 | |
| set l {} | |
| foreach \ | |
| abr [mc MONTHS_ABBREV] \ | |
| full [mc MONTHS_FULL] { | |
| incr i | |
| dict set l [string tolower $abr] $i | |
| dict set l [string tolower $full] $i | |
| } | |
| lassign [UniquePrefixRegexp $l] regex lookup | |
| append re ( $regex ) | |
| dict set fieldSet month [incr fieldCount] | |
| append postcode "dict set date month \[" \ | |
| "dict get " [list $lookup] \ | |
| " " \[ {string tolower $field} \ | |
| [incr captureCount] \] \ | |
| "\]\n" | |
| } | |
| C { # Gregorian century | |
| append re \\s*(\\d\\d?) | |
| dict set fieldSet century [incr fieldCount] | |
| append postcode "dict set date century \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| d - e { # Day of month | |
| append re \\s*(\\d\\d?) | |
| dict set fieldSet dayOfMonth [incr fieldCount] | |
| append postcode "dict set date dayOfMonth \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| E { # Prefix for locale-specific codes | |
| set state %E | |
| } | |
| g { # ISO8601 2-digit year | |
| append re \\s*(\\d\\d) | |
| dict set fieldSet iso8601YearOfCentury \ | |
| [incr fieldCount] | |
| append postcode \ | |
| "dict set date iso8601YearOfCentury \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| G { # ISO8601 4-digit year | |
| append re \\s*(\\d\\d)(\\d\\d) | |
| dict set fieldSet iso8601Century [incr fieldCount] | |
| dict set fieldSet iso8601YearOfCentury \ | |
| [incr fieldCount] | |
| append postcode \ | |
| "dict set date iso8601Century \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" \ | |
| "dict set date iso8601YearOfCentury \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| H - k { # Hour of day | |
| append re \\s*(\\d\\d?) | |
| dict set fieldSet hour [incr fieldCount] | |
| append postcode "dict set date hour \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| I - l { # Hour, AM/PM | |
| append re \\s*(\\d\\d?) | |
| dict set fieldSet hourAMPM [incr fieldCount] | |
| append postcode "dict set date hourAMPM \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| j { # Day of year | |
| append re \\s*(\\d\\d?\\d?) | |
| dict set fieldSet dayOfYear [incr fieldCount] | |
| append postcode "dict set date dayOfYear \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| J { # Julian Day Number | |
| append re \\s*(\\d+) | |
| dict set fieldSet julianDay [incr fieldCount] | |
| append postcode "dict set date julianDay \[" \ | |
| "::scan \$field" [incr captureCount] " %ld" \ | |
| "\]\n" | |
| } | |
| m - N { # Month number | |
| append re \\s*(\\d\\d?) | |
| dict set fieldSet month [incr fieldCount] | |
| append postcode "dict set date month \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| M { # Minute | |
| append re \\s*(\\d\\d?) | |
| dict set fieldSet minute [incr fieldCount] | |
| append postcode "dict set date minute \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| n { # Literal newline | |
| append re \\n | |
| } | |
| O { # Prefix for locale numerics | |
| set state %O | |
| } | |
| p - P { # AM/PM indicator | |
| set l [list [string tolower [mc AM]] 0 \ | |
| [string tolower [mc PM]] 1] | |
| lassign [UniquePrefixRegexp $l] regex lookup | |
| append re ( $regex ) | |
| dict set fieldSet amPmIndicator [incr fieldCount] | |
| append postcode "dict set date amPmIndicator \[" \ | |
| "dict get " [list $lookup] " \[string tolower " \ | |
| "\$field" \ | |
| [incr captureCount] \ | |
| "\]\]\n" | |
| } | |
| Q { # Hi, Jeff! | |
| append re {Stardate\s+([-+]?\d+)(\d\d\d)[.](\d)} | |
| incr captureCount | |
| dict set fieldSet seconds [incr fieldCount] | |
| append postcode {dict set date seconds } \[ \ | |
| {ParseStarDate $field} [incr captureCount] \ | |
| { $field} [incr captureCount] \ | |
| { $field} [incr captureCount] \ | |
| \] \n | |
| } | |
| s { # Seconds from Posix Epoch | |
| # This next case is insanely difficult, because it's | |
| # problematic to determine whether the field is | |
| # actually within the range of a wide integer. | |
| append re {\s*([-+]?\d+)} | |
| dict set fieldSet seconds [incr fieldCount] | |
| append postcode {dict set date seconds } \[ \ | |
| {ScanWide $field} [incr captureCount] \] \n | |
| } | |
| S { # Second | |
| append re \\s*(\\d\\d?) | |
| dict set fieldSet second [incr fieldCount] | |
| append postcode "dict set date second \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| t { # Literal tab character | |
| append re \\t | |
| } | |
| u - w { # Day number within week, 0 or 7 == Sun | |
| # 1=Mon, 6=Sat | |
| append re \\s*(\\d) | |
| dict set fieldSet dayOfWeek [incr fieldCount] | |
| append postcode {::scan $field} [incr captureCount] \ | |
| { %d dow} \n \ | |
| { | |
| if { $dow == 0 } { | |
| set dow 7 | |
| } elseif { $dow > 7 } { | |
| return -code error \ | |
| -errorcode [list CLOCK badDayOfWeek] \ | |
| "day of week is greater than 7" | |
| } | |
| dict set date dayOfWeek $dow | |
| } | |
| } | |
| U { # Week of year. The first Sunday of | |
| # the year is the first day of week | |
| # 01. No scan rule uses this group. | |
| append re \\s*\\d\\d? | |
| } | |
| V { # Week of ISO8601 year | |
| append re \\s*(\\d\\d?) | |
| dict set fieldSet iso8601Week [incr fieldCount] | |
| append postcode "dict set date iso8601Week \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| W { # Week of the year (00-53). The first | |
| # Monday of the year is the first day | |
| # of week 01. No scan rule uses this | |
| # group. | |
| append re \\s*\\d\\d? | |
| } | |
| y { # Two-digit Gregorian year | |
| append re \\s*(\\d\\d?) | |
| dict set fieldSet yearOfCentury [incr fieldCount] | |
| append postcode "dict set date yearOfCentury \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| Y { # 4-digit Gregorian year | |
| append re \\s*(\\d\\d)(\\d\\d) | |
| dict set fieldSet century [incr fieldCount] | |
| dict set fieldSet yearOfCentury [incr fieldCount] | |
| append postcode \ | |
| "dict set date century \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" \ | |
| "dict set date yearOfCentury \[" \ | |
| "::scan \$field" [incr captureCount] " %d" \ | |
| "\]\n" | |
| } | |
| z - Z { # Time zone name | |
| append re {(?:([-+]\d\d(?::?\d\d(?::?\d\d)?)?)|([[:alnum:]]{1,4}))} | |
| dict set fieldSet tzName [incr fieldCount] | |
| append postcode \ | |
| {if } \{ { $field} [incr captureCount] \ | |
| { ne "" } \} { } \{ \n \ | |
| {dict set date tzName $field} \ | |
| $captureCount \n \ | |
| \} { else } \{ \n \ | |
| {dict set date tzName } \[ \ | |
| {ConvertLegacyTimeZone $field} \ | |
| [incr captureCount] \] \n \ | |
| \} \n \ | |
| } | |
| % { # Literal percent character | |
| append re % | |
| } | |
| default { | |
| append re % | |
| if { ! [string is alnum $c] } { | |
| append re \\ | |
| } | |
| append re $c | |
| } | |
| } | |
| } | |
| %E { | |
| switch -exact -- $c { | |
| C { # Locale-dependent era | |
| set d {} | |
| foreach triple [mc LOCALE_ERAS] { | |
| lassign $triple t symbol year | |
| dict set d [string tolower $symbol] $year | |
| } | |
| lassign [UniquePrefixRegexp $d] regex lookup | |
| append re (?: $regex ) | |
| } | |
| E { | |
| set l {} | |
| dict set l [string tolower [mc BCE]] BCE | |
| dict set l [string tolower [mc CE]] CE | |
| dict set l b.c.e. BCE | |
| dict set l c.e. CE | |
| dict set l b.c. BCE | |
| dict set l a.d. CE | |
| lassign [UniquePrefixRegexp $l] regex lookup | |
| append re ( $regex ) | |
| dict set fieldSet era [incr fieldCount] | |
| append postcode "dict set date era \["\ | |
| "dict get " [list $lookup] \ | |
| { } \[ {string tolower $field} \ | |
| [incr captureCount] \] \ | |
| "\]\n" | |
| } | |
| y { # Locale-dependent year of the era | |
| lassign [LocaleNumeralMatcher $locale] regex lookup | |
| append re $regex | |
| incr captureCount | |
| } | |
| default { | |
| append re %E | |
| if { ! [string is alnum $c] } { | |
| append re \\ | |
| } | |
| append re $c | |
| } | |
| } | |
| set state {} | |
| } | |
| %O { | |
| switch -exact -- $c { | |
| d - e { | |
| lassign [LocaleNumeralMatcher $locale] regex lookup | |
| append re $regex | |
| dict set fieldSet dayOfMonth [incr fieldCount] | |
| append postcode "dict set date dayOfMonth \[" \ | |
| "dict get " [list $lookup] " \$field" \ | |
| [incr captureCount] \ | |
| "\]\n" | |
| } | |
| H - k { | |
| lassign [LocaleNumeralMatcher $locale] regex lookup | |
| append re $regex | |
| dict set fieldSet hour [incr fieldCount] | |
| append postcode "dict set date hour \[" \ | |
| "dict get " [list $lookup] " \$field" \ | |
| [incr captureCount] \ | |
| "\]\n" | |
| } | |
| I - l { | |
| lassign [LocaleNumeralMatcher $locale] regex lookup | |
| append re $regex | |
| dict set fieldSet hourAMPM [incr fieldCount] | |
| append postcode "dict set date hourAMPM \[" \ | |
| "dict get " [list $lookup] " \$field" \ | |
| [incr captureCount] \ | |
| "\]\n" | |
| } | |
| m { | |
| lassign [LocaleNumeralMatcher $locale] regex lookup | |
| append re $regex | |
| dict set fieldSet month [incr fieldCount] | |
| append postcode "dict set date month \[" \ | |
| "dict get " [list $lookup] " \$field" \ | |
| [incr captureCount] \ | |
| "\]\n" | |
| } | |
| M { | |
| lassign [LocaleNumeralMatcher $locale] regex lookup | |
| append re $regex | |
| dict set fieldSet minute [incr fieldCount] | |
| append postcode "dict set date minute \[" \ | |
| "dict get " [list $lookup] " \$field" \ | |
| [incr captureCount] \ | |
| "\]\n" | |
| } | |
| S { | |
| lassign [LocaleNumeralMatcher $locale] regex lookup | |
| append re $regex | |
| dict set fieldSet second [incr fieldCount] | |
| append postcode "dict set date second \[" \ | |
| "dict get " [list $lookup] " \$field" \ | |
| [incr captureCount] \ | |
| "\]\n" | |
| } | |
| u - w { | |
| lassign [LocaleNumeralMatcher $locale] regex lookup | |
| append re $regex | |
| dict set fieldSet dayOfWeek [incr fieldCount] | |
| append postcode "set dow \[dict get " [list $lookup] \ | |
| { $field} [incr captureCount] \] \n \ | |
| { | |
| if { $dow == 0 } { | |
| set dow 7 | |
| } elseif { $dow > 7 } { | |
| return -code error \ | |
| -errorcode [list CLOCK badDayOfWeek] \ | |
| "day of week is greater than 7" | |
| } | |
| dict set date dayOfWeek $dow | |
| } | |
| } | |
| y { | |
| lassign [LocaleNumeralMatcher $locale] regex lookup | |
| append re $regex | |
| dict set fieldSet yearOfCentury [incr fieldCount] | |
| append postcode {dict set date yearOfCentury } \[ \ | |
| {dict get } [list $lookup] { $field} \ | |
| [incr captureCount] \] \n | |
| } | |
| default { | |
| append re %O | |
| if { ! [string is alnum $c] } { | |
| append re \\ | |
| } | |
| append re $c | |
| } | |
| } | |
| set state {} | |
| } | |
| } | |
| } | |
| # Clean up any unfinished format groups | |
| append re $state \\s*\$ | |
| # Build the procedure | |
| set procBody {} | |
| append procBody "variable ::tcl::clock::TZData" \n | |
| append procBody "if \{ !\[ regexp -nocase [list $re] \$string ->" | |
| for { set i 1 } { $i <= $captureCount } { incr i } { | |
| append procBody " " field $i | |
| } | |
| append procBody "\] \} \{" \n | |
| append procBody { | |
| return -code error -errorcode [list CLOCK badInputString] \ | |
| {input string does not match supplied format} | |
| } | |
| append procBody \}\n | |
| append procBody "set date \[dict create\]" \n | |
| append procBody {dict set date tzName $timeZone} \n | |
| append procBody $postcode | |
| append procBody [list set changeover [mc GREGORIAN_CHANGE_DATE]] \n | |
| # Set up the time zone before doing anything with a default base date | |
| # that might need a timezone to interpret it. | |
| if { ![dict exists $fieldSet seconds] | |
| && ![dict exists $fieldSet starDate] } { | |
| if { [dict exists $fieldSet tzName] } { | |
| append procBody { | |
| set timeZone [dict get $date tzName] | |
| } | |
| } | |
| append procBody { | |
| ::tcl::clock::SetupTimeZone $timeZone | |
| } | |
| } | |
| # Add code that gets Julian Day Number from the fields. | |
| append procBody [MakeParseCodeFromFields $fieldSet $DateParseActions] | |
| # Get time of day | |
| append procBody [MakeParseCodeFromFields $fieldSet $TimeParseActions] | |
| # Assemble seconds from the Julian day and second of the day. | |
| # Convert to local time unless epoch seconds or stardate are | |
| # being processed - they're always absolute | |
| if { ![dict exists $fieldSet seconds] | |
| && ![dict exists $fieldSet starDate] } { | |
| append procBody { | |
| if { [dict get $date julianDay] > 5373484 } { | |
| return -code error -errorcode [list CLOCK dateTooLarge] \ | |
| "requested date too large to represent" | |
| } | |
| dict set date localSeconds [expr { | |
| -210866803200 | |
| + ( 86400 * wide([dict get $date julianDay]) ) | |
| + [dict get $date secondOfDay] | |
| }] | |
| } | |
| # Finally, convert the date to local time | |
| append procBody { | |
| set date [::tcl::clock::ConvertLocalToUTC $date[set date {}] \ | |
| $TZData($timeZone) $changeover] | |
| } | |
| } | |
| # Return result | |
| append procBody {return [dict get $date seconds]} \n | |
| proc $procName { string baseTime timeZone } $procBody | |
| # puts [list proc $procName [list string baseTime timeZone] $procBody] | |
| return $procName | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # LocaleNumeralMatcher -- | |
| # | |
| # Composes a regexp that captures the numerals in the given locale, and | |
| # a dictionary to map them to conventional numerals. | |
| # | |
| # Parameters: | |
| # locale - Name of the current locale | |
| # | |
| # Results: | |
| # Returns a two-element list comprising the regexp and the dictionary. | |
| # | |
| # Side effects: | |
| # Caches the result. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::LocaleNumeralMatcher {l} { | |
| variable LocaleNumeralCache | |
| if { ![dict exists $LocaleNumeralCache $l] } { | |
| set d {} | |
| set i 0 | |
| set sep \( | |
| foreach n [mc LOCALE_NUMERALS] { | |
| dict set d $n $i | |
| regsub -all {[^[:alnum:]]} $n \\\\& subex | |
| append re $sep $subex | |
| set sep | | |
| incr i | |
| } | |
| append re \) | |
| dict set LocaleNumeralCache $l [list $re $d] | |
| } | |
| return [dict get $LocaleNumeralCache $l] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # UniquePrefixRegexp -- | |
| # | |
| # Composes a regexp that performs unique-prefix matching. The RE | |
| # matches one of a supplied set of strings, or any unique prefix | |
| # thereof. | |
| # | |
| # Parameters: | |
| # data - List of alternating match-strings and values. | |
| # Match-strings with distinct values are considered | |
| # distinct. | |
| # | |
| # Results: | |
| # Returns a two-element list. The first is a regexp that matches any | |
| # unique prefix of any of the strings. The second is a dictionary whose | |
| # keys are match values from the regexp and whose values are the | |
| # corresponding values from 'data'. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::UniquePrefixRegexp { data } { | |
| # The 'successors' dictionary will contain, for each string that is a | |
| # prefix of any key, all characters that may follow that prefix. The | |
| # 'prefixMapping' dictionary will have keys that are prefixes of keys and | |
| # values that correspond to the keys. | |
| set prefixMapping [dict create] | |
| set successors [dict create {} {}] | |
| # Walk the key-value pairs | |
| foreach { key value } $data { | |
| # Construct all prefixes of the key; | |
| set prefix {} | |
| foreach char [split $key {}] { | |
| set oldPrefix $prefix | |
| dict set successors $oldPrefix $char {} | |
| append prefix $char | |
| # Put the prefixes in the 'prefixMapping' and 'successors' | |
| # dictionaries | |
| dict lappend prefixMapping $prefix $value | |
| if { ![dict exists $successors $prefix] } { | |
| dict set successors $prefix {} | |
| } | |
| } | |
| } | |
| # Identify those prefixes that designate unique values, and those that are | |
| # the full keys | |
| set uniquePrefixMapping {} | |
| dict for { key valueList } $prefixMapping { | |
| if { [llength $valueList] == 1 } { | |
| dict set uniquePrefixMapping $key [lindex $valueList 0] | |
| } | |
| } | |
| foreach { key value } $data { | |
| dict set uniquePrefixMapping $key $value | |
| } | |
| # Construct the re. | |
| return [list \ | |
| [MakeUniquePrefixRegexp $successors $uniquePrefixMapping {}] \ | |
| $uniquePrefixMapping] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # MakeUniquePrefixRegexp -- | |
| # | |
| # Service procedure for 'UniquePrefixRegexp' that constructs a regular | |
| # expresison that matches the unique prefixes. | |
| # | |
| # Parameters: | |
| # successors - Dictionary whose keys are all prefixes | |
| # of keys passed to 'UniquePrefixRegexp' and whose | |
| # values are dictionaries whose keys are the characters | |
| # that may follow those prefixes. | |
| # uniquePrefixMapping - Dictionary whose keys are the unique | |
| # prefixes and whose values are not examined. | |
| # prefixString - Current prefix being processed. | |
| # | |
| # Results: | |
| # Returns a constructed regular expression that matches the set of | |
| # unique prefixes beginning with the 'prefixString'. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::MakeUniquePrefixRegexp { successors | |
| uniquePrefixMapping | |
| prefixString } { | |
| # Get the characters that may follow the current prefix string | |
| set schars [lsort -ascii [dict keys [dict get $successors $prefixString]]] | |
| if { [llength $schars] == 0 } { | |
| return {} | |
| } | |
| # If there is more than one successor character, or if the current prefix | |
| # is a unique prefix, surround the generated re with non-capturing | |
| # parentheses. | |
| set re {} | |
| if { | |
| [dict exists $uniquePrefixMapping $prefixString] | |
| || [llength $schars] > 1 | |
| } then { | |
| append re "(?:" | |
| } | |
| # Generate a regexp that matches the successors. | |
| set sep "" | |
| foreach { c } $schars { | |
| set nextPrefix $prefixString$c | |
| regsub -all {[^[:alnum:]]} $c \\\\& rechar | |
| append re $sep $rechar \ | |
| [MakeUniquePrefixRegexp \ | |
| $successors $uniquePrefixMapping $nextPrefix] | |
| set sep | | |
| } | |
| # If the current prefix is a unique prefix, make all following text | |
| # optional. Otherwise, if there is more than one successor character, | |
| # close the non-capturing parentheses. | |
| if { [dict exists $uniquePrefixMapping $prefixString] } { | |
| append re ")?" | |
| } elseif { [llength $schars] > 1 } { | |
| append re ")" | |
| } | |
| return $re | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # MakeParseCodeFromFields -- | |
| # | |
| # Composes Tcl code to extract the Julian Day Number from a dictionary | |
| # containing date fields. | |
| # | |
| # Parameters: | |
| # dateFields -- Dictionary whose keys are fields of the date, | |
| # and whose values are the rightmost positions | |
| # at which those fields appear. | |
| # parseActions -- List of triples: field set, priority, and | |
| # code to emit. Smaller priorities are better, and | |
| # the list must be in ascending order by priority | |
| # | |
| # Results: | |
| # Returns a burst of code that extracts the day number from the given | |
| # date. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::MakeParseCodeFromFields { dateFields parseActions } { | |
| set currPrio 999 | |
| set currFieldPos [list] | |
| set currCodeBurst { | |
| error "in ::tcl::clock::MakeParseCodeFromFields: can't happen" | |
| } | |
| foreach { fieldSet prio parseAction } $parseActions { | |
| # If we've found an answer that's better than any that follow, quit | |
| # now. | |
| if { $prio > $currPrio } { | |
| break | |
| } | |
| # Accumulate the field positions that are used in the current field | |
| # grouping. | |
| set fieldPos [list] | |
| set ok true | |
| foreach field $fieldSet { | |
| if { ! [dict exists $dateFields $field] } { | |
| set ok 0 | |
| break | |
| } | |
| lappend fieldPos [dict get $dateFields $field] | |
| } | |
| # Quit if we don't have a complete set of fields | |
| if { !$ok } { | |
| continue | |
| } | |
| # Determine whether the current answer is better than the last. | |
| set fPos [lsort -integer -decreasing $fieldPos] | |
| if { $prio == $currPrio } { | |
| foreach currPos $currFieldPos newPos $fPos { | |
| if { | |
| ![string is integer $newPos] | |
| || ![string is integer $currPos] | |
| || $newPos > $currPos | |
| } then { | |
| break | |
| } | |
| if { $newPos < $currPos } { | |
| set ok 0 | |
| break | |
| } | |
| } | |
| } | |
| if { !$ok } { | |
| continue | |
| } | |
| # Remember the best possibility for extracting date information | |
| set currPrio $prio | |
| set currFieldPos $fPos | |
| set currCodeBurst $parseAction | |
| } | |
| return $currCodeBurst | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # EnterLocale -- | |
| # | |
| # Switch [mclocale] to a given locale if necessary | |
| # | |
| # Parameters: | |
| # locale -- Desired locale | |
| # | |
| # Results: | |
| # Returns the locale that was previously current. | |
| # | |
| # Side effects: | |
| # Does [mclocale]. If necessary, loades the designated locale's files. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::EnterLocale { locale } { | |
| if { $locale eq {system} } { | |
| if { $::tcl_platform(platform) ne {windows} } { | |
| # On a non-windows platform, the 'system' locale is the same as | |
| # the 'current' locale | |
| set locale current | |
| } else { | |
| # On a windows platform, the 'system' locale is adapted from the | |
| # 'current' locale by applying the date and time formats from the | |
| # Control Panel. First, load the 'current' locale if it's not yet | |
| # loaded | |
| mcpackagelocale set [mclocale] | |
| # Make a new locale string for the system locale, and get the | |
| # Control Panel information | |
| set locale [mclocale]_windows | |
| if { ! [mcpackagelocale present $locale] } { | |
| LoadWindowsDateTimeFormats $locale | |
| } | |
| } | |
| } | |
| if { $locale eq {current}} { | |
| set locale [mclocale] | |
| } | |
| # Eventually load the locale | |
| mcpackagelocale set $locale | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # LoadWindowsDateTimeFormats -- | |
| # | |
| # Load the date/time formats from the Control Panel in Windows and | |
| # convert them so that they're usable by Tcl. | |
| # | |
| # Parameters: | |
| # locale - Name of the locale in whose message catalog | |
| # the converted formats are to be stored. | |
| # | |
| # Results: | |
| # None. | |
| # | |
| # Side effects: | |
| # Updates the given message catalog with the locale strings. | |
| # | |
| # Presumes that on entry, [mclocale] is set to the current locale, so that | |
| # default strings can be obtained if the Registry query fails. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::LoadWindowsDateTimeFormats { locale } { | |
| # Bail out if we can't find the Registry | |
| variable NoRegistry | |
| if { [info exists NoRegistry] } return | |
| if { ![catch { | |
| registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ | |
| sShortDate | |
| } string] } { | |
| set quote {} | |
| set datefmt {} | |
| foreach { unquoted quoted } [split $string '] { | |
| append datefmt $quote [string map { | |
| dddd %A | |
| ddd %a | |
| dd %d | |
| d %e | |
| MMMM %B | |
| MMM %b | |
| MM %m | |
| M %N | |
| yyyy %Y | |
| yy %y | |
| y %y | |
| gg {} | |
| } $unquoted] | |
| if { $quoted eq {} } { | |
| set quote ' | |
| } else { | |
| set quote $quoted | |
| } | |
| } | |
| ::msgcat::mcset $locale DATE_FORMAT $datefmt | |
| } | |
| if { ![catch { | |
| registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ | |
| sLongDate | |
| } string] } { | |
| set quote {} | |
| set ldatefmt {} | |
| foreach { unquoted quoted } [split $string '] { | |
| append ldatefmt $quote [string map { | |
| dddd %A | |
| ddd %a | |
| dd %d | |
| d %e | |
| MMMM %B | |
| MMM %b | |
| MM %m | |
| M %N | |
| yyyy %Y | |
| yy %y | |
| y %y | |
| gg {} | |
| } $unquoted] | |
| if { $quoted eq {} } { | |
| set quote ' | |
| } else { | |
| set quote $quoted | |
| } | |
| } | |
| ::msgcat::mcset $locale LOCALE_DATE_FORMAT $ldatefmt | |
| } | |
| if { ![catch { | |
| registry get "HKEY_CURRENT_USER\\Control Panel\\International" \ | |
| sTimeFormat | |
| } string] } { | |
| set quote {} | |
| set timefmt {} | |
| foreach { unquoted quoted } [split $string '] { | |
| append timefmt $quote [string map { | |
| HH %H | |
| H %k | |
| hh %I | |
| h %l | |
| mm %M | |
| m %M | |
| ss %S | |
| s %S | |
| tt %p | |
| t %p | |
| } $unquoted] | |
| if { $quoted eq {} } { | |
| set quote ' | |
| } else { | |
| set quote $quoted | |
| } | |
| } | |
| ::msgcat::mcset $locale TIME_FORMAT $timefmt | |
| } | |
| catch { | |
| ::msgcat::mcset $locale DATE_TIME_FORMAT "$datefmt $timefmt" | |
| } | |
| catch { | |
| ::msgcat::mcset $locale LOCALE_DATE_TIME_FORMAT "$ldatefmt $timefmt" | |
| } | |
| return | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # LocalizeFormat -- | |
| # | |
| # Map away locale-dependent format groups in a clock format. | |
| # | |
| # Parameters: | |
| # locale -- Current [mclocale] locale, supplied to avoid | |
| # an extra call | |
| # format -- Format supplied to [clock scan] or [clock format] | |
| # | |
| # Results: | |
| # Returns the string with locale-dependent composite format groups | |
| # substituted out. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::LocalizeFormat { locale format } { | |
| # message catalog key to cache this format | |
| set key FORMAT_$format | |
| if { [::msgcat::mcexists -exactlocale -exactnamespace $key] } { | |
| return [mc $key] | |
| } | |
| # Handle locale-dependent format groups by mapping them out of the format | |
| # string. Note that the order of the [string map] operations is | |
| # significant because later formats can refer to later ones; for example | |
| # %c can refer to %X, which in turn can refer to %T. | |
| set list { | |
| %% %% | |
| %D %m/%d/%Y | |
| %+ {%a %b %e %H:%M:%S %Z %Y} | |
| } | |
| lappend list %EY [string map $list [mc LOCALE_YEAR_FORMAT]] | |
| lappend list %T [string map $list [mc TIME_FORMAT_24_SECS]] | |
| lappend list %R [string map $list [mc TIME_FORMAT_24]] | |
| lappend list %r [string map $list [mc TIME_FORMAT_12]] | |
| lappend list %X [string map $list [mc TIME_FORMAT]] | |
| lappend list %EX [string map $list [mc LOCALE_TIME_FORMAT]] | |
| lappend list %x [string map $list [mc DATE_FORMAT]] | |
| lappend list %Ex [string map $list [mc LOCALE_DATE_FORMAT]] | |
| lappend list %c [string map $list [mc DATE_TIME_FORMAT]] | |
| lappend list %Ec [string map $list [mc LOCALE_DATE_TIME_FORMAT]] | |
| set format [string map $list $format] | |
| ::msgcat::mcset $locale $key $format | |
| return $format | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # FormatNumericTimeZone -- | |
| # | |
| # Formats a time zone as +hhmmss | |
| # | |
| # Parameters: | |
| # z - Time zone in seconds east of Greenwich | |
| # | |
| # Results: | |
| # Returns the time zone formatted in a numeric form | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::FormatNumericTimeZone { z } { | |
| if { $z < 0 } { | |
| set z [expr { - $z }] | |
| set retval - | |
| } else { | |
| set retval + | |
| } | |
| append retval [::format %02d [expr { $z / 3600 }]] | |
| set z [expr { $z % 3600 }] | |
| append retval [::format %02d [expr { $z / 60 }]] | |
| set z [expr { $z % 60 }] | |
| if { $z != 0 } { | |
| append retval [::format %02d $z] | |
| } | |
| return $retval | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # FormatStarDate -- | |
| # | |
| # Formats a date as a StarDate. | |
| # | |
| # Parameters: | |
| # date - Dictionary containing 'year', 'dayOfYear', and | |
| # 'localSeconds' fields. | |
| # | |
| # Results: | |
| # Returns the given date formatted as a StarDate. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| # Jeff Hobbs put this in to support an atrocious pun about Tcl being | |
| # "Enterprise ready." Now we're stuck with it. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::FormatStarDate { date } { | |
| variable Roddenberry | |
| # Get day of year, zero based | |
| set doy [expr { [dict get $date dayOfYear] - 1 }] | |
| # Determine whether the year is a leap year | |
| set lp [IsGregorianLeapYear $date] | |
| # Convert day of year to a fractional year | |
| if { $lp } { | |
| set fractYear [expr { 1000 * $doy / 366 }] | |
| } else { | |
| set fractYear [expr { 1000 * $doy / 365 }] | |
| } | |
| # Put together the StarDate | |
| return [::format "Stardate %02d%03d.%1d" \ | |
| [expr { [dict get $date year] - $Roddenberry }] \ | |
| $fractYear \ | |
| [expr { [dict get $date localSeconds] % 86400 | |
| / ( 86400 / 10 ) }]] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ParseStarDate -- | |
| # | |
| # Parses a StarDate | |
| # | |
| # Parameters: | |
| # year - Year from the Roddenberry epoch | |
| # fractYear - Fraction of a year specifiying the day of year. | |
| # fractDay - Fraction of a day | |
| # | |
| # Results: | |
| # Returns a count of seconds from the Posix epoch. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| # Jeff Hobbs put this in to support an atrocious pun about Tcl being | |
| # "Enterprise ready." Now we're stuck with it. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::ParseStarDate { year fractYear fractDay } { | |
| variable Roddenberry | |
| # Build a tentative date from year and fraction. | |
| set date [dict create \ | |
| gregorian 1 \ | |
| era CE \ | |
| year [expr { $year + $Roddenberry }] \ | |
| dayOfYear [expr { $fractYear * 365 / 1000 + 1 }]] | |
| set date [GetJulianDayFromGregorianEraYearDay $date[set date {}]] | |
| # Determine whether the given year is a leap year | |
| set lp [IsGregorianLeapYear $date] | |
| # Reconvert the fractional year according to whether the given year is a | |
| # leap year | |
| if { $lp } { | |
| dict set date dayOfYear \ | |
| [expr { $fractYear * 366 / 1000 + 1 }] | |
| } else { | |
| dict set date dayOfYear \ | |
| [expr { $fractYear * 365 / 1000 + 1 }] | |
| } | |
| dict unset date julianDay | |
| dict unset date gregorian | |
| set date [GetJulianDayFromGregorianEraYearDay $date[set date {}]] | |
| return [expr { | |
| 86400 * [dict get $date julianDay] | |
| - 210866803200 | |
| + ( 86400 / 10 ) * $fractDay | |
| }] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ScanWide -- | |
| # | |
| # Scans a wide integer from an input | |
| # | |
| # Parameters: | |
| # str - String containing a decimal wide integer | |
| # | |
| # Results: | |
| # Returns the string as a pure wide integer. Throws an error if the | |
| # string is misformatted or out of range. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::ScanWide { str } { | |
| set count [::scan $str {%ld %c} result junk] | |
| if { $count != 1 } { | |
| return -code error -errorcode [list CLOCK notAnInteger $str] \ | |
| "\"$str\" is not an integer" | |
| } | |
| if { [incr result 0] != $str } { | |
| return -code error -errorcode [list CLOCK integervalueTooLarge] \ | |
| "integer value too large to represent" | |
| } | |
| return $result | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # InterpretTwoDigitYear -- | |
| # | |
| # Given a date that contains only the year of the century, determines | |
| # the target value of a two-digit year. | |
| # | |
| # Parameters: | |
| # date - Dictionary containing fields of the date. | |
| # baseTime - Base time relative to which the date is expressed. | |
| # twoDigitField - Name of the field that stores the two-digit year. | |
| # Default is 'yearOfCentury' | |
| # fourDigitField - Name of the field that will receive the four-digit | |
| # year. Default is 'year' | |
| # | |
| # Results: | |
| # Returns the dictionary augmented with the four-digit year, stored in | |
| # the given key. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| # The current rule for interpreting a two-digit year is that the year shall be | |
| # between 1937 and 2037, thus staying within the range of a 32-bit signed | |
| # value for time. This rule may change to a sliding window in future | |
| # versions, so the 'baseTime' parameter (which is currently ignored) is | |
| # provided in the procedure signature. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::InterpretTwoDigitYear { date baseTime | |
| { twoDigitField yearOfCentury } | |
| { fourDigitField year } } { | |
| set yr [dict get $date $twoDigitField] | |
| if { $yr <= 37 } { | |
| dict set date $fourDigitField [expr { $yr + 2000 }] | |
| } else { | |
| dict set date $fourDigitField [expr { $yr + 1900 }] | |
| } | |
| return $date | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # AssignBaseYear -- | |
| # | |
| # Places the number of the current year into a dictionary. | |
| # | |
| # Parameters: | |
| # date - Dictionary value to update | |
| # baseTime - Base time from which to extract the year, expressed | |
| # in seconds from the Posix epoch | |
| # timezone - the time zone in which the date is being scanned | |
| # changeover - the Julian Day on which the Gregorian calendar | |
| # was adopted in the target locale. | |
| # | |
| # Results: | |
| # Returns the dictionary with the current year assigned. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::AssignBaseYear { date baseTime timezone changeover } { | |
| variable TZData | |
| # Find the Julian Day Number corresponding to the base time, and | |
| # find the Gregorian year corresponding to that Julian Day. | |
| set date2 [GetDateFields $baseTime $TZData($timezone) $changeover] | |
| # Store the converted year | |
| dict set date era [dict get $date2 era] | |
| dict set date year [dict get $date2 year] | |
| return $date | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # AssignBaseIso8601Year -- | |
| # | |
| # Determines the base year in the ISO8601 fiscal calendar. | |
| # | |
| # Parameters: | |
| # date - Dictionary containing the fields of the date that | |
| # is to be augmented with the base year. | |
| # baseTime - Base time expressed in seconds from the Posix epoch. | |
| # timeZone - Target time zone | |
| # changeover - Julian Day of adoption of the Gregorian calendar in | |
| # the target locale. | |
| # | |
| # Results: | |
| # Returns the given date with "iso8601Year" set to the | |
| # base year. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::AssignBaseIso8601Year {date baseTime timeZone changeover} { | |
| variable TZData | |
| # Find the Julian Day Number corresponding to the base time | |
| set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover] | |
| # Calculate the ISO8601 date and transfer the year | |
| dict set date era CE | |
| dict set date iso8601Year [dict get $date2 iso8601Year] | |
| return $date | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # AssignBaseMonth -- | |
| # | |
| # Places the number of the current year and month into a | |
| # dictionary. | |
| # | |
| # Parameters: | |
| # date - Dictionary value to update | |
| # baseTime - Time from which the year and month are to be | |
| # obtained, expressed in seconds from the Posix epoch. | |
| # timezone - Name of the desired time zone | |
| # changeover - Julian Day on which the Gregorian calendar was adopted. | |
| # | |
| # Results: | |
| # Returns the dictionary with the base year and month assigned. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::AssignBaseMonth {date baseTime timezone changeover} { | |
| variable TZData | |
| # Find the year and month corresponding to the base time | |
| set date2 [GetDateFields $baseTime $TZData($timezone) $changeover] | |
| dict set date era [dict get $date2 era] | |
| dict set date year [dict get $date2 year] | |
| dict set date month [dict get $date2 month] | |
| return $date | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # AssignBaseWeek -- | |
| # | |
| # Determines the base year and week in the ISO8601 fiscal calendar. | |
| # | |
| # Parameters: | |
| # date - Dictionary containing the fields of the date that | |
| # is to be augmented with the base year and week. | |
| # baseTime - Base time expressed in seconds from the Posix epoch. | |
| # changeover - Julian Day on which the Gregorian calendar was adopted | |
| # in the target locale. | |
| # | |
| # Results: | |
| # Returns the given date with "iso8601Year" set to the | |
| # base year and "iso8601Week" to the week number. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::AssignBaseWeek {date baseTime timeZone changeover} { | |
| variable TZData | |
| # Find the Julian Day Number corresponding to the base time | |
| set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover] | |
| # Calculate the ISO8601 date and transfer the year | |
| dict set date era CE | |
| dict set date iso8601Year [dict get $date2 iso8601Year] | |
| dict set date iso8601Week [dict get $date2 iso8601Week] | |
| return $date | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # AssignBaseJulianDay -- | |
| # | |
| # Determines the base day for a time-of-day conversion. | |
| # | |
| # Parameters: | |
| # date - Dictionary that is to get the base day | |
| # baseTime - Base time expressed in seconds from the Posix epoch | |
| # changeover - Julian day on which the Gregorian calendar was | |
| # adpoted in the target locale. | |
| # | |
| # Results: | |
| # Returns the given dictionary augmented with a 'julianDay' field | |
| # that contains the base day. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::AssignBaseJulianDay { date baseTime timeZone changeover } { | |
| variable TZData | |
| # Find the Julian Day Number corresponding to the base time | |
| set date2 [GetDateFields $baseTime $TZData($timeZone) $changeover] | |
| dict set date julianDay [dict get $date2 julianDay] | |
| return $date | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # InterpretHMSP -- | |
| # | |
| # Interprets a time in the form "hh:mm:ss am". | |
| # | |
| # Parameters: | |
| # date -- Dictionary containing "hourAMPM", "minute", "second" | |
| # and "amPmIndicator" fields. | |
| # | |
| # Results: | |
| # Returns the number of seconds from local midnight. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::InterpretHMSP { date } { | |
| set hr [dict get $date hourAMPM] | |
| if { $hr == 12 } { | |
| set hr 0 | |
| } | |
| if { [dict get $date amPmIndicator] } { | |
| incr hr 12 | |
| } | |
| dict set date hour $hr | |
| return [InterpretHMS $date[set date {}]] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # InterpretHMS -- | |
| # | |
| # Interprets a 24-hour time "hh:mm:ss" | |
| # | |
| # Parameters: | |
| # date -- Dictionary containing the "hour", "minute" and "second" | |
| # fields. | |
| # | |
| # Results: | |
| # Returns the given dictionary augmented with a "secondOfDay" | |
| # field containing the number of seconds from local midnight. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::InterpretHMS { date } { | |
| return [expr { | |
| ( [dict get $date hour] * 60 | |
| + [dict get $date minute] ) * 60 | |
| + [dict get $date second] | |
| }] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # GetSystemTimeZone -- | |
| # | |
| # Determines the system time zone, which is the default for the | |
| # 'clock' command if no other zone is supplied. | |
| # | |
| # Parameters: | |
| # None. | |
| # | |
| # Results: | |
| # Returns the system time zone. | |
| # | |
| # Side effects: | |
| # Stores the sustem time zone in the 'CachedSystemTimeZone' | |
| # variable, since determining it may be an expensive process. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::GetSystemTimeZone {} { | |
| variable CachedSystemTimeZone | |
| variable TimeZoneBad | |
| if {[set result [getenv TCL_TZ]] ne {}} { | |
| set timezone $result | |
| } elseif {[set result [getenv TZ]] ne {}} { | |
| set timezone $result | |
| } else { | |
| # Cache the time zone only if it was detected by one of the | |
| # expensive methods. | |
| if { [info exists CachedSystemTimeZone] } { | |
| set timezone $CachedSystemTimeZone | |
| } elseif { $::tcl_platform(platform) eq {windows} } { | |
| set timezone [GuessWindowsTimeZone] | |
| } elseif { [file exists /etc/localtime] | |
| && ![catch {ReadZoneinfoFile \ | |
| Tcl/Localtime /etc/localtime}] } { | |
| set timezone :Tcl/Localtime | |
| } else { | |
| set timezone :localtime | |
| } | |
| set CachedSystemTimeZone $timezone | |
| } | |
| if { ![dict exists $TimeZoneBad $timezone] } { | |
| dict set TimeZoneBad $timezone [catch {SetupTimeZone $timezone}] | |
| } | |
| if { [dict get $TimeZoneBad $timezone] } { | |
| return :localtime | |
| } else { | |
| return $timezone | |
| } | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ConvertLegacyTimeZone -- | |
| # | |
| # Given an alphanumeric time zone identifier and the system time zone, | |
| # convert the alphanumeric identifier to an unambiguous time zone. | |
| # | |
| # Parameters: | |
| # tzname - Name of the time zone to convert | |
| # | |
| # Results: | |
| # Returns a time zone name corresponding to tzname, but in an | |
| # unambiguous form, generally +hhmm. | |
| # | |
| # This procedure is implemented primarily to allow the parsing of RFC822 | |
| # date/time strings. Processing a time zone name on input is not recommended | |
| # practice, because there is considerable room for ambiguity; for instance, is | |
| # BST Brazilian Standard Time, or British Summer Time? | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::ConvertLegacyTimeZone { tzname } { | |
| variable LegacyTimeZone | |
| set tzname [string tolower $tzname] | |
| if { ![dict exists $LegacyTimeZone $tzname] } { | |
| return -code error -errorcode [list CLOCK badTZName $tzname] \ | |
| "time zone \"$tzname\" not found" | |
| } | |
| return [dict get $LegacyTimeZone $tzname] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # SetupTimeZone -- | |
| # | |
| # Given the name or specification of a time zone, sets up its in-memory | |
| # data. | |
| # | |
| # Parameters: | |
| # tzname - Name of a time zone | |
| # | |
| # Results: | |
| # Unless the time zone is ':localtime', sets the TZData array to contain | |
| # the lookup table for local<->UTC conversion. Returns an error if the | |
| # time zone cannot be parsed. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::SetupTimeZone { timezone } { | |
| variable TZData | |
| if {! [info exists TZData($timezone)] } { | |
| variable MINWIDE | |
| if { $timezone eq {:localtime} } { | |
| # Nothing to do, we'll convert using the localtime function | |
| } elseif { | |
| [regexp {^([-+])(\d\d)(?::?(\d\d)(?::?(\d\d))?)?} $timezone \ | |
| -> s hh mm ss] | |
| } then { | |
| # Make a fixed offset | |
| ::scan $hh %d hh | |
| if { $mm eq {} } { | |
| set mm 0 | |
| } else { | |
| ::scan $mm %d mm | |
| } | |
| if { $ss eq {} } { | |
| set ss 0 | |
| } else { | |
| ::scan $ss %d ss | |
| } | |
| set offset [expr { ( $hh * 60 + $mm ) * 60 + $ss }] | |
| if { $s eq {-} } { | |
| set offset [expr { - $offset }] | |
| } | |
| set TZData($timezone) [list [list $MINWIDE $offset -1 $timezone]] | |
| } elseif { [string index $timezone 0] eq {:} } { | |
| # Convert using a time zone file | |
| if { | |
| [catch { | |
| LoadTimeZoneFile [string range $timezone 1 end] | |
| }] && [catch { | |
| LoadZoneinfoFile [string range $timezone 1 end] | |
| }] | |
| } then { | |
| return -code error \ | |
| -errorcode [list CLOCK badTimeZone $timezone] \ | |
| "time zone \"$timezone\" not found" | |
| } | |
| } elseif { ![catch {ParsePosixTimeZone $timezone} tzfields] } { | |
| # This looks like a POSIX time zone - try to process it | |
| if { [catch {ProcessPosixTimeZone $tzfields} data opts] } { | |
| if { [lindex [dict get $opts -errorcode] 0] eq {CLOCK} } { | |
| dict unset opts -errorinfo | |
| } | |
| return -options $opts $data | |
| } else { | |
| set TZData($timezone) $data | |
| } | |
| } else { | |
| # We couldn't parse this as a POSIX time zone. Try again with a | |
| # time zone file - this time without a colon | |
| if { [catch { LoadTimeZoneFile $timezone }] | |
| && [catch { LoadZoneinfoFile $timezone } - opts] } { | |
| dict unset opts -errorinfo | |
| return -options $opts "time zone $timezone not found" | |
| } | |
| set TZData($timezone) $TZData(:$timezone) | |
| } | |
| } | |
| return | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # GuessWindowsTimeZone -- | |
| # | |
| # Determines the system time zone on windows. | |
| # | |
| # Parameters: | |
| # None. | |
| # | |
| # Results: | |
| # Returns a time zone specifier that corresponds to the system time zone | |
| # information found in the Registry. | |
| # | |
| # Bugs: | |
| # Fixed dates for DST change are unimplemented at present, because no | |
| # time zone information supplied with Windows actually uses them! | |
| # | |
| # On a Windows system where neither $env(TCL_TZ) nor $env(TZ) is specified, | |
| # GuessWindowsTimeZone looks in the Registry for the system time zone | |
| # information. It then attempts to find an entry in WinZoneInfo for a time | |
| # zone that uses the same rules. If it finds one, it returns it; otherwise, | |
| # it constructs a Posix-style time zone string and returns that. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::GuessWindowsTimeZone {} { | |
| variable WinZoneInfo | |
| variable NoRegistry | |
| variable TimeZoneBad | |
| if { [info exists NoRegistry] } { | |
| return :localtime | |
| } | |
| # Dredge time zone information out of the registry | |
| if { [catch { | |
| set rpath HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\TimeZoneInformation | |
| set data [list \ | |
| [expr { -60 | |
| * [registry get $rpath Bias] }] \ | |
| [expr { -60 | |
| * [registry get $rpath StandardBias] }] \ | |
| [expr { -60 \ | |
| * [registry get $rpath DaylightBias] }]] | |
| set stdtzi [registry get $rpath StandardStart] | |
| foreach ind {0 2 14 4 6 8 10 12} { | |
| binary scan $stdtzi @${ind}s val | |
| lappend data $val | |
| } | |
| set daytzi [registry get $rpath DaylightStart] | |
| foreach ind {0 2 14 4 6 8 10 12} { | |
| binary scan $daytzi @${ind}s val | |
| lappend data $val | |
| } | |
| }] } { | |
| # Missing values in the Registry - bail out | |
| return :localtime | |
| } | |
| # Make up a Posix time zone specifier if we can't find one. Check here | |
| # that the tzdata file exists, in case we're running in an environment | |
| # (e.g. starpack) where tzdata is incomplete. (Bug 1237907) | |
| if { [dict exists $WinZoneInfo $data] } { | |
| set tzname [dict get $WinZoneInfo $data] | |
| if { ! [dict exists $TimeZoneBad $tzname] } { | |
| dict set TimeZoneBad $tzname [catch {SetupTimeZone $tzname}] | |
| } | |
| } else { | |
| set tzname {} | |
| } | |
| if { $tzname eq {} || [dict get $TimeZoneBad $tzname] } { | |
| lassign $data \ | |
| bias stdBias dstBias \ | |
| stdYear stdMonth stdDayOfWeek stdDayOfMonth \ | |
| stdHour stdMinute stdSecond stdMillisec \ | |
| dstYear dstMonth dstDayOfWeek dstDayOfMonth \ | |
| dstHour dstMinute dstSecond dstMillisec | |
| set stdDelta [expr { $bias + $stdBias }] | |
| set dstDelta [expr { $bias + $dstBias }] | |
| if { $stdDelta <= 0 } { | |
| set stdSignum + | |
| set stdDelta [expr { - $stdDelta }] | |
| set dispStdSignum - | |
| } else { | |
| set stdSignum - | |
| set dispStdSignum + | |
| } | |
| set hh [::format %02d [expr { $stdDelta / 3600 }]] | |
| set mm [::format %02d [expr { ($stdDelta / 60 ) % 60 }]] | |
| set ss [::format %02d [expr { $stdDelta % 60 }]] | |
| set tzname {} | |
| append tzname < $dispStdSignum $hh $mm > $stdSignum $hh : $mm : $ss | |
| if { $stdMonth >= 0 } { | |
| if { $dstDelta <= 0 } { | |
| set dstSignum + | |
| set dstDelta [expr { - $dstDelta }] | |
| set dispDstSignum - | |
| } else { | |
| set dstSignum - | |
| set dispDstSignum + | |
| } | |
| set hh [::format %02d [expr { $dstDelta / 3600 }]] | |
| set mm [::format %02d [expr { ($dstDelta / 60 ) % 60 }]] | |
| set ss [::format %02d [expr { $dstDelta % 60 }]] | |
| append tzname < $dispDstSignum $hh $mm > $dstSignum $hh : $mm : $ss | |
| if { $dstYear == 0 } { | |
| append tzname ,M $dstMonth . $dstDayOfMonth . $dstDayOfWeek | |
| } else { | |
| # I have not been able to find any locale on which Windows | |
| # converts time zone on a fixed day of the year, hence don't | |
| # know how to interpret the fields. If someone can inform me, | |
| # I'd be glad to code it up. For right now, we bail out in | |
| # such a case. | |
| return :localtime | |
| } | |
| append tzname / [::format %02d $dstHour] \ | |
| : [::format %02d $dstMinute] \ | |
| : [::format %02d $dstSecond] | |
| if { $stdYear == 0 } { | |
| append tzname ,M $stdMonth . $stdDayOfMonth . $stdDayOfWeek | |
| } else { | |
| # I have not been able to find any locale on which Windows | |
| # converts time zone on a fixed day of the year, hence don't | |
| # know how to interpret the fields. If someone can inform me, | |
| # I'd be glad to code it up. For right now, we bail out in | |
| # such a case. | |
| return :localtime | |
| } | |
| append tzname / [::format %02d $stdHour] \ | |
| : [::format %02d $stdMinute] \ | |
| : [::format %02d $stdSecond] | |
| } | |
| dict set WinZoneInfo $data $tzname | |
| } | |
| return [dict get $WinZoneInfo $data] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # LoadTimeZoneFile -- | |
| # | |
| # Load the data file that specifies the conversion between a | |
| # given time zone and Greenwich. | |
| # | |
| # Parameters: | |
| # fileName -- Name of the file to load | |
| # | |
| # Results: | |
| # None. | |
| # | |
| # Side effects: | |
| # TZData(:fileName) contains the time zone data | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::LoadTimeZoneFile { fileName } { | |
| variable DataDir | |
| variable TZData | |
| if { [info exists TZData($fileName)] } { | |
| return | |
| } | |
| # Since an unsafe interp uses the [clock] command in the parent, this code | |
| # is security sensitive. Make sure that the path name cannot escape the | |
| # given directory. | |
| if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } { | |
| return -code error \ | |
| -errorcode [list CLOCK badTimeZone $:fileName] \ | |
| "time zone \":$fileName\" not valid" | |
| } | |
| try { | |
| source -encoding utf-8 [file join $DataDir $fileName] | |
| } on error {} { | |
| return -code error \ | |
| -errorcode [list CLOCK badTimeZone :$fileName] \ | |
| "time zone \":$fileName\" not found" | |
| } | |
| return | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # LoadZoneinfoFile -- | |
| # | |
| # Loads a binary time zone information file in Olson format. | |
| # | |
| # Parameters: | |
| # fileName - Relative path name of the file to load. | |
| # | |
| # Results: | |
| # Returns an empty result normally; returns an error if no Olson file | |
| # was found or the file was malformed in some way. | |
| # | |
| # Side effects: | |
| # TZData(:fileName) contains the time zone data | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::LoadZoneinfoFile { fileName } { | |
| variable ZoneinfoPaths | |
| # Since an unsafe interp uses the [clock] command in the parent, this code | |
| # is security sensitive. Make sure that the path name cannot escape the | |
| # given directory. | |
| if { ![regexp {^[[.-.][:alpha:]_]+(?:/[[.-.][:alpha:]_]+)*$} $fileName] } { | |
| return -code error \ | |
| -errorcode [list CLOCK badTimeZone $:fileName] \ | |
| "time zone \":$fileName\" not valid" | |
| } | |
| foreach d $ZoneinfoPaths { | |
| set fname [file join $d $fileName] | |
| if { [file readable $fname] && [file isfile $fname] } { | |
| break | |
| } | |
| unset fname | |
| } | |
| ReadZoneinfoFile $fileName $fname | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ReadZoneinfoFile -- | |
| # | |
| # Loads a binary time zone information file in Olson format. | |
| # | |
| # Parameters: | |
| # fileName - Name of the time zone (relative path name of the | |
| # file). | |
| # fname - Absolute path name of the file. | |
| # | |
| # Results: | |
| # Returns an empty result normally; returns an error if no Olson file | |
| # was found or the file was malformed in some way. | |
| # | |
| # Side effects: | |
| # TZData(:fileName) contains the time zone data | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::ReadZoneinfoFile {fileName fname} { | |
| variable MINWIDE | |
| variable TZData | |
| if { ![file exists $fname] } { | |
| return -code error "$fileName not found" | |
| } | |
| if { [file size $fname] > 262144 } { | |
| return -code error "$fileName too big" | |
| } | |
| # Suck in all the data from the file | |
| set f [open $fname r] | |
| fconfigure $f -translation binary | |
| set d [read $f] | |
| close $f | |
| # The file begins with a magic number, sixteen reserved bytes, and then | |
| # six 4-byte integers giving counts of fileds in the file. | |
| binary scan $d a4a1x15IIIIII \ | |
| magic version nIsGMT nIsStd nLeap nTime nType nChar | |
| set seek 44 | |
| set ilen 4 | |
| set iformat I | |
| if { $magic != {TZif} } { | |
| return -code error "$fileName not a time zone information file" | |
| } | |
| if { $nType > 255 } { | |
| return -code error "$fileName contains too many time types" | |
| } | |
| # Accept only Posix-style zoneinfo. Sorry, 'leaps' bigots. | |
| if { $nLeap != 0 } { | |
| return -code error "$fileName contains leap seconds" | |
| } | |
| # In a version 2 file, we use the second part of the file, which contains | |
| # 64-bit transition times. | |
| if {$version eq "2"} { | |
| set seek [expr { | |
| 44 | |
| + 5 * $nTime | |
| + 6 * $nType | |
| + 4 * $nLeap | |
| + $nIsStd | |
| + $nIsGMT | |
| + $nChar | |
| }] | |
| binary scan $d @${seek}a4a1x15IIIIII \ | |
| magic version nIsGMT nIsStd nLeap nTime nType nChar | |
| if {$magic ne {TZif}} { | |
| return -code error "seek address $seek miscomputed, magic = $magic" | |
| } | |
| set iformat W | |
| set ilen 8 | |
| incr seek 44 | |
| } | |
| # Next come ${nTime} transition times, followed by ${nTime} time type | |
| # codes. The type codes are unsigned 1-byte quantities. We insert an | |
| # arbitrary start time in front of the transitions. | |
| binary scan $d @${seek}${iformat}${nTime}c${nTime} times tempCodes | |
| incr seek [expr { ($ilen + 1) * $nTime }] | |
| set times [linsert $times 0 $MINWIDE] | |
| set codes {} | |
| foreach c $tempCodes { | |
| lappend codes [expr { $c & 0xFF }] | |
| } | |
| set codes [linsert $codes 0 0] | |
| # Next come ${nType} time type descriptions, each of which has an offset | |
| # (seconds east of GMT), a DST indicator, and an index into the | |
| # abbreviation text. | |
| for { set i 0 } { $i < $nType } { incr i } { | |
| binary scan $d @${seek}Icc gmtOff isDst abbrInd | |
| lappend types [list $gmtOff $isDst $abbrInd] | |
| incr seek 6 | |
| } | |
| # Next come $nChar characters of time zone name abbreviations, which are | |
| # null-terminated. | |
| # We build them up into a dictionary indexed by character index, because | |
| # that's what's in the indices above. | |
| binary scan $d @${seek}a${nChar} abbrs | |
| incr seek ${nChar} | |
| set abbrList [split $abbrs \0] | |
| set i 0 | |
| set abbrevs {} | |
| foreach a $abbrList { | |
| for {set j 0} {$j <= [string length $a]} {incr j} { | |
| dict set abbrevs $i [string range $a $j end] | |
| incr i | |
| } | |
| } | |
| # Package up a list of tuples, each of which contains transition time, | |
| # seconds east of Greenwich, DST flag and time zone abbreviation. | |
| set r {} | |
| set lastTime $MINWIDE | |
| foreach t $times c $codes { | |
| if { $t < $lastTime } { | |
| return -code error "$fileName has times out of order" | |
| } | |
| set lastTime $t | |
| lassign [lindex $types $c] gmtoff isDst abbrInd | |
| set abbrev [dict get $abbrevs $abbrInd] | |
| lappend r [list $t $gmtoff $isDst $abbrev] | |
| } | |
| # In a version 2 file, there is also a POSIX-style time zone description | |
| # at the very end of the file. To get to it, skip over nLeap leap second | |
| # values (8 bytes each), | |
| # nIsStd standard/DST indicators and nIsGMT UTC/local indicators. | |
| if {$version eq {2}} { | |
| set seek [expr {$seek + 8 * $nLeap + $nIsStd + $nIsGMT + 1}] | |
| set last [string first \n $d $seek] | |
| set posix [string range $d $seek [expr {$last-1}]] | |
| if {[llength $posix] > 0} { | |
| set posixFields [ParsePosixTimeZone $posix] | |
| foreach tuple [ProcessPosixTimeZone $posixFields] { | |
| lassign $tuple t gmtoff isDst abbrev | |
| if {$t > $lastTime} { | |
| lappend r $tuple | |
| } | |
| } | |
| } | |
| } | |
| set TZData(:$fileName) $r | |
| return | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ParsePosixTimeZone -- | |
| # | |
| # Parses the TZ environment variable in Posix form | |
| # | |
| # Parameters: | |
| # tz Time zone specifier to be interpreted | |
| # | |
| # Results: | |
| # Returns a dictionary whose values contain the various pieces of the | |
| # time zone specification. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| # Errors: | |
| # Throws an error if the syntax of the time zone is incorrect. | |
| # | |
| # The following keys are present in the dictionary: | |
| # stdName - Name of the time zone when Daylight Saving Time | |
| # is not in effect. | |
| # stdSignum - Sign (+, -, or empty) of the offset from Greenwich | |
| # to the given (non-DST) time zone. + and the empty | |
| # string denote zones west of Greenwich, - denotes east | |
| # of Greenwich; this is contrary to the ISO convention | |
| # but follows Posix. | |
| # stdHours - Hours part of the offset from Greenwich to the given | |
| # (non-DST) time zone. | |
| # stdMinutes - Minutes part of the offset from Greenwich to the | |
| # given (non-DST) time zone. Empty denotes zero. | |
| # stdSeconds - Seconds part of the offset from Greenwich to the | |
| # given (non-DST) time zone. Empty denotes zero. | |
| # dstName - Name of the time zone when DST is in effect, or the | |
| # empty string if the time zone does not observe Daylight | |
| # Saving Time. | |
| # dstSignum, dstHours, dstMinutes, dstSeconds - | |
| # Fields corresponding to stdSignum, stdHours, stdMinutes, | |
| # stdSeconds for the Daylight Saving Time version of the | |
| # time zone. If dstHours is empty, it is presumed to be 1. | |
| # startDayOfYear - The ordinal number of the day of the year on which | |
| # Daylight Saving Time begins. If this field is | |
| # empty, then DST begins on a given month-week-day, | |
| # as below. | |
| # startJ - The letter J, or an empty string. If a J is present in | |
| # this field, then startDayOfYear does not count February 29 | |
| # even in leap years. | |
| # startMonth - The number of the month in which Daylight Saving Time | |
| # begins, supplied if startDayOfYear is empty. If both | |
| # startDayOfYear and startMonth are empty, then US rules | |
| # are presumed. | |
| # startWeekOfMonth - The number of the week in the month in which | |
| # Daylight Saving Time begins, in the range 1-5. | |
| # 5 denotes the last week of the month even in a | |
| # 4-week month. | |
| # startDayOfWeek - The number of the day of the week (Sunday=0, | |
| # Saturday=6) on which Daylight Saving Time begins. | |
| # startHours - The hours part of the time of day at which Daylight | |
| # Saving Time begins. An empty string is presumed to be 2. | |
| # startMinutes - The minutes part of the time of day at which DST begins. | |
| # An empty string is presumed zero. | |
| # startSeconds - The seconds part of the time of day at which DST begins. | |
| # An empty string is presumed zero. | |
| # endDayOfYear, endJ, endMonth, endWeekOfMonth, endDayOfWeek, | |
| # endHours, endMinutes, endSeconds - | |
| # Specify the end of DST in the same way that the start* fields | |
| # specify the beginning of DST. | |
| # | |
| # This procedure serves only to break the time specifier into fields. No | |
| # attempt is made to canonicalize the fields or supply default values. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::ParsePosixTimeZone { tz } { | |
| if {[regexp -expanded -nocase -- { | |
| ^ | |
| # 1 - Standard time zone name | |
| ([[:alpha:]]+ | <[-+[:alnum:]]+>) | |
| # 2 - Standard time zone offset, signum | |
| ([-+]?) | |
| # 3 - Standard time zone offset, hours | |
| ([[:digit:]]{1,2}) | |
| (?: | |
| # 4 - Standard time zone offset, minutes | |
| : ([[:digit:]]{1,2}) | |
| (?: | |
| # 5 - Standard time zone offset, seconds | |
| : ([[:digit:]]{1,2} ) | |
| )? | |
| )? | |
| (?: | |
| # 6 - DST time zone name | |
| ([[:alpha:]]+ | <[-+[:alnum:]]+>) | |
| (?: | |
| (?: | |
| # 7 - DST time zone offset, signum | |
| ([-+]?) | |
| # 8 - DST time zone offset, hours | |
| ([[:digit:]]{1,2}) | |
| (?: | |
| # 9 - DST time zone offset, minutes | |
| : ([[:digit:]]{1,2}) | |
| (?: | |
| # 10 - DST time zone offset, seconds | |
| : ([[:digit:]]{1,2}) | |
| )? | |
| )? | |
| )? | |
| (?: | |
| , | |
| (?: | |
| # 11 - Optional J in n and Jn form 12 - Day of year | |
| ( J ? ) ( [[:digit:]]+ ) | |
| | M | |
| # 13 - Month number 14 - Week of month 15 - Day of week | |
| ( [[:digit:]] + ) | |
| [.] ( [[:digit:]] + ) | |
| [.] ( [[:digit:]] + ) | |
| ) | |
| (?: | |
| # 16 - Start time of DST - hours | |
| / ( [[:digit:]]{1,2} ) | |
| (?: | |
| # 17 - Start time of DST - minutes | |
| : ( [[:digit:]]{1,2} ) | |
| (?: | |
| # 18 - Start time of DST - seconds | |
| : ( [[:digit:]]{1,2} ) | |
| )? | |
| )? | |
| )? | |
| , | |
| (?: | |
| # 19 - Optional J in n and Jn form 20 - Day of year | |
| ( J ? ) ( [[:digit:]]+ ) | |
| | M | |
| # 21 - Month number 22 - Week of month 23 - Day of week | |
| ( [[:digit:]] + ) | |
| [.] ( [[:digit:]] + ) | |
| [.] ( [[:digit:]] + ) | |
| ) | |
| (?: | |
| # 24 - End time of DST - hours | |
| / ( [[:digit:]]{1,2} ) | |
| (?: | |
| # 25 - End time of DST - minutes | |
| : ( [[:digit:]]{1,2} ) | |
| (?: | |
| # 26 - End time of DST - seconds | |
| : ( [[:digit:]]{1,2} ) | |
| )? | |
| )? | |
| )? | |
| )? | |
| )? | |
| )? | |
| $ | |
| } $tz -> x(stdName) x(stdSignum) x(stdHours) x(stdMinutes) x(stdSeconds) \ | |
| x(dstName) x(dstSignum) x(dstHours) x(dstMinutes) x(dstSeconds) \ | |
| x(startJ) x(startDayOfYear) \ | |
| x(startMonth) x(startWeekOfMonth) x(startDayOfWeek) \ | |
| x(startHours) x(startMinutes) x(startSeconds) \ | |
| x(endJ) x(endDayOfYear) \ | |
| x(endMonth) x(endWeekOfMonth) x(endDayOfWeek) \ | |
| x(endHours) x(endMinutes) x(endSeconds)] } { | |
| # it's a good timezone | |
| return [array get x] | |
| } | |
| return -code error\ | |
| -errorcode [list CLOCK badTimeZone $tz] \ | |
| "unable to parse time zone specification \"$tz\"" | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ProcessPosixTimeZone -- | |
| # | |
| # Handle a Posix time zone after it's been broken out into fields. | |
| # | |
| # Parameters: | |
| # z - Dictionary returned from 'ParsePosixTimeZone' | |
| # | |
| # Results: | |
| # Returns time zone information for the 'TZData' array. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::ProcessPosixTimeZone { z } { | |
| variable MINWIDE | |
| variable TZData | |
| # Determine the standard time zone name and seconds east of Greenwich | |
| set stdName [dict get $z stdName] | |
| if { [string index $stdName 0] eq {<} } { | |
| set stdName [string range $stdName 1 end-1] | |
| } | |
| if { [dict get $z stdSignum] eq {-} } { | |
| set stdSignum +1 | |
| } else { | |
| set stdSignum -1 | |
| } | |
| set stdHours [lindex [::scan [dict get $z stdHours] %d] 0] | |
| if { [dict get $z stdMinutes] ne {} } { | |
| set stdMinutes [lindex [::scan [dict get $z stdMinutes] %d] 0] | |
| } else { | |
| set stdMinutes 0 | |
| } | |
| if { [dict get $z stdSeconds] ne {} } { | |
| set stdSeconds [lindex [::scan [dict get $z stdSeconds] %d] 0] | |
| } else { | |
| set stdSeconds 0 | |
| } | |
| set stdOffset [expr { | |
| (($stdHours * 60 + $stdMinutes) * 60 + $stdSeconds) * $stdSignum | |
| }] | |
| set data [list [list $MINWIDE $stdOffset 0 $stdName]] | |
| # If there's no daylight zone, we're done | |
| set dstName [dict get $z dstName] | |
| if { $dstName eq {} } { | |
| return $data | |
| } | |
| if { [string index $dstName 0] eq {<} } { | |
| set dstName [string range $dstName 1 end-1] | |
| } | |
| # Determine the daylight name | |
| if { [dict get $z dstSignum] eq {-} } { | |
| set dstSignum +1 | |
| } else { | |
| set dstSignum -1 | |
| } | |
| if { [dict get $z dstHours] eq {} } { | |
| set dstOffset [expr { 3600 + $stdOffset }] | |
| } else { | |
| set dstHours [lindex [::scan [dict get $z dstHours] %d] 0] | |
| if { [dict get $z dstMinutes] ne {} } { | |
| set dstMinutes [lindex [::scan [dict get $z dstMinutes] %d] 0] | |
| } else { | |
| set dstMinutes 0 | |
| } | |
| if { [dict get $z dstSeconds] ne {} } { | |
| set dstSeconds [lindex [::scan [dict get $z dstSeconds] %d] 0] | |
| } else { | |
| set dstSeconds 0 | |
| } | |
| set dstOffset [expr { | |
| (($dstHours*60 + $dstMinutes) * 60 + $dstSeconds) * $dstSignum | |
| }] | |
| } | |
| # Fill in defaults for European or US DST rules | |
| # US start time is the second Sunday in March | |
| # EU start time is the last Sunday in March | |
| # US end time is the first Sunday in November. | |
| # EU end time is the last Sunday in October | |
| if { | |
| [dict get $z startDayOfYear] eq {} | |
| && [dict get $z startMonth] eq {} | |
| } then { | |
| if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} { | |
| # EU | |
| dict set z startWeekOfMonth 5 | |
| if {$stdHours>2} { | |
| dict set z startHours 2 | |
| } else { | |
| dict set z startHours [expr {$stdHours+1}] | |
| } | |
| } else { | |
| # US | |
| dict set z startWeekOfMonth 2 | |
| dict set z startHours 2 | |
| } | |
| dict set z startMonth 3 | |
| dict set z startDayOfWeek 0 | |
| dict set z startMinutes 0 | |
| dict set z startSeconds 0 | |
| } | |
| if { | |
| [dict get $z endDayOfYear] eq {} | |
| && [dict get $z endMonth] eq {} | |
| } then { | |
| if {($stdSignum * $stdHours>=0) && ($stdSignum * $stdHours<=12)} { | |
| # EU | |
| dict set z endMonth 10 | |
| dict set z endWeekOfMonth 5 | |
| if {$stdHours>2} { | |
| dict set z endHours 3 | |
| } else { | |
| dict set z endHours [expr {$stdHours+2}] | |
| } | |
| } else { | |
| # US | |
| dict set z endMonth 11 | |
| dict set z endWeekOfMonth 1 | |
| dict set z endHours 2 | |
| } | |
| dict set z endDayOfWeek 0 | |
| dict set z endMinutes 0 | |
| dict set z endSeconds 0 | |
| } | |
| # Put DST in effect in all years from 1916 to 2099. | |
| for { set y 1916 } { $y < 2100 } { incr y } { | |
| set startTime [DeterminePosixDSTTime $z start $y] | |
| incr startTime [expr { - wide($stdOffset) }] | |
| set endTime [DeterminePosixDSTTime $z end $y] | |
| incr endTime [expr { - wide($dstOffset) }] | |
| if { $startTime < $endTime } { | |
| lappend data \ | |
| [list $startTime $dstOffset 1 $dstName] \ | |
| [list $endTime $stdOffset 0 $stdName] | |
| } else { | |
| lappend data \ | |
| [list $endTime $stdOffset 0 $stdName] \ | |
| [list $startTime $dstOffset 1 $dstName] | |
| } | |
| } | |
| return $data | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # DeterminePosixDSTTime -- | |
| # | |
| # Determines the time that Daylight Saving Time starts or ends from a | |
| # Posix time zone specification. | |
| # | |
| # Parameters: | |
| # z - Time zone data returned from ParsePosixTimeZone. | |
| # Missing fields are expected to be filled in with | |
| # default values. | |
| # bound - The word 'start' or 'end' | |
| # y - The year for which the transition time is to be determined. | |
| # | |
| # Results: | |
| # Returns the transition time as a count of seconds from the epoch. The | |
| # time is relative to the wall clock, not UTC. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::DeterminePosixDSTTime { z bound y } { | |
| variable FEB_28 | |
| # Determine the start or end day of DST | |
| set date [dict create era CE year $y] | |
| set doy [dict get $z ${bound}DayOfYear] | |
| if { $doy ne {} } { | |
| # Time was specified as a day of the year | |
| if { [dict get $z ${bound}J] ne {} | |
| && [IsGregorianLeapYear $y] | |
| && ( $doy > $FEB_28 ) } { | |
| incr doy | |
| } | |
| dict set date dayOfYear $doy | |
| set date [GetJulianDayFromEraYearDay $date[set date {}] 2361222] | |
| } else { | |
| # Time was specified as a day of the week within a month | |
| dict set date month [dict get $z ${bound}Month] | |
| dict set date dayOfWeek [dict get $z ${bound}DayOfWeek] | |
| set dowim [dict get $z ${bound}WeekOfMonth] | |
| if { $dowim >= 5 } { | |
| set dowim -1 | |
| } | |
| dict set date dayOfWeekInMonth $dowim | |
| set date [GetJulianDayFromEraYearMonthWeekDay $date[set date {}] 2361222] | |
| } | |
| set jd [dict get $date julianDay] | |
| set seconds [expr { | |
| wide($jd) * wide(86400) - wide(210866803200) | |
| }] | |
| set h [dict get $z ${bound}Hours] | |
| if { $h eq {} } { | |
| set h 2 | |
| } else { | |
| set h [lindex [::scan $h %d] 0] | |
| } | |
| set m [dict get $z ${bound}Minutes] | |
| if { $m eq {} } { | |
| set m 0 | |
| } else { | |
| set m [lindex [::scan $m %d] 0] | |
| } | |
| set s [dict get $z ${bound}Seconds] | |
| if { $s eq {} } { | |
| set s 0 | |
| } else { | |
| set s [lindex [::scan $s %d] 0] | |
| } | |
| set tod [expr { ( $h * 60 + $m ) * 60 + $s }] | |
| return [expr { $seconds + $tod }] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # GetLocaleEra -- | |
| # | |
| # Given local time expressed in seconds from the Posix epoch, | |
| # determine localized era and year within the era. | |
| # | |
| # Parameters: | |
| # date - Dictionary that must contain the keys, 'localSeconds', | |
| # whose value is expressed as the appropriate local time; | |
| # and 'year', whose value is the Gregorian year. | |
| # etable - Value of the LOCALE_ERAS key in the message catalogue | |
| # for the target locale. | |
| # | |
| # Results: | |
| # Returns the dictionary, augmented with the keys, 'localeEra' and | |
| # 'localeYear'. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::GetLocaleEra { date etable } { | |
| set index [BSearch $etable [dict get $date localSeconds]] | |
| if { $index < 0} { | |
| dict set date localeEra \ | |
| [::format %02d [expr { [dict get $date year] / 100 }]] | |
| dict set date localeYear [expr { | |
| [dict get $date year] % 100 | |
| }] | |
| } else { | |
| dict set date localeEra [lindex $etable $index 1] | |
| dict set date localeYear [expr { | |
| [dict get $date year] - [lindex $etable $index 2] | |
| }] | |
| } | |
| return $date | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # GetJulianDayFromEraYearDay -- | |
| # | |
| # Given a year, month and day on the Gregorian calendar, determines | |
| # the Julian Day Number beginning at noon on that date. | |
| # | |
| # Parameters: | |
| # date -- A dictionary in which the 'era', 'year', and | |
| # 'dayOfYear' slots are populated. The calendar in use | |
| # is determined by the date itself relative to: | |
| # changeover -- Julian day on which the Gregorian calendar was | |
| # adopted in the current locale. | |
| # | |
| # Results: | |
| # Returns the given dictionary augmented with a 'julianDay' key whose | |
| # value is the desired Julian Day Number, and a 'gregorian' key that | |
| # specifies whether the calendar is Gregorian (1) or Julian (0). | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| # Bugs: | |
| # This code needs to be moved to the C layer. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::GetJulianDayFromEraYearDay {date changeover} { | |
| # Get absolute year number from the civil year | |
| switch -exact -- [dict get $date era] { | |
| BCE { | |
| set year [expr { 1 - [dict get $date year] }] | |
| } | |
| CE { | |
| set year [dict get $date year] | |
| } | |
| } | |
| set ym1 [expr { $year - 1 }] | |
| # Try the Gregorian calendar first. | |
| dict set date gregorian 1 | |
| set jd [expr { | |
| 1721425 | |
| + [dict get $date dayOfYear] | |
| + ( 365 * $ym1 ) | |
| + ( $ym1 / 4 ) | |
| - ( $ym1 / 100 ) | |
| + ( $ym1 / 400 ) | |
| }] | |
| # If the date is before the Gregorian change, use the Julian calendar. | |
| if { $jd < $changeover } { | |
| dict set date gregorian 0 | |
| set jd [expr { | |
| 1721423 | |
| + [dict get $date dayOfYear] | |
| + ( 365 * $ym1 ) | |
| + ( $ym1 / 4 ) | |
| }] | |
| } | |
| dict set date julianDay $jd | |
| return $date | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # GetJulianDayFromEraYearMonthWeekDay -- | |
| # | |
| # Determines the Julian Day number corresponding to the nth given | |
| # day-of-the-week in a given month. | |
| # | |
| # Parameters: | |
| # date - Dictionary containing the keys, 'era', 'year', 'month' | |
| # 'weekOfMonth', 'dayOfWeek', and 'dayOfWeekInMonth'. | |
| # changeover - Julian Day of adoption of the Gregorian calendar | |
| # | |
| # Results: | |
| # Returns the given dictionary, augmented with a 'julianDay' key. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| # Bugs: | |
| # This code needs to be moved to the C layer. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::GetJulianDayFromEraYearMonthWeekDay {date changeover} { | |
| # Come up with a reference day; either the zeroeth day of the given month | |
| # (dayOfWeekInMonth >= 0) or the seventh day of the following month | |
| # (dayOfWeekInMonth < 0) | |
| set date2 $date | |
| set week [dict get $date dayOfWeekInMonth] | |
| if { $week >= 0 } { | |
| dict set date2 dayOfMonth 0 | |
| } else { | |
| dict incr date2 month | |
| dict set date2 dayOfMonth 7 | |
| } | |
| set date2 [GetJulianDayFromEraYearMonthDay $date2[set date2 {}] \ | |
| $changeover] | |
| set wd0 [WeekdayOnOrBefore [dict get $date dayOfWeek] \ | |
| [dict get $date2 julianDay]] | |
| dict set date julianDay [expr { $wd0 + 7 * $week }] | |
| return $date | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # IsGregorianLeapYear -- | |
| # | |
| # Determines whether a given date represents a leap year in the | |
| # Gregorian calendar. | |
| # | |
| # Parameters: | |
| # date -- The date to test. The fields, 'era', 'year' and 'gregorian' | |
| # must be set. | |
| # | |
| # Results: | |
| # Returns 1 if the year is a leap year, 0 otherwise. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::IsGregorianLeapYear { date } { | |
| switch -exact -- [dict get $date era] { | |
| BCE { | |
| set year [expr { 1 - [dict get $date year]}] | |
| } | |
| CE { | |
| set year [dict get $date year] | |
| } | |
| } | |
| if { $year % 4 != 0 } { | |
| return 0 | |
| } elseif { ![dict get $date gregorian] } { | |
| return 1 | |
| } elseif { $year % 400 == 0 } { | |
| return 1 | |
| } elseif { $year % 100 == 0 } { | |
| return 0 | |
| } else { | |
| return 1 | |
| } | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # WeekdayOnOrBefore -- | |
| # | |
| # Determine the nearest day of week (given by the 'weekday' parameter, | |
| # Sunday==0) on or before a given Julian Day. | |
| # | |
| # Parameters: | |
| # weekday -- Day of the week | |
| # j -- Julian Day number | |
| # | |
| # Results: | |
| # Returns the Julian Day Number of the desired date. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::WeekdayOnOrBefore { weekday j } { | |
| set k [expr { ( $weekday + 6 ) % 7 }] | |
| return [expr { $j - ( $j - $k ) % 7 }] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # BSearch -- | |
| # | |
| # Service procedure that does binary search in several places inside the | |
| # 'clock' command. | |
| # | |
| # Parameters: | |
| # list - List of lists, sorted in ascending order by the | |
| # first elements | |
| # key - Value to search for | |
| # | |
| # Results: | |
| # Returns the index of the greatest element in $list that is less than | |
| # or equal to $key. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::BSearch { list key } { | |
| if {[llength $list] == 0} { | |
| return -1 | |
| } | |
| if { $key < [lindex $list 0 0] } { | |
| return -1 | |
| } | |
| set l 0 | |
| set u [expr { [llength $list] - 1 }] | |
| while { $l < $u } { | |
| # At this point, we know that | |
| # $k >= [lindex $list $l 0] | |
| # Either $u == [llength $list] or else $k < [lindex $list $u+1 0] | |
| # We find the midpoint of the interval {l,u} rounded UP, compare | |
| # against it, and set l or u to maintain the invariant. Note that the | |
| # interval shrinks at each step, guaranteeing convergence. | |
| set m [expr { ( $l + $u + 1 ) / 2 }] | |
| if { $key >= [lindex $list $m 0] } { | |
| set l $m | |
| } else { | |
| set u [expr { $m - 1 }] | |
| } | |
| } | |
| return $l | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # clock add -- | |
| # | |
| # Adds an offset to a given time. | |
| # | |
| # Syntax: | |
| # clock add clockval ?count unit?... ?-option value? | |
| # | |
| # Parameters: | |
| # clockval -- Starting time value | |
| # count -- Amount of a unit of time to add | |
| # unit -- Unit of time to add, must be one of: | |
| # years year months month weeks week | |
| # days day hours hour minutes minute | |
| # seconds second | |
| # | |
| # Options: | |
| # -gmt BOOLEAN | |
| # (Deprecated) Flag synonymous with '-timezone :GMT' | |
| # -timezone ZONE | |
| # Name of the time zone in which calculations are to be done. | |
| # -locale NAME | |
| # Name of the locale in which calculations are to be done. | |
| # Used to determine the Gregorian change date. | |
| # | |
| # Results: | |
| # Returns the given time adjusted by the given offset(s) in | |
| # order. | |
| # | |
| # Notes: | |
| # It is possible that adding a number of months or years will adjust the | |
| # day of the month as well. For instance, the time at one month after | |
| # 31 January is either 28 or 29 February, because February has fewer | |
| # than 31 days. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::add { clockval args } { | |
| if { [llength $args] % 2 != 0 } { | |
| set cmdName "clock add" | |
| return -code error \ | |
| -errorcode [list CLOCK wrongNumArgs] \ | |
| "wrong \# args: should be\ | |
| \"$cmdName clockval ?number units?...\ | |
| ?-gmt boolean? ?-locale LOCALE? ?-timezone ZONE?\"" | |
| } | |
| if { [catch { expr {wide($clockval)} } result] } { | |
| return -code error $result | |
| } | |
| set offsets {} | |
| set gmt 0 | |
| set locale c | |
| set timezone [GetSystemTimeZone] | |
| foreach { a b } $args { | |
| if { [string is integer -strict $a] } { | |
| lappend offsets $a $b | |
| } else { | |
| switch -exact -- $a { | |
| -g - -gm - -gmt { | |
| set gmt $b | |
| } | |
| -l - -lo - -loc - -loca - -local - -locale { | |
| set locale [string tolower $b] | |
| } | |
| -t - -ti - -tim - -time - -timez - -timezo - -timezon - | |
| -timezone { | |
| set timezone $b | |
| } | |
| default { | |
| throw [list CLOCK badOption $a] \ | |
| "bad option \"$a\",\ | |
| must be -gmt, -locale or -timezone" | |
| } | |
| } | |
| } | |
| } | |
| # Check options for validity | |
| if { [info exists saw(-gmt)] && [info exists saw(-timezone)] } { | |
| return -code error \ | |
| -errorcode [list CLOCK gmtWithTimezone] \ | |
| "cannot use -gmt and -timezone in same call" | |
| } | |
| if { [catch { expr { wide($clockval) } } result] } { | |
| return -code error "expected integer but got \"$clockval\"" | |
| } | |
| if { ![string is boolean -strict $gmt] } { | |
| return -code error "expected boolean value but got \"$gmt\"" | |
| } elseif { $gmt } { | |
| set timezone :GMT | |
| } | |
| EnterLocale $locale | |
| set changeover [mc GREGORIAN_CHANGE_DATE] | |
| if {[catch {SetupTimeZone $timezone} retval opts]} { | |
| dict unset opts -errorinfo | |
| return -options $opts $retval | |
| } | |
| try { | |
| foreach { quantity unit } $offsets { | |
| switch -exact -- $unit { | |
| years - year { | |
| set clockval [AddMonths [expr { 12 * $quantity }] \ | |
| $clockval $timezone $changeover] | |
| } | |
| months - month { | |
| set clockval [AddMonths $quantity $clockval $timezone \ | |
| $changeover] | |
| } | |
| weeks - week { | |
| set clockval [AddDays [expr { 7 * $quantity }] \ | |
| $clockval $timezone $changeover] | |
| } | |
| days - day { | |
| set clockval [AddDays $quantity $clockval $timezone \ | |
| $changeover] | |
| } | |
| hours - hour { | |
| set clockval [expr { 3600 * $quantity + $clockval }] | |
| } | |
| minutes - minute { | |
| set clockval [expr { 60 * $quantity + $clockval }] | |
| } | |
| seconds - second { | |
| set clockval [expr { $quantity + $clockval }] | |
| } | |
| default { | |
| throw [list CLOCK badUnit $unit] \ | |
| "unknown unit \"$unit\", must be \ | |
| years, months, weeks, days, hours, minutes or seconds" | |
| } | |
| } | |
| } | |
| return $clockval | |
| } trap CLOCK {result opts} { | |
| # Conceal the innards of [clock] when it's an expected error | |
| dict unset opts -errorinfo | |
| return -options $opts $result | |
| } | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # AddMonths -- | |
| # | |
| # Add a given number of months to a given clock value in a given | |
| # time zone. | |
| # | |
| # Parameters: | |
| # months - Number of months to add (may be negative) | |
| # clockval - Seconds since the epoch before the operation | |
| # timezone - Time zone in which the operation is to be performed | |
| # | |
| # Results: | |
| # Returns the new clock value as a number of seconds since | |
| # the epoch. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::AddMonths { months clockval timezone changeover } { | |
| variable DaysInRomanMonthInCommonYear | |
| variable DaysInRomanMonthInLeapYear | |
| variable TZData | |
| # Convert the time to year, month, day, and fraction of day. | |
| set date [GetDateFields $clockval $TZData($timezone) $changeover] | |
| dict set date secondOfDay [expr { | |
| [dict get $date localSeconds] % 86400 | |
| }] | |
| dict set date tzName $timezone | |
| # Add the requisite number of months | |
| set m [dict get $date month] | |
| incr m $months | |
| incr m -1 | |
| set delta [expr { $m / 12 }] | |
| set mm [expr { $m % 12 }] | |
| dict set date month [expr { $mm + 1 }] | |
| dict incr date year $delta | |
| # If the date doesn't exist in the current month, repair it | |
| if { [IsGregorianLeapYear $date] } { | |
| set hath [lindex $DaysInRomanMonthInLeapYear $mm] | |
| } else { | |
| set hath [lindex $DaysInRomanMonthInCommonYear $mm] | |
| } | |
| if { [dict get $date dayOfMonth] > $hath } { | |
| dict set date dayOfMonth $hath | |
| } | |
| # Reconvert to a number of seconds | |
| set date [GetJulianDayFromEraYearMonthDay \ | |
| $date[set date {}]\ | |
| $changeover] | |
| dict set date localSeconds [expr { | |
| -210866803200 | |
| + ( 86400 * wide([dict get $date julianDay]) ) | |
| + [dict get $date secondOfDay] | |
| }] | |
| set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \ | |
| $changeover] | |
| return [dict get $date seconds] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # AddDays -- | |
| # | |
| # Add a given number of days to a given clock value in a given time | |
| # zone. | |
| # | |
| # Parameters: | |
| # days - Number of days to add (may be negative) | |
| # clockval - Seconds since the epoch before the operation | |
| # timezone - Time zone in which the operation is to be performed | |
| # changeover - Julian Day on which the Gregorian calendar was adopted | |
| # in the target locale. | |
| # | |
| # Results: | |
| # Returns the new clock value as a number of seconds since the epoch. | |
| # | |
| # Side effects: | |
| # None. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::AddDays { days clockval timezone changeover } { | |
| variable TZData | |
| # Convert the time to Julian Day | |
| set date [GetDateFields $clockval $TZData($timezone) $changeover] | |
| dict set date secondOfDay [expr { | |
| [dict get $date localSeconds] % 86400 | |
| }] | |
| dict set date tzName $timezone | |
| # Add the requisite number of days | |
| dict incr date julianDay $days | |
| # Reconvert to a number of seconds | |
| dict set date localSeconds [expr { | |
| -210866803200 | |
| + ( 86400 * wide([dict get $date julianDay]) ) | |
| + [dict get $date secondOfDay] | |
| }] | |
| set date [ConvertLocalToUTC $date[set date {}] $TZData($timezone) \ | |
| $changeover] | |
| return [dict get $date seconds] | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ChangeCurrentLocale -- | |
| # | |
| # The global locale was changed within msgcat. | |
| # Clears the buffered parse functions of the current locale. | |
| # | |
| # Parameters: | |
| # loclist (ignored) | |
| # | |
| # Results: | |
| # None. | |
| # | |
| # Side effects: | |
| # Buffered parse functions are cleared. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::ChangeCurrentLocale {args} { | |
| variable FormatProc | |
| variable LocaleNumeralCache | |
| variable CachedSystemTimeZone | |
| variable TimeZoneBad | |
| foreach p [info procs [namespace current]::scanproc'*'current] { | |
| rename $p {} | |
| } | |
| foreach p [info procs [namespace current]::formatproc'*'current] { | |
| rename $p {} | |
| } | |
| catch {array unset FormatProc *'current} | |
| set LocaleNumeralCache {} | |
| } | |
| #---------------------------------------------------------------------- | |
| # | |
| # ClearCaches -- | |
| # | |
| # Clears all caches to reclaim the memory used in [clock] | |
| # | |
| # Parameters: | |
| # None. | |
| # | |
| # Results: | |
| # None. | |
| # | |
| # Side effects: | |
| # Caches are cleared. | |
| # | |
| #---------------------------------------------------------------------- | |
| proc ::tcl::clock::ClearCaches {} { | |
| variable FormatProc | |
| variable LocaleNumeralCache | |
| variable CachedSystemTimeZone | |
| variable TimeZoneBad | |
| foreach p [info procs [namespace current]::scanproc'*] { | |
| rename $p {} | |
| } | |
| foreach p [info procs [namespace current]::formatproc'*] { | |
| rename $p {} | |
| } | |
| catch {unset FormatProc} | |
| set LocaleNumeralCache {} | |
| catch {unset CachedSystemTimeZone} | |
| set TimeZoneBad {} | |
| InitTZData | |
| } | |