tzselect.ksh revision 1.21 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.21 christos # $NetBSD: tzselect.ksh,v 1.21 2022/10/15 18:57:37 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.1 jtc # Specify default values for environment variables if they are unset.
42 1.1 jtc : ${AWK=awk}
43 1.10 christos : ${TZDIR=`pwd`}
44 1.1 jtc
45 1.14 christos # Output one argument as-is to standard output.
46 1.14 christos # Safer than 'echo', which can mishandle '\' or leading '-'.
47 1.14 christos say() {
48 1.14 christos printf '%s\n' "$1"
49 1.14 christos }
50 1.14 christos
51 1.1 jtc # Check for awk Posix compliance.
52 1.1 jtc ($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1
53 1.1 jtc [ $? = 123 ] || {
54 1.14 christos say >&2 "$0: Sorry, your '$AWK' program is not Posix compatible."
55 1.1 jtc exit 1
56 1.1 jtc }
57 1.1 jtc
58 1.9 christos coord=
59 1.9 christos location_limit=10
60 1.11 christos zonetabtype=zone1970
61 1.9 christos
62 1.9 christos usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT]
63 1.18 christos Select a timezone interactively.
64 1.6 mlelstv
65 1.9 christos Options:
66 1.9 christos
67 1.9 christos -c COORD
68 1.9 christos Instead of asking for continent and then country and then city,
69 1.9 christos ask for selection from time zones whose largest cities
70 1.9 christos are closest to the location with geographical coordinates COORD.
71 1.9 christos COORD should use ISO 6709 notation, for example, '-c +4852+00220'
72 1.9 christos for Paris (in degrees and minutes, North and East), or
73 1.9 christos '-c -35-058' for Buenos Aires (in degrees, South and West).
74 1.9 christos
75 1.9 christos -n LIMIT
76 1.9 christos Display at most LIMIT locations when -c is used (default $location_limit).
77 1.9 christos
78 1.9 christos --version
79 1.9 christos Output version information.
80 1.9 christos
81 1.9 christos --help
82 1.9 christos Output this help.
83 1.9 christos
84 1.9 christos Report bugs to $REPORT_BUGS_TO."
85 1.9 christos
86 1.10 christos # Ask the user to select from the function's arguments,
87 1.10 christos # and assign the selected argument to the variable 'select_result'.
88 1.10 christos # Exit on EOF or I/O error. Use the shell's 'select' builtin if available,
89 1.10 christos # falling back on a less-nice but portable substitute otherwise.
90 1.10 christos if
91 1.10 christos case $BASH_VERSION in
92 1.10 christos ?*) : ;;
93 1.10 christos '')
94 1.10 christos # '; exit' should be redundant, but Dash doesn't properly fail without it.
95 1.11 christos (eval 'set --; select x; do break; done; exit') </dev/null 2>/dev/null
96 1.10 christos esac
97 1.10 christos then
98 1.10 christos # Do this inside 'eval', as otherwise the shell might exit when parsing it
99 1.10 christos # even though it is never executed.
100 1.10 christos eval '
101 1.10 christos doselect() {
102 1.10 christos select select_result
103 1.10 christos do
104 1.10 christos case $select_result in
105 1.10 christos "") echo >&2 "Please enter a number in range." ;;
106 1.10 christos ?*) break
107 1.10 christos esac
108 1.10 christos done || exit
109 1.10 christos }
110 1.10 christos '
111 1.10 christos else
112 1.10 christos doselect() {
113 1.10 christos # Field width of the prompt numbers.
114 1.10 christos select_width=`expr $# : '.*'`
115 1.10 christos
116 1.10 christos select_i=
117 1.10 christos
118 1.10 christos while :
119 1.10 christos do
120 1.10 christos case $select_i in
121 1.10 christos '')
122 1.10 christos select_i=0
123 1.10 christos for select_word
124 1.10 christos do
125 1.10 christos select_i=`expr $select_i + 1`
126 1.10 christos printf >&2 "%${select_width}d) %s\\n" $select_i "$select_word"
127 1.10 christos done ;;
128 1.10 christos *[!0-9]*)
129 1.10 christos echo >&2 'Please enter a number in range.' ;;
130 1.10 christos *)
131 1.10 christos if test 1 -le $select_i && test $select_i -le $#; then
132 1.10 christos shift `expr $select_i - 1`
133 1.10 christos select_result=$1
134 1.10 christos break
135 1.10 christos fi
136 1.10 christos echo >&2 'Please enter a number in range.'
137 1.10 christos esac
138 1.10 christos
139 1.10 christos # Prompt and read input.
140 1.10 christos printf >&2 %s "${PS3-#? }"
141 1.10 christos read select_i || exit
142 1.10 christos done
143 1.10 christos }
144 1.10 christos fi
145 1.10 christos
146 1.12 christos while getopts c:n:t:-: opt
147 1.9 christos do
148 1.9 christos case $opt$OPTARG in
149 1.9 christos c*)
150 1.9 christos coord=$OPTARG ;;
151 1.9 christos n*)
152 1.9 christos location_limit=$OPTARG ;;
153 1.11 christos t*) # Undocumented option, used for developer testing.
154 1.11 christos zonetabtype=$OPTARG ;;
155 1.9 christos -help)
156 1.9 christos exec echo "$usage" ;;
157 1.9 christos -version)
158 1.9 christos exec echo "tzselect $PKGVERSION$TZVERSION" ;;
159 1.9 christos -*)
160 1.14 christos say >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1 ;;
161 1.9 christos *)
162 1.14 christos say >&2 "$0: try '$0 --help'"; exit 1 ;;
163 1.9 christos esac
164 1.9 christos done
165 1.9 christos
166 1.10 christos shift `expr $OPTIND - 1`
167 1.9 christos case $# in
168 1.9 christos 0) ;;
169 1.14 christos *) say >&2 "$0: $1: unknown argument"; exit 1 ;;
170 1.9 christos esac
171 1.6 mlelstv
172 1.1 jtc # Make sure the tables are readable.
173 1.1 jtc TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab
174 1.11 christos TZ_ZONE_TABLE=$TZDIR/$zonetabtype.tab
175 1.1 jtc for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
176 1.1 jtc do
177 1.11 christos <"$f" || {
178 1.14 christos say >&2 "$0: time zone files are not set up correctly"
179 1.1 jtc exit 1
180 1.1 jtc }
181 1.1 jtc done
182 1.1 jtc
183 1.14 christos # If the current locale does not support UTF-8, convert data to current
184 1.14 christos # locale's format if possible, as the shell aligns columns better that way.
185 1.14 christos # Check the UTF-8 of U+12345 CUNEIFORM SIGN URU TIMES KI.
186 1.19 christos $AWK 'BEGIN { u12345 = "\360\222\215\205"; exit length(u12345) != 1 }' || {
187 1.14 christos { tmp=`(mktemp -d) 2>/dev/null` || {
188 1.14 christos tmp=${TMPDIR-/tmp}/tzselect.$$ &&
189 1.14 christos (umask 77 && mkdir -- "$tmp")
190 1.14 christos };} &&
191 1.14 christos trap 'status=$?; rm -fr -- "$tmp"; exit $status' 0 HUP INT PIPE TERM &&
192 1.14 christos (iconv -f UTF-8 -t //TRANSLIT <"$TZ_COUNTRY_TABLE" >$tmp/iso3166.tab) \
193 1.14 christos 2>/dev/null &&
194 1.14 christos TZ_COUNTRY_TABLE=$tmp/iso3166.tab &&
195 1.14 christos iconv -f UTF-8 -t //TRANSLIT <"$TZ_ZONE_TABLE" >$tmp/$zonetabtype.tab &&
196 1.14 christos TZ_ZONE_TABLE=$tmp/$zonetabtype.tab
197 1.19 christos }
198 1.14 christos
199 1.1 jtc newline='
200 1.1 jtc '
201 1.1 jtc IFS=$newline
202 1.1 jtc
203 1.1 jtc
204 1.9 christos # Awk script to read a time zone table and output the same table,
205 1.9 christos # with each column preceded by its distance from 'here'.
206 1.9 christos output_distances='
207 1.9 christos BEGIN {
208 1.9 christos FS = "\t"
209 1.9 christos while (getline <TZ_COUNTRY_TABLE)
210 1.9 christos if ($0 ~ /^[^#]/)
211 1.9 christos country[$1] = $2
212 1.9 christos country["US"] = "US" # Otherwise the strings get too long.
213 1.9 christos }
214 1.12 christos function abs(x) {
215 1.12 christos return x < 0 ? -x : x;
216 1.12 christos }
217 1.12 christos function min(x, y) {
218 1.12 christos return x < y ? x : y;
219 1.12 christos }
220 1.12 christos function convert_coord(coord, deg, minute, ilen, sign, sec) {
221 1.9 christos if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9][0-9][0-9]([^0-9]|$)/) {
222 1.9 christos degminsec = coord
223 1.9 christos intdeg = degminsec < 0 ? -int(-degminsec / 10000) : int(degminsec / 10000)
224 1.9 christos minsec = degminsec - intdeg * 10000
225 1.9 christos intmin = minsec < 0 ? -int(-minsec / 100) : int(minsec / 100)
226 1.9 christos sec = minsec - intmin * 100
227 1.9 christos deg = (intdeg * 3600 + intmin * 60 + sec) / 3600
228 1.9 christos } else if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9]([^0-9]|$)/) {
229 1.9 christos degmin = coord
230 1.9 christos intdeg = degmin < 0 ? -int(-degmin / 100) : int(degmin / 100)
231 1.12 christos minute = degmin - intdeg * 100
232 1.12 christos deg = (intdeg * 60 + minute) / 60
233 1.9 christos } else
234 1.9 christos deg = coord
235 1.9 christos return deg * 0.017453292519943296
236 1.9 christos }
237 1.9 christos function convert_latitude(coord) {
238 1.9 christos match(coord, /..*[-+]/)
239 1.9 christos return convert_coord(substr(coord, 1, RLENGTH - 1))
240 1.9 christos }
241 1.9 christos function convert_longitude(coord) {
242 1.9 christos match(coord, /..*[-+]/)
243 1.9 christos return convert_coord(substr(coord, RLENGTH))
244 1.9 christos }
245 1.9 christos # Great-circle distance between points with given latitude and longitude.
246 1.9 christos # Inputs and output are in radians. This uses the great-circle special
247 1.9 christos # case of the Vicenty formula for distances on ellipsoids.
248 1.12 christos function gcdist(lat1, long1, lat2, long2, dlong, x, y, num, denom) {
249 1.9 christos dlong = long2 - long1
250 1.13 christos x = cos(lat2) * sin(dlong)
251 1.13 christos y = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlong)
252 1.13 christos num = sqrt(x * x + y * y)
253 1.13 christos denom = sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(dlong)
254 1.9 christos return atan2(num, denom)
255 1.9 christos }
256 1.12 christos # Parallel distance between points with given latitude and longitude.
257 1.12 christos # This is the product of the longitude difference and the cosine
258 1.12 christos # of the latitude of the point that is further from the equator.
259 1.12 christos # I.e., it considers longitudes to be further apart if they are
260 1.12 christos # nearer the equator.
261 1.12 christos function pardist(lat1, long1, lat2, long2) {
262 1.13 christos return abs(long1 - long2) * min(cos(lat1), cos(lat2))
263 1.12 christos }
264 1.12 christos # The distance function is the sum of the great-circle distance and
265 1.12 christos # the parallel distance. It could be weighted.
266 1.12 christos function dist(lat1, long1, lat2, long2) {
267 1.13 christos return gcdist(lat1, long1, lat2, long2) + pardist(lat1, long1, lat2, long2)
268 1.12 christos }
269 1.9 christos BEGIN {
270 1.9 christos coord_lat = convert_latitude(coord)
271 1.9 christos coord_long = convert_longitude(coord)
272 1.9 christos }
273 1.9 christos /^[^#]/ {
274 1.9 christos here_lat = convert_latitude($2)
275 1.9 christos here_long = convert_longitude($2)
276 1.11 christos line = $1 "\t" $2 "\t" $3
277 1.11 christos sep = "\t"
278 1.11 christos ncc = split($1, cc, /,/)
279 1.11 christos for (i = 1; i <= ncc; i++) {
280 1.11 christos line = line sep country[cc[i]]
281 1.11 christos sep = ", "
282 1.11 christos }
283 1.9 christos if (NF == 4)
284 1.9 christos line = line " - " $4
285 1.9 christos printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), line
286 1.9 christos }
287 1.9 christos '
288 1.1 jtc
289 1.1 jtc # Begin the main loop. We come back here if the user wants to retry.
290 1.1 jtc while
291 1.1 jtc
292 1.1 jtc echo >&2 'Please identify a location' \
293 1.1 jtc 'so that time zone rules can be set correctly.'
294 1.1 jtc
295 1.1 jtc continent=
296 1.1 jtc country=
297 1.1 jtc region=
298 1.1 jtc
299 1.9 christos case $coord in
300 1.9 christos ?*)
301 1.9 christos continent=coord;;
302 1.9 christos '')
303 1.1 jtc
304 1.1 jtc # Ask the user for continent or ocean.
305 1.1 jtc
306 1.9 christos echo >&2 'Please select a continent, ocean, "coord", or "TZ".'
307 1.1 jtc
308 1.10 christos quoted_continents=`
309 1.10 christos $AWK '
310 1.20 christos function handle_entry(entry) {
311 1.20 christos entry = substr(entry, 1, index(entry, "/") - 1)
312 1.20 christos if (entry == "America")
313 1.20 christos entry = entry "s"
314 1.20 christos if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/)
315 1.20 christos entry = entry " Ocean"
316 1.20 christos printf "'\''%s'\''\n", entry
317 1.20 christos }
318 1.10 christos BEGIN { FS = "\t" }
319 1.9 christos /^[^#]/ {
320 1.20 christos handle_entry($3)
321 1.9 christos }
322 1.20 christos /^#@/ {
323 1.20 christos ncont = split($2, cont, /,/)
324 1.20 christos for (ci = 1; ci <= ncont; ci++) {
325 1.20 christos handle_entry(cont[ci])
326 1.20 christos }
327 1.20 christos }
328 1.11 christos ' <"$TZ_ZONE_TABLE" |
329 1.9 christos sort -u |
330 1.9 christos tr '\n' ' '
331 1.9 christos echo ''
332 1.10 christos `
333 1.9 christos
334 1.9 christos eval '
335 1.10 christos doselect '"$quoted_continents"' \
336 1.9 christos "coord - I want to use geographical coordinates." \
337 1.18 christos "TZ - I want to specify the timezone using the Posix TZ format."
338 1.10 christos continent=$select_result
339 1.10 christos case $continent in
340 1.10 christos Americas) continent=America;;
341 1.10 christos *" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''`
342 1.10 christos esac
343 1.9 christos '
344 1.9 christos esac
345 1.9 christos
346 1.1 jtc case $continent in
347 1.9 christos TZ)
348 1.1 jtc # Ask the user for a Posix TZ string. Check that it conforms.
349 1.1 jtc while
350 1.1 jtc echo >&2 'Please enter the desired value' \
351 1.1 jtc 'of the TZ environment variable.'
352 1.18 christos echo >&2 'For example, AEST-10 is abbreviated' \
353 1.18 christos 'AEST and is 10 hours'
354 1.17 christos echo >&2 'ahead (east) of Greenwich,' \
355 1.17 christos 'with no daylight saving time.'
356 1.1 jtc read TZ
357 1.1 jtc $AWK -v TZ="$TZ" 'BEGIN {
358 1.15 christos tzname = "(<[[:alnum:]+-]{3,}>|[[:alpha:]]{3,})"
359 1.15 christos time = "(2[0-4]|[0-1]?[0-9])" \
360 1.15 christos "(:[0-5][0-9](:[0-5][0-9])?)?"
361 1.1 jtc offset = "[-+]?" time
362 1.15 christos mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]"
363 1.15 christos jdate = "((J[1-9]|[0-9]|J?[1-9][0-9]" \
364 1.15 christos "|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])"
365 1.15 christos datetime = ",(" mdate "|" jdate ")(/" time ")?"
366 1.1 jtc tzpattern = "^(:.*|" tzname offset "(" tzname \
367 1.1 jtc "(" offset ")?(" datetime datetime ")?)?)$"
368 1.1 jtc if (TZ ~ tzpattern) exit 1
369 1.1 jtc exit 0
370 1.1 jtc }'
371 1.1 jtc do
372 1.18 christos say >&2 "'$TZ' is not a conforming Posix timezone string."
373 1.1 jtc done
374 1.1 jtc TZ_for_date=$TZ;;
375 1.1 jtc *)
376 1.9 christos case $continent in
377 1.9 christos coord)
378 1.9 christos case $coord in
379 1.9 christos '')
380 1.9 christos echo >&2 'Please enter coordinates' \
381 1.9 christos 'in ISO 6709 notation.'
382 1.9 christos echo >&2 'For example, +4042-07403 stands for'
383 1.9 christos echo >&2 '40 degrees 42 minutes north,' \
384 1.9 christos '74 degrees 3 minutes west.'
385 1.9 christos read coord;;
386 1.9 christos esac
387 1.10 christos distance_table=`$AWK \
388 1.9 christos -v coord="$coord" \
389 1.9 christos -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
390 1.11 christos "$output_distances" <"$TZ_ZONE_TABLE" |
391 1.9 christos sort -n |
392 1.9 christos sed "${location_limit}q"
393 1.10 christos `
394 1.14 christos regions=`say "$distance_table" | $AWK '
395 1.9 christos BEGIN { FS = "\t" }
396 1.9 christos { print $NF }
397 1.10 christos '`
398 1.18 christos echo >&2 'Please select one of the following timezones,' \
399 1.9 christos echo >&2 'listed roughly in increasing order' \
400 1.9 christos "of distance from $coord".
401 1.10 christos doselect $regions
402 1.10 christos region=$select_result
403 1.14 christos TZ=`say "$distance_table" | $AWK -v region="$region" '
404 1.9 christos BEGIN { FS="\t" }
405 1.9 christos $NF == region { print $4 }
406 1.10 christos '`
407 1.9 christos ;;
408 1.9 christos *)
409 1.1 jtc # Get list of names of countries in the continent or ocean.
410 1.10 christos countries=`$AWK \
411 1.20 christos -v continent_re="^$continent/" \
412 1.1 jtc -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
413 1.1 jtc '
414 1.10 christos BEGIN { FS = "\t" }
415 1.20 christos /^#$/ { next }
416 1.20 christos /^#[^@]/ { next }
417 1.20 christos {
418 1.20 christos commentary = $0 ~ /^#@/
419 1.20 christos if (commentary) {
420 1.20 christos col1ccs = substr($1, 3)
421 1.20 christos conts = $2
422 1.20 christos } else {
423 1.20 christos col1ccs = $1
424 1.20 christos conts = $3
425 1.20 christos }
426 1.20 christos ncc = split(col1ccs, cc, /,/)
427 1.20 christos ncont = split(conts, cont, /,/)
428 1.20 christos for (i = 1; i <= ncc; i++) {
429 1.20 christos elsewhere = commentary
430 1.20 christos for (ci = 1; ci <= ncont; ci++) {
431 1.20 christos if (cont[ci] ~ continent_re) {
432 1.11 christos if (!cc_seen[cc[i]]++) cc_list[++ccs] = cc[i]
433 1.20 christos elsewhere = 0
434 1.20 christos }
435 1.20 christos }
436 1.20 christos if (elsewhere) {
437 1.20 christos for (i = 1; i <= ncc; i++) {
438 1.20 christos cc_elsewhere[cc[i]] = 1
439 1.20 christos }
440 1.20 christos }
441 1.20 christos }
442 1.1 jtc }
443 1.1 jtc END {
444 1.1 jtc while (getline <TZ_COUNTRY_TABLE) {
445 1.1 jtc if ($0 !~ /^#/) cc_name[$1] = $2
446 1.1 jtc }
447 1.1 jtc for (i = 1; i <= ccs; i++) {
448 1.1 jtc country = cc_list[i]
449 1.20 christos if (cc_elsewhere[country]) continue
450 1.1 jtc if (cc_name[country]) {
451 1.1 jtc country = cc_name[country]
452 1.1 jtc }
453 1.1 jtc print country
454 1.1 jtc }
455 1.1 jtc }
456 1.11 christos ' <"$TZ_ZONE_TABLE" | sort -f`
457 1.1 jtc
458 1.1 jtc
459 1.1 jtc # If there's more than one country, ask the user which one.
460 1.1 jtc case $countries in
461 1.1 jtc *"$newline"*)
462 1.9 christos echo >&2 'Please select a country' \
463 1.9 christos 'whose clocks agree with yours.'
464 1.10 christos doselect $countries
465 1.10 christos country=$select_result;;
466 1.1 jtc *)
467 1.1 jtc country=$countries
468 1.1 jtc esac
469 1.1 jtc
470 1.1 jtc
471 1.18 christos # Get list of timezones in the country.
472 1.10 christos regions=`$AWK \
473 1.1 jtc -v country="$country" \
474 1.1 jtc -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
475 1.1 jtc '
476 1.1 jtc BEGIN {
477 1.10 christos FS = "\t"
478 1.1 jtc cc = country
479 1.1 jtc while (getline <TZ_COUNTRY_TABLE) {
480 1.1 jtc if ($0 !~ /^#/ && country == $2) {
481 1.1 jtc cc = $1
482 1.1 jtc break
483 1.1 jtc }
484 1.1 jtc }
485 1.1 jtc }
486 1.14 christos /^#/ { next }
487 1.11 christos $1 ~ cc { print $4 }
488 1.11 christos ' <"$TZ_ZONE_TABLE"`
489 1.1 jtc
490 1.1 jtc
491 1.1 jtc # If there's more than one region, ask the user which one.
492 1.1 jtc case $regions in
493 1.1 jtc *"$newline"*)
494 1.18 christos echo >&2 'Please select one of the following timezones.'
495 1.10 christos doselect $regions
496 1.10 christos region=$select_result;;
497 1.1 jtc *)
498 1.1 jtc region=$regions
499 1.1 jtc esac
500 1.1 jtc
501 1.1 jtc # Determine TZ from country and region.
502 1.10 christos TZ=`$AWK \
503 1.1 jtc -v country="$country" \
504 1.1 jtc -v region="$region" \
505 1.1 jtc -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
506 1.1 jtc '
507 1.1 jtc BEGIN {
508 1.10 christos FS = "\t"
509 1.1 jtc cc = country
510 1.1 jtc while (getline <TZ_COUNTRY_TABLE) {
511 1.1 jtc if ($0 !~ /^#/ && country == $2) {
512 1.1 jtc cc = $1
513 1.1 jtc break
514 1.1 jtc }
515 1.1 jtc }
516 1.1 jtc }
517 1.14 christos /^#/ { next }
518 1.11 christos $1 ~ cc && $4 == region { print $3 }
519 1.11 christos ' <"$TZ_ZONE_TABLE"`
520 1.9 christos esac
521 1.1 jtc
522 1.1 jtc # Make sure the corresponding zoneinfo file exists.
523 1.1 jtc TZ_for_date=$TZDIR/$TZ
524 1.11 christos <"$TZ_for_date" || {
525 1.14 christos say >&2 "$0: time zone files are not set up correctly"
526 1.1 jtc exit 1
527 1.1 jtc }
528 1.1 jtc esac
529 1.1 jtc
530 1.1 jtc
531 1.1 jtc # Use the proposed TZ to output the current date relative to UTC.
532 1.1 jtc # Loop until they agree in seconds.
533 1.1 jtc # Give up after 8 unsuccessful tries.
534 1.1 jtc
535 1.1 jtc extra_info=
536 1.1 jtc for i in 1 2 3 4 5 6 7 8
537 1.1 jtc do
538 1.10 christos TZdate=`LANG=C TZ="$TZ_for_date" date`
539 1.10 christos UTdate=`LANG=C TZ=UTC0 date`
540 1.10 christos TZsec=`expr "$TZdate" : '.*:\([0-5][0-9]\)'`
541 1.10 christos UTsec=`expr "$UTdate" : '.*:\([0-5][0-9]\)'`
542 1.1 jtc case $TZsec in
543 1.1 jtc $UTsec)
544 1.1 jtc extra_info="
545 1.15 christos Selected time is now: $TZdate.
546 1.1 jtc Universal Time is now: $UTdate."
547 1.1 jtc break
548 1.1 jtc esac
549 1.1 jtc done
550 1.1 jtc
551 1.1 jtc
552 1.1 jtc # Output TZ info and ask the user to confirm.
553 1.1 jtc
554 1.1 jtc echo >&2 ""
555 1.1 jtc echo >&2 "The following information has been given:"
556 1.1 jtc echo >&2 ""
557 1.9 christos case $country%$region%$coord in
558 1.14 christos ?*%?*%) say >&2 " $country$newline $region";;
559 1.14 christos ?*%%) say >&2 " $country";;
560 1.14 christos %?*%?*) say >&2 " coord $coord$newline $region";;
561 1.14 christos %%?*) say >&2 " coord $coord";;
562 1.14 christos *) say >&2 " TZ='$TZ'"
563 1.1 jtc esac
564 1.14 christos say >&2 ""
565 1.14 christos say >&2 "Therefore TZ='$TZ' will be used.$extra_info"
566 1.14 christos say >&2 "Is the above information OK?"
567 1.1 jtc
568 1.10 christos doselect Yes No
569 1.10 christos ok=$select_result
570 1.1 jtc case $ok in
571 1.1 jtc Yes) break
572 1.1 jtc esac
573 1.9 christos do coord=
574 1.1 jtc done
575 1.1 jtc
576 1.5 kleink case $SHELL in
577 1.5 kleink *csh) file=.login line="setenv TZ '$TZ'";;
578 1.5 kleink *) file=.profile line="TZ='$TZ'; export TZ"
579 1.5 kleink esac
580 1.5 kleink
581 1.15 christos test -t 1 && say >&2 "
582 1.5 kleink You can make this change permanent for yourself by appending the line
583 1.5 kleink $line
584 1.5 kleink to the file '$file' in your home directory; then log out and log in again.
585 1.5 kleink
586 1.5 kleink Here is that TZ value again, this time on standard output so that you
587 1.5 kleink can use the $0 command in shell scripts:"
588 1.5 kleink
589 1.14 christos say "$TZ"
590