tzselect 21 KB

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