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