tools/llvm: Do not build with symbols
[minix3.git] / lib / libc / time / tzselect.ksh
blob2c588571f017f459aa38fba6ae0f048141e1b933
1 #! /bin/bash
3 # $NetBSD: tzselect.ksh,v 1.9 2013/09/20 19:06:54 christos Exp $
5 PKGVERSION='(tzcode) '
6 TZVERSION=see_Makefile
7 REPORT_BUGS_TO=tz@iana.org
9 # Ask the user about the time zone, and output the resulting TZ value to stdout.
10 # Interact with the user via stderr and stdin.
12 # Contributed by Paul Eggert.
14 # Porting notes:
16 # This script requires a Posix-like shell with the extension of a
17 # 'select' statement. The 'select' statement was introduced in the
18 # Korn shell and is available in Bash and other shell implementations.
19 # If your host lacks both Bash and the Korn shell, you can get their
20 # source from one of these locations:
22 # Bash <http://www.gnu.org/software/bash/bash.html>
23 # Korn Shell <http://www.kornshell.com/>
24 # Public Domain Korn Shell <http://www.cs.mun.ca/~michael/pdksh/>
26 # This script also uses several features of modern awk programs.
27 # If your host lacks awk, or has an old awk that does not conform to Posix,
28 # you can use either of the following free programs instead:
30 # Gawk (GNU awk) <http://www.gnu.org/software/gawk/>
31 # mawk <http://invisible-island.net/mawk/>
34 # Specify default values for environment variables if they are unset.
35 : ${AWK=awk}
36 : ${TZDIR=$(pwd)}
38 # Check for awk Posix compliance.
39 ($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1
40 [ $? = 123 ] || {
41 echo >&2 "$0: Sorry, your \`$AWK' program is not Posix compatible."
42 exit 1
45 coord=
46 location_limit=10
48 usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT]
49 Select a time zone interactively.
51 Options:
53 -c COORD
54 Instead of asking for continent and then country and then city,
55 ask for selection from time zones whose largest cities
56 are closest to the location with geographical coordinates COORD.
57 COORD should use ISO 6709 notation, for example, '-c +4852+00220'
58 for Paris (in degrees and minutes, North and East), or
59 '-c -35-058' for Buenos Aires (in degrees, South and West).
61 -n LIMIT
62 Display at most LIMIT locations when -c is used (default $location_limit).
64 --version
65 Output version information.
67 --help
68 Output this help.
70 Report bugs to $REPORT_BUGS_TO."
72 while getopts c:n:-: opt
74 case $opt$OPTARG in
75 c*)
76 coord=$OPTARG ;;
77 n*)
78 location_limit=$OPTARG ;;
79 -help)
80 exec echo "$usage" ;;
81 -version)
82 exec echo "tzselect $PKGVERSION$TZVERSION" ;;
83 -*)
84 echo >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1 ;;
86 echo >&2 "$0: try '$0 --help'"; exit 1 ;;
87 esac
88 done
90 shift $((OPTIND-1))
91 case $# in
92 0) ;;
93 *) echo >&2 "$0: $1: unknown argument"; exit 1 ;;
94 esac
96 # Make sure the tables are readable.
97 TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab
98 TZ_ZONE_TABLE=$TZDIR/zone.tab
99 for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
101 <$f || {
102 echo >&2 "$0: time zone files are not set up correctly"
103 exit 1
105 done
107 newline='
109 IFS=$newline
112 # Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout.
113 case $(echo 1 | (select x in x; do break; done) 2>/dev/null) in
114 ?*) PS3=
115 esac
117 # Awk script to read a time zone table and output the same table,
118 # with each column preceded by its distance from 'here'.
119 output_distances='
120 BEGIN {
121 FS = "\t"
122 while (getline <TZ_COUNTRY_TABLE)
123 if ($0 ~ /^[^#]/)
124 country[$1] = $2
125 country["US"] = "US" # Otherwise the strings get too long.
127 function convert_coord(coord, deg, min, ilen, sign, sec) {
128 if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9][0-9][0-9]([^0-9]|$)/) {
129 degminsec = coord
130 intdeg = degminsec < 0 ? -int(-degminsec / 10000) : int(degminsec / 10000)
131 minsec = degminsec - intdeg * 10000
132 intmin = minsec < 0 ? -int(-minsec / 100) : int(minsec / 100)
133 sec = minsec - intmin * 100
134 deg = (intdeg * 3600 + intmin * 60 + sec) / 3600
135 } else if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9]([^0-9]|$)/) {
136 degmin = coord
137 intdeg = degmin < 0 ? -int(-degmin / 100) : int(degmin / 100)
138 min = degmin - intdeg * 100
139 deg = (intdeg * 60 + min) / 60
140 } else
141 deg = coord
142 return deg * 0.017453292519943296
144 function convert_latitude(coord) {
145 match(coord, /..*[-+]/)
146 return convert_coord(substr(coord, 1, RLENGTH - 1))
148 function convert_longitude(coord) {
149 match(coord, /..*[-+]/)
150 return convert_coord(substr(coord, RLENGTH))
152 # Great-circle distance between points with given latitude and longitude.
153 # Inputs and output are in radians. This uses the great-circle special
154 # case of the Vicenty formula for distances on ellipsoids.
155 function dist(lat1, long1, lat2, long2, dlong, x, y, num, denom) {
156 dlong = long2 - long1
157 x = cos (lat2) * sin (dlong)
158 y = cos (lat1) * sin (lat2) - sin (lat1) * cos (lat2) * cos (dlong)
159 num = sqrt (x * x + y * y)
160 denom = sin (lat1) * sin (lat2) + cos (lat1) * cos (lat2) * cos (dlong)
161 return atan2(num, denom)
163 BEGIN {
164 coord_lat = convert_latitude(coord)
165 coord_long = convert_longitude(coord)
167 /^[^#]/ {
168 here_lat = convert_latitude($2)
169 here_long = convert_longitude($2)
170 line = $1 "\t" $2 "\t" $3 "\t" country[$1]
171 if (NF == 4)
172 line = line " - " $4
173 printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), line
177 # Begin the main loop. We come back here if the user wants to retry.
178 while
180 echo >&2 'Please identify a location' \
181 'so that time zone rules can be set correctly.'
183 continent=
184 country=
185 region=
187 case $coord in
189 continent=coord;;
192 # Ask the user for continent or ocean.
194 echo >&2 'Please select a continent, ocean, "coord", or "TZ".'
196 quoted_continents=$(
197 $AWK -F'\t' '
198 /^[^#]/ {
199 entry = substr($3, 1, index($3, "/") - 1)
200 if (entry == "America")
201 entry = entry "s"
202 if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/)
203 entry = entry " Ocean"
204 printf "'\''%s'\''\n", entry
206 ' $TZ_ZONE_TABLE |
207 sort -u |
208 tr '\n' ' '
209 echo ''
212 eval '
213 select continent in '"$quoted_continents"' \
214 "coord - I want to use geographical coordinates." \
215 "TZ - I want to specify the time zone using the Posix TZ format."
217 case $continent in
219 echo >&2 "Please enter a number in range.";;
221 case $continent in
222 Americas) continent=America;;
223 *" "*) continent=$(expr "$continent" : '\''\([^ ]*\)'\'')
224 esac
225 break
226 esac
227 done
229 esac
231 case $continent in
233 exit 1;;
235 # Ask the user for a Posix TZ string. Check that it conforms.
236 while
237 echo >&2 'Please enter the desired value' \
238 'of the TZ environment variable.'
239 echo >&2 'For example, GST-10 is a zone named GST' \
240 'that is 10 hours ahead (east) of UTC.'
241 read TZ
242 $AWK -v TZ="$TZ" 'BEGIN {
243 tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+"
244 time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?"
245 offset = "[-+]?" time
246 date = "(J?[0-9]+|M[0-9]+\.[0-9]+\.[0-9]+)"
247 datetime = "," date "(/" time ")?"
248 tzpattern = "^(:.*|" tzname offset "(" tzname \
249 "(" offset ")?(" datetime datetime ")?)?)$"
250 if (TZ ~ tzpattern) exit 1
251 exit 0
254 echo >&2 "\`$TZ' is not a conforming" \
255 'Posix time zone string.'
256 done
257 TZ_for_date=$TZ;;
259 case $continent in
260 coord)
261 case $coord in
263 echo >&2 'Please enter coordinates' \
264 'in ISO 6709 notation.'
265 echo >&2 'For example, +4042-07403 stands for'
266 echo >&2 '40 degrees 42 minutes north,' \
267 '74 degrees 3 minutes west.'
268 read coord;;
269 esac
270 distance_table=$($AWK \
271 -v coord="$coord" \
272 -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
273 "$output_distances" <$TZ_ZONE_TABLE |
274 sort -n |
275 sed "${location_limit}q"
277 regions=$(echo "$distance_table" | $AWK '
278 BEGIN { FS = "\t" }
279 { print $NF }
281 echo >&2 'Please select one of the following' \
282 'time zone regions,'
283 echo >&2 'listed roughly in increasing order' \
284 "of distance from $coord".
285 select region in $regions
287 case $region in
288 '') echo >&2 'Please enter a number in range.';;
289 ?*) break;;
290 esac
291 done
292 TZ=$(echo "$distance_table" | $AWK -v region="$region" '
293 BEGIN { FS="\t" }
294 $NF == region { print $4 }
298 # Get list of names of countries in the continent or ocean.
299 countries=$($AWK -F'\t' \
300 -v continent="$continent" \
301 -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
303 /^#/ { next }
304 $3 ~ ("^" continent "/") {
305 if (!cc_seen[$1]++) cc_list[++ccs] = $1
307 END {
308 while (getline <TZ_COUNTRY_TABLE) {
309 if ($0 !~ /^#/) cc_name[$1] = $2
311 for (i = 1; i <= ccs; i++) {
312 country = cc_list[i]
313 if (cc_name[country]) {
314 country = cc_name[country]
316 print country
319 ' <$TZ_ZONE_TABLE | sort -f)
322 # If there's more than one country, ask the user which one.
323 case $countries in
324 *"$newline"*)
325 echo >&2 'Please select a country' \
326 'whose clocks agree with yours.'
327 select country in $countries
329 case $country in
330 '') echo >&2 'Please enter a number in range.';;
331 ?*) break
332 esac
333 done
335 case $country in
336 '') exit 1
337 esac;;
339 country=$countries
340 esac
343 # Get list of names of time zone rule regions in the country.
344 regions=$($AWK -F'\t' \
345 -v country="$country" \
346 -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
348 BEGIN {
349 cc = country
350 while (getline <TZ_COUNTRY_TABLE) {
351 if ($0 !~ /^#/ && country == $2) {
352 cc = $1
353 break
357 $1 == cc { print $4 }
358 ' <$TZ_ZONE_TABLE)
361 # If there's more than one region, ask the user which one.
362 case $regions in
363 *"$newline"*)
364 echo >&2 'Please select one of the following' \
365 'time zone regions.'
366 select region in $regions
368 case $region in
369 '') echo >&2 'Please enter a number in range.';;
370 ?*) break
371 esac
372 done
373 case $region in
374 '') exit 1
375 esac;;
377 region=$regions
378 esac
380 # Determine TZ from country and region.
381 TZ=$($AWK -F'\t' \
382 -v country="$country" \
383 -v region="$region" \
384 -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
386 BEGIN {
387 cc = country
388 while (getline <TZ_COUNTRY_TABLE) {
389 if ($0 !~ /^#/ && country == $2) {
390 cc = $1
391 break
395 $1 == cc && $4 == region { print $3 }
396 ' <$TZ_ZONE_TABLE)
397 esac
399 # Make sure the corresponding zoneinfo file exists.
400 TZ_for_date=$TZDIR/$TZ
401 <$TZ_for_date || {
402 echo >&2 "$0: time zone files are not set up correctly"
403 exit 1
405 esac
408 # Use the proposed TZ to output the current date relative to UTC.
409 # Loop until they agree in seconds.
410 # Give up after 8 unsuccessful tries.
412 extra_info=
413 for i in 1 2 3 4 5 6 7 8
415 TZdate=$(LANG=C TZ="$TZ_for_date" date)
416 UTdate=$(LANG=C TZ=UTC0 date)
417 TZsec=$(expr "$TZdate" : '.*:\([0-5][0-9]\)')
418 UTsec=$(expr "$UTdate" : '.*:\([0-5][0-9]\)')
419 case $TZsec in
420 $UTsec)
421 extra_info="
422 Local time is now: $TZdate.
423 Universal Time is now: $UTdate."
424 break
425 esac
426 done
429 # Output TZ info and ask the user to confirm.
431 echo >&2 ""
432 echo >&2 "The following information has been given:"
433 echo >&2 ""
434 case $country%$region%$coord in
435 ?*%?*%) echo >&2 " $country$newline $region";;
436 ?*%%) echo >&2 " $country";;
437 %?*%?*) echo >&2 " coord $coord$newline $region";;
438 %%?*) echo >&2 " coord $coord";;
439 +) echo >&2 " TZ='$TZ'"
440 esac
441 echo >&2 ""
442 echo >&2 "Therefore TZ='$TZ' will be used.$extra_info"
443 echo >&2 "Is the above information OK?"
446 select ok in Yes No
448 case $ok in
449 '') echo >&2 'Please enter 1 for Yes, or 2 for No.';;
450 ?*) break
451 esac
452 done
453 case $ok in
454 '') exit 1;;
455 Yes) break
456 esac
457 do coord=
458 done
460 case $SHELL in
461 *csh) file=.login line="setenv TZ '$TZ'";;
462 *) file=.profile line="TZ='$TZ'; export TZ"
463 esac
465 echo >&2 "
466 You can make this change permanent for yourself by appending the line
467 $line
468 to the file '$file' in your home directory; then log out and log in again.
470 Here is that TZ value again, this time on standard output so that you
471 can use the $0 command in shell scripts:"
473 echo "$TZ"