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