Home | History | Annotate | Line # | Download | only in time
      1  1.24  christos #!/bin/bash
      2  1.18  christos # Ask the user about the time zone, and output the resulting TZ value to stdout.
      3  1.18  christos # Interact with the user via stderr and stdin.
      4  1.24  christos 
      5   1.8  christos PKGVERSION='(tzcode) '
      6   1.8  christos TZVERSION=see_Makefile
      7   1.8  christos REPORT_BUGS_TO=tz@iana.org
      8   1.5    kleink 
      9  1.15  christos # Contributed by Paul Eggert.  This file is in the public domain.
     10   1.1       jtc 
     11   1.1       jtc # Porting notes:
     12   1.1       jtc #
     13  1.24  christos # This script requires a POSIX-like shell and prefers the extension of a
     14   1.8  christos # 'select' statement.  The 'select' statement was introduced in the
     15   1.8  christos # Korn shell and is available in Bash and other shell implementations.
     16   1.8  christos # If your host lacks both Bash and the Korn shell, you can get their
     17   1.8  christos # source from one of these locations:
     18   1.1       jtc #
     19  1.16  christos #	Bash <https://www.gnu.org/software/bash/>
     20   1.8  christos #	Korn Shell <http://www.kornshell.com/>
     21  1.19  christos #	MirBSD Korn Shell <http://www.mirbsd.org/mksh.htm>
     22   1.1       jtc #
     23  1.24  christos # This script also uses several features of POSIX awk.
     24  1.24  christos # If your host lacks awk, or has an old awk that does not conform to POSIX,
     25  1.24  christos # you can use any of the following free programs instead:
     26   1.1       jtc #
     27  1.16  christos #	Gawk (GNU awk) <https://www.gnu.org/software/gawk/>
     28  1.16  christos #	mawk <https://invisible-island.net/mawk/>
     29  1.21  christos #	nawk <https://github.com/onetrueawk/awk>
     30  1.24  christos #
     31  1.24  christos # Because 'awk "VAR=VALUE" ...' and 'awk -v "VAR=VALUE" ...' are not portable
     32  1.24  christos # if VALUE contains \, ", or newline, awk scripts in this file use:
     33  1.24  christos #   awk 'BEGIN { VAR = substr(ARGV[1], 2); ARGV[1] = "" } ...' ="VALUE"
     34  1.24  christos # The substr avoids problems when VALUE is of the form X=Y and would be
     35  1.24  christos # misinterpreted as an assignment.
     36   1.1       jtc 
     37  1.23  christos # This script does not want path expansion.
     38  1.23  christos set -f
     39  1.23  christos 
     40   1.1       jtc # Specify default values for environment variables if they are unset.
     41   1.1       jtc : ${AWK=awk}
     42  1.23  christos : ${TZDIR=$PWD}
     43   1.1       jtc 
     44  1.22  christos # Output one argument as-is to standard output, with trailing newline.
     45  1.14  christos # Safer than 'echo', which can mishandle '\' or leading '-'.
     46  1.14  christos say() {
     47  1.24  christos   printf '%s\n' "$1"
     48   1.1       jtc }
     49   1.1       jtc 
     50   1.9  christos coord=
     51   1.9  christos location_limit=10
     52  1.11  christos zonetabtype=zone1970
     53   1.9  christos 
     54   1.9  christos usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT]
     55  1.18  christos Select a timezone interactively.
     56   1.6   mlelstv 
     57   1.9  christos Options:
     58   1.9  christos 
     59   1.9  christos   -c COORD
     60   1.9  christos     Instead of asking for continent and then country and then city,
     61   1.9  christos     ask for selection from time zones whose largest cities
     62   1.9  christos     are closest to the location with geographical coordinates COORD.
     63   1.9  christos     COORD should use ISO 6709 notation, for example, '-c +4852+00220'
     64   1.9  christos     for Paris (in degrees and minutes, North and East), or
     65   1.9  christos     '-c -35-058' for Buenos Aires (in degrees, South and West).
     66   1.9  christos 
     67   1.9  christos   -n LIMIT
     68   1.9  christos     Display at most LIMIT locations when -c is used (default $location_limit).
     69   1.9  christos 
     70   1.9  christos   --version
     71   1.9  christos     Output version information.
     72   1.9  christos 
     73   1.9  christos   --help
     74   1.9  christos     Output this help.
     75   1.9  christos 
     76   1.9  christos Report bugs to $REPORT_BUGS_TO."
     77   1.9  christos 
     78  1.10  christos # Ask the user to select from the function's arguments,
     79  1.10  christos # and assign the selected argument to the variable 'select_result'.
     80  1.22  christos # Exit on EOF or I/O error.  Use the shell's nicer 'select' builtin if
     81  1.22  christos # available, falling back on a portable substitute otherwise.
     82  1.10  christos if
     83  1.10  christos   case $BASH_VERSION in
     84  1.24  christos   ?*) :;;
     85  1.10  christos   '')
     86  1.10  christos     # '; exit' should be redundant, but Dash doesn't properly fail without it.
     87  1.24  christos     (eval 'set --; select x; do break; done; exit') <>/dev/null 2>&0
     88  1.10  christos   esac
     89  1.10  christos then
     90  1.10  christos   # Do this inside 'eval', as otherwise the shell might exit when parsing it
     91  1.10  christos   # even though it is never executed.
     92  1.10  christos   eval '
     93  1.10  christos     doselect() {
     94  1.10  christos       select select_result
     95  1.10  christos       do
     96  1.10  christos 	case $select_result in
     97  1.24  christos 	"") echo >&2 "Please enter a number in range.";;
     98  1.10  christos 	?*) break
     99  1.10  christos 	esac
    100  1.10  christos       done || exit
    101  1.10  christos     }
    102  1.10  christos   '
    103  1.10  christos else
    104  1.10  christos   doselect() {
    105  1.10  christos     # Field width of the prompt numbers.
    106  1.24  christos     select_width=${##}
    107  1.10  christos 
    108  1.10  christos     select_i=
    109  1.10  christos 
    110  1.10  christos     while :
    111  1.10  christos     do
    112  1.10  christos       case $select_i in
    113  1.10  christos       '')
    114  1.10  christos 	select_i=0
    115  1.10  christos 	for select_word
    116  1.10  christos 	do
    117  1.24  christos 	  select_i=$(($select_i + 1))
    118  1.10  christos 	  printf >&2 "%${select_width}d) %s\\n" $select_i "$select_word"
    119  1.24  christos 	done;;
    120  1.10  christos       *[!0-9]*)
    121  1.24  christos 	echo >&2 'Please enter a number in range.';;
    122  1.10  christos       *)
    123  1.10  christos 	if test 1 -le $select_i && test $select_i -le $#; then
    124  1.24  christos 	  shift $(($select_i - 1))
    125  1.10  christos 	  select_result=$1
    126  1.10  christos 	  break
    127  1.10  christos 	fi
    128  1.10  christos 	echo >&2 'Please enter a number in range.'
    129  1.10  christos       esac
    130  1.10  christos 
    131  1.10  christos       # Prompt and read input.
    132  1.10  christos       printf >&2 %s "${PS3-#? }"
    133  1.10  christos       read select_i || exit
    134  1.10  christos     done
    135  1.10  christos   }
    136  1.10  christos fi
    137  1.10  christos 
    138  1.12  christos while getopts c:n:t:-: opt
    139   1.9  christos do
    140  1.24  christos   case $opt$OPTARG in
    141  1.24  christos   c*)
    142  1.24  christos     coord=$OPTARG;;
    143  1.24  christos   n*)
    144  1.24  christos     location_limit=$OPTARG;;
    145  1.24  christos   t*) # Undocumented option, used for developer testing.
    146  1.24  christos     zonetabtype=$OPTARG;;
    147  1.24  christos   -help)
    148  1.24  christos     exec echo "$usage";;
    149  1.24  christos   -version)
    150  1.24  christos     exec echo "tzselect $PKGVERSION$TZVERSION";;
    151  1.24  christos   -*)
    152  1.24  christos     say >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1;;
    153  1.24  christos   *)
    154  1.24  christos     say >&2 "$0: try '$0 --help'"; exit 1
    155  1.24  christos   esac
    156   1.9  christos done
    157   1.9  christos 
    158  1.24  christos shift $(($OPTIND - 1))
    159   1.9  christos case $# in
    160   1.9  christos 0) ;;
    161  1.24  christos *) say >&2 "$0: $1: unknown argument"; exit 1
    162   1.9  christos esac
    163   1.6   mlelstv 
    164  1.24  christos # translit=true to try transliteration.
    165  1.24  christos # This is false if U+12345 CUNEIFORM SIGN URU TIMES KI has length 1
    166  1.24  christos # which means the shell and (presumably) awk do not need transliteration.
    167  1.24  christos # It is true if the byte string has some other length in characters, or
    168  1.24  christos # if this is a POSIX.1-2017 or earlier shell that does not support $'...'.
    169  1.24  christos CUNEIFORM_SIGN_URU_TIMES_KI=$'\360\222\215\205'
    170  1.24  christos if test ${#CUNEIFORM_SIGN_URU_TIMES_KI} = 1
    171  1.24  christos then translit=false
    172  1.24  christos else translit=true
    173  1.24  christos fi
    174   1.1       jtc 
    175  1.24  christos # Read into shell variable $1 the contents of file $2.
    176  1.24  christos # Convert to the current locale's encoding if possible,
    177  1.24  christos # as the shell aligns columns better that way.
    178  1.24  christos # If GNU iconv's //TRANSLIT does not work, fall back on POSIXish iconv;
    179  1.24  christos # if that does not work, fall back on 'cat'.
    180  1.24  christos read_file() {
    181  1.24  christos   { $translit && {
    182  1.24  christos     eval "$1=\$( (iconv -f UTF-8 -t //TRANSLIT) 2>/dev/null <\"\$2\")" ||
    183  1.24  christos     eval "$1=\$( (iconv -f UTF-8) 2>/dev/null <\"\$2\")"
    184  1.24  christos   }; } ||
    185  1.24  christos   eval "$1=\$(cat <\"\$2\")" || {
    186  1.24  christos     say >&2 "$0: time zone files are not set up correctly"
    187  1.24  christos     exit 1
    188  1.24  christos   }
    189  1.19  christos }
    190  1.24  christos read_file TZ_COUNTRY_TABLE "$TZDIR/iso3166.tab"
    191  1.24  christos read_file TZ_ZONETABTYPE_TABLE "$TZDIR/$zonetabtype.tab"
    192  1.24  christos TZ_ZONENOW_TABLE=
    193  1.14  christos 
    194   1.1       jtc newline='
    195   1.1       jtc '
    196   1.1       jtc IFS=$newline
    197   1.1       jtc 
    198  1.22  christos # Awk script to output a country list.
    199  1.22  christos output_country_list='
    200  1.24  christos   BEGIN {
    201  1.24  christos     continent_re = substr(ARGV[1], 2)
    202  1.24  christos     TZ_COUNTRY_TABLE = substr(ARGV[2], 2)
    203  1.24  christos     TZ_ZONE_TABLE = substr(ARGV[3], 2)
    204  1.24  christos     ARGV[1] = ARGV[2] = ARGV[3] = ""
    205  1.24  christos     FS = "\t"
    206  1.24  christos     nlines = split(TZ_ZONE_TABLE, line, /\n/)
    207  1.24  christos     for (iline = 1; iline <= nlines; iline++) {
    208  1.24  christos       $0 = line[iline]
    209  1.24  christos       commentary = $0 ~ /^#@/
    210  1.24  christos       if (commentary) {
    211  1.24  christos 	if ($0 !~ /^#@/)
    212  1.24  christos 	  continue
    213  1.24  christos 	col1ccs = substr($1, 3)
    214  1.24  christos 	conts = $2
    215  1.24  christos       } else {
    216  1.24  christos 	col1ccs = $1
    217  1.24  christos 	conts = $3
    218  1.22  christos       }
    219  1.24  christos       ncc = split(col1ccs, cc, /,/)
    220  1.24  christos       ncont = split(conts, cont, /,/)
    221  1.24  christos       for (i = 1; i <= ncc; i++) {
    222  1.24  christos 	elsewhere = commentary
    223  1.24  christos 	for (ci = 1; ci <= ncont; ci++) {
    224  1.24  christos 	  if (cont[ci] ~ continent_re) {
    225  1.24  christos 	    if (!cc_seen[cc[i]]++)
    226  1.24  christos 	      cc_list[++ccs] = cc[i]
    227  1.24  christos 	    elsewhere = 0
    228  1.24  christos 	  }
    229  1.22  christos 	}
    230  1.24  christos 	if (elsewhere)
    231  1.24  christos 	  for (i = 1; i <= ncc; i++)
    232  1.24  christos 	    cc_elsewhere[cc[i]] = 1
    233  1.22  christos       }
    234  1.22  christos     }
    235  1.24  christos     nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
    236  1.24  christos     for (i = 1; i <= nlines; i++) {
    237  1.24  christos       $0 = line[i]
    238  1.24  christos       if ($0 !~ /^#/)
    239  1.24  christos 	cc_name[$1] = $2
    240  1.24  christos     }
    241  1.24  christos     for (i = 1; i <= ccs; i++) {
    242  1.24  christos       country = cc_list[i]
    243  1.24  christos       if (cc_elsewhere[country])
    244  1.24  christos 	continue
    245  1.24  christos       if (cc_name[country])
    246  1.24  christos 	country = cc_name[country]
    247  1.24  christos       print country
    248  1.24  christos     }
    249  1.22  christos   }
    250  1.22  christos '
    251   1.1       jtc 
    252  1.24  christos # Awk script to process a time zone table and output the same table,
    253  1.22  christos # with each row preceded by its distance from 'here'.
    254  1.22  christos # If output_times is set, each row is instead preceded by its local time
    255  1.22  christos # and any apostrophes are escaped for the shell.
    256  1.22  christos output_distances_or_times='
    257   1.9  christos   BEGIN {
    258  1.24  christos     coord = substr(ARGV[1], 2)
    259  1.24  christos     TZ_COUNTRY_TABLE = substr(ARGV[2], 2)
    260  1.24  christos     TZ_ZONE_TABLE = substr(ARGV[3], 2)
    261  1.24  christos     ARGV[1] = ARGV[2] = ARGV[3] = ""
    262   1.9  christos     FS = "\t"
    263  1.22  christos     if (!output_times) {
    264  1.24  christos       nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
    265  1.24  christos       for (i = 1; i <= nlines; i++) {
    266  1.24  christos 	$0 = line[i]
    267  1.24  christos 	if ($0 ~ /^#/)
    268  1.24  christos 	  continue
    269  1.24  christos 	country[$1] = $2
    270  1.24  christos       }
    271  1.22  christos       country["US"] = "US" # Otherwise the strings get too long.
    272  1.22  christos     }
    273   1.9  christos   }
    274  1.12  christos   function abs(x) {
    275  1.12  christos     return x < 0 ? -x : x;
    276  1.12  christos   }
    277  1.12  christos   function min(x, y) {
    278  1.12  christos     return x < y ? x : y;
    279  1.12  christos   }
    280  1.12  christos   function convert_coord(coord, deg, minute, ilen, sign, sec) {
    281   1.9  christos     if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9][0-9][0-9]([^0-9]|$)/) {
    282   1.9  christos       degminsec = coord
    283   1.9  christos       intdeg = degminsec < 0 ? -int(-degminsec / 10000) : int(degminsec / 10000)
    284   1.9  christos       minsec = degminsec - intdeg * 10000
    285   1.9  christos       intmin = minsec < 0 ? -int(-minsec / 100) : int(minsec / 100)
    286   1.9  christos       sec = minsec - intmin * 100
    287   1.9  christos       deg = (intdeg * 3600 + intmin * 60 + sec) / 3600
    288   1.9  christos     } else if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9]([^0-9]|$)/) {
    289   1.9  christos       degmin = coord
    290   1.9  christos       intdeg = degmin < 0 ? -int(-degmin / 100) : int(degmin / 100)
    291  1.12  christos       minute = degmin - intdeg * 100
    292  1.12  christos       deg = (intdeg * 60 + minute) / 60
    293   1.9  christos     } else
    294   1.9  christos       deg = coord
    295   1.9  christos     return deg * 0.017453292519943296
    296   1.9  christos   }
    297   1.9  christos   function convert_latitude(coord) {
    298   1.9  christos     match(coord, /..*[-+]/)
    299   1.9  christos     return convert_coord(substr(coord, 1, RLENGTH - 1))
    300   1.9  christos   }
    301   1.9  christos   function convert_longitude(coord) {
    302   1.9  christos     match(coord, /..*[-+]/)
    303   1.9  christos     return convert_coord(substr(coord, RLENGTH))
    304   1.9  christos   }
    305   1.9  christos   # Great-circle distance between points with given latitude and longitude.
    306   1.9  christos   # Inputs and output are in radians.  This uses the great-circle special
    307   1.9  christos   # case of the Vicenty formula for distances on ellipsoids.
    308  1.12  christos   function gcdist(lat1, long1, lat2, long2, dlong, x, y, num, denom) {
    309   1.9  christos     dlong = long2 - long1
    310  1.13  christos     x = cos(lat2) * sin(dlong)
    311  1.13  christos     y = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlong)
    312  1.13  christos     num = sqrt(x * x + y * y)
    313  1.13  christos     denom = sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(dlong)
    314   1.9  christos     return atan2(num, denom)
    315   1.9  christos   }
    316  1.12  christos   # Parallel distance between points with given latitude and longitude.
    317  1.12  christos   # This is the product of the longitude difference and the cosine
    318  1.12  christos   # of the latitude of the point that is further from the equator.
    319  1.12  christos   # I.e., it considers longitudes to be further apart if they are
    320  1.12  christos   # nearer the equator.
    321  1.12  christos   function pardist(lat1, long1, lat2, long2) {
    322  1.13  christos     return abs(long1 - long2) * min(cos(lat1), cos(lat2))
    323  1.12  christos   }
    324  1.12  christos   # The distance function is the sum of the great-circle distance and
    325  1.12  christos   # the parallel distance.  It could be weighted.
    326  1.12  christos   function dist(lat1, long1, lat2, long2) {
    327  1.13  christos     return gcdist(lat1, long1, lat2, long2) + pardist(lat1, long1, lat2, long2)
    328  1.12  christos   }
    329   1.9  christos   BEGIN {
    330   1.9  christos     coord_lat = convert_latitude(coord)
    331   1.9  christos     coord_long = convert_longitude(coord)
    332  1.24  christos     nlines = split(TZ_ZONE_TABLE, line, /\n/)
    333  1.24  christos     for (h = 1; h <= nlines; h++) {
    334  1.24  christos       $0 = line[h]
    335  1.24  christos       if ($0 ~ /^#/)
    336  1.24  christos 	continue
    337  1.24  christos       inline[inlines++] = $0
    338  1.24  christos       ncc = split($1, cc, /,/)
    339  1.24  christos       for (i = 1; i <= ncc; i++)
    340  1.24  christos 	cc_used[cc[i]]++
    341  1.22  christos     }
    342  1.24  christos     for (h = 0; h < inlines; h++) {
    343  1.24  christos       $0 = inline[h]
    344  1.24  christos       outline = $1 "\t" $2 "\t" $3
    345  1.24  christos       sep = "\t"
    346  1.24  christos       ncc = split($1, cc, /,/)
    347  1.24  christos       split("", item_seen)
    348  1.24  christos       item_seen[""] = 1
    349  1.24  christos       for (i = 1; i <= ncc; i++) {
    350  1.24  christos 	item = cc_used[cc[i]] <= 1 ? country[cc[i]] : $4
    351  1.24  christos 	if (item_seen[item]++)
    352  1.24  christos 	  continue
    353  1.24  christos 	outline = outline sep item
    354  1.24  christos 	sep = "; "
    355  1.24  christos       }
    356  1.24  christos       if (output_times) {
    357  1.24  christos 	fmt = "TZ='\''%s'\'' date +'\''%d %%Y %%m %%d %%H:%%M %%a %%b\t%s'\''\n"
    358  1.24  christos 	gsub(/'\''/, "&\\\\&&", outline)
    359  1.24  christos 	printf fmt, $3, h, outline
    360  1.24  christos       } else {
    361  1.24  christos 	here_lat = convert_latitude($2)
    362  1.24  christos 	here_long = convert_longitude($2)
    363  1.24  christos 	printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), \
    364  1.24  christos 	  outline
    365  1.24  christos       }
    366  1.11  christos     }
    367   1.9  christos   }
    368   1.9  christos '
    369   1.1       jtc 
    370   1.1       jtc # Begin the main loop.  We come back here if the user wants to retry.
    371   1.1       jtc while
    372   1.1       jtc 
    373  1.24  christos   echo >&2 'Please identify a location' \
    374  1.24  christos     'so that time zone rules can be set correctly.'
    375   1.1       jtc 
    376  1.24  christos   continent=
    377  1.24  christos   country=
    378  1.24  christos   country_result=
    379  1.24  christos   region=
    380  1.24  christos   time=
    381  1.24  christos   TZ_ZONE_TABLE=$TZ_ZONETABTYPE_TABLE
    382  1.24  christos 
    383  1.24  christos   case $coord in
    384  1.24  christos   ?*)
    385  1.24  christos     continent=coord;;
    386  1.24  christos   '')
    387   1.1       jtc 
    388  1.24  christos     # Ask the user for continent or ocean.
    389   1.1       jtc 
    390  1.24  christos     echo >&2 \
    391  1.24  christos       'Please select a continent, ocean, "coord", "TZ", "time", or "now".'
    392   1.1       jtc 
    393  1.24  christos     quoted_continents=$(
    394  1.24  christos       $AWK '
    395  1.24  christos 	function handle_entry(entry) {
    396  1.24  christos 	  entry = substr(entry, 1, index(entry, "/") - 1)
    397  1.24  christos 	  if (entry == "America")
    398  1.24  christos 	    entry = entry "s"
    399  1.24  christos 	  if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/)
    400  1.24  christos 	    entry = entry " Ocean"
    401  1.24  christos 	  printf "'\''%s'\''\n", entry
    402  1.24  christos 	}
    403  1.24  christos 	BEGIN {
    404  1.24  christos 	  TZ_ZONETABTYPE_TABLE = substr(ARGV[1], 2)
    405  1.24  christos 	  ARGV[1] = ""
    406  1.24  christos 	  FS = "\t"
    407  1.24  christos 	  nlines = split(TZ_ZONETABTYPE_TABLE, line, /\n/)
    408  1.24  christos 	  for (i = 1; i <= nlines; i++) {
    409  1.24  christos 	    $0 = line[i]
    410  1.24  christos 	    if ($0 ~ /^[^#]/)
    411  1.24  christos 	      handle_entry($3)
    412  1.24  christos 	    else if ($0 ~ /^#@/) {
    413  1.24  christos 	      ncont = split($2, cont, /,/)
    414  1.24  christos 	      for (ci = 1; ci <= ncont; ci++)
    415  1.24  christos 		handle_entry(cont[ci])
    416  1.24  christos 	    }
    417  1.24  christos 	  }
    418  1.24  christos 	}
    419  1.24  christos       ' ="$TZ_ZONETABTYPE_TABLE" |
    420  1.24  christos       sort -u |
    421  1.24  christos       tr '\n' ' '
    422  1.24  christos       echo ''
    423  1.24  christos     )
    424  1.24  christos 
    425  1.24  christos     eval '
    426  1.24  christos       doselect '"$quoted_continents"' \
    427  1.24  christos 	"coord - I want to use geographical coordinates." \
    428  1.24  christos 	"TZ - I want to specify the timezone using a proleptic TZ string." \
    429  1.24  christos 	"time - I know local time already." \
    430  1.24  christos 	"now - Like \"time\", but configure only for timestamps from now on."
    431  1.24  christos       continent=$select_result
    432  1.24  christos       case $continent in
    433  1.24  christos       Americas) continent=America;;
    434  1.24  christos       *)
    435  1.24  christos 	# Get the first word of $continent.  Path expansion is disabled
    436  1.24  christos 	# so this works even with "*", which should not happen.
    437  1.24  christos 	IFS=" "
    438  1.24  christos 	for continent in $continent ""; do break; done
    439  1.24  christos 	IFS=$newline;;
    440  1.24  christos       esac
    441  1.24  christos       case $zonetabtype,$continent in
    442  1.24  christos       zonenow,*) ;;
    443  1.24  christos       *,now)
    444  1.24  christos 	${TZ_ZONENOW_TABLE:+:} read_file TZ_ZONENOW_TABLE "$TZDIR/zonenow.tab"
    445  1.24  christos 	TZ_ZONE_TABLE=$TZ_ZONENOW_TABLE
    446  1.24  christos       esac
    447  1.24  christos     '
    448  1.24  christos   esac
    449   1.1       jtc 
    450  1.24  christos   case $continent in
    451  1.24  christos   TZ)
    452  1.24  christos     # Ask the user for a proleptic TZ string.  Check that it conforms.
    453  1.24  christos     check_POSIX_TZ_string='
    454  1.24  christos       BEGIN {
    455  1.24  christos 	tz = substr(ARGV[1], 2)
    456  1.24  christos 	ARGV[1] = ""
    457  1.24  christos 	tzname = ("(<[[:alnum:]+-][[:alnum:]+-][[:alnum:]+-]+>" \
    458  1.24  christos 		  "|[[:alpha:]][[:alpha:]][[:alpha:]]+)")
    459  1.24  christos 	sign = "[-+]?"
    460  1.24  christos 	hhmm = "(:[0-5][0-9](:[0-5][0-9])?)?"
    461  1.24  christos 	offset = sign "(2[0-4]|[0-1]?[0-9])" hhmm
    462  1.24  christos 	time = sign "(16[0-7]|(1[0-5]|[0-9]?)[0-9])" hhmm
    463  1.24  christos 	mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]"
    464  1.24  christos 	jdate = ("((J[1-9]|[0-9]|J?[1-9][0-9]" \
    465  1.24  christos 		 "|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])")
    466  1.24  christos 	datetime = ",(" mdate "|" jdate ")(/" time ")?"
    467  1.24  christos 	tzpattern = ("^(:.*|" tzname offset "(" tzname \
    468  1.24  christos 		     "(" offset ")?(" datetime datetime ")?)?)$")
    469  1.24  christos 	exit tz ~ tzpattern
    470  1.24  christos       }
    471  1.24  christos     '
    472  1.24  christos 
    473  1.24  christos     while
    474  1.24  christos       echo >&2 'Please enter the desired value' \
    475  1.24  christos 	'of the TZ environment variable.'
    476  1.24  christos       echo >&2 'For example, AEST-10 is abbreviated' \
    477  1.24  christos 	'AEST and is 10 hours'
    478  1.24  christos       echo >&2 'ahead (east) of Greenwich,' \
    479  1.24  christos 	'with no daylight saving time.'
    480  1.24  christos       read tz
    481  1.24  christos       $AWK "$check_POSIX_TZ_string" ="$tz"
    482  1.24  christos     do
    483  1.24  christos       say >&2 "'$tz' is not a conforming POSIX proleptic TZ string."
    484  1.24  christos     done
    485  1.24  christos     TZ_for_date=$tz;;
    486  1.24  christos   *)
    487  1.24  christos     case $continent in
    488  1.24  christos     coord)
    489  1.24  christos       case $coord in
    490  1.24  christos       '')
    491  1.24  christos 	echo >&2 'Please enter coordinates' \
    492  1.24  christos 	  'in ISO 6709 notation.'
    493  1.24  christos 	echo >&2 'For example, +4042-07403 stands for'
    494  1.24  christos 	echo >&2 '40 degrees 42 minutes north,' \
    495  1.24  christos 	  '74 degrees 3 minutes west.'
    496  1.24  christos 	read coord
    497  1.24  christos       esac
    498  1.24  christos       distance_table=$(
    499  1.24  christos 	$AWK \
    500  1.24  christos 	  "$output_distances_or_times" \
    501  1.24  christos 	  ="$coord" ="$TZ_COUNTRY_TABLE" ="$TZ_ZONE_TABLE" |
    502  1.24  christos 	sort -n |
    503  1.24  christos 	$AWK "{print} NR == $location_limit { exit }"
    504  1.24  christos       )
    505  1.24  christos       regions=$(
    506  1.24  christos 	$AWK '
    507  1.24  christos 	  BEGIN {
    508  1.24  christos 	    distance_table = substr(ARGV[1], 2)
    509  1.24  christos 	    ARGV[1] = ""
    510  1.24  christos 	    nlines = split(distance_table, line, /\n/)
    511  1.24  christos 	    for (nr = 1; nr <= nlines; nr++) {
    512  1.24  christos 	      nf = split(line[nr], f, /\t/)
    513  1.24  christos 	      print f[nf]
    514  1.24  christos 	    }
    515  1.24  christos 	  }
    516  1.24  christos 	' ="$distance_table"
    517  1.24  christos       )
    518  1.24  christos       echo >&2 'Please select one of the following timezones,'
    519  1.24  christos       echo >&2 'listed roughly in increasing order' \
    520  1.24  christos 	"of distance from $coord".
    521  1.24  christos       doselect $regions
    522  1.24  christos       region=$select_result
    523  1.24  christos       tz=$(
    524  1.24  christos 	$AWK '
    525  1.24  christos 	  BEGIN {
    526  1.24  christos 	    distance_table = substr(ARGV[1], 2)
    527  1.24  christos 	    region = substr(ARGV[2], 2)
    528  1.24  christos 	    ARGV[1] = ARGV[2] = ""
    529  1.24  christos 	    nlines = split(distance_table, line, /\n/)
    530  1.24  christos 	    for (nr = 1; nr <= nlines; nr++) {
    531  1.24  christos 	      nf = split(line[nr], f, /\t/)
    532  1.24  christos 	      if (f[nf] == region)
    533  1.24  christos 		print f[4]
    534  1.24  christos 	    }
    535  1.24  christos 	  }
    536  1.24  christos 	' ="$distance_table" ="$region"
    537  1.24  christos       );;
    538  1.24  christos     *)
    539  1.24  christos       case $continent in
    540  1.24  christos       now|time)
    541  1.24  christos 	minute_format='%a %b %d %H:%M'
    542  1.24  christos 	old_minute=$(TZ=UTC0 date +"$minute_format")
    543  1.24  christos 	for i in 1 2 3
    544  1.24  christos 	do
    545  1.24  christos 	  time_table_command=$(
    546  1.24  christos 	    $AWK \
    547  1.24  christos 	      -v output_times=1 \
    548  1.24  christos 	      "$output_distances_or_times" \
    549  1.24  christos 	      = = ="$TZ_ZONE_TABLE"
    550  1.24  christos 	  )
    551  1.24  christos 	  time_table=$(eval "$time_table_command")
    552  1.24  christos 	  new_minute=$(TZ=UTC0 date +"$minute_format")
    553  1.24  christos 	  case $old_minute in
    554  1.24  christos 	  "$new_minute") break
    555  1.24  christos 	  esac
    556  1.24  christos 	  old_minute=$new_minute
    557  1.24  christos 	done
    558  1.24  christos 	echo >&2 "The system says Universal Time is $new_minute."
    559  1.24  christos 	echo >&2 "Assuming that's correct, what is the local time?"
    560  1.24  christos 	sorted_table=$(say "$time_table" | sort -k2n -k2,5 -k1n) || {
    561  1.24  christos 	  say >&2 "$0: cannot sort time table"
    562  1.24  christos 	  exit 1
    563  1.24  christos 	}
    564  1.24  christos 	eval doselect $(
    565  1.24  christos 	  $AWK '
    566  1.24  christos 	    BEGIN {
    567  1.24  christos 	      sorted_table = substr(ARGV[1], 2)
    568  1.24  christos 	      ARGV[1] = ""
    569  1.24  christos 	      nlines = split(sorted_table, line, /\n/)
    570  1.24  christos 	      for (i = 1; i <= nlines; i++) {
    571  1.24  christos 		$0 = line[i]
    572  1.24  christos 		outline = $6 " " $7 " " $4 " " $5
    573  1.24  christos 		if (outline == oldline)
    574  1.24  christos 		  continue
    575  1.24  christos 		oldline = outline
    576  1.24  christos 		gsub(/'\''/, "&\\\\&&", outline)
    577  1.24  christos 		printf "'\''%s'\''\n", outline
    578  1.24  christos 	      }
    579  1.24  christos 	    }
    580  1.24  christos 	  ' ="$sorted_table"
    581  1.24  christos 	)
    582  1.24  christos 	time=$select_result
    583  1.24  christos 	continent_re='^'
    584  1.24  christos 	zone_table=$(
    585  1.10  christos 	  $AWK '
    586  1.24  christos 	    BEGIN {
    587  1.24  christos 	      time = substr(ARGV[1], 2)
    588  1.24  christos 	      time_table = substr(ARGV[2], 2)
    589  1.24  christos 	      ARGV[1] = ARGV[2] = ""
    590  1.24  christos 	      nlines = split(time_table, line, /\n/)
    591  1.24  christos 	      for (i = 1; i <= nlines; i++) {
    592  1.24  christos 		$0 = line[i]
    593  1.24  christos 		if ($6 " " $7 " " $4 " " $5 == time) {
    594  1.24  christos 		  sub(/[^\t]*\t/, "")
    595  1.24  christos 		  print
    596  1.24  christos 		}
    597  1.24  christos 	      }
    598  1.20  christos 	    }
    599  1.24  christos 	  ' ="$time" ="$time_table"
    600  1.24  christos 	)
    601  1.24  christos 	countries=$(
    602  1.24  christos 	  $AWK \
    603  1.24  christos 	    "$output_country_list" \
    604  1.24  christos 	    ="$continent_re" ="$TZ_COUNTRY_TABLE" ="$zone_table" |
    605  1.24  christos 	  sort -f
    606  1.24  christos 	)
    607  1.24  christos 	;;
    608  1.24  christos       *)
    609  1.24  christos 	continent_re="^$continent/"
    610  1.24  christos 	zone_table=$TZ_ZONE_TABLE
    611  1.24  christos       esac
    612  1.24  christos 
    613  1.24  christos       # Get list of names of countries in the continent or ocean.
    614  1.24  christos       countries=$(
    615  1.24  christos 	$AWK \
    616  1.24  christos 	  "$output_country_list" \
    617  1.24  christos 	  ="$continent_re" ="$TZ_COUNTRY_TABLE" ="$zone_table" |
    618  1.24  christos 	sort -f
    619  1.24  christos       )
    620  1.24  christos       # If all zone table entries have comments, and there are
    621  1.24  christos       # at most 22 entries, asked based on those comments.
    622  1.24  christos       # This fits the prompt onto old-fashioned 24-line screens.
    623  1.24  christos       regions=$(
    624  1.24  christos 	$AWK '
    625  1.24  christos 	  BEGIN {
    626  1.24  christos 	    TZ_ZONE_TABLE = substr(ARGV[1], 2)
    627  1.24  christos 	    ARGV[1] = ""
    628  1.24  christos 	    FS = "\t"
    629  1.24  christos 	    nlines = split(TZ_ZONE_TABLE, line, /\n/)
    630  1.24  christos 	    for (i = 1; i <= nlines; i++) {
    631  1.24  christos 	      $0 = line[i]
    632  1.24  christos 	      if ($0 ~ /^[^#]/ && !missing_comment) {
    633  1.24  christos 		if ($4)
    634  1.24  christos 		  comment[++inlines] = $4
    635  1.24  christos 		else
    636  1.24  christos 		  missing_comment = 1
    637  1.20  christos 	      }
    638  1.20  christos 	    }
    639  1.24  christos 	    if (!missing_comment && inlines <= 22)
    640  1.24  christos 	      for (i = 1; i <= inlines; i++)
    641  1.24  christos 		print comment[i]
    642  1.24  christos 	  }
    643  1.24  christos 	' ="$zone_table"
    644  1.24  christos       )
    645  1.24  christos 
    646  1.24  christos       # If there's more than one country, ask the user which one.
    647  1.24  christos       case $countries in
    648  1.24  christos       *"$newline"*)
    649  1.24  christos 	echo >&2 'Please select a country' \
    650  1.24  christos 	  'whose clocks agree with yours.'
    651  1.24  christos 	doselect $countries
    652  1.24  christos 	country_result=$select_result
    653  1.24  christos 	country=$select_result;;
    654  1.24  christos       *)
    655  1.24  christos 	country=$countries
    656  1.24  christos       esac
    657   1.9  christos 
    658   1.1       jtc 
    659  1.24  christos       # Get list of timezones in the country.
    660  1.24  christos       regions=$(
    661  1.24  christos 	$AWK '
    662  1.24  christos 	  BEGIN {
    663  1.24  christos 	    country = substr(ARGV[1], 2)
    664  1.24  christos 	    TZ_COUNTRY_TABLE = substr(ARGV[2], 2)
    665  1.24  christos 	    TZ_ZONE_TABLE = substr(ARGV[3], 2)
    666  1.24  christos 	    ARGV[1] = ARGV[2] = ARGV[3] = ""
    667  1.24  christos 	    FS = "\t"
    668  1.24  christos 	    cc = country
    669  1.24  christos 	    nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
    670  1.24  christos 	    for (i = 1; i <= nlines; i++) {
    671  1.24  christos 	      $0 = line[i]
    672  1.24  christos 	      if ($0 !~ /^#/  &&  country == $2) {
    673  1.24  christos 		cc = $1
    674  1.24  christos 		break
    675  1.24  christos 	      }
    676  1.24  christos 	    }
    677  1.24  christos 	    nlines = split(TZ_ZONE_TABLE, line, /\n/)
    678  1.24  christos 	    for (i = 1; i <= nlines; i++) {
    679  1.24  christos 	      $0 = line[i]
    680  1.24  christos 	      if ($0 ~ /^#/)
    681  1.24  christos 		continue
    682  1.24  christos 	      if ($1 ~ cc)
    683  1.24  christos 		print $4
    684  1.24  christos 	    }
    685  1.24  christos 	  }
    686  1.24  christos 	' ="$country" ="$TZ_COUNTRY_TABLE" ="$zone_table"
    687  1.24  christos       )
    688   1.1       jtc 
    689  1.24  christos       # If there's more than one region, ask the user which one.
    690  1.24  christos       case $regions in
    691  1.24  christos       *"$newline"*)
    692  1.24  christos 	echo >&2 'Please select one of the following timezones.'
    693  1.24  christos 	doselect $regions
    694  1.24  christos 	region=$select_result
    695  1.24  christos       esac
    696   1.1       jtc 
    697  1.24  christos       # Determine tz from country and region.
    698  1.24  christos       tz=$(
    699  1.24  christos 	$AWK '
    700  1.24  christos 	  BEGIN {
    701  1.24  christos 	    country = substr(ARGV[1], 2)
    702  1.24  christos 	    region = substr(ARGV[2], 2)
    703  1.24  christos 	    TZ_COUNTRY_TABLE = substr(ARGV[3], 2)
    704  1.24  christos 	    TZ_ZONE_TABLE = substr(ARGV[4], 2)
    705  1.24  christos 	    ARGV[1] = ARGV[2] = ARGV[3] = ARGV[4] = ""
    706  1.24  christos 	    FS = "\t"
    707  1.24  christos 	    cc = country
    708  1.24  christos 	    nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
    709  1.24  christos 	    for (i = 1; i <= nlines; i++) {
    710  1.24  christos 	      $0 = line[i]
    711  1.24  christos 	      if ($0 !~ /^#/  &&  country == $2) {
    712  1.24  christos 		cc = $1
    713  1.24  christos 		break
    714  1.24  christos 	      }
    715  1.24  christos 	    }
    716  1.24  christos 	    nlines = split(TZ_ZONE_TABLE, line, /\n/)
    717  1.24  christos 	    for (i = 1; i <= nlines; i++) {
    718  1.24  christos 	      $0 = line[i]
    719  1.24  christos 	      if ($0 ~ /^#/)
    720  1.24  christos 		continue
    721  1.24  christos 	      if ($1 ~ cc && ($4 == region || !region))
    722  1.24  christos 		print $3
    723  1.24  christos 	    }
    724  1.24  christos 	  }
    725  1.24  christos 	' ="$country" ="$region" ="$TZ_COUNTRY_TABLE" ="$zone_table"
    726  1.24  christos       )
    727  1.24  christos     esac
    728   1.1       jtc 
    729  1.24  christos     # Make sure the corresponding zoneinfo file exists.
    730  1.24  christos     TZ_for_date=$TZDIR/$tz
    731  1.24  christos     <"$TZ_for_date" || {
    732  1.24  christos       say >&2 "$0: time zone files are not set up correctly"
    733  1.24  christos       exit 1
    734  1.24  christos     }
    735  1.24  christos   esac
    736   1.1       jtc 
    737   1.1       jtc 
    738  1.24  christos   # Use the proposed TZ to output the current date relative to UTC.
    739  1.24  christos   # Loop until they agree in seconds.
    740  1.24  christos   # Give up after 8 unsuccessful tries.
    741  1.24  christos 
    742  1.24  christos   extra_info=
    743  1.24  christos   for i in 1 2 3 4 5 6 7 8
    744  1.24  christos   do
    745  1.24  christos     TZdate=$(LANG=C TZ="$TZ_for_date" date)
    746  1.24  christos     UTdate=$(LANG=C TZ=UTC0 date)
    747  1.24  christos     TZsecsetc=${TZdate##*[0-5][0-9]:}
    748  1.24  christos     UTsecsetc=${UTdate##*[0-5][0-9]:}
    749  1.24  christos     if test "${TZsecsetc%%[!0-9]*}" = "${UTsecsetc%%[!0-9]*}"
    750  1.24  christos     then
    751  1.24  christos       extra_info="
    752  1.24  christos Selected time is now:	$TZdate.
    753  1.24  christos Universal Time is now:	$UTdate."
    754  1.24  christos       break
    755  1.24  christos     fi
    756  1.24  christos   done
    757  1.24  christos 
    758  1.24  christos 
    759  1.24  christos   # Output TZ info and ask the user to confirm.
    760  1.24  christos 
    761  1.24  christos   echo >&2 ""
    762  1.24  christos   echo >&2 "Based on the following information:"
    763  1.24  christos   echo >&2 ""
    764  1.24  christos   case $time%$country_result%$region%$coord in
    765  1.24  christos   ?*%?*%?*%)
    766  1.24  christos     say >&2 "	$time$newline	$country_result$newline	$region";;
    767  1.24  christos   ?*%?*%%|?*%%?*%) say >&2 "	$time$newline	$country_result$region";;
    768  1.24  christos   ?*%%%)	say >&2 "	$time";;
    769  1.24  christos   %?*%?*%)	say >&2 "	$country_result$newline	$region";;
    770  1.24  christos   %?*%%)	say >&2 "	$country_result";;
    771  1.24  christos   %%?*%?*)	say >&2 "	coord $coord$newline	$region";;
    772  1.24  christos   %%%?*)	say >&2 "	coord $coord";;
    773  1.24  christos   *)		say >&2 "	TZ='$tz'"
    774  1.24  christos   esac
    775  1.24  christos   say >&2 ""
    776  1.24  christos   say >&2 "TZ='$tz' will be used.$extra_info"
    777  1.24  christos   say >&2 "Is the above information OK?"
    778  1.24  christos 
    779  1.24  christos   doselect Yes No
    780  1.24  christos   ok=$select_result
    781  1.24  christos   case $ok in
    782  1.24  christos   Yes) break
    783  1.24  christos   esac
    784   1.9  christos do coord=
    785   1.1       jtc done
    786   1.1       jtc 
    787   1.5    kleink case $SHELL in
    788  1.24  christos *csh) file=.login line="setenv TZ '$tz'";;
    789  1.24  christos *)    file=.profile line="export TZ='$tz'"
    790   1.5    kleink esac
    791   1.5    kleink 
    792  1.15  christos test -t 1 && say >&2 "
    793   1.5    kleink You can make this change permanent for yourself by appending the line
    794   1.5    kleink 	$line
    795   1.5    kleink to the file '$file' in your home directory; then log out and log in again.
    796   1.5    kleink 
    797   1.5    kleink Here is that TZ value again, this time on standard output so that you
    798   1.5    kleink can use the $0 command in shell scripts:"
    799   1.5    kleink 
    800  1.24  christos say "$tz"
    801