diff options
Diffstat (limited to 'timezone/tzselect.ksh')
-rwxr-xr-x | timezone/tzselect.ksh | 923 |
1 files changed, 590 insertions, 333 deletions
diff --git a/timezone/tzselect.ksh b/timezone/tzselect.ksh index 18fce27e24..38941bbc55 100755 --- a/timezone/tzselect.ksh +++ b/timezone/tzselect.ksh @@ -10,7 +10,7 @@ REPORT_BUGS_TO=tz@iana.org # Porting notes: # -# This script requires a Posix-like shell and prefers the extension of a +# This script requires a POSIX-like shell and prefers the extension of a # 'select' statement. The 'select' statement was introduced in the # Korn shell and is available in Bash and other shell implementations. # If your host lacks both Bash and the Korn shell, you can get their @@ -18,35 +18,47 @@ REPORT_BUGS_TO=tz@iana.org # # Bash <https://www.gnu.org/software/bash/> # Korn Shell <http://www.kornshell.com/> -# MirBSD Korn Shell <https://www.mirbsd.org/mksh.htm> +# MirBSD Korn Shell <http://www.mirbsd.org/mksh.htm> # -# For portability to Solaris 9 /bin/sh this script avoids some POSIX -# features and common extensions, such as $(...) (which works sometimes -# but not others), $((...)), and $10. +# For portability to Solaris 10 /bin/sh (supported by Oracle through +# January 2027) this script avoids some POSIX features and common +# extensions, such as $(...), $((...)), ! CMD, unquoted ^, ${#ID}, +# ${ID##PAT}, ${ID%%PAT}, and $10. Although some of these constructs +# work sometimes, it's simpler to avoid them entirely. # -# This script also uses several features of modern awk programs. -# If your host lacks awk, or has an old awk that does not conform to Posix, -# you can use either of the following free programs instead: +# This script also uses several features of POSIX awk. +# If your host lacks awk, or has an old awk that does not conform to POSIX, +# you can use any of the following free programs instead: # # Gawk (GNU awk) <https://www.gnu.org/software/gawk/> # mawk <https://invisible-island.net/mawk/> +# nawk <https://github.com/onetrueawk/awk> +# +# Because 'awk "VAR=VALUE" ...' and 'awk -v "VAR=VALUE" ...' are not portable +# if VALUE contains \, ", or newline, awk scripts in this file use: +# awk 'BEGIN { VAR = substr(ARGV[1], 2); ARGV[1] = "" } ...' ="VALUE" +# The substr avoids problems when VALUE is of the form X=Y and would be +# misinterpreted as an assignment. +# This script does not want path expansion. +set -f # Specify default values for environment variables if they are unset. : ${AWK=awk} -: ${TZDIR=`pwd`} +: ${PWD=`pwd`} +: ${TZDIR=$PWD} -# Output one argument as-is to standard output. +# Output one argument as-is to standard output, with trailing newline. # Safer than 'echo', which can mishandle '\' or leading '-'. say() { - printf '%s\n' "$1" + printf '%s\n' "$1" } -# Check for awk Posix compliance. -($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1 +# Check for awk POSIX compliance. +($AWK -v x=y 'BEGIN { exit 123 }') <>/dev/null >&0 2>&0 [ $? = 123 ] || { - say >&2 "$0: Sorry, your '$AWK' program is not Posix compatible." - exit 1 + say >&2 "$0: Sorry, your '$AWK' program is not POSIX compatible." + exit 1 } coord= @@ -79,14 +91,14 @@ Report bugs to $REPORT_BUGS_TO." # Ask the user to select from the function's arguments, # and assign the selected argument to the variable 'select_result'. -# Exit on EOF or I/O error. Use the shell's 'select' builtin if available, -# falling back on a less-nice but portable substitute otherwise. +# Exit on EOF or I/O error. Use the shell's nicer 'select' builtin if +# available, falling back on a portable substitute otherwise. if case $BASH_VERSION in - ?*) : ;; + ?*) :;; '') # '; exit' should be redundant, but Dash doesn't properly fail without it. - (eval 'set --; select x; do break; done; exit') </dev/null 2>/dev/null + (eval 'set --; select x; do break; done; exit') <>/dev/null 2>&0 esac then # Do this inside 'eval', as otherwise the shell might exit when parsing it @@ -96,24 +108,17 @@ then select select_result do case $select_result in - "") echo >&2 "Please enter a number in range." ;; + "") echo >&2 "Please enter a number in range.";; ?*) break esac done || exit } - - # Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout. - case $BASH_VERSION in - [01].*) - case `echo 1 | (select x in x; do break; done) 2>/dev/null` in - ?*) PS3= - esac - esac ' else doselect() { # Field width of the prompt numbers. - select_width=`expr $# : '.*'` + print_nargs_length="BEGIN {print length(\"$#\");}" + select_width=`$AWK "$print_nargs_length"` select_i= @@ -124,14 +129,14 @@ else select_i=0 for select_word do - select_i=`expr $select_i + 1` + select_i=`$AWK "BEGIN { print $select_i + 1 }"` printf >&2 "%${select_width}d) %s\\n" $select_i "$select_word" - done ;; + done;; *[!0-9]*) - echo >&2 'Please enter a number in range.' ;; + echo >&2 'Please enter a number in range.';; *) if test 1 -le $select_i && test $select_i -le $#; then - shift `expr $select_i - 1` + shift `$AWK "BEGIN { print $select_i - 1 }"` select_result=$1 break fi @@ -147,70 +152,137 @@ fi while getopts c:n:t:-: opt do - case $opt$OPTARG in - c*) - coord=$OPTARG ;; - n*) - location_limit=$OPTARG ;; - t*) # Undocumented option, used for developer testing. - zonetabtype=$OPTARG ;; - -help) - exec echo "$usage" ;; - -version) - exec echo "tzselect $PKGVERSION$TZVERSION" ;; - -*) - say >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1 ;; - *) - say >&2 "$0: try '$0 --help'"; exit 1 ;; - esac + case $opt$OPTARG in + c*) + coord=$OPTARG;; + n*) + location_limit=$OPTARG;; + t*) # Undocumented option, used for developer testing. + zonetabtype=$OPTARG;; + -help) + exec echo "$usage";; + -version) + exec echo "tzselect $PKGVERSION$TZVERSION";; + -*) + say >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1;; + *) + say >&2 "$0: try '$0 --help'"; exit 1 + esac done -shift `expr $OPTIND - 1` +shift `$AWK "BEGIN { print $OPTIND - 1 }"` case $# in 0) ;; -*) say >&2 "$0: $1: unknown argument"; exit 1 ;; +*) say >&2 "$0: $1: unknown argument"; exit 1 esac -# Make sure the tables are readable. -TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab -TZ_ZONE_TABLE=$TZDIR/$zonetabtype.tab -for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE -do - <"$f" || { - say >&2 "$0: time zone files are not set up correctly" - exit 1 - } -done +# translit=true to try transliteration. +# This is false if U+12345 CUNEIFORM SIGN URU TIMES KI has length 1 +# which means awk (and presumably the shell) do not need transliteration. +if $AWK 'BEGIN { u12345 = "\360\222\215\205"; exit length(u12345) == 1 }'; then + translit=true +else + translit=false +fi -# If the current locale does not support UTF-8, convert data to current -# locale's format if possible, as the shell aligns columns better that way. -# Check the UTF-8 of U+12345 CUNEIFORM SIGN URU TIMES KI. -! $AWK 'BEGIN { u12345 = "\360\222\215\205"; exit length(u12345) != 1 }' && - { tmp=`(mktemp -d) 2>/dev/null` || { - tmp=${TMPDIR-/tmp}/tzselect.$$ && - (umask 77 && mkdir -- "$tmp") - };} && - trap 'status=$?; rm -fr -- "$tmp"; exit $status' 0 HUP INT PIPE TERM && - (iconv -f UTF-8 -t //TRANSLIT <"$TZ_COUNTRY_TABLE" >$tmp/iso3166.tab) \ - 2>/dev/null && - TZ_COUNTRY_TABLE=$tmp/iso3166.tab && - iconv -f UTF-8 -t //TRANSLIT <"$TZ_ZONE_TABLE" >$tmp/$zonetabtype.tab && - TZ_ZONE_TABLE=$tmp/$zonetabtype.tab +# Read into shell variable $1 the contents of file $2. +# Convert to the current locale's encoding if possible, +# as the shell aligns columns better that way. +# If GNU iconv's //TRANSLIT does not work, fall back on POSIXish iconv; +# if that does not work, fall back on 'cat'. +read_file() { + { $translit && { + eval "$1=\`(iconv -f UTF-8 -t //TRANSLIT) 2>/dev/null <\"\$2\"\`" || + eval "$1=\`(iconv -f UTF-8) 2>/dev/null <\"\$2\"\`" + }; } || + eval "$1=\`cat <\"\$2\"\`" || { + say >&2 "$0: time zone files are not set up correctly" + exit 1 + } +} +read_file TZ_COUNTRY_TABLE "$TZDIR/iso3166.tab" +read_file TZ_ZONETABTYPE_TABLE "$TZDIR/$zonetabtype.tab" +TZ_ZONENOW_TABLE= newline=' ' IFS=$newline +# Awk script to output a country list. +output_country_list=' + BEGIN { + continent_re = substr(ARGV[1], 2) + TZ_COUNTRY_TABLE = substr(ARGV[2], 2) + TZ_ZONE_TABLE = substr(ARGV[3], 2) + ARGV[1] = ARGV[2] = ARGV[3] = "" + FS = "\t" + nlines = split(TZ_ZONE_TABLE, line, /\n/) + for (iline = 1; iline <= nlines; iline++) { + $0 = line[iline] + commentary = $0 ~ /^#@/ + if (commentary) { + if ($0 !~ /^#@/) + continue + col1ccs = substr($1, 3) + conts = $2 + } else { + col1ccs = $1 + conts = $3 + } + ncc = split(col1ccs, cc, /,/) + ncont = split(conts, cont, /,/) + for (i = 1; i <= ncc; i++) { + elsewhere = commentary + for (ci = 1; ci <= ncont; ci++) { + if (cont[ci] ~ continent_re) { + if (!cc_seen[cc[i]]++) + cc_list[++ccs] = cc[i] + elsewhere = 0 + } + } + if (elsewhere) + for (i = 1; i <= ncc; i++) + cc_elsewhere[cc[i]] = 1 + } + } + nlines = split(TZ_COUNTRY_TABLE, line, /\n/) + for (i = 1; i <= nlines; i++) { + $0 = line[i] + if ($0 !~ /^#/) + cc_name[$1] = $2 + } + for (i = 1; i <= ccs; i++) { + country = cc_list[i] + if (cc_elsewhere[country]) + continue + if (cc_name[country]) + country = cc_name[country] + print country + } + } +' -# Awk script to read a time zone table and output the same table, -# with each column preceded by its distance from 'here'. -output_distances=' +# Awk script to process a time zone table and output the same table, +# with each row preceded by its distance from 'here'. +# If output_times is set, each row is instead preceded by its local time +# and any apostrophes are escaped for the shell. +output_distances_or_times=' BEGIN { + coord = substr(ARGV[1], 2) + TZ_COUNTRY_TABLE = substr(ARGV[2], 2) + TZ_ZONE_TABLE = substr(ARGV[3], 2) + ARGV[1] = ARGV[2] = ARGV[3] = "" FS = "\t" - while (getline <TZ_COUNTRY_TABLE) - if ($0 ~ /^[^#]/) - country[$1] = $2 - country["US"] = "US" # Otherwise the strings get too long. + if (!output_times) { + nlines = split(TZ_COUNTRY_TABLE, line, /\n/) + for (i = 1; i <= nlines; i++) { + $0 = line[i] + if ($0 ~ /^#/) + continue + country[$1] = $2 + } + country["US"] = "US" # Otherwise the strings get too long. + } } function abs(x) { return x < 0 ? -x : x; @@ -270,281 +342,466 @@ output_distances=' BEGIN { coord_lat = convert_latitude(coord) coord_long = convert_longitude(coord) - } - /^[^#]/ { - here_lat = convert_latitude($2) - here_long = convert_longitude($2) - line = $1 "\t" $2 "\t" $3 - sep = "\t" - ncc = split($1, cc, /,/) - for (i = 1; i <= ncc; i++) { - line = line sep country[cc[i]] - sep = ", " + nlines = split(TZ_ZONE_TABLE, line, /\n/) + for (h = 1; h <= nlines; h++) { + $0 = line[h] + if ($0 ~ /^#/) + continue + inline[inlines++] = $0 + ncc = split($1, cc, /,/) + for (i = 1; i <= ncc; i++) + cc_used[cc[i]]++ + } + for (h = 0; h < inlines; h++) { + $0 = inline[h] + outline = $1 "\t" $2 "\t" $3 + sep = "\t" + ncc = split($1, cc, /,/) + split("", item_seen) + item_seen[""] = 1 + for (i = 1; i <= ncc; i++) { + item = cc_used[cc[i]] <= 1 ? country[cc[i]] : $4 + if (item_seen[item]++) + continue + outline = outline sep item + sep = "; " + } + if (output_times) { + fmt = "TZ='\''%s'\'' date +'\''%d %%Y %%m %%d %%H:%%M %%a %%b\t%s'\''\n" + gsub(/'\''/, "&\\\\&&", outline) + printf fmt, $3, h, outline + } else { + here_lat = convert_latitude($2) + here_long = convert_longitude($2) + printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), \ + outline + } } - if (NF == 4) - line = line " - " $4 - printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), line } ' # Begin the main loop. We come back here if the user wants to retry. while - echo >&2 'Please identify a location' \ - 'so that time zone rules can be set correctly.' + echo >&2 'Please identify a location' \ + 'so that time zone rules can be set correctly.' - continent= - country= - region= + continent= + country= + country_result= + region= + time= + TZ_ZONE_TABLE=$TZ_ZONETABTYPE_TABLE - case $coord in - ?*) - continent=coord;; - '') + case $coord in + ?*) + continent=coord;; + '') - # Ask the user for continent or ocean. + # Ask the user for continent or ocean. - echo >&2 'Please select a continent, ocean, "coord", or "TZ".' + echo >&2 \ + 'Please select a continent, ocean, "coord", "TZ", "time", or "now".' - quoted_continents=` + quoted_continents=` + $AWK ' + function handle_entry(entry) { + entry = substr(entry, 1, index(entry, "/") - 1) + if (entry == "America") + entry = entry "s" + if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/) + entry = entry " Ocean" + printf "'\''%s'\''\n", entry + } + BEGIN { + TZ_ZONETABTYPE_TABLE = substr(ARGV[1], 2) + ARGV[1] = "" + FS = "\t" + nlines = split(TZ_ZONETABTYPE_TABLE, line, /\n/) + for (i = 1; i <= nlines; i++) { + $0 = line[i] + if ($0 ~ /^[^#]/) + handle_entry($3) + else if ($0 ~ /^#@/) { + ncont = split($2, cont, /,/) + for (ci = 1; ci <= ncont; ci++) + handle_entry(cont[ci]) + } + } + } + ' ="$TZ_ZONETABTYPE_TABLE" | + sort -u | + tr '\n' ' ' + echo '' + ` + + eval ' + doselect '"$quoted_continents"' \ + "coord - I want to use geographical coordinates." \ + "TZ - I want to specify the timezone using a POSIX.1-2017 TZ string." \ + "time - I know local time already." \ + "now - Like \"time\", but configure only for timestamps from now on." + continent=$select_result + case $continent in + Americas) continent=America;; + *) + # Get the first word of $continent. Path expansion is disabled + # so this works even with "*", which should not happen. + IFS=" " + for continent in $continent ""; do break; done + IFS=$newline;; + esac + case $zonetabtype,$continent in + zonenow,*) ;; + *,now) + ${TZ_ZONENOW_TABLE:+:} read_file TZ_ZONENOW_TABLE "$TZDIR/zonenow.tab" + TZ_ZONE_TABLE=$TZ_ZONENOW_TABLE + esac + ' + esac + + case $continent in + TZ) + # Ask the user for a POSIX.1-2017 TZ string. Check that it conforms. + check_POSIX_TZ_string=' + BEGIN { + tz = substr(ARGV[1], 2) + ARGV[1] = "" + tzname = ("(<[[:alnum:]+-][[:alnum:]+-][[:alnum:]+-]+>" \ + "|[[:alpha:]][[:alpha:]][[:alpha:]]+)") + time = ("(2[0-4]|[0-1]?[0-9])" \ + "(:[0-5][0-9](:[0-5][0-9])?)?") + offset = "[-+]?" time + mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]" + jdate = ("((J[1-9]|[0-9]|J?[1-9][0-9]" \ + "|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])") + datetime = ",(" mdate "|" jdate ")(/" time ")?" + tzpattern = ("^(:.*|" tzname offset "(" tzname \ + "(" offset ")?(" datetime datetime ")?)?)$") + exit tz ~ tzpattern + } + ' + + while + echo >&2 'Please enter the desired value' \ + 'of the TZ environment variable.' + echo >&2 'For example, AEST-10 is abbreviated' \ + 'AEST and is 10 hours' + echo >&2 'ahead (east) of Greenwich,' \ + 'with no daylight saving time.' + read tz + $AWK "$check_POSIX_TZ_string" ="$tz" + do + say >&2 "'$tz' is not a conforming POSIX.1-2017 timezone string." + done + TZ_for_date=$tz;; + *) + case $continent in + coord) + case $coord in + '') + echo >&2 'Please enter coordinates' \ + 'in ISO 6709 notation.' + echo >&2 'For example, +4042-07403 stands for' + echo >&2 '40 degrees 42 minutes north,' \ + '74 degrees 3 minutes west.' + read coord + esac + distance_table=` + $AWK \ + "$output_distances_or_times" \ + ="$coord" ="$TZ_COUNTRY_TABLE" ="$TZ_ZONE_TABLE" | + sort -n | + $AWK "{print} NR == $location_limit { exit }" + ` + regions=` + $AWK ' + BEGIN { + distance_table = substr(ARGV[1], 2) + ARGV[1] = "" + nlines = split(distance_table, line, /\n/) + for (nr = 1; nr <= nlines; nr++) { + nf = split(line[nr], f, /\t/) + print f[nf] + } + } + ' ="$distance_table" + ` + echo >&2 'Please select one of the following timezones,' + echo >&2 'listed roughly in increasing order' \ + "of distance from $coord". + doselect $regions + region=$select_result + tz=` + $AWK ' + BEGIN { + distance_table = substr(ARGV[1], 2) + region = substr(ARGV[2], 2) + ARGV[1] = ARGV[2] = "" + nlines = split(distance_table, line, /\n/) + for (nr = 1; nr <= nlines; nr++) { + nf = split(line[nr], f, /\t/) + if (f[nf] == region) + print f[4] + } + } + ' ="$distance_table" ="$region" + `;; + *) + case $continent in + now|time) + minute_format='%a %b %d %H:%M' + old_minute=`TZ=UTC0 date +"$minute_format"` + for i in 1 2 3 + do + time_table_command=` + $AWK \ + -v output_times=1 \ + "$output_distances_or_times" \ + = = ="$TZ_ZONE_TABLE" + ` + time_table=`eval "$time_table_command"` + new_minute=`TZ=UTC0 date +"$minute_format"` + case $old_minute in + "$new_minute") break + esac + old_minute=$new_minute + done + echo >&2 "The system says Universal Time is $new_minute." + echo >&2 "Assuming that's correct, what is the local time?" + sorted_table=`say "$time_table" | sort -k2n -k2,5 -k1n` || { + say >&2 "$0: cannot sort time table" + exit 1 + } + eval doselect ` $AWK ' - BEGIN { FS = "\t" } - /^[^#]/ { - entry = substr($3, 1, index($3, "/") - 1) - if (entry == "America") - entry = entry "s" - if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/) - entry = entry " Ocean" - printf "'\''%s'\''\n", entry - } - ' <"$TZ_ZONE_TABLE" | - sort -u | - tr '\n' ' ' - echo '' + BEGIN { + sorted_table = substr(ARGV[1], 2) + ARGV[1] = "" + nlines = split(sorted_table, line, /\n/) + for (i = 1; i <= nlines; i++) { + $0 = line[i] + outline = $6 " " $7 " " $4 " " $5 + if (outline == oldline) + continue + oldline = outline + gsub(/'\''/, "&\\\\&&", outline) + printf "'\''%s'\''\n", outline + } + } + ' ="$sorted_table" ` - - eval ' - doselect '"$quoted_continents"' \ - "coord - I want to use geographical coordinates." \ - "TZ - I want to specify the timezone using the Posix TZ format." - continent=$select_result - case $continent in - Americas) continent=America;; - *" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''` - esac - ' - esac - - case $continent in - TZ) - # Ask the user for a Posix TZ string. Check that it conforms. - while - echo >&2 'Please enter the desired value' \ - 'of the TZ environment variable.' - echo >&2 'For example, AEST-10 is abbreviated' \ - 'AEST and is 10 hours' - echo >&2 'ahead (east) of Greenwich,' \ - 'with no daylight saving time.' - read TZ - $AWK -v TZ="$TZ" 'BEGIN { - tzname = "(<[[:alnum:]+-]{3,}>|[[:alpha:]]{3,})" - time = "(2[0-4]|[0-1]?[0-9])" \ - "(:[0-5][0-9](:[0-5][0-9])?)?" - offset = "[-+]?" time - mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]" - jdate = "((J[1-9]|[0-9]|J?[1-9][0-9]" \ - "|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])" - datetime = ",(" mdate "|" jdate ")(/" time ")?" - tzpattern = "^(:.*|" tzname offset "(" tzname \ - "(" offset ")?(" datetime datetime ")?)?)$" - if (TZ ~ tzpattern) exit 1 - exit 0 - }' - do - say >&2 "'$TZ' is not a conforming Posix timezone string." - done - TZ_for_date=$TZ;; - *) - case $continent in - coord) - case $coord in - '') - echo >&2 'Please enter coordinates' \ - 'in ISO 6709 notation.' - echo >&2 'For example, +4042-07403 stands for' - echo >&2 '40 degrees 42 minutes north,' \ - '74 degrees 3 minutes west.' - read coord;; - esac - distance_table=`$AWK \ - -v coord="$coord" \ - -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ - "$output_distances" <"$TZ_ZONE_TABLE" | - sort -n | - sed "${location_limit}q" - ` - regions=`say "$distance_table" | $AWK ' - BEGIN { FS = "\t" } - { print $NF } - '` - echo >&2 'Please select one of the following timezones,' \ - echo >&2 'listed roughly in increasing order' \ - "of distance from $coord". - doselect $regions - region=$select_result - TZ=`say "$distance_table" | $AWK -v region="$region" ' - BEGIN { FS="\t" } - $NF == region { print $4 } - '` - ;; - *) - # Get list of names of countries in the continent or ocean. - countries=`$AWK \ - -v continent="$continent" \ - -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ - ' - BEGIN { FS = "\t" } - /^#/ { next } - $3 ~ ("^" continent "/") { - ncc = split($1, cc, /,/) - for (i = 1; i <= ncc; i++) - if (!cc_seen[cc[i]]++) cc_list[++ccs] = cc[i] - } - END { - while (getline <TZ_COUNTRY_TABLE) { - if ($0 !~ /^#/) cc_name[$1] = $2 - } - for (i = 1; i <= ccs; i++) { - country = cc_list[i] - if (cc_name[country]) { - country = cc_name[country] - } - print country - } - } - ' <"$TZ_ZONE_TABLE" | sort -f` - - - # If there's more than one country, ask the user which one. - case $countries in - *"$newline"*) - echo >&2 'Please select a country' \ - 'whose clocks agree with yours.' - doselect $countries - country=$select_result;; - *) - country=$countries - esac - - - # Get list of timezones in the country. - regions=`$AWK \ - -v country="$country" \ - -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ - ' - BEGIN { - FS = "\t" - cc = country - while (getline <TZ_COUNTRY_TABLE) { - if ($0 !~ /^#/ && country == $2) { - cc = $1 - break - } - } - } - /^#/ { next } - $1 ~ cc { print $4 } - ' <"$TZ_ZONE_TABLE"` - - - # If there's more than one region, ask the user which one. - case $regions in - *"$newline"*) - echo >&2 'Please select one of the following timezones.' - doselect $regions - region=$select_result;; - *) - region=$regions - esac - - # Determine TZ from country and region. - TZ=`$AWK \ - -v country="$country" \ - -v region="$region" \ - -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ - ' - BEGIN { - FS = "\t" - cc = country - while (getline <TZ_COUNTRY_TABLE) { - if ($0 !~ /^#/ && country == $2) { - cc = $1 - break - } - } - } - /^#/ { next } - $1 ~ cc && $4 == region { print $3 } - ' <"$TZ_ZONE_TABLE"` - esac - - # Make sure the corresponding zoneinfo file exists. - TZ_for_date=$TZDIR/$TZ - <"$TZ_for_date" || { - say >&2 "$0: time zone files are not set up correctly" - exit 1 + time=$select_result + continent_re='^' + zone_table=` + $AWK ' + BEGIN { + time = substr(ARGV[1], 2) + time_table = substr(ARGV[2], 2) + ARGV[1] = ARGV[2] = "" + nlines = split(time_table, line, /\n/) + for (i = 1; i <= nlines; i++) { + $0 = line[i] + if ($6 " " $7 " " $4 " " $5 == time) { + sub(/[^\t]*\t/, "") + print } - esac + } + } + ' ="$time" ="$time_table" + ` + countries=` + $AWK \ + "$output_country_list" \ + ="$continent_re" ="$TZ_COUNTRY_TABLE" ="$zone_table" | + sort -f + ` + ;; + *) + continent_re="^$continent/" + zone_table=$TZ_ZONE_TABLE + esac + # Get list of names of countries in the continent or ocean. + countries=` + $AWK \ + "$output_country_list" \ + ="$continent_re" ="$TZ_COUNTRY_TABLE" ="$zone_table" | + sort -f + ` + # If all zone table entries have comments, and there are + # at most 22 entries, asked based on those comments. + # This fits the prompt onto old-fashioned 24-line screens. + regions=` + $AWK ' + BEGIN { + TZ_ZONE_TABLE = substr(ARGV[1], 2) + ARGV[1] = "" + FS = "\t" + nlines = split(TZ_ZONE_TABLE, line, /\n/) + for (i = 1; i <= nlines; i++) { + $0 = line[i] + if ($0 ~ /^[^#]/ && !missing_comment) { + if ($4) + comment[++inlines] = $4 + else + missing_comment = 1 + } + } + if (!missing_comment && inlines <= 22) + for (i = 1; i <= inlines; i++) + print comment[i] + } + ' ="$zone_table" + ` + + # If there's more than one country, ask the user which one. + case $countries in + *"$newline"*) + echo >&2 'Please select a country' \ + 'whose clocks agree with yours.' + doselect $countries + country_result=$select_result + country=$select_result;; + *) + country=$countries + esac - # Use the proposed TZ to output the current date relative to UTC. - # Loop until they agree in seconds. - # Give up after 8 unsuccessful tries. - extra_info= - for i in 1 2 3 4 5 6 7 8 - do - TZdate=`LANG=C TZ="$TZ_for_date" date` - UTdate=`LANG=C TZ=UTC0 date` - TZsec=`expr "$TZdate" : '.*:\([0-5][0-9]\)'` - UTsec=`expr "$UTdate" : '.*:\([0-5][0-9]\)'` - case $TZsec in - $UTsec) - extra_info=" -Selected time is now: $TZdate. -Universal Time is now: $UTdate." - break - esac - done + # Get list of timezones in the country. + regions=` + $AWK ' + BEGIN { + country = substr(ARGV[1], 2) + TZ_COUNTRY_TABLE = substr(ARGV[2], 2) + TZ_ZONE_TABLE = substr(ARGV[3], 2) + ARGV[1] = ARGV[2] = ARGV[3] = "" + FS = "\t" + cc = country + nlines = split(TZ_COUNTRY_TABLE, line, /\n/) + for (i = 1; i <= nlines; i++) { + $0 = line[i] + if ($0 !~ /^#/ && country == $2) { + cc = $1 + break + } + } + nlines = split(TZ_ZONE_TABLE, line, /\n/) + for (i = 1; i <= nlines; i++) { + $0 = line[i] + if ($0 ~ /^#/) + continue + if ($1 ~ cc) + print $4 + } + } + ' ="$country" ="$TZ_COUNTRY_TABLE" ="$zone_table" + ` + + # If there's more than one region, ask the user which one. + case $regions in + *"$newline"*) + echo >&2 'Please select one of the following timezones.' + doselect $regions + region=$select_result + esac + + # Determine tz from country and region. + tz=` + $AWK ' + BEGIN { + country = substr(ARGV[1], 2) + region = substr(ARGV[2], 2) + TZ_COUNTRY_TABLE = substr(ARGV[3], 2) + TZ_ZONE_TABLE = substr(ARGV[4], 2) + ARGV[1] = ARGV[2] = ARGV[3] = ARGV[4] = "" + FS = "\t" + cc = country + nlines = split(TZ_COUNTRY_TABLE, line, /\n/) + for (i = 1; i <= nlines; i++) { + $0 = line[i] + if ($0 !~ /^#/ && country == $2) { + cc = $1 + break + } + } + nlines = split(TZ_ZONE_TABLE, line, /\n/) + for (i = 1; i <= nlines; i++) { + $0 = line[i] + if ($0 ~ /^#/) + continue + if ($1 ~ cc && ($4 == region || !region)) + print $3 + } + } + ' ="$country" ="$region" ="$TZ_COUNTRY_TABLE" ="$zone_table" + ` + esac + # Make sure the corresponding zoneinfo file exists. + TZ_for_date=$TZDIR/$tz + <"$TZ_for_date" || { + say >&2 "$0: time zone files are not set up correctly" + exit 1 + } + esac - # Output TZ info and ask the user to confirm. - echo >&2 "" - echo >&2 "The following information has been given:" - echo >&2 "" - case $country%$region%$coord in - ?*%?*%) say >&2 " $country$newline $region";; - ?*%%) say >&2 " $country";; - %?*%?*) say >&2 " coord $coord$newline $region";; - %%?*) say >&2 " coord $coord";; - *) say >&2 " TZ='$TZ'" - esac - say >&2 "" - say >&2 "Therefore TZ='$TZ' will be used.$extra_info" - say >&2 "Is the above information OK?" - - doselect Yes No - ok=$select_result - case $ok in - Yes) break - esac + # Use the proposed TZ to output the current date relative to UTC. + # Loop until they agree in seconds. + # Give up after 8 unsuccessful tries. + + extra_info= + for i in 1 2 3 4 5 6 7 8 + do + TZdate=`LANG=C TZ="$TZ_for_date" date` + UTdate=`LANG=C TZ=UTC0 date` + if $AWK ' + function getsecs(d) { + return match(d, /.*:[0-5][0-9]/) ? substr(d, RLENGTH - 1, 2) : "" + } + BEGIN { exit getsecs(ARGV[1]) != getsecs(ARGV[2]) } + ' ="$TZdate" ="$UTdate" + then + extra_info=" +Selected time is now: $TZdate. +Universal Time is now: $UTdate." + break + fi + done + + + # Output TZ info and ask the user to confirm. + + echo >&2 "" + echo >&2 "Based on the following information:" + echo >&2 "" + case $time%$country_result%$region%$coord in + ?*%?*%?*%) + say >&2 " $time$newline $country_result$newline $region";; + ?*%?*%%|?*%%?*%) say >&2 " $time$newline $country_result$region";; + ?*%%%) say >&2 " $time";; + %?*%?*%) say >&2 " $country_result$newline $region";; + %?*%%) say >&2 " $country_result";; + %%?*%?*) say >&2 " coord $coord$newline $region";; + %%%?*) say >&2 " coord $coord";; + *) say >&2 " TZ='$tz'" + esac + say >&2 "" + say >&2 "TZ='$tz' will be used.$extra_info" + say >&2 "Is the above information OK?" + + doselect Yes No + ok=$select_result + case $ok in + Yes) break + esac do coord= done case $SHELL in -*csh) file=.login line="setenv TZ '$TZ'";; -*) file=.profile line="TZ='$TZ'; export TZ" +*csh) file=.login line="setenv TZ '$tz'";; +*) file=.profile line="TZ='$tz'; export TZ" esac test -t 1 && say >&2 " @@ -555,4 +812,4 @@ to the file '$file' in your home directory; then log out and log in again. Here is that TZ value again, this time on standard output so that you can use the $0 command in shell scripts:" -say "$TZ" +say "$tz" |