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 5PKGVERSION='(tzcode) ' 6TZVERSION=see_Makefile 7REPORT_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. 44set -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 '-'. 53say() { 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 64coord= 65location_limit=10 66zonetabtype=zone1970 67 68usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT] 69Select a timezone interactively. 70 71Options: 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 90Report 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. 96if 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 103then 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 ' 117else 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 } 151fi 152 153while getopts c:n:t:-: opt 154do 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 171done 172 173shift `$AWK "BEGIN { print $OPTIND - 1 }"` 174case $# in 1750) ;; 176*) say >&2 "$0: $1: unknown argument"; exit 1 177esac 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. 182if $AWK 'BEGIN { u12345 = "\360\222\215\205"; exit length(u12345) == 1 }'; then 183 translit=true 184else 185 translit=false 186fi 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'. 193read_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} 203read_file TZ_COUNTRY_TABLE "$TZDIR/iso3166.tab" 204read_file TZ_ZONETABTYPE_TABLE "$TZDIR/$zonetabtype.tab" 205TZ_ZONENOW_TABLE= 206 207newline=' 208' 209IFS=$newline 210 211# Awk script to output a country list. 212output_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. 269output_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. 384while 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=" 767Selected time is now: $TZdate. 768Universal 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 799do coord= 800done 801 802case $SHELL in 803*csh) file=.login line="setenv TZ '$tz'";; 804*) file=.profile line="TZ='$tz'; export TZ" 805esac 806 807test -t 1 && say >&2 " 808You can make this change permanent for yourself by appending the line 809 $line 810to the file '$file' in your home directory; then log out and log in again. 811 812Here is that TZ value again, this time on standard output so that you 813can use the $0 command in shell scripts:" 814 815say "$tz" 816