tzselect.ksh revision 1.8 1 #! /bin/bash
2 #
3 # $NetBSD: tzselect.ksh,v 1.8 2013/03/02 21:24:28 christos Exp $
4 #
5 PKGVERSION='(tzcode) '
6 TZVERSION=see_Makefile
7 REPORT_BUGS_TO=tz@iana.org
8
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.
11
12 # Contributed by Paul Eggert.
13
14 # Porting notes:
15 #
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:
21 #
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/>
25 #
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:
29 #
30 # Gawk (GNU awk) <http://www.gnu.org/software/gawk/>
31 # mawk <http://invisible-island.net/mawk/>
32
33
34 # Specify default values for environment variables if they are unset.
35 : ${AWK=awk}
36 : ${TZDIR=$(pwd)}
37
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
43 }
44
45 if [ "$1" = "--help" ]; then
46 cat <<EOF
47 Usage: tzselect
48 Select a time zone interactively.
49
50 Report bugs to $REPORT_BUGS_TO.
51 EOF
52 exit
53 elif [ "$1" = "--version" ]; then
54 cat <<EOF
55 tzselect $TZVERSION
56 EOF
57 exit
58 fi
59
60 # Make sure the tables are readable.
61 TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab
62 TZ_ZONE_TABLE=$TZDIR/zone.tab
63 for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
64 do
65 <$f || {
66 echo >&2 "$0: time zone files are not set up correctly"
67 exit 1
68 }
69 done
70
71 newline='
72 '
73 IFS=$newline
74
75
76 # Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout.
77 case $(echo 1 | (select x in x; do break; done) 2>/dev/null) in
78 ?*) PS3=
79 esac
80
81
82 # Begin the main loop. We come back here if the user wants to retry.
83 while
84
85 echo >&2 'Please identify a location' \
86 'so that time zone rules can be set correctly.'
87
88 continent=
89 country=
90 region=
91
92
93 # Ask the user for continent or ocean.
94
95 echo >&2 'Please select a continent or ocean.'
96
97 select continent in \
98 Africa \
99 Americas \
100 Antarctica \
101 'Arctic Ocean' \
102 Asia \
103 'Atlantic Ocean' \
104 Australia \
105 Europe \
106 'Indian Ocean' \
107 'Pacific Ocean' \
108 'none - I want to specify the time zone using the Posix TZ format.'
109 do
110 case $continent in
111 '')
112 echo >&2 'Please enter a number in range.';;
113 ?*)
114 case $continent in
115 Americas) continent=America;;
116 *' '*) continent=$(expr "$continent" : '\([^ ]*\)')
117 esac
118 break
119 esac
120 done
121 case $continent in
122 '')
123 exit 1;;
124 none)
125 # Ask the user for a Posix TZ string. Check that it conforms.
126 while
127 echo >&2 'Please enter the desired value' \
128 'of the TZ environment variable.'
129 echo >&2 'For example, GST-10 is a zone named GST' \
130 'that is 10 hours ahead (east) of UTC.'
131 read TZ
132 $AWK -v TZ="$TZ" 'BEGIN {
133 tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+"
134 time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?"
135 offset = "[-+]?" time
136 date = "(J?[0-9]+|M[0-9]+\.[0-9]+\.[0-9]+)"
137 datetime = "," date "(/" time ")?"
138 tzpattern = "^(:.*|" tzname offset "(" tzname \
139 "(" offset ")?(" datetime datetime ")?)?)$"
140 if (TZ ~ tzpattern) exit 1
141 exit 0
142 }'
143 do
144 echo >&2 "\`$TZ' is not a conforming" \
145 'Posix time zone string.'
146 done
147 TZ_for_date=$TZ;;
148 *)
149 # Get list of names of countries in the continent or ocean.
150 countries=$($AWK -F'\t' \
151 -v continent="$continent" \
152 -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
153 '
154 /^#/ { next }
155 $3 ~ ("^" continent "/") {
156 if (!cc_seen[$1]++) cc_list[++ccs] = $1
157 }
158 END {
159 while (getline <TZ_COUNTRY_TABLE) {
160 if ($0 !~ /^#/) cc_name[$1] = $2
161 }
162 for (i = 1; i <= ccs; i++) {
163 country = cc_list[i]
164 if (cc_name[country]) {
165 country = cc_name[country]
166 }
167 print country
168 }
169 }
170 ' <$TZ_ZONE_TABLE | sort -f)
171
172
173 # If there's more than one country, ask the user which one.
174 case $countries in
175 *"$newline"*)
176 echo >&2 'Please select a country.'
177 select country in $countries
178 do
179 case $country in
180 '') echo >&2 'Please enter a number in range.';;
181 ?*) break
182 esac
183 done
184
185 case $country in
186 '') exit 1
187 esac;;
188 *)
189 country=$countries
190 esac
191
192
193 # Get list of names of time zone rule regions in the country.
194 regions=$($AWK -F'\t' \
195 -v country="$country" \
196 -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
197 '
198 BEGIN {
199 cc = country
200 while (getline <TZ_COUNTRY_TABLE) {
201 if ($0 !~ /^#/ && country == $2) {
202 cc = $1
203 break
204 }
205 }
206 }
207 $1 == cc { print $4 }
208 ' <$TZ_ZONE_TABLE)
209
210
211 # If there's more than one region, ask the user which one.
212 case $regions in
213 *"$newline"*)
214 echo >&2 'Please select one of the following' \
215 'time zone regions.'
216 select region in $regions
217 do
218 case $region in
219 '') echo >&2 'Please enter a number in range.';;
220 ?*) break
221 esac
222 done
223 case $region in
224 '') exit 1
225 esac;;
226 *)
227 region=$regions
228 esac
229
230 # Determine TZ from country and region.
231 TZ=$($AWK -F'\t' \
232 -v country="$country" \
233 -v region="$region" \
234 -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
235 '
236 BEGIN {
237 cc = country
238 while (getline <TZ_COUNTRY_TABLE) {
239 if ($0 !~ /^#/ && country == $2) {
240 cc = $1
241 break
242 }
243 }
244 }
245 $1 == cc && $4 == region { print $3 }
246 ' <$TZ_ZONE_TABLE)
247
248 # Make sure the corresponding zoneinfo file exists.
249 TZ_for_date=$TZDIR/$TZ
250 <$TZ_for_date || {
251 echo >&2 "$0: time zone files are not set up correctly"
252 exit 1
253 }
254 esac
255
256
257 # Use the proposed TZ to output the current date relative to UTC.
258 # Loop until they agree in seconds.
259 # Give up after 8 unsuccessful tries.
260
261 extra_info=
262 for i in 1 2 3 4 5 6 7 8
263 do
264 TZdate=$(LANG=C TZ="$TZ_for_date" date)
265 UTdate=$(LANG=C TZ=UTC0 date)
266 TZsec=$(expr "$TZdate" : '.*:\([0-5][0-9]\)')
267 UTsec=$(expr "$UTdate" : '.*:\([0-5][0-9]\)')
268 case $TZsec in
269 $UTsec)
270 extra_info="
271 Local time is now: $TZdate.
272 Universal Time is now: $UTdate."
273 break
274 esac
275 done
276
277
278 # Output TZ info and ask the user to confirm.
279
280 echo >&2 ""
281 echo >&2 "The following information has been given:"
282 echo >&2 ""
283 case $country+$region in
284 ?*+?*) echo >&2 " $country$newline $region";;
285 ?*+) echo >&2 " $country";;
286 +) echo >&2 " TZ='$TZ'"
287 esac
288 echo >&2 ""
289 echo >&2 "Therefore TZ='$TZ' will be used.$extra_info"
290 echo >&2 "Is the above information OK?"
291
292 ok=
293 select ok in Yes No
294 do
295 case $ok in
296 '') echo >&2 'Please enter 1 for Yes, or 2 for No.';;
297 ?*) break
298 esac
299 done
300 case $ok in
301 '') exit 1;;
302 Yes) break
303 esac
304 do :
305 done
306
307 case $SHELL in
308 *csh) file=.login line="setenv TZ '$TZ'";;
309 *) file=.profile line="TZ='$TZ'; export TZ"
310 esac
311
312 echo >&2 "
313 You can make this change permanent for yourself by appending the line
314 $line
315 to the file '$file' in your home directory; then log out and log in again.
316
317 Here is that TZ value again, this time on standard output so that you
318 can use the $0 command in shell scripts:"
319
320 echo "$TZ"
321