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