Home | History | Annotate | Line # | Download | only in time
tzselect.ksh revision 1.18
      1   1.8  christos #! /bin/bash
      2   1.2     perry #
      3  1.18  christos # Ask the user about the time zone, and output the resulting TZ value to stdout.
      4  1.18  christos # Interact with the user via stderr and stdin.
      5  1.18  christos #
      6  1.18  christos #	$NetBSD: tzselect.ksh,v 1.18 2018/10/19 23:05:35 christos Exp $
      7   1.2     perry #
      8   1.8  christos PKGVERSION='(tzcode) '
      9   1.8  christos TZVERSION=see_Makefile
     10   1.8  christos REPORT_BUGS_TO=tz@iana.org
     11   1.5    kleink 
     12  1.15  christos # Contributed by Paul Eggert.  This file is in the public domain.
     13   1.1       jtc 
     14   1.1       jtc # Porting notes:
     15   1.1       jtc #
     16  1.10  christos # This script requires a Posix-like shell and prefers the extension of a
     17   1.8  christos # 'select' statement.  The 'select' statement was introduced in the
     18   1.8  christos # Korn shell and is available in Bash and other shell implementations.
     19   1.8  christos # If your host lacks both Bash and the Korn shell, you can get their
     20   1.8  christos # source from one of these locations:
     21   1.1       jtc #
     22  1.16  christos #	Bash <https://www.gnu.org/software/bash/>
     23   1.8  christos #	Korn Shell <http://www.kornshell.com/>
     24  1.16  christos #	MirBSD Korn Shell <https://www.mirbsd.org/mksh.htm>
     25   1.1       jtc #
     26  1.10  christos # For portability to Solaris 9 /bin/sh this script avoids some POSIX
     27  1.10  christos # features and common extensions, such as $(...) (which works sometimes
     28  1.10  christos # but not others), $((...)), and $10.
     29  1.10  christos #
     30   1.1       jtc # This script also uses several features of modern awk programs.
     31   1.8  christos # If your host lacks awk, or has an old awk that does not conform to Posix,
     32   1.1       jtc # you can use either of the following free programs instead:
     33   1.1       jtc #
     34  1.16  christos #	Gawk (GNU awk) <https://www.gnu.org/software/gawk/>
     35  1.16  christos #	mawk <https://invisible-island.net/mawk/>
     36   1.1       jtc 
     37   1.1       jtc 
     38   1.1       jtc # Specify default values for environment variables if they are unset.
     39   1.1       jtc : ${AWK=awk}
     40  1.10  christos : ${TZDIR=`pwd`}
     41   1.1       jtc 
     42  1.14  christos # Output one argument as-is to standard output.
     43  1.14  christos # Safer than 'echo', which can mishandle '\' or leading '-'.
     44  1.14  christos say() {
     45  1.14  christos     printf '%s\n' "$1"
     46  1.14  christos }
     47  1.14  christos 
     48   1.1       jtc # Check for awk Posix compliance.
     49   1.1       jtc ($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1
     50   1.1       jtc [ $? = 123 ] || {
     51  1.14  christos 	say >&2 "$0: Sorry, your '$AWK' program is not Posix compatible."
     52   1.1       jtc 	exit 1
     53   1.1       jtc }
     54   1.1       jtc 
     55   1.9  christos coord=
     56   1.9  christos location_limit=10
     57  1.11  christos zonetabtype=zone1970
     58   1.9  christos 
     59   1.9  christos usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT]
     60  1.18  christos Select a timezone interactively.
     61   1.6   mlelstv 
     62   1.9  christos Options:
     63   1.9  christos 
     64   1.9  christos   -c COORD
     65   1.9  christos     Instead of asking for continent and then country and then city,
     66   1.9  christos     ask for selection from time zones whose largest cities
     67   1.9  christos     are closest to the location with geographical coordinates COORD.
     68   1.9  christos     COORD should use ISO 6709 notation, for example, '-c +4852+00220'
     69   1.9  christos     for Paris (in degrees and minutes, North and East), or
     70   1.9  christos     '-c -35-058' for Buenos Aires (in degrees, South and West).
     71   1.9  christos 
     72   1.9  christos   -n LIMIT
     73   1.9  christos     Display at most LIMIT locations when -c is used (default $location_limit).
     74   1.9  christos 
     75   1.9  christos   --version
     76   1.9  christos     Output version information.
     77   1.9  christos 
     78   1.9  christos   --help
     79   1.9  christos     Output this help.
     80   1.9  christos 
     81   1.9  christos Report bugs to $REPORT_BUGS_TO."
     82   1.9  christos 
     83  1.10  christos # Ask the user to select from the function's arguments,
     84  1.10  christos # and assign the selected argument to the variable 'select_result'.
     85  1.10  christos # Exit on EOF or I/O error.  Use the shell's 'select' builtin if available,
     86  1.10  christos # falling back on a less-nice but portable substitute otherwise.
     87  1.10  christos if
     88  1.10  christos   case $BASH_VERSION in
     89  1.10  christos   ?*) : ;;
     90  1.10  christos   '')
     91  1.10  christos     # '; exit' should be redundant, but Dash doesn't properly fail without it.
     92  1.11  christos     (eval 'set --; select x; do break; done; exit') </dev/null 2>/dev/null
     93  1.10  christos   esac
     94  1.10  christos then
     95  1.10  christos   # Do this inside 'eval', as otherwise the shell might exit when parsing it
     96  1.10  christos   # even though it is never executed.
     97  1.10  christos   eval '
     98  1.10  christos     doselect() {
     99  1.10  christos       select select_result
    100  1.10  christos       do
    101  1.10  christos 	case $select_result in
    102  1.10  christos 	"") echo >&2 "Please enter a number in range." ;;
    103  1.10  christos 	?*) break
    104  1.10  christos 	esac
    105  1.10  christos       done || exit
    106  1.10  christos     }
    107  1.10  christos 
    108  1.10  christos     # Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout.
    109  1.10  christos     case $BASH_VERSION in
    110  1.10  christos     [01].*)
    111  1.10  christos       case `echo 1 | (select x in x; do break; done) 2>/dev/null` in
    112  1.10  christos       ?*) PS3=
    113  1.10  christos       esac
    114  1.10  christos     esac
    115  1.10  christos   '
    116  1.10  christos else
    117  1.10  christos   doselect() {
    118  1.10  christos     # Field width of the prompt numbers.
    119  1.10  christos     select_width=`expr $# : '.*'`
    120  1.10  christos 
    121  1.10  christos     select_i=
    122  1.10  christos 
    123  1.10  christos     while :
    124  1.10  christos     do
    125  1.10  christos       case $select_i in
    126  1.10  christos       '')
    127  1.10  christos 	select_i=0
    128  1.10  christos 	for select_word
    129  1.10  christos 	do
    130  1.10  christos 	  select_i=`expr $select_i + 1`
    131  1.10  christos 	  printf >&2 "%${select_width}d) %s\\n" $select_i "$select_word"
    132  1.10  christos 	done ;;
    133  1.10  christos       *[!0-9]*)
    134  1.10  christos 	echo >&2 'Please enter a number in range.' ;;
    135  1.10  christos       *)
    136  1.10  christos 	if test 1 -le $select_i && test $select_i -le $#; then
    137  1.10  christos 	  shift `expr $select_i - 1`
    138  1.10  christos 	  select_result=$1
    139  1.10  christos 	  break
    140  1.10  christos 	fi
    141  1.10  christos 	echo >&2 'Please enter a number in range.'
    142  1.10  christos       esac
    143  1.10  christos 
    144  1.10  christos       # Prompt and read input.
    145  1.10  christos       printf >&2 %s "${PS3-#? }"
    146  1.10  christos       read select_i || exit
    147  1.10  christos     done
    148  1.10  christos   }
    149  1.10  christos fi
    150  1.10  christos 
    151  1.12  christos while getopts c:n:t:-: opt
    152   1.9  christos do
    153   1.9  christos     case $opt$OPTARG in
    154   1.9  christos     c*)
    155   1.9  christos 	coord=$OPTARG ;;
    156   1.9  christos     n*)
    157   1.9  christos 	location_limit=$OPTARG ;;
    158  1.11  christos     t*) # Undocumented option, used for developer testing.
    159  1.11  christos 	zonetabtype=$OPTARG ;;
    160   1.9  christos     -help)
    161   1.9  christos 	exec echo "$usage" ;;
    162   1.9  christos     -version)
    163   1.9  christos 	exec echo "tzselect $PKGVERSION$TZVERSION" ;;
    164   1.9  christos     -*)
    165  1.14  christos 	say >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1 ;;
    166   1.9  christos     *)
    167  1.14  christos 	say >&2 "$0: try '$0 --help'"; exit 1 ;;
    168   1.9  christos     esac
    169   1.9  christos done
    170   1.9  christos 
    171  1.10  christos shift `expr $OPTIND - 1`
    172   1.9  christos case $# in
    173   1.9  christos 0) ;;
    174  1.14  christos *) say >&2 "$0: $1: unknown argument"; exit 1 ;;
    175   1.9  christos esac
    176   1.6   mlelstv 
    177   1.1       jtc # Make sure the tables are readable.
    178   1.1       jtc TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab
    179  1.11  christos TZ_ZONE_TABLE=$TZDIR/$zonetabtype.tab
    180   1.1       jtc for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
    181   1.1       jtc do
    182  1.11  christos 	<"$f" || {
    183  1.14  christos 		say >&2 "$0: time zone files are not set up correctly"
    184   1.1       jtc 		exit 1
    185   1.1       jtc 	}
    186   1.1       jtc done
    187   1.1       jtc 
    188  1.14  christos # If the current locale does not support UTF-8, convert data to current
    189  1.14  christos # locale's format if possible, as the shell aligns columns better that way.
    190  1.14  christos # Check the UTF-8 of U+12345 CUNEIFORM SIGN URU TIMES KI.
    191  1.14  christos ! $AWK 'BEGIN { u12345 = "\360\222\215\205"; exit length(u12345) != 1 }' &&
    192  1.14  christos     { tmp=`(mktemp -d) 2>/dev/null` || {
    193  1.14  christos 	tmp=${TMPDIR-/tmp}/tzselect.$$ &&
    194  1.14  christos 	(umask 77 && mkdir -- "$tmp")
    195  1.14  christos     };} &&
    196  1.14  christos     trap 'status=$?; rm -fr -- "$tmp"; exit $status' 0 HUP INT PIPE TERM &&
    197  1.14  christos     (iconv -f UTF-8 -t //TRANSLIT <"$TZ_COUNTRY_TABLE" >$tmp/iso3166.tab) \
    198  1.14  christos         2>/dev/null &&
    199  1.14  christos     TZ_COUNTRY_TABLE=$tmp/iso3166.tab &&
    200  1.14  christos     iconv -f UTF-8 -t //TRANSLIT <"$TZ_ZONE_TABLE" >$tmp/$zonetabtype.tab &&
    201  1.14  christos     TZ_ZONE_TABLE=$tmp/$zonetabtype.tab
    202  1.14  christos 
    203   1.1       jtc newline='
    204   1.1       jtc '
    205   1.1       jtc IFS=$newline
    206   1.1       jtc 
    207   1.1       jtc 
    208   1.9  christos # Awk script to read a time zone table and output the same table,
    209   1.9  christos # with each column preceded by its distance from 'here'.
    210   1.9  christos output_distances='
    211   1.9  christos   BEGIN {
    212   1.9  christos     FS = "\t"
    213   1.9  christos     while (getline <TZ_COUNTRY_TABLE)
    214   1.9  christos       if ($0 ~ /^[^#]/)
    215   1.9  christos         country[$1] = $2
    216   1.9  christos     country["US"] = "US" # Otherwise the strings get too long.
    217   1.9  christos   }
    218  1.12  christos   function abs(x) {
    219  1.12  christos     return x < 0 ? -x : x;
    220  1.12  christos   }
    221  1.12  christos   function min(x, y) {
    222  1.12  christos     return x < y ? x : y;
    223  1.12  christos   }
    224  1.12  christos   function convert_coord(coord, deg, minute, ilen, sign, sec) {
    225   1.9  christos     if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9][0-9][0-9]([^0-9]|$)/) {
    226   1.9  christos       degminsec = coord
    227   1.9  christos       intdeg = degminsec < 0 ? -int(-degminsec / 10000) : int(degminsec / 10000)
    228   1.9  christos       minsec = degminsec - intdeg * 10000
    229   1.9  christos       intmin = minsec < 0 ? -int(-minsec / 100) : int(minsec / 100)
    230   1.9  christos       sec = minsec - intmin * 100
    231   1.9  christos       deg = (intdeg * 3600 + intmin * 60 + sec) / 3600
    232   1.9  christos     } else if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9]([^0-9]|$)/) {
    233   1.9  christos       degmin = coord
    234   1.9  christos       intdeg = degmin < 0 ? -int(-degmin / 100) : int(degmin / 100)
    235  1.12  christos       minute = degmin - intdeg * 100
    236  1.12  christos       deg = (intdeg * 60 + minute) / 60
    237   1.9  christos     } else
    238   1.9  christos       deg = coord
    239   1.9  christos     return deg * 0.017453292519943296
    240   1.9  christos   }
    241   1.9  christos   function convert_latitude(coord) {
    242   1.9  christos     match(coord, /..*[-+]/)
    243   1.9  christos     return convert_coord(substr(coord, 1, RLENGTH - 1))
    244   1.9  christos   }
    245   1.9  christos   function convert_longitude(coord) {
    246   1.9  christos     match(coord, /..*[-+]/)
    247   1.9  christos     return convert_coord(substr(coord, RLENGTH))
    248   1.9  christos   }
    249   1.9  christos   # Great-circle distance between points with given latitude and longitude.
    250   1.9  christos   # Inputs and output are in radians.  This uses the great-circle special
    251   1.9  christos   # case of the Vicenty formula for distances on ellipsoids.
    252  1.12  christos   function gcdist(lat1, long1, lat2, long2, dlong, x, y, num, denom) {
    253   1.9  christos     dlong = long2 - long1
    254  1.13  christos     x = cos(lat2) * sin(dlong)
    255  1.13  christos     y = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlong)
    256  1.13  christos     num = sqrt(x * x + y * y)
    257  1.13  christos     denom = sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(dlong)
    258   1.9  christos     return atan2(num, denom)
    259   1.9  christos   }
    260  1.12  christos   # Parallel distance between points with given latitude and longitude.
    261  1.12  christos   # This is the product of the longitude difference and the cosine
    262  1.12  christos   # of the latitude of the point that is further from the equator.
    263  1.12  christos   # I.e., it considers longitudes to be further apart if they are
    264  1.12  christos   # nearer the equator.
    265  1.12  christos   function pardist(lat1, long1, lat2, long2) {
    266  1.13  christos     return abs(long1 - long2) * min(cos(lat1), cos(lat2))
    267  1.12  christos   }
    268  1.12  christos   # The distance function is the sum of the great-circle distance and
    269  1.12  christos   # the parallel distance.  It could be weighted.
    270  1.12  christos   function dist(lat1, long1, lat2, long2) {
    271  1.13  christos     return gcdist(lat1, long1, lat2, long2) + pardist(lat1, long1, lat2, long2)
    272  1.12  christos   }
    273   1.9  christos   BEGIN {
    274   1.9  christos     coord_lat = convert_latitude(coord)
    275   1.9  christos     coord_long = convert_longitude(coord)
    276   1.9  christos   }
    277   1.9  christos   /^[^#]/ {
    278   1.9  christos     here_lat = convert_latitude($2)
    279   1.9  christos     here_long = convert_longitude($2)
    280  1.11  christos     line = $1 "\t" $2 "\t" $3
    281  1.11  christos     sep = "\t"
    282  1.11  christos     ncc = split($1, cc, /,/)
    283  1.11  christos     for (i = 1; i <= ncc; i++) {
    284  1.11  christos       line = line sep country[cc[i]]
    285  1.11  christos       sep = ", "
    286  1.11  christos     }
    287   1.9  christos     if (NF == 4)
    288   1.9  christos       line = line " - " $4
    289   1.9  christos     printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), line
    290   1.9  christos   }
    291   1.9  christos '
    292   1.1       jtc 
    293   1.1       jtc # Begin the main loop.  We come back here if the user wants to retry.
    294   1.1       jtc while
    295   1.1       jtc 
    296   1.1       jtc 	echo >&2 'Please identify a location' \
    297   1.1       jtc 		'so that time zone rules can be set correctly.'
    298   1.1       jtc 
    299   1.1       jtc 	continent=
    300   1.1       jtc 	country=
    301   1.1       jtc 	region=
    302   1.1       jtc 
    303   1.9  christos 	case $coord in
    304   1.9  christos 	?*)
    305   1.9  christos 		continent=coord;;
    306   1.9  christos 	'')
    307   1.1       jtc 
    308   1.1       jtc 	# Ask the user for continent or ocean.
    309   1.1       jtc 
    310   1.9  christos 	echo >&2 'Please select a continent, ocean, "coord", or "TZ".'
    311   1.1       jtc 
    312  1.10  christos         quoted_continents=`
    313  1.10  christos 	  $AWK '
    314  1.10  christos 	    BEGIN { FS = "\t" }
    315   1.9  christos 	    /^[^#]/ {
    316   1.9  christos               entry = substr($3, 1, index($3, "/") - 1)
    317   1.9  christos               if (entry == "America")
    318   1.9  christos 		entry = entry "s"
    319   1.9  christos               if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/)
    320   1.9  christos 		entry = entry " Ocean"
    321   1.9  christos               printf "'\''%s'\''\n", entry
    322   1.9  christos             }
    323  1.11  christos           ' <"$TZ_ZONE_TABLE" |
    324   1.9  christos 	  sort -u |
    325   1.9  christos 	  tr '\n' ' '
    326   1.9  christos 	  echo ''
    327  1.10  christos 	`
    328   1.9  christos 
    329   1.9  christos 	eval '
    330  1.10  christos 	    doselect '"$quoted_continents"' \
    331   1.9  christos 		"coord - I want to use geographical coordinates." \
    332  1.18  christos 		"TZ - I want to specify the timezone using the Posix TZ format."
    333  1.10  christos 	    continent=$select_result
    334  1.10  christos 	    case $continent in
    335  1.10  christos 	    Americas) continent=America;;
    336  1.10  christos 	    *" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''`
    337  1.10  christos 	    esac
    338   1.9  christos 	'
    339   1.9  christos 	esac
    340   1.9  christos 
    341   1.1       jtc 	case $continent in
    342   1.9  christos 	TZ)
    343   1.1       jtc 		# Ask the user for a Posix TZ string.  Check that it conforms.
    344   1.1       jtc 		while
    345   1.1       jtc 			echo >&2 'Please enter the desired value' \
    346   1.1       jtc 				'of the TZ environment variable.'
    347  1.18  christos 			echo >&2 'For example, AEST-10 is abbreviated' \
    348  1.18  christos 				'AEST and is 10 hours'
    349  1.17  christos 			echo >&2 'ahead (east) of Greenwich,' \
    350  1.17  christos 				'with no daylight saving time.'
    351   1.1       jtc 			read TZ
    352   1.1       jtc 			$AWK -v TZ="$TZ" 'BEGIN {
    353  1.15  christos 				tzname = "(<[[:alnum:]+-]{3,}>|[[:alpha:]]{3,})"
    354  1.15  christos 				time = "(2[0-4]|[0-1]?[0-9])" \
    355  1.15  christos 				  "(:[0-5][0-9](:[0-5][0-9])?)?"
    356   1.1       jtc 				offset = "[-+]?" time
    357  1.15  christos 				mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]"
    358  1.15  christos 				jdate = "((J[1-9]|[0-9]|J?[1-9][0-9]" \
    359  1.15  christos 				  "|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])"
    360  1.15  christos 				datetime = ",(" mdate "|" jdate ")(/" time ")?"
    361   1.1       jtc 				tzpattern = "^(:.*|" tzname offset "(" tzname \
    362   1.1       jtc 				  "(" offset ")?(" datetime datetime ")?)?)$"
    363   1.1       jtc 				if (TZ ~ tzpattern) exit 1
    364   1.1       jtc 				exit 0
    365   1.1       jtc 			}'
    366   1.1       jtc 		do
    367  1.18  christos 		    say >&2 "'$TZ' is not a conforming Posix timezone string."
    368   1.1       jtc 		done
    369   1.1       jtc 		TZ_for_date=$TZ;;
    370   1.1       jtc 	*)
    371   1.9  christos 		case $continent in
    372   1.9  christos 		coord)
    373   1.9  christos 		    case $coord in
    374   1.9  christos 		    '')
    375   1.9  christos 			echo >&2 'Please enter coordinates' \
    376   1.9  christos 				'in ISO 6709 notation.'
    377   1.9  christos 			echo >&2 'For example, +4042-07403 stands for'
    378   1.9  christos 			echo >&2 '40 degrees 42 minutes north,' \
    379   1.9  christos 				'74 degrees 3 minutes west.'
    380   1.9  christos 			read coord;;
    381   1.9  christos 		    esac
    382  1.10  christos 		    distance_table=`$AWK \
    383   1.9  christos 			    -v coord="$coord" \
    384   1.9  christos 			    -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
    385  1.11  christos 			    "$output_distances" <"$TZ_ZONE_TABLE" |
    386   1.9  christos 		      sort -n |
    387   1.9  christos 		      sed "${location_limit}q"
    388  1.10  christos 		    `
    389  1.14  christos 		    regions=`say "$distance_table" | $AWK '
    390   1.9  christos 		      BEGIN { FS = "\t" }
    391   1.9  christos 		      { print $NF }
    392  1.10  christos 		    '`
    393  1.18  christos 		    echo >&2 'Please select one of the following timezones,' \
    394   1.9  christos 		    echo >&2 'listed roughly in increasing order' \
    395   1.9  christos 			    "of distance from $coord".
    396  1.10  christos 		    doselect $regions
    397  1.10  christos 		    region=$select_result
    398  1.14  christos 		    TZ=`say "$distance_table" | $AWK -v region="$region" '
    399   1.9  christos 		      BEGIN { FS="\t" }
    400   1.9  christos 		      $NF == region { print $4 }
    401  1.10  christos 		    '`
    402   1.9  christos 		    ;;
    403   1.9  christos 		*)
    404   1.1       jtc 		# Get list of names of countries in the continent or ocean.
    405  1.10  christos 		countries=`$AWK \
    406   1.1       jtc 			-v continent="$continent" \
    407   1.1       jtc 			-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
    408   1.1       jtc 		'
    409  1.10  christos 			BEGIN { FS = "\t" }
    410   1.1       jtc 			/^#/ { next }
    411   1.1       jtc 			$3 ~ ("^" continent "/") {
    412  1.11  christos 			    ncc = split($1, cc, /,/)
    413  1.11  christos 			    for (i = 1; i <= ncc; i++)
    414  1.11  christos 				if (!cc_seen[cc[i]]++) cc_list[++ccs] = cc[i]
    415   1.1       jtc 			}
    416   1.1       jtc 			END {
    417   1.1       jtc 				while (getline <TZ_COUNTRY_TABLE) {
    418   1.1       jtc 					if ($0 !~ /^#/) cc_name[$1] = $2
    419   1.1       jtc 				}
    420   1.1       jtc 				for (i = 1; i <= ccs; i++) {
    421   1.1       jtc 					country = cc_list[i]
    422   1.1       jtc 					if (cc_name[country]) {
    423   1.1       jtc 					  country = cc_name[country]
    424   1.1       jtc 					}
    425   1.1       jtc 					print country
    426   1.1       jtc 				}
    427   1.1       jtc 			}
    428  1.11  christos 		' <"$TZ_ZONE_TABLE" | sort -f`
    429   1.1       jtc 
    430   1.1       jtc 
    431   1.1       jtc 		# If there's more than one country, ask the user which one.
    432   1.1       jtc 		case $countries in
    433   1.1       jtc 		*"$newline"*)
    434   1.9  christos 			echo >&2 'Please select a country' \
    435   1.9  christos 				'whose clocks agree with yours.'
    436  1.10  christos 			doselect $countries
    437  1.10  christos 			country=$select_result;;
    438   1.1       jtc 		*)
    439   1.1       jtc 			country=$countries
    440   1.1       jtc 		esac
    441   1.1       jtc 
    442   1.1       jtc 
    443  1.18  christos 		# Get list of timezones in the country.
    444  1.10  christos 		regions=`$AWK \
    445   1.1       jtc 			-v country="$country" \
    446   1.1       jtc 			-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
    447   1.1       jtc 		'
    448   1.1       jtc 			BEGIN {
    449  1.10  christos 				FS = "\t"
    450   1.1       jtc 				cc = country
    451   1.1       jtc 				while (getline <TZ_COUNTRY_TABLE) {
    452   1.1       jtc 					if ($0 !~ /^#/  &&  country == $2) {
    453   1.1       jtc 						cc = $1
    454   1.1       jtc 						break
    455   1.1       jtc 					}
    456   1.1       jtc 				}
    457   1.1       jtc 			}
    458  1.14  christos 			/^#/ { next }
    459  1.11  christos 			$1 ~ cc { print $4 }
    460  1.11  christos 		' <"$TZ_ZONE_TABLE"`
    461   1.1       jtc 
    462   1.1       jtc 
    463   1.1       jtc 		# If there's more than one region, ask the user which one.
    464   1.1       jtc 		case $regions in
    465   1.1       jtc 		*"$newline"*)
    466  1.18  christos 			echo >&2 'Please select one of the following timezones.'
    467  1.10  christos 			doselect $regions
    468  1.10  christos 			region=$select_result;;
    469   1.1       jtc 		*)
    470   1.1       jtc 			region=$regions
    471   1.1       jtc 		esac
    472   1.1       jtc 
    473   1.1       jtc 		# Determine TZ from country and region.
    474  1.10  christos 		TZ=`$AWK \
    475   1.1       jtc 			-v country="$country" \
    476   1.1       jtc 			-v region="$region" \
    477   1.1       jtc 			-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
    478   1.1       jtc 		'
    479   1.1       jtc 			BEGIN {
    480  1.10  christos 				FS = "\t"
    481   1.1       jtc 				cc = country
    482   1.1       jtc 				while (getline <TZ_COUNTRY_TABLE) {
    483   1.1       jtc 					if ($0 !~ /^#/  &&  country == $2) {
    484   1.1       jtc 						cc = $1
    485   1.1       jtc 						break
    486   1.1       jtc 					}
    487   1.1       jtc 				}
    488   1.1       jtc 			}
    489  1.14  christos 			/^#/ { next }
    490  1.11  christos 			$1 ~ cc && $4 == region { print $3 }
    491  1.11  christos 		' <"$TZ_ZONE_TABLE"`
    492   1.9  christos 		esac
    493   1.1       jtc 
    494   1.1       jtc 		# Make sure the corresponding zoneinfo file exists.
    495   1.1       jtc 		TZ_for_date=$TZDIR/$TZ
    496  1.11  christos 		<"$TZ_for_date" || {
    497  1.14  christos 			say >&2 "$0: time zone files are not set up correctly"
    498   1.1       jtc 			exit 1
    499   1.1       jtc 		}
    500   1.1       jtc 	esac
    501   1.1       jtc 
    502   1.1       jtc 
    503   1.1       jtc 	# Use the proposed TZ to output the current date relative to UTC.
    504   1.1       jtc 	# Loop until they agree in seconds.
    505   1.1       jtc 	# Give up after 8 unsuccessful tries.
    506   1.1       jtc 
    507   1.1       jtc 	extra_info=
    508   1.1       jtc 	for i in 1 2 3 4 5 6 7 8
    509   1.1       jtc 	do
    510  1.10  christos 		TZdate=`LANG=C TZ="$TZ_for_date" date`
    511  1.10  christos 		UTdate=`LANG=C TZ=UTC0 date`
    512  1.10  christos 		TZsec=`expr "$TZdate" : '.*:\([0-5][0-9]\)'`
    513  1.10  christos 		UTsec=`expr "$UTdate" : '.*:\([0-5][0-9]\)'`
    514   1.1       jtc 		case $TZsec in
    515   1.1       jtc 		$UTsec)
    516   1.1       jtc 			extra_info="
    517  1.15  christos Selected time is now:	$TZdate.
    518   1.1       jtc Universal Time is now:	$UTdate."
    519   1.1       jtc 			break
    520   1.1       jtc 		esac
    521   1.1       jtc 	done
    522   1.1       jtc 
    523   1.1       jtc 
    524   1.1       jtc 	# Output TZ info and ask the user to confirm.
    525   1.1       jtc 
    526   1.1       jtc 	echo >&2 ""
    527   1.1       jtc 	echo >&2 "The following information has been given:"
    528   1.1       jtc 	echo >&2 ""
    529   1.9  christos 	case $country%$region%$coord in
    530  1.14  christos 	?*%?*%)	say >&2 "	$country$newline	$region";;
    531  1.14  christos 	?*%%)	say >&2 "	$country";;
    532  1.14  christos 	%?*%?*) say >&2 "	coord $coord$newline	$region";;
    533  1.14  christos 	%%?*)	say >&2 "	coord $coord";;
    534  1.14  christos 	*)	say >&2 "	TZ='$TZ'"
    535   1.1       jtc 	esac
    536  1.14  christos 	say >&2 ""
    537  1.14  christos 	say >&2 "Therefore TZ='$TZ' will be used.$extra_info"
    538  1.14  christos 	say >&2 "Is the above information OK?"
    539   1.1       jtc 
    540  1.10  christos 	doselect Yes No
    541  1.10  christos 	ok=$select_result
    542   1.1       jtc 	case $ok in
    543   1.1       jtc 	Yes) break
    544   1.1       jtc 	esac
    545   1.9  christos do coord=
    546   1.1       jtc done
    547   1.1       jtc 
    548   1.5    kleink case $SHELL in
    549   1.5    kleink *csh) file=.login line="setenv TZ '$TZ'";;
    550   1.5    kleink *) file=.profile line="TZ='$TZ'; export TZ"
    551   1.5    kleink esac
    552   1.5    kleink 
    553  1.15  christos test -t 1 && say >&2 "
    554   1.5    kleink You can make this change permanent for yourself by appending the line
    555   1.5    kleink 	$line
    556   1.5    kleink to the file '$file' in your home directory; then log out and log in again.
    557   1.5    kleink 
    558   1.5    kleink Here is that TZ value again, this time on standard output so that you
    559   1.5    kleink can use the $0 command in shell scripts:"
    560   1.5    kleink 
    561  1.14  christos say "$TZ"
    562