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