diff options
author | kennykb <kennykb@noemail.net> | 2004-08-18 19:58:55 (GMT) |
---|---|---|
committer | kennykb <kennykb@noemail.net> | 2004-08-18 19:58:55 (GMT) |
commit | aa78e8796794a04d8828e5657f594a9d0e356dc3 (patch) | |
tree | 0bfbd9e68acb81b08b317b956ce8ac4cca0824cd /tools | |
parent | e8cbda0d70b49a2d4c71bbbcdb7cd3bc0514bdf2 (diff) | |
download | tcl-aa78e8796794a04d8828e5657f594a9d0e356dc3.zip tcl-aa78e8796794a04d8828e5657f594a9d0e356dc3.tar.gz tcl-aa78e8796794a04d8828e5657f594a9d0e356dc3.tar.bz2 |
TIP #173 and #209 implementation - see ChangeLog for details
FossilOrigin-Name: 251b9c6ecacca6de5ae9b2efc34597b499c02b3c
Diffstat (limited to 'tools')
-rw-r--r-- | tools/installData.tcl | 53 | ||||
-rwxr-xr-x | tools/loadICU.tcl | 622 | ||||
-rwxr-xr-x | tools/makeTestCases.tcl | 1154 | ||||
-rwxr-xr-x | tools/tclZIC.tcl | 1440 |
4 files changed, 3269 insertions, 0 deletions
diff --git a/tools/installData.tcl b/tools/installData.tcl new file mode 100644 index 0000000..cf067a3 --- /dev/null +++ b/tools/installData.tcl @@ -0,0 +1,53 @@ +#!/bin/sh +#\ + exec tclsh "$0" ${1+"$@"} + +#---------------------------------------------------------------------- +# +# installData.tcl -- +# +# This file installs a hierarchy of data found in the directory +# specified by its first argument into the directory specified +# by its second. +# +#---------------------------------------------------------------------- +# +# Copyright (c) 2004 by Kevin B. Kenny. All rights reserved. +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# RCS: @(#) $Id: installData.tcl,v 1.1 2004/08/18 19:59:09 kennykb Exp $ +# +#---------------------------------------------------------------------- + +proc copyDir { d1 d2 } { + + puts [format {%*sCreating %s} [expr { 4 * [info level] }] {} \ + [file tail $d2]] + + file delete -force -- $d2 + file mkdir $d2 + + foreach ftail [glob -directory $d1 -nocomplain -tails *] { + set f [file join $d1 $ftail] + if { [file isdirectory $f] && [string compare CVS $ftail] } { + copyDir $f [file join $d2 $ftail] + } elseif { [file isfile $f] } { + file copy -force $f [file join $d2 $ftail] + if { $::tcl_platform(platform) eq {unix} } { + file attributes [file join $d2 $ftail] -permissions 0644 + } else { + file attributes [file join $d2 $ftail] -readonly 1 + } + } + } + + if { $::tcl_platform(platform) eq {unix} } { + file attributes $d2 -permissions 0755 + } else { + file attributes $d2 -readonly 1 + } + +} + +copyDir [lindex $argv 0] [lindex $argv 1] diff --git a/tools/loadICU.tcl b/tools/loadICU.tcl new file mode 100755 index 0000000..a41a8dc --- /dev/null +++ b/tools/loadICU.tcl @@ -0,0 +1,622 @@ +#---------------------------------------------------------------------- +# +# loadICU,tcl -- +# +# Extracts locale strings from a distribution of ICU +# (http://oss.software.ibm.com/developerworks/opensource/icu/project/) +# and makes Tcl message catalogs for the 'clock' command. +# +# Usage: +# loadICU.tcl sourceDir destDir +# +# Parameters: +# sourceDir -- Path name of the 'data' directory of your ICU4C +# distribution. +# destDir -- Directory into which the Tcl message catalogs should go. +# +# Results: +# None. +# +# Side effects: +# Creates the message catalogs. +# +#---------------------------------------------------------------------- +# +# Copyright (c) 2004 by Kevin B. Kenny. All rights reserved. +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# RCS: @(#) $Id: loadICU.tcl,v 1.1 2004/08/18 19:59:09 kennykb Exp $ +# +#---------------------------------------------------------------------- + +# Calculate the Chinese numerals from zero to ninety-nine. + +set zhDigits [list {} \u4e00 \u4e8c \u4e09 \u56db \ + \u4e94 \u516d \u4e03 \u516b \u4e5d] +set t 0 +foreach zt $zhDigits { + if { $t == 0 } { + set zt {} + } elseif { $t == 10 } { + set zt \u5341 + } else { + append zt \u5341 + } + set d 0 + foreach zd $zhDigits { + if { $t == 0 && $d == 0 } { + set zd \u3007 + } elseif { $t == 20 && $d != 0 } { + set zt \u5eff + } elseif { $t == 30 && $d != 0 } { + set zt \u5345 + } + lappend zhNumbers $zt$zd + incr d + } + incr t 10 +} + +# Set format overrides for various locales. + +set format(zh,LOCALE_NUMERALS) $zhNumbers +set format(ja,LOCALE_NUMERALS) $zhNumbers +set format(ja,LOCALE_ERAS) [list \ + [list -9223372036854775808 \u897f\u66a6 0 ] \ + [list -3060979200 \u660e\u6cbb 1867] \ + [list -1812153600 \u5927\u6b63 1911] \ + [list -1357603200 \u662d\u548c 1925] \ + [list 568512000 \u5e73\u6210 1987]] +set format(zh,LOCALE_DATE_FORMAT) "\u516c\u5143%Y\u5e74%B%Od\u65E5" +set format(ja,LOCALE_DATE_FORMAT) "%EY\u5e74%B%Od\u65E5" +set format(ko,LOCALE_DATE_FORMAT) "%Y\ub144%B%Od\uc77c" +set format(zh,LOCALE_TIME_FORMAT) "%OH\u65f6%OM\u5206%OS\u79d2" +set format(ja,LOCALE_TIME_FORMAT) "%OH\u6642%OM\u5206%OS\u79d2" +set format(ko,LOCALE_TIME_FORMAT) "%H\uc2dc%M\ubd84%S\ucd08" +set format(zh,LOCALE_DATE_TIME_FORMAT) "%A %Y\u5e74%B%Od\u65E5%OH\u65f6%OM\u5206%OS\u79d2 %z" +set format(ja,LOCALE_DATE_TIME_FORMAT) "%A %EY\u5e74%B%Od\u65E5%OH\u6642%OM\u5206%OS\u79d2 %z" +set format(ko,LOCALE_DATE_TIME_FORMAT) "%A %Y\ub144%B%Od\uc77c%H\uc2dc%M\ubd84%S\ucd08 %z" + +# The next set of format overrides were obtained from the glibc +# localization strings. + +set format(cs_CZ,DATE_FORMAT) %d.%m.%Y +set format(cs_CZ,DATE_TIME_FORMAT) {%a %e. %B %Y, %H:%M:%S %z} +set format(cs_CZ,TIME_FORMAT) %H:%M:%S +set format(cs_CZ,TIME_FORMAT_12) %I:%M:%S +set format(da_DK,DATE_FORMAT) %d-%m-%Y +set format(da_DK,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(da_DK,TIME_FORMAT) %T +set format(da_DK,TIME_FORMAT_12) %T +set format(de_AT,DATE_FORMAT) %Y-%m-%d +set format(de_AT,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(de_AT,TIME_FORMAT) %T +set format(de_AT,TIME_FORMAT_12) %T +set format(de_BE,DATE_FORMAT) %Y-%m-%d +set format(de_BE,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(de_BE,TIME_FORMAT) %T +set format(de_BE,TIME_FORMAT_12) %T +set format(de_CH,DATE_FORMAT) %Y-%m-%d +set format(de_CH,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(de_CH,TIME_FORMAT) %T +set format(de_CH,TIME_FORMAT_12) %T +set format(de_DE,DATE_FORMAT) %Y-%m-%d +set format(de_DE,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(de_DE,TIME_FORMAT) %T +set format(de_DE,TIME_FORMAT_12) %T +set format(de_LU,DATE_FORMAT) %Y-%m-%d +set format(de_LU,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(de_LU,TIME_FORMAT) %T +set format(de_LU,TIME_FORMAT_12) %T +set format(en_CA,DATE_FORMAT) %d/%m/%y +set format(en_CA,DATE_TIME_FORMAT) {%a %d %b %Y %r %z} +set format(en_CA,TIME_FORMAT) %r +set format(en_CA,TIME_FORMAT_12) {%I:%M:%S %p} +set format(en_DK,DATE_FORMAT) %Y-%m-%d +set format(en_DK,DATE_TIME_FORMAT) {%Y-%m-%dT%T %z} +set format(en_DK,TIME_FORMAT) %T +set format(en_DK,TIME_FORMAT_12) %T +set format(en_GB,DATE_FORMAT) %d/%m/%y +set format(en_GB,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(en_GB,TIME_FORMAT) %T +set format(en_GB,TIME_FORMAT_12) %T +set format(en_IE,DATE_FORMAT) %d/%m/%y +set format(en_IE,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(en_IE,TIME_FORMAT) %T +set format(en_IE,TIME_FORMAT_12) %T +set format(en_US,DATE_FORMAT) %m/%d/%y +set format(en_US,DATE_TIME_FORMAT) {%a %d %b %Y %r %z} +set format(en_US,TIME_FORMAT) %r +set format(en_US,TIME_FORMAT_12) {%I:%M:%S %p} +set format(es_ES,DATE_FORMAT) %d/%m/%y +set format(es_ES,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(es_ES,TIME_FORMAT) %T +set format(es_ES,TIME_FORMAT_12) %T +set format(et_EE,DATE_FORMAT) %d.%m.%Y +set format(et_EE,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(et_EE,TIME_FORMAT) %T +set format(et_EE,TIME_FORMAT_12) %T +set format(eu_ES,DATE_FORMAT) {%a, %Yeko %bren %da} +set format(eu_ES,DATE_TIME_FORMAT) {%y-%m-%d %T %z} +set format(eu_ES,TIME_FORMAT) %T +set format(eu_ES,TIME_FORMAT_12) %T +set format(fi_FI,DATE_FORMAT) %d.%m.%Y +set format(fi_FI,DATE_TIME_FORMAT) {%a %e %B %Y %T} +set format(fi_FI,TIME_FORMAT) %T +set format(fi_FI,TIME_FORMAT_12) %T +set format(fo_FO,DATE_FORMAT) %d/%m-%Y +set format(fo_FO,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(fo_FO,TIME_FORMAT) %T +set format(fo_FO,TIME_FORMAT_12) %T +set format(fr_BE,DATE_FORMAT) %d/%m/%y +set format(fr_BE,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(fr_BE,TIME_FORMAT) %T +set format(fr_BE,TIME_FORMAT_12) %T +set format(fr_CA,DATE_FORMAT) %Y-%m-%d +set format(fr_CA,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(fr_CA,TIME_FORMAT) %T +set format(fr_CA,TIME_FORMAT_12) %T +set format(fr_CH,DATE_FORMAT) {%d. %m. %y} +set format(fr_CH,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(fr_CH,TIME_FORMAT) %T +set format(fr_CH,TIME_FORMAT_12) %T +set format(fr_FR,DATE_FORMAT) %d.%m.%Y +set format(fr_FR,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(fr_FR,TIME_FORMAT) %T +set format(fr_FR,TIME_FORMAT_12) %T +set format(fr_LU,DATE_FORMAT) %d.%m.%Y +set format(fr_LU,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(fr_LU,TIME_FORMAT) %T +set format(fr_LU,TIME_FORMAT_12) %T +set format(ga_IE,DATE_FORMAT) %d.%m.%y +set format(ga_IE,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(ga_IE,TIME_FORMAT) %T +set format(ga_IE,TIME_FORMAT_12) %T +set format(gr_GR,DATE_FORMAT) %d/%m/%Y +set format(gr_GR,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(gr_GR,TIME_FORMAT) %T +set format(gr_GR,TIME_FORMAT_12) %T +set format(hr_HR,DATE_FORMAT) %d.%m.%y +set format(hr_HR,DATE_TIME_FORMAT) {%a %d %b %Y %T} +set format(hr_HR,TIME_FORMAT) %T +set format(hr_HR,TIME_FORMAT_12) %T +set format(hu_HU,DATE_FORMAT) %Y-%m-%d +set format(hu_HU,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(hu_HU,TIME_FORMAT) %T +set format(hu_HU,TIME_FORMAT_12) %T +set format(is_IS,DATE_FORMAT) {%a %e.%b %Y} +set format(is_IS,DATE_TIME_FORMAT) {%a %e.%b %Y, %T %z} +set format(is_IS,TIME_FORMAT) %T +set format(is_IS,TIME_FORMAT_12) %T +set format(it_IT,DATE_FORMAT) %d/%m/%Y +set format(it_IT,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(it_IT,TIME_FORMAT) %T +set format(it_IT,TIME_FORMAT_12) %T +set format(iw_IL,DATE_FORMAT) %d/%m/%y +set format(iw_IL,DATE_TIME_FORMAT) {%z %H:%M:%S %Y %b %d %a} +set format(iw_IL,TIME_FORMAT) %H:%M:%S +set format(iw_IL,TIME_FORMAT_12) {%I:%M:%S %P} +set format(kl_GL,DATE_FORMAT) {%d %b %Y} +set format(kl_GL,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(kl_GL,TIME_FORMAT) %T +set format(kl_GL,TIME_FORMAT_12) %T +set format(lt_LT,DATE_FORMAT) %Y.%m.%d +set format(lt_LT,DATE_TIME_FORMAT) {%Y m. %B %d d. %T} +set format(lt_LT,TIME_FORMAT) %T +set format(lt_LT,TIME_FORMAT_12) %T +set format(lv_LV,DATE_FORMAT) %Y.%m.%d. +set format(lv_LV,DATE_TIME_FORMAT) {%A, %Y. gada %e. %B, plkst. %H un %M} +set format(lv_LV,TIME_FORMAT) %T +set format(lv_LV,TIME_FORMAT_12) %T +set format(nl_BE,DATE_FORMAT) %d-%m-%y +set format(nl_BE,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(nl_BE,TIME_FORMAT) %T +set format(nl_BE,TIME_FORMAT_12) %T +set format(nl_NL,DATE_FORMAT) %d-%m-%y +set format(nl_NL,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(nl_NL,TIME_FORMAT) %T +set format(nl_NL,TIME_FORMAT_12) %T +set format(no_NO,DATE_FORMAT) %d-%m-%Y +set format(no_NO,DATE_TIME_FORMAT) {%a %d-%m-%Y %T %z} +set format(no_NO,TIME_FORMAT) %T +set format(no_NO,TIME_FORMAT_12) %T +set format(pl_PL,DATE_FORMAT) %Y-%m-%d +set format(pl_PL,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(pl_PL,TIME_FORMAT) %T +set format(pl_PL,TIME_FORMAT_12) %T +set format(pt_BR,DATE_FORMAT) %d-%m-%Y +set format(pt_BR,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(pt_BR,TIME_FORMAT) %T +set format(pt_BR,TIME_FORMAT_12) %T +set format(pt_PT,DATE_FORMAT) %d-%m-%Y +set format(pt_PT,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(pt_PT,TIME_FORMAT) %T +set format(pt_PT,TIME_FORMAT_12) %T +set format(ro_RO,DATE_FORMAT) %Y-%m-%d +set format(ro_RO,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(ro_RO,TIME_FORMAT) %T +set format(ro_RO,TIME_FORMAT_12) %T +set format(ru_RU,DATE_FORMAT) %d.%m.%Y +set format(ru_RU,DATE_TIME_FORMAT) {%a %d %b %Y %T} +set format(ru_RU,TIME_FORMAT) %T +set format(ru_RU,TIME_FORMAT_12) %T +set format(sl_SI,DATE_FORMAT) %d.%m.%Y +set format(sl_SI,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(sl_SI,TIME_FORMAT) %T +set format(sl_SI,TIME_FORMAT_12) %T +set format(sv_FI,DATE_FORMAT) %Y-%m-%d +set format(sv_FI,DATE_TIME_FORMAT) {%a %e %b %Y %H.%M.%S} +set format(sv_FI,TIME_FORMAT) %H.%M.%S +set format(sv_FI,TIME_FORMAT_12) %H.%M.%S +set format(sv_SE,DATE_FORMAT) %Y-%m-%d +set format(sv_SE,DATE_TIME_FORMAT) {%a %e %b %Y %H.%M.%S} +set format(sv_SE,TIME_FORMAT) %H.%M.%S +set format(sv_SE,TIME_FORMAT_12) %H.%M.%S +set format(tr_TR,DATE_FORMAT) %Y-%m-%d +set format(tr_TR,DATE_TIME_FORMAT) {%a %d %b %Y %T %z} +set format(tr_TR,TIME_FORMAT) %T +set format(tr_TR,TIME_FORMAT_12) %T + +#---------------------------------------------------------------------- +# +# handleLocaleFile -- +# +# Extracts strings from an ICU locale definition. +# +# Parameters: +# localeName - Name of the locale (e.g., de_AT_euro) +# fileName - Name of the file containing the data +# msgFileName - Name of the file containing the Tcl message catalog +# +# Results: +# None. +# +# Side effects: +# Writes the Tcl message catalog. +# +#---------------------------------------------------------------------- + +proc handleLocaleFile { localeName fileName msgFileName } { + variable format + + # Get the content of the ICU file + + set f [open $fileName r] + fconfigure $f -encoding utf-8 + set data [read $f] + close $f + + # Parse the ICU data + + set state {} + foreach line [split $data \n] { + switch -exact -- $state { + {} { + + # Look for the beginnings of data blocks + + switch -regexp -- $line { + {^[[:space:]]*AmPmMarkers[[:space:]]+[\{]} { + set state data + set key AmPmMarkers + } + {^[[:space:]]*DateTimePatterns[[:space:]]+[\{]} { + set state data + set key DateTimePatterns + } + {^[[:space:]]*DayAbbreviations[[:space:]]+[\{]} { + set state data + set key DayAbbreviations + } + {^[[:space:]]*DayNames[[:space:]]+[\{]} { + set state data + set key DayNames + } + {^[[:space:]]*Eras[[:space:]]+[\{]} { + set state data + set key Eras + } + {^[[:space:]]*MonthAbbreviations[[:space:]]+[\{]} { + set state data + set key MonthAbbreviations + } + {^[[:space:]]*MonthNames[[:space:]]+[\{]} { + set state data + set key MonthNames + } + } + } + data { + + + # Inside a data block, collect the strings, doing backslash + # expansion to pick up the Unicodes + + if { [regexp {"(.*)",} $line -> item] } { + lappend items($key) [subst -nocommands -novariables $item] + } elseif { [regexp {^[[:space:]]*[\}][[:space:]]*$} $line] } { + set state {} + } + } + } + } + + # Skip locales that don't change time strings. + + if {![array exists items]} return + + # Write the Tcl message catalog + + set f [open $msgFileName w] + + # Write a header + + puts $f "\# created by $::argv0 -- do not edit" + puts $f "namespace eval ::tcl::clock \{" + + # Do ordinary sets of strings (weekday and month names) + + foreach key { + DayAbbreviations DayNames MonthAbbreviations MonthNames + } tkey { + DAYS_OF_WEEK_ABBREV DAYS_OF_WEEK_FULL + MONTHS_ABBREV MONTHS_FULL + } { + if { [info exists items($key)] } { + set itemList $items($key) + set cmd1 " ::msgcat::mcset " + append cmd1 $localeName " " $tkey " \[list " + foreach item $itemList { + append cmd1 \\\n { } \" [backslashify $item] \" + } + append cmd1 \] + puts $f $cmd1 + } + } + + # Do the eras, B.C.E., and C.E. + + if { [info exists items(Eras)] } { + foreach { bce ce } $items(Eras) break + set cmd " ::msgcat::mcset " + append cmd $localeName " " BCE " \"" [backslashify $bce] \" + puts $f $cmd + set cmd " ::msgcat::mcset " + append cmd $localeName " " CE " \"" [backslashify $ce] \" + puts $f $cmd + } + + # Do the AM and PM markers + + if { [info exists items(AmPmMarkers)] } { + foreach { am pm } $items(AmPmMarkers) break + set cmd " ::msgcat::mcset " + append cmd $localeName " " AM " \"" [backslashify $am] \" + puts $f $cmd + set cmd " ::msgcat::mcset " + append cmd $localeName " " PM " \"" [backslashify $pm] \" + puts $f $cmd + } + + # Do the date/time patterns. First date... + + if { [info exists format($localeName,DATE_FORMAT)] + || [info exists items(DateTimePatterns)] } { + + # Find the shortest date format that includes a 4-digit year. + + if { ![info exists format($localeName,DATE_FORMAT)] } { + for { set i 7 } { $i >= 4 } { incr i -1 } { + if { [regexp yyyy [lindex $items(DateTimePatterns) $i]] } { + break + } + } + set fmt \ + [backslashify \ + [percentify [lindex $items(DateTimePatterns) $i]]] + set format($localeName,DATE_FORMAT) $fmt + } + + # Put it to the message catalog + + set cmd " ::msgcat::mcset " + append cmd $localeName " DATE_FORMAT \"" \ + $format($localeName,DATE_FORMAT) "\"" + puts $f $cmd + } + + # Time + + if { [info exists format($localeName,TIME_FORMAT)] + || [info exists items(DateTimePatterns)] } { + + # Find the shortest time pattern that includes the seconds + + if { ![info exists format($localeName,TIME_FORMAT)] } { + for { set i 3 } { $i >= 0 } { incr i -1 } { + if { [regexp H [lindex $items(DateTimePatterns) $i]] + && [regexp s [lindex $items(DateTimePatterns) $i]] } { + break + } + } + if { $i >= 0 } { + set fmt \ + [backslashify \ + [percentify [lindex $items(DateTimePatterns) $i]]] + regsub { %Z} $fmt {} format($localeName,TIME_FORMAT) + } + } + + # Put it to the message catalog + + if { [info exists format($localeName,TIME_FORMAT)] } { + set cmd " ::msgcat::mcset " + append cmd $localeName " TIME_FORMAT \"" \ + $format($localeName,TIME_FORMAT) "\"" + puts $f $cmd + } + } + + # 12-hour time... + + if { [info exists format($localeName,TIME_FORMAT_12)] + || [info exists items(DateTimePatterns)] } { + + # Shortest patterm with 12-hour time that includes seconds + + if { ![info exists format($localeName,TIME_FORMAT_12)] } { + for { set i 3 } { $i >= 0 } { incr i -1 } { + if { [regexp h [lindex $items(DateTimePatterns) $i]] + && [regexp s [lindex $items(DateTimePatterns) $i]] } { + break + } + } + if { $i >= 0 } { + set fmt \ + [backslashify \ + [percentify [lindex $items(DateTimePatterns) $i]]] + regsub { %Z} $fmt {} format($localeName,TIME_FORMAT_12) + } + } + + # Put it to the catalog + + if { [info exists format($localeName,TIME_FORMAT_12)] } { + set cmd " ::msgcat::mcset " + append cmd $localeName " TIME_FORMAT_12 \"" \ + $format($localeName,TIME_FORMAT_12) "\"" + puts $f $cmd + } + } + + # Date and time... Prefer 24-hour format to 12-hour format. + + if { ![info exists format($localeName,DATE_TIME_FORMAT)] + && [info exists format($localeName,DATE_FORMAT)] + && [info exists format($localeName,TIME_FORMAT)]} { + set format($localeName,DATE_TIME_FORMAT) \ + $format($localeName,DATE_FORMAT) + append format($localeName,DATE_TIME_FORMAT) \ + " " $format($localeName,TIME_FORMAT) " %z" + } + if { ![info exists format($localeName,DATE_TIME_FORMAT)] + && [info exists format($localeName,DATE_FORMAT)] + && [info exists format($localeName,TIME_FORMAT_12)]} { + set format($localeName,DATE_TIME_FORMAT) \ + $format($localeName,DATE_FORMAT) + append format($localeName,DATE_TIME_FORMAT) \ + " " $format($localeName,TIME_FORMAT_12) " %z" + } + + # Write date/time format to the file + + if { [info exists format($localeName,DATE_TIME_FORMAT)] } { + set cmd " ::msgcat::mcset " + append cmd $localeName " DATE_TIME_FORMAT \"" \ + $format($localeName,DATE_TIME_FORMAT) "\"" + puts $f $cmd + } + + # Write the string sets to the file. + + foreach key { + LOCALE_NUMERALS LOCALE_DATE_FORMAT LOCALE_TIME_FORMAT + LOCALE_DATE_TIME_FORMAT LOCALE_ERAS LOCALE_YEAR_FORMAT + } { + if { [info exists format($localeName,$key)] } { + set cmd " ::msgcat::mcset " + append cmd $localeName " " $key " \"" \ + [backslashify $format($localeName,$key)] "\"" + puts $f $cmd + } + } + + # Footer + + puts $f "\}" + close $f +} + +#---------------------------------------------------------------------- +# +# percentify -- +# +# Converts a Java/ICU-style time format to a C/Tcl style one. +# +# Parameters: +# string -- Format to convert +# +# Results: +# Returns the converted format. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc percentify { string } { + set retval {} + foreach { unquoted quoted } [split $string '] { + append retval [string map { + EEEE %A MMMM %B yyyy %Y + MMM %b EEE %a + dd %d hh %I HH %H mm %M MM %m ss %S yy %y + a %P d %e h %l H %k M %m z %z + } $unquoted] + append retval $quoted + } + return $retval +} + +#---------------------------------------------------------------------- +# +# backslashify -- +# +# Converts a UTF-8 string to a plain ASCII one with escapes. +# +# Parameters: +# string -- String to convert +# +# Results: +# Returns the converted string +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc backslashify { string } { + + set retval {} + foreach char [split $string {}] { + scan $char %c ccode + if { $ccode >= 0x0020 && $ccode < 0x007f + && $char ne "\{" && $char ne "\}" && $char ne "\[" + && $char ne "\]" && $char ne "\\" && $char ne "\$" } { + append retval $char + } else { + append retval \\u [format %04x $ccode] + } + } + return $retval +} + +#---------------------------------------------------------------------- +# +# MAIN PROGRAM +# +#---------------------------------------------------------------------- + +# Extract directories from command line + +foreach { icudir msgdir } $argv break + +# Walk the ICU files and create corresponding Tcl message catalogs + +foreach fileName [glob -directory $icudir *.txt] { + set n [file rootname [file tail $fileName]] + if { [regexp {^[a-z]{2,3}(_[A-Z]{2,3}(_.*)?)?$} $n] } { + handleLocaleFile $n $fileName [file join $msgdir ${n}.msg] + } +} diff --git a/tools/makeTestCases.tcl b/tools/makeTestCases.tcl new file mode 100755 index 0000000..497205b --- /dev/null +++ b/tools/makeTestCases.tcl @@ -0,0 +1,1154 @@ +# TODO - When integrating this with the Core, path names will need to be +# swizzled here. + +package require newclock +set d [file dirname [file dirname [info script]]] +source [file join $d library/tzdata/America/Detroit] + +namespace eval ::tcl::clock { + ::msgcat::mcmset en_US_roman { + LOCALE_ERAS { + {-62164627200 {} 0} + {-59008867200 c 100} + {-55853107200 cc 200} + {-52697347200 ccc 300} + {-49541587200 cd 400} + {-46385827200 d 500} + {-43230067200 dc 600} + {-40074307200 dcc 700} + {-36918547200 dccc 800} + {-33762787200 cm 900} + {-30607027200 m 1000} + {-27451267200 mc 1100} + {-24295507200 mcc 1200} + {-21139747200 mccc 1300} + {-17983987200 mcd 1400} + {-14828227200 md 1500} + {-11672467200 mdc 1600} + {-8516707200 mdcc 1700} + {-5364662400 mdccc 1800} + {-2208988800 mcm 1900} + {946684800 mm 2000} + } + LOCALE_NUMERALS { + ? i ii iii iv v vi vii viii ix + x xi xii xiii xiv xv xvi xvii xviii xix + xx xxi xxii xxiii xxiv xxv xxvi xxvii xxviii xxix + xxx xxxi xxxii xxxiii xxxiv xxxv xxxvi xxxvii xxxviii xxxix + xl xli xlii xliii xliv xlv xlvi xlvii xlviii xlix + l li lii liii liv lv lvi lvii lviii lix + lx lxi lxii lxiii lxiv lxv lxvi lxvii lxviii lxix + lxx lxxi lxxii lxxiii lxxiv lxxv lxxvi lxxvii lxxviii lxxix + lxxx lxxxi lxxxii lxxxiii lxxxiv lxxxv lxxxvi lxxxvii lxxxviii + lxxxix + xc xci xcii xciii xciv xcv xcvi xcvii xcviii xcix + c + } + DATE_FORMAT {%m/%d/%Y} + TIME_FORMAT {%H:%M:%S} + DATE_TIME_FORMAT {%x %X} + LOCALE_DATE_FORMAT {die %Od mensis %Om annoque %EY} + LOCALE_TIME_FORMAT {%OH h %OM m %OS s} + LOCALE_DATE_TIME_FORMAT {%Ex %EX} + } +} + +#---------------------------------------------------------------------- +# +# listYears -- +# +# List the years to test in the common clock test cases. +# +# Parameters: +# startOfYearArray - Name of an array in caller's scope that will +# be initialized as +# Results: +# None +# +# Side effects: +# Determines the year numbers of one common year, one leap year, one year +# following a common year, and one year following a leap year -- starting +# on each day of the week -- in the XIXth, XXth and XXIth centuries. +# Initializes the given array to have keys equal to the year numbers and +# values equal to [clock seconds] at the start of the corresponding +# years. +# +#---------------------------------------------------------------------- + +proc listYears { startOfYearArray } { + + upvar 1 $startOfYearArray startOfYear + + # List years after 1970 + + set y 1970 + set s 0 + set dw 4 ;# Thursday + while { $y < 2100 } { + if { $y % 4 == 0 && $y % 100 != 0 || $y % 400 == 0 } { + set l 1 + incr dw 366 + set s2 [expr { $s + wide( 366 * 86400 ) }] + } else { + set l 0 + incr dw 365 + set s2 [expr { $s + wide( 365 * 86400 ) }] + } + set x [expr { $y >= 2037 }] + set dw [expr {$dw % 7}] + set c [expr { $y / 100 }] + if { ![info exists do($x$c$dw$l)] } { + set do($x$c$dw$l) $y + set startOfYear($y) $s + set startOfYear([expr {$y + 1}]) $s2 + } + set s $s2 + incr y + } + + # List years before 1970 + + set y 1970 + set s 0 + set dw 4; # Thursday + while { $y >= 1801 } { + set s0 $s + incr dw 371 + incr y -1 + if { $y % 4 == 0 && $y % 100 != 0 || $y % 400 == 0 } { + set l 1 + incr dw -366 + set s [expr { $s - wide(366 * 86400) }] + } else { + set l 0 + incr dw -365 + set s [expr { $s - wide(365 * 86400) }] + } + set dw [expr {$dw % 7}] + set c [expr { $y / 100 }] + if { ![info exists do($c$dw$l)] } { + set do($c$dw$l) $y + set startOfYear($y) $s + set startOfYear([expr {$y + 1}]) $s0 + } + } + +} + +#---------------------------------------------------------------------- +# +# processFile - +# +# Processes the 'clock.test' file, updating the test cases in it. +# +# Parameters: +# None. +# +# Side effects: +# Replaces the file with a new copy, constructing needed test cases. +# +#---------------------------------------------------------------------- + +proc processFile {d} { + + # Open two files + + set f1 [open [file join $d tests/clock.test] r] + set f2 [open [file join $d tests/clock.new] w] + + # Copy leading portion of the test file + + set state {} + while { [gets $f1 line] >= 0 } { + switch -exact -- $state { + {} { + puts $f2 $line + if { [regexp "^\# BEGIN (.*)" $line -> cases] + && [string compare {} [info commands $cases]] } { + set state inCaseSet + $cases $f2 + } + } + inCaseSet { + if { [regexp "^\#\ END $cases\$" $line] } { + puts $f2 $line + set state {} + } + } + } + } + + # Rotate the files + + close $f1 + close $f2 + file delete -force [file join $d tests/clock.bak] + file rename -force [file join $d tests/clock.test] \ + [file join $d tests/clock.bak] + file rename [file join $d tests/clock.new] [file join $d tests/clock.test] + +} + +#---------------------------------------------------------------------- +# +# testcases2 -- +# +# Outputs the 'clock-2.x' test cases. +# +# Parameters: +# f2 -- Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Test cases for formatting in Gregorian calendar are written to the +# output file. +# +#---------------------------------------------------------------------- + +proc testcases2 { f2 } { + + listYears startOfYear + + # Define the roman numerals + + set roman { + ? i ii iii iv v vi vii viii ix + x xi xii xiii xiv xv xvi xvii xviii xix + xx xxi xxii xxiii xxiv xxv xxvi xxvii xxviii xxix + xxx xxxi xxxii xxxiii xxxiv xxxv xxxvi xxxvii xxxviii xxxix + xl xli xlii xliii xliv xlv xlvi xlvii xlviii xlix + l li lii liii liv lv lvi lvii lviii lix + lx lxi lxii lxiii lxiv lxv lxvi lxvii lxviii lxix + lxx lxxi lxxii lxxiii lxxiv lxxv lxxvi lxxvii lxxviii lxxix + lxxx lxxxi lxxxii lxxxiii lxxxiv lxxxv lxxxvi lxxxvii lxxxviii lxxxix + xc xci xcii xciii xciv xcv xcvi xcvii xcviii xcix + c + } + set romanc { + ? c cc ccc cd d dc dcc dccc cm + m mc mcc mccc mcd md mdc mdcc mdccc mcm + mm mmc mmcc mmccc mmcd mmd mmdc mmdcc mmdccc mmcm + mmm mmmc mmmcc mmmccc mmmcd mmmd mmmdc mmmdcc mmmdccc mmmcm + } + + # Names of the months + + set short {{} Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec} + set long { + {} January February March April May June July August September + October November December + } + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test formatting of Gregorian year, month, day, all formats" + puts $f2 "\# Formats tested: %b %B %c %Ec %C %EC %d %Od %e %Oe %h %j %J %m %Om %N %x %Ex %y %Oy %Y %EY" + puts $f2 "" + + # Generate the test cases for the first and last day of every month + # from 1896 to 2045 + + set n 0 + foreach { y } [lsort -integer [array names startOfYear]] { + set s [expr { $startOfYear($y) + wide(12*3600 + 34*60 + 56) }] + set m 0 + set yd 1 + foreach hath { 31 28 31 30 31 30 31 31 30 31 30 31 } { + incr m + if { $m == 2 && ( $y%4 == 0 && $y%100 != 0 || $y%400 == 0 ) } { + incr hath + } + + set b [lindex $short $m] + set B [lindex $long $m] + set C [format %02d [expr { $y / 100 }]] + set h $b + set j [format %03d $yd] + set mm [format %02d $m] + set N [format %2d $m] + set yy [format %02d [expr { $y % 100 }]] + + set J [expr { ( $s / 86400 ) + 2440588 }] + + set dt $y-$mm-01 + set result "" + append result $b " " $B " " \ + $mm /01/ $y " 12:34:56 " \ + "die i mensis " [lindex $roman $m] " annoque " \ + [lindex $romanc [expr { $y / 100 }]] \ + [lindex $roman [expr { $y % 100 }]] " " \ + [lindex $roman 12] " h " [lindex $roman 34] " m " \ + [lindex $roman 56] " s " \ + $C " " [lindex $romanc [expr { $y / 100 }]] \ + " 01 i 1 i " \ + $h " " $j " " $J " " $mm " " [lindex $roman $m] " " $N \ + " " $mm "/01/" $y \ + " die i mensis " [lindex $roman $m] " annoque " \ + [lindex $romanc [expr { $y / 100 }]] \ + [lindex $roman [expr { $y % 100 }]] \ + " " $yy " " [lindex $roman [expr { $y % 100 }]] " " $y + puts $f2 "test clock-2.[incr n] {conversion of $dt} {" + puts $f2 " clock format $s \\" + puts $f2 "\t-format {%b %B %c %Ec %C %EC %d %Od %e %Oe %h %j %J %m %Om %N %x %Ex %y %Oy %Y} \\" + puts $f2 "\t-gmt true -locale en_US_roman" + puts $f2 "} {$result}" + + set hm1 [expr { $hath - 1 }] + incr s [expr { 86400 * ( $hath - 1 ) }] + incr yd $hm1 + + set dd [format %02d $hath] + set ee [format %2d $hath] + set j [format %03d $yd] + + set J [expr { ( $s / 86400 ) + 2440588 }] + + set dt $y-$mm-$dd + set result "" + append result $b " " $B " " \ + $mm / $dd / $y " 12:34:56 " \ + "die " [lindex $roman $hath] " mensis " [lindex $roman $m] \ + " annoque " \ + [lindex $romanc [expr { $y / 100 }]] \ + [lindex $roman [expr { $y % 100 }]] " " \ + [lindex $roman 12] " h " [lindex $roman 34] " m " \ + [lindex $roman 56] " s " \ + $C " " [lindex $romanc [expr { $y / 100 }]] \ + " " $dd " " [lindex $roman $hath] " " \ + $ee " " [lindex $roman $hath] " "\ + $h " " $j " " $J " " $mm " " [lindex $roman $m] " " $N \ + " " $mm "/" $dd "/" $y \ + " die " [lindex $roman $hath] " mensis " [lindex $roman $m] \ + " annoque " \ + [lindex $romanc [expr { $y / 100 }]] \ + [lindex $roman [expr { $y % 100 }]] \ + " " $yy " " [lindex $roman [expr { $y % 100 }]] " " $y + puts $f2 "test clock-2.[incr n] {conversion of $dt} {" + puts $f2 " clock format $s \\" + puts $f2 "\t-format {%b %B %c %Ec %C %EC %d %Od %e %Oe %h %j %J %m %Om %N %x %Ex %y %Oy %Y} \\" + puts $f2 "\t-gmt true -locale en_US_roman" + puts $f2 "} {$result}" + + incr s 86400 + incr yd + } + } + puts "testcases2: $n test cases" +} + +#---------------------------------------------------------------------- +# +# testcases3 -- +# +# Generate test cases for ISO8601 calendar. +# +# Parameters: +# f2 - Channel handle to the output file +# +# Results: +# None +# +# Side effects: +# Makes a test case for the first and last day of weeks 51, 52, and 1 +# plus the first and last day of a year. Does so for each possible +# weekday on which a Common Year or Leap Year can begin. +# +#---------------------------------------------------------------------- + +proc testcases3 { f2 } { + + listYears startOfYear + + set case 0 + foreach { y } [lsort -integer [array names startOfYear]] { + set secs $startOfYear($y) + set ym1 [expr { $y - 1 }] + set dow [expr { ( $secs / 86400 + 4 ) % 7}] + switch -exact $dow { + 0 { + testISO $f2 $ym1 52 1 [expr { $secs - 6*86400 }] + testISO $f2 $ym1 52 6 [expr { $secs - 86400 }] + testISO $f2 $ym1 52 7 $secs + testISO $f2 $y 1 1 [expr { $secs + 86400 }] + testISO $f2 $y 1 7 [expr { $secs + 7*86400 }] + testISO $f2 $y 2 1 [expr { $secs + 8*86400 }] + } + 1 { + testISO $f2 $ym1 52 1 [expr { $secs - 7*86400 }] + testISO $f2 $ym1 52 7 [expr { $secs - 86400 }] + testISO $f2 $y 1 1 $secs + testISO $f2 $y 1 7 [expr { $secs + 6*86400 }] + testISO $f2 $y 2 1 [expr { $secs + 7*86400 }] + } + 2 { + testISO $f2 $ym1 52 1 [expr { $secs - 8*86400 }] + testISO $f2 $ym1 52 7 [expr { $secs - 2*86400 }] + testISO $f2 $y 1 1 [expr { $secs - 86400 }] + testISO $f2 $y 1 2 $secs + testISO $f2 $y 1 7 [expr { $secs + 5*86400 }] + testISO $f2 $y 2 1 [expr { $secs + 6*86400 }] + } + 3 { + testISO $f2 $ym1 52 1 [expr { $secs - 9*86400 }] + testISO $f2 $ym1 52 7 [expr { $secs - 3*86400 }] + testISO $f2 $y 1 1 [expr { $secs - 2*86400 }] + testISO $f2 $y 1 3 $secs + testISO $f2 $y 1 7 [expr { $secs + 4*86400 }] + testISO $f2 $y 2 1 [expr { $secs + 5*86400 }] + } + 4 { + testISO $f2 $ym1 52 1 [expr { $secs - 10*86400 }] + testISO $f2 $ym1 52 7 [expr { $secs - 4*86400 }] + testISO $f2 $y 1 1 [expr { $secs - 3*86400 }] + testISO $f2 $y 1 4 $secs + testISO $f2 $y 1 7 [expr { $secs + 3*86400 }] + testISO $f2 $y 2 1 [expr { $secs + 4*86400 }] + } + 5 { + testISO $f2 $ym1 53 1 [expr { $secs - 4*86400 }] + testISO $f2 $ym1 53 5 $secs + testISO $f2 $ym1 53 7 [expr { $secs + 2*86400 }] + testISO $f2 $y 1 1 [expr { $secs + 3*86400 }] + testISO $f2 $y 1 7 [expr { $secs + 9*86400 }] + testISO $f2 $y 2 1 [expr { $secs + 10*86400 }] + } + 6 { + # messy case because previous year may have had 52 or 53 weeks + if { $y%4 == 1 } { + testISO $f2 $ym1 53 1 [expr { $secs - 5*86400 }] + testISO $f2 $ym1 53 6 $secs + testISO $f2 $ym1 53 7 [expr { $secs + 86400 }] + } else { + testISO $f2 $ym1 52 1 [expr { $secs - 5*86400 }] + testISO $f2 $ym1 52 6 $secs + testISO $f2 $ym1 52 7 [expr { $secs + 86400 }] + } + testISO $f2 $y 1 1 [expr { $secs + 2*86400 }] + testISO $f2 $y 1 7 [expr { $secs + 8*86400 }] + testISO $f2 $y 2 1 [expr { $secs + 9*86400 }] + } + } + } + puts "testcases3: $case test cases." + +} + +proc testISO { f2 G V u secs } { + + upvar 1 case case + + set longdays {Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday} + set shortdays {Sun Mon Tue Wed Thu Fri Sat Sun} + + puts $f2 "test clock-3.[incr case] {ISO week-based calendar [format %04d-W%02d-%d $G $V $u]} {" + puts $f2 " clock format $secs -format {%a %A %g %G %u %V %w} -gmt true; \# $G-W[format %02d $V]-$u" + puts $f2 "} {[lindex $shortdays $u] [lindex $longdays $u]\ + [format %02d [expr { $G % 100 }]] $G\ + $u\ + [format %02d $V] [expr { $u % 7 }]}" + +} + +#---------------------------------------------------------------------- +# +# testcases4 -- +# +# Makes the test cases that test formatting of time of day. +# +# Parameters: +# f2 - Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Writes test cases to the output. +# +#---------------------------------------------------------------------- + +proc testcases4 { f2 } { + + puts $f2 {} + puts $f2 "\# Test formatting of time of day" + puts $f2 "\# Format groups tested: %H %OH %I %OI %k %Ok %l %Ol %M %OM %p %P %r %R %S %OS %T %X %EX %+" + puts $f2 {} + + set i 0 + set fmt "%H %OH %I %OI %k %Ok %l %Ol %M %OM %p %P %r %R %S %OS %T %X %EX %+" + foreach { h romanH I romanI am } { + 0 ? 12 xii AM + 1 i 1 i AM + 11 xi 11 xi AM + 12 xii 12 xii PM + 13 xiii 1 i PM + 23 xxiii 11 xi PM + } { + set hh [format %02d $h] + set II [format %02d $I] + set hs [format %2d $h] + set Is [format %2d $I] + foreach { m romanM } { 0 ? 1 i 58 lviii 59 lix } { + set mm [format %02d $m] + foreach { s romanS } { 0 ? 1 i 58 lviii 59 lix } { + set ss [format %02d $s] + set x [expr { ( $h * 60 + $m ) * 60 + $s }] + set result "" + append result $hh " " $romanH " " $II " " $romanI " " \ + $hs " " $romanH " " $Is " " $romanI " " $mm " " $romanM " " \ + $am " " [string tolower $am] " " \ + $II ":" $mm ":" $ss " " [string tolower $am] " " \ + $hh ":" $mm " " \ + $ss " " $romanS " " \ + $hh ":" $mm ":" $ss " " \ + $hh ":" $mm ":" $ss " " \ + $romanH " h " $romanM " m " $romanS " s " \ + "Thu Jan 1 " $hh : $mm : $ss " GMT 1970" + puts $f2 "test clock-4.[incr i] { format time of day $hh:$mm:$ss } {" + puts $f2 " clock format $x \\" + puts $f2 " -format [list $fmt] \\" + puts $f2 " -locale en_US_roman \\" + puts $f2 " -gmt true" + puts $f2 "} {$result}" + } + } + } + + puts "testcases4: $i test cases." +} + +#---------------------------------------------------------------------- +# +# testcases5 -- +# +# Generates the test cases for Daylight Saving Time +# +# Parameters: +# f2 - Channel handle for the input file +# +# Results: +# None. +# +# Side effects: +# Makes test cases for each known or anticipated time change +# in Detroit. +# +#---------------------------------------------------------------------- + +proc testcases5 { f2 } { + variable TZData + + puts $f2 {} + puts $f2 "\# Test formatting of Daylight Saving Time" + puts $f2 {} + + set fmt {%H:%M:%S %z %Z} + + set i 0 + puts $f2 "::tcltest::testConstraint detroit 0" + puts $f2 "test clock-5.[incr i] {does Detroit exist} {" + puts $f2 " clock format 0 -format {} -timezone :America/Detroit" + puts $f2 " ::tcltest::testConstraint detroit 1" + puts $f2 " concat" + puts $f2 "} {}" + puts $f2 "test clock-5.[incr i] {does Detroit have a Y2038 problem} detroit {" + puts $f2 " if { \[clock format 2158894800 -format %z -timezone :America/Detroit\] ne {-0400} } {" + puts $f2 " concat {y2038 problem}" + puts $f2 " } else {" + puts $f2 " ::tcltest::testConstraint y2038 1" + puts $f2 " concat {ok}" + puts $f2 " }" + puts $f2 "} ok" + + foreach row $TZData(:America/Detroit) { + foreach { t offset isdst tzname } $row break + if { $t > -4000000000000 } { + set conds [list detroit] + if { $t > wide(0x7fffffff) } { + set conds [list detroit y2038] + } + incr t -1 + set x [clock format $t -format {%Y-%m-%d %H:%M:%S} \ + -timezone :America/Detroit] + set r [clock format $t -format $fmt \ + -timezone :America/Detroit] + puts $f2 "test clock-5.[incr i] {time zone boundary case $x} [list $conds] {" + puts $f2 " clock format $t -format [list $fmt] \\" + puts $f2 " -timezone :America/Detroit" + puts $f2 "} [list $r]" + incr t + set x [clock format $t -format {%Y-%m-%d %H:%M:%S} \ + -timezone :America/Detroit] + set r [clock format $t -format $fmt \ + -timezone :America/Detroit] + puts $f2 "test clock-5.[incr i] {time zone boundary case $x} [list $conds] {" + puts $f2 " clock format $t -format [list $fmt] \\" + puts $f2 " -timezone :America/Detroit" + puts $f2 "} [list $r]" + incr t + set x [clock format $t -format {%Y-%m-%d %H:%M:%S} \ + -timezone :America/Detroit] + set r [clock format $t -format $fmt \ + -timezone :America/Detroit] + puts $f2 "test clock-5.[incr i] {time zone boundary case $x} [list $conds] {" + puts $f2 " clock format $t -format [list $fmt] \\" + puts $f2 " -timezone :America/Detroit" + puts $f2 "} [list $r]" + } + } + puts "testcases5: $i test cases" +} + +#---------------------------------------------------------------------- +# +# testcases8 -- +# +# Outputs the 'clock-8.x' test cases. +# +# Parameters: +# f2 -- Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Test cases for parsing dates in ccyymmdd format are written to the +# output file. +# +#---------------------------------------------------------------------- + +proc testcases8 { f2 } { + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test parsing of ccyymmdd" + puts $f2 "" + + set n 0 + foreach year {1970 1971 2000 2001} { + foreach month {01 12} { + foreach day {02 31} { + set scanned [clock scan $year$month$day -gmt true] + foreach ccyy {%C%y %Y} { + foreach mm {%b %B %h %m %Om %N} { + foreach dd {%d %Od %e %Oe} { + set string [clock format $scanned \ + -format "$ccyy $mm $dd" \ + -locale en_US_roman \ + -gmt true] + puts $f2 "test clock-8.[incr n] {parse ccyymmdd} {" + puts $f2 " [list clock scan $string -format [list $ccyy $mm $dd] -locale en_US_roman -gmt 1]" + puts $f2 "} $scanned" + } + } + } + foreach fmt {%x %D} { + set string [clock format $scanned \ + -format $fmt \ + -locale en_US_roman \ + -gmt true] + puts $f2 "test clock-8.[incr n] {parse ccyymmdd} {" + puts $f2 " [list clock scan $string -format $fmt -locale en_US_roman -gmt 1]" + puts $f2 "} $scanned" + } + } + } + } + + puts "testcases8: $n test cases" +} + +#---------------------------------------------------------------------- +# +# testcases11 -- +# +# Outputs the 'clock-11.x' test cases. +# +# Parameters: +# f2 -- Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Test cases for precedence among YYYYMMDD and YYYYDDD are written +# to f2. +# +#---------------------------------------------------------------------- + +proc testcases11 { f2 } { + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test precedence among yyyymmdd and yyyyddd" + puts $f2 "" + + array set v { + Y 1970 + m 01 + d 01 + j 002 + } + + set n 0 + + foreach {a b c d} { + Y m d j m Y d j d Y m j j Y m d + Y m j d m Y j d d Y j m j Y d m + Y d m j m d Y j d m Y j j m Y d + Y d j m m d j Y d m j Y j m d Y + Y j m d m j Y d d j Y m j d Y m + Y j d m m j d Y d j m Y j d m Y + } { + foreach x [list $a $b $c $d] { + switch -exact -- $x { + m - d { + set value 0 + } + j { + set value 86400 + } + } + } + set format "%$a%$b%$c%$d" + set string "$v($a)$v($b)$v($c)$v($d)" + puts $f2 "test clock-11.[incr n] {precedence of ccyyddd and ccyymmdd} {" + puts $f2 " [list clock scan $string -format $format -gmt 1]" + puts $f2 "} $value" + } + + puts "testcases11: $n test cases" +} + +#---------------------------------------------------------------------- +# +# testcases12 -- +# +# Outputs the 'clock-12.x' test cases, parsing CCyyWwwd +# +# Parameters: +# f2 -- Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Test cases for parsing dates in Gregorian calendar are written to the +# output file. +# +#---------------------------------------------------------------------- + +proc testcases12 { f2 } { + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test parsing of ccyyWwwd" + puts $f2 "" + + set n 0 + foreach year {1970 1971 2000 2001} { + foreach month {01 12} { + foreach day {02 31} { + set scanned [clock scan $year$month$day -gmt true] + foreach d {%a %A %u %w %Ou %Ow} { + set string [clock format $scanned \ + -format "%G W%V $d" \ + -locale en_US_roman \ + -gmt true] + puts $f2 "test clock-12.[incr n] {parse ccyyWwwd} {" + puts $f2 " [list clock scan $string -format [list %G W%V $d] -locale en_US_roman -gmt 1]" + puts $f2 "} $scanned" + } + } + } + } + + puts "testcases12: $n test cases" +} + +#---------------------------------------------------------------------- +# +# testcases14 -- +# +# Outputs the 'clock-14.x' test cases. +# +# Parameters: +# f2 -- Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Test cases for parsing yymmdd dates are output. +# +#---------------------------------------------------------------------- + +proc testcases14 { f2 } { + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test parsing of yymmdd" + puts $f2 "" + + set n 0 + foreach year {1938 1970 2000 2037} { + foreach month {01 12} { + foreach day {02 31} { + set scanned [clock scan $year$month$day -gmt true] + foreach yy {%y %Oy} { + foreach mm {%b %B %h %m %Om %N} { + foreach dd {%d %Od %e %Oe} { + set string [clock format $scanned \ + -format "$yy $mm $dd" \ + -locale en_US_roman \ + -gmt true] + puts $f2 "test clock-14.[incr n] {parse yymmdd} {" + puts $f2 " [list clock scan $string -format [list $yy $mm $dd] -locale en_US_roman -gmt 1]" + puts $f2 "} $scanned" + } + } + } + } + } + } + + puts "testcases14: $n test cases" +} + +#---------------------------------------------------------------------- +# +# testcases17 -- +# +# Outputs the 'clock-17.x' test cases, parsing yyWwwd +# +# Parameters: +# f2 -- Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Test cases for parsing dates in Gregorian calendar are written to the +# output file. +# +#---------------------------------------------------------------------- + +proc testcases17 { f2 } { + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test parsing of yyWwwd" + puts $f2 "" + + set n 0 + foreach year {1970 1971 2000 2001} { + foreach month {01 12} { + foreach day {02 31} { + set scanned [clock scan $year$month$day -gmt true] + foreach d {%a %A %u %w %Ou %Ow} { + set string [clock format $scanned \ + -format "%g W%V $d" \ + -locale en_US_roman \ + -gmt true] + puts $f2 "test clock-17.[incr n] {parse yyWwwd} {" + puts $f2 " [list clock scan $string -format [list %g W%V $d] -locale en_US_roman -gmt 1]" + puts $f2 "} $scanned" + } + } + } + } + + puts "testcases17: $n test cases" +} + +#---------------------------------------------------------------------- +# +# testcases19 -- +# +# Outputs the 'clock-19.x' test cases. +# +# Parameters: +# f2 -- Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Test cases for parsing mmdd dates are output. +# +#---------------------------------------------------------------------- + +proc testcases19 { f2 } { + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test parsing of mmdd" + puts $f2 "" + + set n 0 + foreach year {1938 1970 2000 2037} { + set base [clock scan ${year}0101 -gmt true] + foreach month {01 12} { + foreach day {02 31} { + set scanned [clock scan $year$month$day -gmt true] + foreach mm {%b %B %h %m %Om %N} { + foreach dd {%d %Od %e %Oe} { + set string [clock format $scanned \ + -format "$mm $dd" \ + -locale en_US_roman \ + -gmt true] + puts $f2 "test clock-19.[incr n] {parse mmdd} {" + puts $f2 " [list clock scan $string -format [list $mm $dd] -locale en_US_roman -base $base -gmt 1]" + puts $f2 "} $scanned" + } + } + } + } + } + + puts "testcases19: $n test cases" +} + +#---------------------------------------------------------------------- +# +# testcases21 -- +# +# Outputs the 'clock-21.x' test cases, parsing Wwwd +# +# Parameters: +# f2 -- Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Test cases for parsing dates in Gregorian calendar are written to the +# output file. +# +#---------------------------------------------------------------------- + +proc testcases22 { f2 } { + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test parsing of Wwwd" + puts $f2 "" + + set n 0 + foreach year {1970 1971 2000 2001} { + set base [clock scan ${year}0104 -gmt true] + foreach month {03 10} { + foreach day {01 31} { + set scanned [clock scan $year$month$day -gmt true] + foreach d {%a %A %u %w %Ou %Ow} { + set string [clock format $scanned \ + -format "W%V $d" \ + -locale en_US_roman \ + -gmt true] + puts $f2 "test clock-22.[incr n] {parse Wwwd} {" + puts $f2 " [list clock scan $string -format [list W%V $d] -locale en_US_roman -gmt 1] -base $base" + puts $f2 "} $scanned" + } + } + } + } + + puts "testcases22: $n test cases" +} + +#---------------------------------------------------------------------- +# +# testcases24 -- +# +# Outputs the 'clock-24.x' test cases. +# +# Parameters: +# f2 -- Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Test cases for parsing naked day of the month are output. +# +#---------------------------------------------------------------------- + +proc testcases24 { f2 } { + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test parsing of naked day-of-month" + puts $f2 "" + + set n 0 + foreach year {1970 2000} { + foreach month {01 12} { + set base [clock scan ${year}${month}01 -gmt true] + foreach day {02 28} { + set scanned [clock scan $year$month$day -gmt true] + foreach dd {%d %Od %e %Oe} { + set string [clock format $scanned \ + -format "$dd" \ + -locale en_US_roman \ + -gmt true] + puts $f2 "test clock-24.[incr n] {parse naked day of month} {" + puts $f2 " [list clock scan $string -format $dd -locale en_US_roman -base $base -gmt 1]" + puts $f2 "} $scanned" + } + } + } + } + + puts "testcases24: $n test cases" +} + +#---------------------------------------------------------------------- +# +# testcases26 -- +# +# Outputs the 'clock-26.x' test cases, parsing naked day of week +# +# Parameters: +# f2 -- Channel handle to the output file +# +# Results: +# None. +# +# Side effects: +# Test cases for parsing dates in Gregorian calendar are written to the +# output file. +# +#---------------------------------------------------------------------- + +proc testcases26 { f2 } { + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test parsing of naked day of week" + puts $f2 "" + + set n 0 + foreach year {1970 2001} { + foreach week {01 52} { + set base [clock scan ${year}W${week}4 \ + -format %GW%V%u -gmt true] + foreach day {1 7} { + set scanned [clock scan ${year}W${week}${day} \ + -format %GW%V%u -gmt true] + foreach d {%a %A %u %w %Ou %Ow} { + set string [clock format $scanned \ + -format "$d" \ + -locale en_US_roman \ + -gmt true] + puts $f2 "test clock-26.[incr n] {parse naked day of week} {" + puts $f2 " [list clock scan $string -format $d -locale en_US_roman -gmt 1] -base $base" + puts $f2 "} $scanned" + } + } + } + } + + puts "testcases26: $n test cases" +} + +#---------------------------------------------------------------------- +# +# testcases29 -- +# +# Makes test cases for parsing of time of day. +# +# Parameters: +# f2 -- Channel where tests are to be written +# +# Results: +# None. +# +# Side effects: +# Writes the tests. +# +#---------------------------------------------------------------------- + +proc testcases29 { f2 } { + + # Put out a header describing the tests + + puts $f2 "" + puts $f2 "\# Test parsing of time of day" + puts $f2 "" + + set n 0 + foreach hour {0 1 11 12 13 23} \ + hampm {12 1 11 12 1 11} \ + lhour {? i xi xii xiii xxiii} \ + lhampm {xii i xi xii i xi} \ + ampmind {am am am pm pm pm} { + set sphr [format %2d $hour] + set 2dhr [format %02d $hour] + set sphampm [format %2d $hampm] + set 2dhampm [format %02d $hampm] + set AMPMind [string toupper $ampmind] + foreach minute {00 01 59} lminute {? i lix} { + foreach second {00 01 59} lsecond {? i lix} { + set time [expr { ( 60 * $hour + $minute ) * 60 + $second }] + foreach {hfmt afmt} [list \ + %H {} %k {} %OH {} %Ok {} \ + %I %p %l %p \ + %OI %p %Ol %p \ + %I %P %l %P \ + %OI %P %Ol %P] \ + {hfld afld} [list \ + $2dhr {} $sphr {} $lhour {} $lhour {} \ + $2dhampm $AMPMind $sphampm $AMPMind \ + $lhampm $AMPMind $lhampm $AMPMind \ + $2dhampm $ampmind $sphampm $ampmind \ + $lhampm $ampmind $lhampm $ampmind] \ + { + if { $second eq "00" } { + if { $minute eq "00" } { + puts $f2 "test clock-29.[incr n] {time parsing} {" + puts $f2 " clock scan {2440588 $hfld $afld} \\" + puts $f2 " -gmt true -locale en_US_roman \\" + puts $f2 " -format {%J $hfmt $afmt}" + puts $f2 "} $time" + } + puts $f2 "test clock-29.[incr n] {time parsing} {" + puts $f2 " clock scan {2440588 $hfld:$minute $afld} \\" + puts $f2 " -gmt true -locale en_US_roman \\" + puts $f2 " -format {%J $hfmt:%M $afmt}" + puts $f2 "} $time" + puts $f2 "test clock-29.[incr n] {time parsing} {" + puts $f2 " clock scan {2440588 $hfld:$lminute $afld} \\" + puts $f2 " -gmt true -locale en_US_roman \\" + puts $f2 " -format {%J $hfmt:%OM $afmt}" + puts $f2 "} $time" + } + puts $f2 "test clock-29.[incr n] {time parsing} {" + puts $f2 " clock scan {2440588 $hfld:$minute:$second $afld} \\" + puts $f2 " -gmt true -locale en_US_roman \\" + puts $f2 " -format {%J $hfmt:%M:%S $afmt}" + puts $f2 "} $time" + puts $f2 "test clock-29.[incr n] {time parsing} {" + puts $f2 " clock scan {2440588 $hfld:$lminute:$lsecond $afld} \\" + puts $f2 " -gmt true -locale en_US_roman \\" + puts $f2 " -format {%J $hfmt:%OM:%OS $afmt}" + puts $f2 "} $time" + } + } + } + + } + puts "testcases29: $n test cases" +} + +processFile $d diff --git a/tools/tclZIC.tcl b/tools/tclZIC.tcl new file mode 100755 index 0000000..e6bbb5a --- /dev/null +++ b/tools/tclZIC.tcl @@ -0,0 +1,1440 @@ +#---------------------------------------------------------------------- +# +# tclZIC.tcl -- +# +# Take the time zone data source files from Arthur Olson's +# repository at elsie.nci.nih.gov, and prepare time zone +# information files for Tcl. +# +# Usage: +# tclsh tclZIC.tcl inputDir outputDir +# +# Parameters: +# inputDir - Directory (e.g., tzdata2003e) where Olson's source +# files are to be found. +# outputDir - Directory (e.g., ../library/clock/tzdata) where +# the time zone information files are to be placed. +# +# Results: +# May produce error messages on the standard error. An exit +# code of zero denotes success; any other exit code is failure. +# +# This program parses the timezone data in a means analogous to the +# 'zic' command, and produces Tcl time zone information files suitable +# for loading into the 'clock' namespace. +# +#---------------------------------------------------------------------- +# +# Copyright (c) 2004 by Kevin B. Kenny. All rights reserved. +# See the file "license.terms" for information on usage and redistribution +# of this file, and for a DISCLAIMER OF ALL WARRANTIES. +# +# RCS: @(#) $Id: tclZIC.tcl,v 1.1 2004/08/18 19:59:09 kennykb Exp $ +# +#---------------------------------------------------------------------- + +# Define the names of the Olson files that we need to load. +# We avoid the solar time files and the leap seconds. + +set olsonFiles { + africa antarctica asia australasia + backward etcetera europe northamerica + pacificnew southamerica systemv +} + +# Temporary scaffolding - load up the new 'clock' package. + +source [file join [file dirname [info script]] .. library clock.tcl] + +# Define the year at which the DST information will stop. + +set maxyear 2100 + +# Determine how big a wide integer is. + +set MAXWIDE [expr { wide(1) }] +while 1 { + set next [expr { $MAXWIDE + $MAXWIDE + 1}] + if { $next < 0 } { + break + } + set MAXWIDE $next +} +set MINWIDE [expr { - $MAXWIDE - 1 }] + +#---------------------------------------------------------------------- +# +# K -- +# +# The K combinator returns its first argument. It's used for +# reference count management. +# +# Parameters: +# x - Argument to be unreferenced. +# y - Unused. +# +# Results: +# Returns the first argument. +# +# Side effects: +# None. +# +# The K combinator is used for its effect that [K $x [set x {}]] +# reads out the value of x destructively, giving an unshared Tcl +# object and avoiding 'copy on write' +# +#---------------------------------------------------------------------- + +proc K {x y} {return $x} + +#---------------------------------------------------------------------- +# +# loadFiles -- +# +# Loads the time zone files for each continent into memory +# +# Parameters: +# dir - Directory where the time zone source files are found +# +# Results: +# None. +# +# Side effects: +# Calls 'loadZIC' for each continent's data file in turn. +# Reports progress on stdout. +# +#---------------------------------------------------------------------- + +proc loadFiles { dir } { + variable olsonFiles + foreach file $olsonFiles { + puts "loading: [file join $dir $file]" + loadZIC [file join $dir $file] + } + return +} + +#---------------------------------------------------------------------- +# +# checkForwardRuleRefs -- +# +# Checks to make sure that all references to Daylight Saving +# Time rules designate defined rules. +# +# Parameters: +# None. +# +# Results: +# None. +# +# Side effects: +# Produces an error message and increases the error count if +# any undefined rules are present. +# +#---------------------------------------------------------------------- + +proc checkForwardRuleRefs {} { + variable forwardRuleRefs + variable rules + foreach { rule where } [array get forwardRuleRefs] { + if { ![info exists rules($rule)] } { + foreach { fileName lno } $where { + puts stderr "$fileName:$lno:can't locate rule \"$rule\"" + incr errorCount + } + } + } +} + +#---------------------------------------------------------------------- +# +# loadZIC -- +# +# Load one continent's data into memory. +# +# Parameters: +# fileName -- Name of the time zone source file. +# +# Results: +# None. +# +# Side effects: +# The global variable, 'errorCount' counts the number of errors. +# The global array, 'links', contains a distillation of the +# 'Link' directives in the file. The keys are 'links to' and +# the values are 'links from'. The 'parseRule' and 'parseZone' +# procedures are called to handle 'Rule' and 'Zone' directives. +# +#---------------------------------------------------------------------- + +proc loadZIC { fileName } { + + variable errorCount + variable links + + # Suck the text into memory. + + set f [open $fileName r] + set data [read $f] + close $f + + # Break the input into lines, and count line numbers. + + set lno 0 + foreach line [split $data \n] { + incr lno + + # Break a line of input into words. + + regsub {[[:space:]]*(\#.*)?$} $line {} line + if { $line eq {} } { + continue + } + set words {} + if { [regexp {^[[:space:]]+(.*)} $line -> l] } { + lappend words {} + set line $l + } + while {[regexp {^([^[:space:]]+)[[:space:]]*(.*)} $line -> \ + word line]} { + lappend words $word + } + + # Switch on the directive + + switch -exact -- [lindex $words 0] { + Rule { + parseRule $fileName $lno $words + } + Link { + set links([lindex $words 2]) [lindex $words 1] + } + Zone { + set lastZone [lindex $words 1] + set until [parseZone $fileName $lno \ + $lastZone [lrange $words 2 end] minimum] + } + {} { # Continuation of a Zone + set i 0 + foreach word $words { + if { [lindex $words $i] ne {} } break + incr i + } + set words [lrange $words $i end] + set until [parseZone $fileName $lno $lastZone $words $until] + } + default { + incr errorCount + puts stderr "$fileName:$lno:unknown line type \"[lindex $words 0]\"" + } + } + } + + return + +} + +#---------------------------------------------------------------------- +# +# parseRule -- +# +# Parses a Rule directive in an Olson file. +# +# Parameters: +# fileName -- Name of the file being parsed. +# lno - Line number within the file +# words - The line itself, broken into words. +# +# Results: +# None. +# +# Side effects: +# The rule is analyzed and added to the 'rules' array. +# Errors are reported and counted. +# +#---------------------------------------------------------------------- + +proc parseRule { fileName lno words } { + + variable rules + variable errorCount + + # Break out the columns + + foreach { Rule name from to type in on at save letter } $words {} + + # Handle the 'only' keyword + + if { $to eq {only} } { + set to $from + } + + # Process the start year + + set l [string length $from] + if { ![string is integer $from] } { + if { $from ne [string range {minumum} 0 [expr { $l - 1 }]] } { + puts stderr "$fileName:$lno:FROM field \"$from\" not an integer." + incr errorCount + return + } else { + set from minimum + } + } + + # Process the end year + + set l [string length $to] + if { ![string is integer $to] } { + if { $to ne [string range {maximum} 0 [expr { $l - 1 }]] } { + puts stderr "$fileName:$lno:TO field \"$to\" not an integer." + incr errorCount + return + } else { + set to maximum + } + } + + # Process the type of year in which the rule applies + + if { $type ne {-} } { + puts stderr "$fileName:$lno:year types are not yet supported." + incr errorCount + return + } + + # Process the month in which the rule starts + + if { [catch {lookupMonth $in} in] } { + puts stderr "$fileName:$lno:$in" + incr errorCount + return + } + + # Process the day of the month on which the rule starts + + if { [catch {parseON $on} on] } { + puts stderr "$fileName:$lno:$on" + incr errorCount + return + } + + # Process the time of day on which the rule starts + + if { [catch {parseTOD $at} at] } { + puts stderr "$fileName:$lno:$at" + incr errorCount + return + } + + # Process the DST adder + + if { [catch {parseOffsetTime $save} save] } { + puts stderr "$fileName:$lno:$save" + incr errorCount + return + } + + # Process the letter to use for summer time + + if { $letter eq {-} } { + set letter {} + } + + # Accumulate all the data. + + lappend rules($name) $from $to $type $in $on $at $save $letter + return + +} + +#---------------------------------------------------------------------- +# +# parseON -- +# +# Parse a specification for a day of the month +# +# Parameters: +# on - the ON field from a line in an Olson file. +# +# Results: +# Returns a partial Tcl command. When the year and number of the +# month are appended, the command will return the Julian Day Number +# of the desired date. +# +# Side effects: +# None. +# +# The specification can be: +# - a simple number, which designates a constant date. +# - The name of a weekday, followed by >= or <=, followed by a number. +# This designates the nearest occurrence of the given weekday on +# or before (on or after) the given day of the month. +# - The word 'last' followed by a weekday name with no intervening +# space. This designates the last occurrence of the given weekday +# in the month. +# +#---------------------------------------------------------------------- + +proc parseON { on } { + if { ! [regexp -expanded { + ^(?: + # first possibility - simple number - field 1 + ([[:digit:]]+) + | + # second possibility - weekday >= (or <=) number + # field 2 - weekday + ([[:alpha:]]+) + # field 3 - direction + ([<>]=) + # field 4 - number + ([[:digit:]]+) + | + # third possibility - lastWeekday - field 5 + last([[:alpha:]]+) + )$ + } $on -> dom1 wday2 dir2 num2 wday3] } { + error "can't parse ON field \"$on\"" + } + if { $dom1 ne {} } { + return [list onDayOfMonth $dom1] + } elseif { $wday2 ne {} } { + set wday2 [lookupDayOfWeek $wday2] + return [list onWeekdayInMonth $wday2 $dir2 $num2] + } elseif { $wday3 ne {} } { + set wday3 [lookupDayOfWeek $wday3] + return [list onLastWeekdayInMonth $wday3] + } else { + error "in parseOn \"$on\": can't happen" + } +} + +#---------------------------------------------------------------------- +# +# onDayOfMonth -- +# +# Find a given day of a given month +# +# Parameters: +# day - Day of the month +# year - Gregorian year +# month - Number of the month (1-12) +# +# Results: +# Returns the Julian Day Number of the desired day. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc onDayOfMonth { day year month } { + set date [dict create era CE year $year month $month dayOfMonth $day] + set date [::tcl::clock::GetJulianDayFromEraYearMonthDay \ + [K $date [set date {}]]] + return [dict get $date julianDay] +} + +#---------------------------------------------------------------------- +# +# onWeekdayInMonth -- +# +# Find the weekday falling on or after (on or before) a +# given day of the month +# +# Parameters: +# dayOfWeek - Day of the week (Monday=1, Sunday=7) +# relation - <= for the weekday on or before a given date, >= for +# the weekday on or after the given date. +# dayOfMonth - Day of the month +# year - Gregorian year +# month - Number of the month (1-12) +# +# Results: +# Returns the Juloan Day Number of the desired day. +# +# Side effects: +# None. +# +# onWeekdayInMonth is used to compute Daylight Saving Time rules +# like 'Sun>=1' (for the nearest Sunday on or after the first of the month) +# or "Mon<=4' (for the Monday on or before the fourth of the month). +# +#---------------------------------------------------------------------- + +proc onWeekdayInMonth { dayOfWeek relation dayOfMonth year month } { + set date [dict create \ + era CE year $year month $month dayOfMonth $dayOfMonth] + set date [::tcl::clock::GetJulianDayFromEraYearMonthDay \ + [K $date [set date {}]]] + switch -exact -- $relation { + <= { + return [::tcl::clock::WeekdayOnOrBefore $dayOfWeek \ + [dict get $date julianDay]] + } + >= { + return [::tcl::clock::WeekdayOnOrBefore $dayOfWeek \ + [expr { [dict get $date julianDay] + 6 }]] + } + } +} + +#---------------------------------------------------------------------- +# +# onLastWeekdayInMonth -- +# +# Find the last instance of a given weekday in a month. +# +# Parameters: +# dayOfWeek - Weekday to find (Monday=1, Sunday=7) +# year - Gregorian year +# month - Month (1-12) +# +# Results: +# Returns the Julian Day number of the last instance of +# the given weekday in the given month +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc onLastWeekdayInMonth { dayOfWeek year month } { + incr month + # Find day 0 of the following month, which is the last day of + # the current month. Yes, it works to ask for day 0 of month 13! + set date [dict create \ + era CE year $year month $month dayOfMonth 0] + set date [::tcl::clock::GetJulianDayFromEraYearMonthDay \ + [K $date [set date {}]]] + return [::tcl::clock::WeekdayOnOrBefore $dayOfWeek \ + [dict get $date julianDay]] +} + +#---------------------------------------------------------------------- +# +# parseTOD -- +# +# Parses the specification of a time of day in an Olson file. +# +# Parameters: +# tod - Time of day, which may be followed by 'w', 's', 'u', 'g' +# or 'z'. 'w' (or no letter) designates a wall clock time, +# 's' designates Standard Time in the given zone, and +# 'u', 'g', and 'z' all designate UTC. +# +# Results: +# Returns a two element list containing a count of seconds from +# midnight and the letter that followed the time. +# +# Side effects: +# Reports and counts an error if the time cannot be parsed. +# +#---------------------------------------------------------------------- + +proc parseTOD { tod } { + if { ![regexp -expanded { + ^ + # field 1 - hour + ([[:digit:]]{1,2}) + (?: + # field 2 - minute + :([[:digit:]]{2}) + (?: + # field 3 - second + :([[:digit:]]{2}) + )? + )? + (?: + # field 4 - type indicator + ([wsugz]) + )? + } $tod -> hour minute second ind] } { + puts stderr "$fileName:$lno:can't parse time field \"$tod\"" + incr errorCount + } + scan $hour %d hour + if { $minute ne {} } { + scan $minute %d minute + } else { + set minute 0 + } + if { $second ne {} } { + scan $second %d second + } else { + set second 0 + } + if { $ind eq {} } { + set ind w + } + return [list [expr { ( $hour * 60 + $minute ) * 60 + $second }] $ind] +} + +#---------------------------------------------------------------------- +# +# parseOffsetTime -- +# +# Parses the specification of an offset time in an Olson file. +# +# Parameters: +# offset - Offset time as [+-]hh:mm:ss +# +# Results: +# Returns the offset time as a count of seconds. +# +# Side effects: +# Reports and counts an error if the time cannot be parsed. +# +#---------------------------------------------------------------------- + +proc parseOffsetTime { offset } { + if { ![regexp -expanded { + ^ + # field 1 - signum + ([-+])? + # field 2 - hour + ([[:digit:]]{1,2}) + (?: + # field 3 - minute + :([[:digit:]]{2}) + (?: + # field 4 - second + :([[:digit:]]{2}) + )? + )? + } $offset -> signum hour minute second] } { + puts stderr "$fileName:$lno:can't parse offset time \"$offset\"" + incr errorCount + } + append signum 1 + scan $hour %d hour + if { $minute ne {} } { + scan $minute %d minute + } else { + set minute 0 + } + if { $second ne {} } { + scan $second %d second + } else { + set second 0 + } + return [expr { ( ( $hour * 60 + $minute ) * 60 + $second ) * $signum }] + +} + +#---------------------------------------------------------------------- +# +# lookupMonth - +# Looks up a month by name +# +# Parameters: +# month - Name of a month. +# +# Results: +# Returns the number of the month. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc lookupMonth { month } { + + set indx [lsearch -regexp { + {} January February March April May June + July August September October November December + } ${month}.*] + if { $indx < 1 } { + error "unknown month name \"$month\"" + } + return $indx +} + +#---------------------------------------------------------------------- +# +# lookupDayOfWeek -- +# +# Looks up the name of a weekday. +# +# Parameters: +# wday - Weekday name (or a unique prefix). +# +# Results: +# Returns the weekday number (Monday=1, Sunday=7) +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc lookupDayOfWeek { wday } { + set indx [lsearch -regexp { + {} Monday Tuesday Wednesday Thursday Friday Saturday Sunday + } ${wday}.*] + if { $indx < 1 } { + error "unknown weekday name \"$wday\"" + } + return $indx +} + +#---------------------------------------------------------------------- +# +# parseZone -- +# +# Parses a Zone directive in an Olson file +# +# Parameters: +# fileName -- Name of the file being parsed. +# lno -- Line number within the file. +# zone -- Name of the time zone +# words -- Remaining words on the line. +# start -- 'Until' time from the previous line if this is a +# continuation line, or 'minimum' if this is the first line. +# +# Results: +# Returns the 'until' field of the current line +# +# Side effects: +# Stores a row in the 'zones' array describing the current zone. +# The row consists of a start time (year month day tod), a Standard +# Time offset from Greenwich, a Daylight Saving Time offset from +# Standard Time, and a format for printing the time zone. +# +# The start time is the result of an earlier call to 'parseUntil' +# or else the keyword 'minimum'. The GMT offset is the +# result of a call to 'parseOffsetTime'. The Daylight Saving +# Time offset is represented as a partial Tcl command. To the +# command will be appended a start time (seconds from epoch) +# the current offset of Standard Time from Greenwich, the current +# offset of Daylight Saving Time from Greenwich, the default +# offset from this line, the name pattern from this line, +# the 'until' field from this line, and a variable name where points +# are to be stored. This command is implemented by the 'applyNoRule', +# 'applyDSTOffset' and 'applyRules' procedures. +# +#---------------------------------------------------------------------- + +proc parseZone { fileName lno zone words start } { + variable zones + variable rules + variable errorCount + variable forwardRuleRefs + foreach { gmtoff save format } $words break + if { [catch {parseOffsetTime $gmtoff} gmtoff] } { + puts stderr "$fileName:$lno:$gmtoff" + incr errorCount + return + } + if { [info exists rules($save)] } { + set save [list applyRules $save] + } elseif { $save eq {-} } { + set save [list applyNoRule] + } else { + if { [catch { parseOffsetTime $save } save2] } { + lappend forwardRuleRefs($save) $fileName $lno + set save [list applyRules $save] + } else { + set save [list applyDSTOffset $save2] + } + } + lappend zones($zone) $start $gmtoff $save $format + if { [llength $words] >= 4 } { + return [parseUntil [lrange $words 3 end]] + } else { + return {} + } +} + +#---------------------------------------------------------------------- +# +# parseUntil -- +# +# Parses the 'UNTIL' part of a 'Zone' directive. +# +# Parameters: +# words - The 'UNTIL' part of the directie. +# +# Results: +# Returns a list comprising the year, the month, the day, and +# the time of day. Time of day is represented as the result of +# 'parseTOD'. +# +#---------------------------------------------------------------------- + +proc parseUntil { words } { + variable firstYear + if { [llength $words] >= 1 } { + set year [lindex $words 0] + if { ![string is integer $year] } { + error "can't parse UNTIL field \"$words\"" + } + if { ![info exists firstYear] || $year < $firstYear } { + set firstYear $year + } + } else { + set year maximum + } + if { [llength $words] >= 2 } { + set month [lookupMonth [lindex $words 1]] + } else { + set month 1 + } + if { [llength $words] >= 3 } { + set day [parseON [lindex $words 2]] + } else { + set day {onDayOfMonth 1} + } + if { [llength $words] >= 4 } { + set tod [parseTOD [lindex $words 3]] + } else { + set tod {0 w} + } + return [list $year $month $day $tod] +} + +#---------------------------------------------------------------------- +# +# applyNoRule -- +# +# Generates time zone data for a zone without Daylight Saving +# Time. +# +# Parameters: +# year - Year in which the rule applies +# startSecs - Time at which the rule starts. +# stdGMTOffset - Offset from Greenwich prior to the start of the +# rule +# DSTOffset - Offset of Daylight from Standard prior to the +# start of the rule. +# nextGMTOffset - Offset from Greenwich when the rule is in effect. +# namePattern - Name of the timezone. +# until - Time at which the rule expires. +# pointsVar - Name of a variable in callers scope that receives +# transition times +# +# Results: +# Returns a two element list comprising 'nextGMTOffset' and +# 0 - the zero indicates that Daylight Saving Time is not +# in effect. +# +# Side effects: +# Appends a row to the 'points' variable comprising the start time, +# the offset from GMT, a zero (indicating that DST is not in effect), +# and the name of the time zone. +# +#---------------------------------------------------------------------- + +proc applyNoRule { year startSecs stdGMTOffset DSTOffset nextGMTOffset + namePattern until pointsVar } { + upvar 1 $pointsVar points + lappend points $startSecs $nextGMTOffset 0 \ + [convertNamePattern $namePattern -] + return [list $nextGMTOffset 0] + +} + +#---------------------------------------------------------------------- +# +# applyNoRule -- +# +# Generates time zone data for a zone with permanent Daylight +# Saving Time. +# +# Parameters: +# nextDSTOffset - Offset of Daylight from Standard while the +# rule is in effect. +# year - Year in which the rule applies +# startSecs - Time at which the rule starts. +# stdGMTOffset - Offset from Greenwich prior to the start of the +# rule +# DSTOffset - Offset of Daylight from Standard prior to the +# start of the rule. +# nextGMTOffset - Offset from Greenwich when the rule is in effect. +# namePattern - Name of the timezone. +# until - Time at which the rule expires. +# pointsVar - Name of a variable in callers scope that receives +# transition times +# +# Results: +# Returns a two element list comprising 'nextGMTOffset' and +# 'nextDSTOffset'. +# +# Side effects: +# Appends a row to the 'points' variable comprising the start time, +# the offset from GMT, a one (indicating that DST is in effect), +# and the name of the time zone. +# +#---------------------------------------------------------------------- + +proc applyDSTOffset { nextDSTOffset year startSecs + stdGMTOffset DSTOffset nextGMTOffset + namePattern until pointsVar } { + upvar 1 $pointsVar points + lappend points \ + $startSecs \ + [expr { $nextGMTOffset + $nextDSTOffset }] \ + 1 \ + [convertNamePattern $namePattern S] + return [list $nextGMTOffset $nextDSTOffset] +} + +#---------------------------------------------------------------------- +# +# applyRules -- +# +# Applies a rule set to a time zone for a given range of time +# +# Parameters: +# ruleSet - Name of the rule set to apply +# year - Starting year for the rules +# startSecs - Time at which the rules begin to apply +# stdGMTOffset - Offset from Greenwich prior to the start of the +# rules. +# DSTOffset - Offset of Daylight from Standard prior to the +# start of the rules. +# nextGMTOffset - Offset from Greenwich when the rules are in effect. +# namePattern - Name pattern for the time zone. +# until - Time at which the rule set expires. +# pointsVar - Name of a variable in callers scope that receives +# transition times +# +# Results: +# Returns a two element list comprising the offset from GMT +# to Standard and the offset from Standard to Daylight (if DST +# is in effect) at the end of the period in which the rules apply +# +# Side effects: +# Appends one or more rows to the 'points' variable, each of which +# comprises a transition time, the offset from GMT that is +# in effect after the transition, a flag for whether DST is in +# effect, and the name of the time zone. +# +#---------------------------------------------------------------------- + +proc applyRules { ruleSet year startSecs stdGMTOffset DSTOffset nextGMTOffset \ + namePattern until pointsVar } { + variable done + variable rules + variable maxyear + + upvar 1 $pointsVar points + + # Extract the rules that apply to the current year, and the number + # of rules (now or in future) that will end at a specific year. + # Ignore rules entirely in the past. + + foreach { + currentRules nSunsetRules + } [divideRules $ruleSet $year] break + + # If the first transition is later than $startSecs, and $stdGMTOffset is + # different from $nextGMTOffset, we will need an initial record like: + # lappend points $startSecs $stdGMTOffset 0 \ + # [convertNamePattern $namePattern -] + + set didTransitionIn false + + # Determine the letter to use in Standard Time + + set prevLetter {} + foreach { + fromYear toYear yearType monthIn daySpecOn timeAt save letter + } $rules($ruleSet) { + if { $save == 0 } { + set prevLetter $letter + break + } + } + + # Walk through each year in turn. This loop will break when + # (a) the 'until' time is passed + # or (b) the 'until' time is empty and all remaining rules extend to + # the end of time + + set stdGMTOffset $nextGMTOffset + + # convert "until" to seconds from epoch in current time zone + + if { $until ne {} } { + foreach { + untilYear untilMonth untilDaySpec untilTimeOfDay + } $until break + lappend untilDaySpec $untilYear $untilMonth + set untilJCD [eval $untilDaySpec] + set untilBaseSecs [expr { + wide(86400) * wide($untilJCD) + - $::tcl::clock::PosixEpochAsJulianSeconds }] + set untilSecs [eval [linsert $untilTimeOfDay 0 convertTimeOfDay \ + $untilBaseSecs $stdGMTOffset $DSTOffset]] + } + + set origStartSecs $startSecs + + while { ( $until ne {} && + $startSecs < $untilSecs ) + || ( $until eq {} && + ( $nSunsetRules > 0 || $year < $maxyear ) ) } { + + set remainingRules $currentRules + while { [llength $remainingRules] > 0 } { + + + # Find the rule with the earliest start time from among the + # active rules that haven't yet been processed. + + foreach { + earliestSecs earliestIndex + } [findEarliestRule $remainingRules $year \ + $stdGMTOffset $DSTOffset] break + + set endi [expr {$earliestIndex + 7}] + set rule [lrange $remainingRules $earliestIndex $endi] + foreach { + fromYear toYear yearType monthIn daySpecOn timeAt save letter + } $rule break + + # Test if the rule is in effect. + + if { $earliestSecs > $startSecs && + ( $until eq {} || $earliestSecs < $untilSecs ) } { + + # Test if the initial transition has been done. + # If not, do it now. + + if { !$didTransitionIn && $earliestSecs > $origStartSecs } { + set nm [convertNamePattern $namePattern $prevLetter] + lappend points \ + $origStartSecs \ + [expr { $stdGMTOffset + $DSTOffset }] \ + 0 \ + $nm + set didTransitionIn true + } + + # Add a row to 'points' for the rule + + set nm [convertNamePattern $namePattern $letter] + lappend points \ + $earliestSecs \ + [expr { $stdGMTOffset + $save }] \ + [expr { $save != 0 }] \ + $nm + } + + # Remove the rule just applied from the queue + + set remainingRules [lreplace \ + [K $remainingRules \ + [set remainingRules {}]] \ + $earliestIndex $endi] + + # Update current DST offset and time zone letter + + set DSTOffset $save + set prevLetter $letter + + # Reconvert the 'until' time in the current zone. + + if { $until ne {} } { + set untilSecs [eval [linsert $untilTimeOfDay 0 \ + convertTimeOfDay $untilBaseSecs \ + $stdGMTOffset $DSTOffset]] + } + } + + # Advance to the next year + + incr year + set date [::tcl::clock::GetJulianDayFromEraYearMonthDay \ + [dict create era CE year $year month 1 dayOfMonth 1]] + set startSecs [expr { [dict get $date julianDay] * wide(86400) \ + - $::tcl::clock::PosixEpochAsJulianSeconds }] + set startSecs [expr { $startSecs - $stdGMTOffset - $DSTOffset }] + + + # Get rules in effect in the new year. + + foreach { + currentRules nSunsetRules + } [divideRules $ruleSet $year] break + + } + + return [list $stdGMTOffset $DSTOffset] +} + +#---------------------------------------------------------------------- +# +# divideRules -- +# Determine what Daylight Saving Time rules may be in effect in +# a given year. +# +# Parameters: +# ruleSet - Set of rules from 'parseRule' +# year - Year to test +# +# Results: +# Returns a two element list comprising the subset of 'ruleSet' +# that is in effect in the given year, and the count of rules +# that expire in the future (as opposed to those that expire in +# the past or not at all). If this count is zero, the rules do +# not change in future years. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc divideRules { ruleSet year } { + + variable rules + + set currentRules {} + set nSunsetRules 0 + + foreach { + fromYear toYear yearType monthIn daySpecOn timeAt save letter + } $rules($ruleSet) { + if { $toYear ne {maximum} && $year > $toYear } { + # ignore - rule is in the past + } else { + if { $fromYear eq {minimum} || $fromYear <= $year } { + lappend currentRules $fromYear $toYear $yearType $monthIn \ + $daySpecOn $timeAt $save $letter + } + if { $toYear ne {maximum} } { + incr nSunsetRules + } + } + } + + return [list $currentRules $nSunsetRules] + +} + +#---------------------------------------------------------------------- +# +# findEarliestRule -- +# +# Find the rule in a rule set that has the earliest start time. +# +# Parameters: +# remainingRules -- Rules to search +# year - Year being processed. +# stdGMTOffset - Current offset of standard time from GMT +# DSTOffset - Current offset of daylight time from standard, +# if daylight time is in effect. +# +# Results: +# Returns the index in remainingRules of the next rule to +# go into effect. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc findEarliestRule { remainingRules year stdGMTOffset DSTOffset } { + + set earliest $::MAXWIDE + set i 0 + foreach { + fromYear toYear yearType monthIn daySpecOn timeAt save letter + } $remainingRules { + lappend daySpecOn $year $monthIn + set dayIn [eval $daySpecOn] + set secs [expr { + wide(86400) * wide($dayIn) + - $::tcl::clock::PosixEpochAsJulianSeconds }] + set secs [eval [linsert $timeAt 0 convertTimeOfDay \ + $secs $stdGMTOffset $DSTOffset]] + if { $secs < $earliest } { + set earliest $secs + set earliestIdx $i + } + incr i 8 + } + + return [list $earliest $earliestIdx] + +} + +#---------------------------------------------------------------------- +# +# convertNamePattern -- +# +# Converts a name pattern to the name of the time zone. +# +# Parameters: +# pattern - Patthern to convert +# flag - Daylight Time flag. An empty string denotes Standard +# Time, anything else is Daylight Time. +# +# Results; +# Returns the name of the time zone. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc convertNamePattern { pattern flag } { + if { [regexp {(.*)/(.*)} $pattern -> standard daylight] } { + if { $flag ne {} } { + set pattern $daylight + } else { + set pattern $standard + } + } + return [string map [list %s $flag] $pattern] +} + +#---------------------------------------------------------------------- +# +# convertTimeOfDay -- +# +# Takes a time of day specifier from 'parseAt' and converts +# to seconds from the Epoch, +# +# Parameters: +# seconds -- Time at which the GMT day starts, in seconds +# from the Posix epoch +# stdGMTOffset - Offset of Standard Time from Greenwich +# DSTOffset - Offset of Daylight Time from standard. +# timeOfDay - Time of day to convert, in seconds from midnight +# flag - Flag indicating whether the time is Greenwich, Standard +# or wall-clock. (g, s, or w) +# +# Results: +# Returns the time of day in seconds from the Posix epoch. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc convertTimeOfDay { seconds stdGMTOffset DSTOffset timeOfDay flag } { + incr seconds $timeOfDay + switch -exact $flag { + g - u - z { + } + w { + incr seconds [expr { -$stdGMTOffset }] + incr seconds [expr { -$DSTOffset }] + } + z { + incr seconds [expr { -$stdGMTOffset }] + } + } + return $seconds +} + +#---------------------------------------------------------------------- +# +# processTimeZone -- +# +# Generate the information about all time transitions in a +# time zone. +# +# Parameters: +# zoneName - Name of the time zone +# zoneData - List containing the rows describing the time zone, +# obtained from 'parseZone. +# +# Results: +# Returns a list of rows. Each row consists of a time in +# seconds from the Posix epoch, an offset from GMT to local +# that begins at that time, a flag indicating whether DST +# is in effect after that time, and the printable name of the +# timezone that goes into effect at that time. +# +# Side effects: +# None. +# +#---------------------------------------------------------------------- + +proc processTimeZone { zoneName zoneData } { + + set points {} + set i 0 + foreach { startTime nextGMTOffset dstRule namePattern } $zoneData { + incr i 4 + set until [lindex $zoneData $i] + if {! [info exists stdGMTOffset] } { + set stdGMTOffset $nextGMTOffset + } + if {! [info exists DSTOffset] } { + set DSTOffset 0 + } + if { $startTime eq {minimum} } { + set secs $::MINWIDE + set year 0 + } else { + foreach { year month dayRule timeOfDay } $startTime break + lappend dayRule $year $month + set startDay [eval $dayRule] + set secs [expr { + wide(86400) * wide($startDay) + - $::tcl::clock::PosixEpochAsJulianSeconds}] + set secs [eval [linsert $timeOfDay 0 convertTimeOfDay \ + $secs $stdGMTOffset $DSTOffset]] + } + lappend dstRule \ + $year $secs $stdGMTOffset $DSTOffset $nextGMTOffset \ + $namePattern $until points + foreach {stdGMTOffset DSTOffset} [eval $dstRule] break + } + return $points +} + +#---------------------------------------------------------------------- +# +# writeZones -- +# +# Writes all the time zone information files. +# +# Parameters: +# outDir - Directory in which to store the files. +# +# Results: +# None. +# +# Side effects: +# Writes the time zone information files; traces what's happening +# on the standard output. +# +#---------------------------------------------------------------------- + +proc writeZones { outDir } { + + variable zones + + # Walk the zones + + foreach zoneName [lsort -dictionary [array names zones]] { + puts "calculating: $zoneName" + set fileName [eval [list file join $outDir] [file split $zoneName]] + + # Create directories as needed + + set dirName [file dirname $fileName] + if { ![file exists $dirName] } { + puts "creating directory: $dirName" + file mkdir $dirName + } + + # Generate data for a zone + + set data {} + foreach { + time offset dst name + } [processTimeZone $zoneName $zones($zoneName)] { + append data \n { } [list [list $time $offset $dst $name]] + } + append data \n + + # Write the data to the information file + + set f [open $fileName w] + puts $f "\# created by $::argv0 - do not edit" + puts $f {} + puts $f [list set TZData(:$zoneName) $data] + close $f + + } + + return +} + +#---------------------------------------------------------------------- +# +# writeLinks -- +# +# Write files describing time zone synonyms (the Link directives +# from the Olson files) +# +# Parameters: +# outDir - Name of the directory where the output files go. +# +# Results: +# None. +# +# Side effects: +# Creates a file for each link. + +proc writeLinks { outDir } { + + variable links + + # Walk the links + + foreach zoneName [lsort -dictionary [array names links]] { + puts "creating link: $zoneName" + set fileName [eval [list file join $outDir] [file split $zoneName]] + + # Create directories as needed + + set dirName [file dirname $fileName] + if { ![file exists $dirName] } { + puts "creating directory: $dirName" + file mkdir $dirName + } + + # Create code for the synonym + + set linkTo $links($zoneName) + set sourceCmd "\n [list loadTimeZoneFile $linkTo]\n" + set ifCmd [list if "!\[info exists TZData($linkTo)\]" $sourceCmd] + set setCmd "set tzdata(:$zoneName) \$TZData(:$linkTo)" + + # Write the file + + set f [open $fileName w] + puts $f "\# created by $::argv0 - do not edit" + puts $f $ifCmd + puts $f $setCmd + close $f + } + + return +} + +#---------------------------------------------------------------------- +# +# MAIN PROGRAM +# +#---------------------------------------------------------------------- + +# Determine directories + +foreach { inDir outDir } $argv break + +# Initialize count of errors + +set errorCount 0 + +# Parse the Olson files + +loadFiles $inDir +if { $errorCount > 0 } { + exit 1 +} + +# Check that all riles appearing in Zone and Link lines actually exist + +checkForwardRuleRefs +if { $errorCount > 0 } { + exit 1 +} + +# Write the time zone information files + +writeZones $outDir +writeLinks $outDir +if { $errorCount > 0 } { + exit 1 +} + +# All done! + +exit
\ No newline at end of file |