xref: /illumos-gate/usr/src/cmd/zic/tzselect.ksh (revision 7f7322febbcfe774b7270abc3b191c094bfcc517)
1#! /usr/bin/ksh
2#
3# Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
4# Use is subject to license terms.
5#
6# ident	"%Z%%M%	%I%	%E% SMI"
7#
8# '@(#)tzselect.ksh	1.8'
9
10# Ask the user about the time zone, and output the resulting TZ value to stdout.
11# Interact with the user via stderr and stdin.
12
13# Contributed by Paul Eggert
14
15# Porting notes:
16#
17# This script requires several features of the Korn shell.
18# If your host lacks the Korn shell,
19# you can use either of the following free programs instead:
20#
21#	<a href=ftp://ftp.gnu.org/pub/gnu/>
22#	Bourne-Again shell (bash)
23#	</a>
24#
25#	<a href=ftp://ftp.cs.mun.ca/pub/pdksh/pdksh.tar.gz>
26#	Public domain ksh
27#	</a>
28#
29# This script also uses several features of modern awk programs.
30# If your host lacks awk, or has an old awk that does not conform to POSIX.2,
31# you can use either of the following free programs instead:
32#
33#	<a href=ftp://ftp.gnu.org/pub/gnu/>
34#	GNU awk (gawk)
35#	</a>
36#
37#	<a href=ftp://ftp.whidbey.net/pub/brennan/>
38#	mawk
39#	</a>
40
41AWK=/usr/bin/nawk
42GREP=/usr/bin/grep
43EXPR=/usr/bin/expr
44LOCALE=/usr/bin/locale
45SORT=/usr/bin/sort
46PRINTF=/usr/bin/printf
47DATE=/usr/bin/date
48GETTEXT=/usr/bin/gettext
49
50TZDIR=/usr/share/lib/zoneinfo
51
52# Messages
53ERR_NO_SETUP="%s: time zone files are not set up correctly"
54INFO_LOCATION="Please identify a location so that time zone rules \
55can be set correctly."
56INFO_SELECT_CONT="Please select a continent or ocean."
57INFO_POSIX="none - I want to specify the time zone using the POSIX \
58TZ format."
59WARN_ENTER_NUM="Please enter a number in range."
60INFO_ENTER_POSIX="Please enter the desired value of the TZ environment \
61variable."
62INFO_POSIX_EX="For example, GST-10 is a zone named GST that is 10 hours \
63ahead (east) of UTC."
64ERR_INV_POSIX="\`%s\' is not a conforming POSIX time zone string."
65INFO_SELECT_CNTRY="Please select a country or region."
66INFO_SELECT_TZ="Please select one of the following time zone regions."
67INFO_EXTRA1="Local time is now:       %s"
68INFO_EXTRA2="Universal Time is now:   %s"
69INFO_INFO="The following information has been given:"
70INFO_TZ="Therefore TZ='%s' will be used."
71INFO_OK="Is the above information OK?"
72INFO_YES="Yes"
73INFO_NO="No"
74WARN_ENTER_YORN="Please enter 1 for Yes, or 2 for No."
75INFO_FINE="Here is the TZ value again, this time on standard output:"
76
77# I18n support
78TEXTDOMAINDIR=/usr/lib/locale; export TEXTDOMAINDIR
79TEXTDOMAIN=SUNW_OST_OSCMD; export TEXTDOMAIN
80DOMAIN2=SUNW_OST_ZONEINFO
81
82# Make sure the tables are readable.
83TZ_COUNTRY_TABLE=$TZDIR/tab/country.tab
84TZ_ZONE_TABLE=$TZDIR/tab/zone_sun.tab
85for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
86do
87	<$f || {
88		$PRINTF >&2 "`$GETTEXT "$ERR_NO_SETUP"`\n"  $0
89		exit 1
90	}
91done
92
93newline='
94'
95IFS=$newline
96
97# For C locale, don't need to call gettext(1)
98loc_messages=`$LOCALE | $GREP LC_MESSAGES | $AWK -F"=" '{print $2}`
99if [ "$loc_messages" = "\"C\""  -o "$loc_messages" = "C" ]; then
100	is_C=1
101else
102	is_C=0
103fi
104
105iafrica=`$GETTEXT $DOMAIN2 Africa`
106iamerica=`$GETTEXT $DOMAIN2 Americas`
107iantarctica=`$GETTEXT $DOMAIN2 Antarctica`
108iarcticocean=`$GETTEXT $DOMAIN2 "Arctic Ocean"`
109iasia=`$GETTEXT $DOMAIN2 Asia`
110iatlanticocean=`$GETTEXT $DOMAIN2 "Atlantic Ocean"`
111iaustralia=`$GETTEXT $DOMAIN2 Australia`
112ieurope=`$GETTEXT $DOMAIN2 Europe`
113ipacificocean=`$GETTEXT $DOMAIN2 "Pacific Ocean"`
114iindianocean=`$GETTEXT $DOMAIN2 "Indian Ocean"`
115none=`$GETTEXT "$INFO_POSIX"`
116
117# Begin the main loop.  We come back here if the user wants to retry.
118while
119	$PRINTF >&2 "`$GETTEXT "$INFO_LOCATION"`\n"
120
121	continent=
122	country=
123	region=
124
125	# Ask the user for continent or ocean.
126	$PRINTF >&2 "`$GETTEXT "$INFO_SELECT_CONT"`\n"
127
128	select continent in \
129	    $iafrica \
130	    $iamerica \
131	    $iantarctica \
132	    $iarcticocean \
133	    $iasia \
134	    $iatlanticocean \
135	    $iaustralia \
136	    $ieurope \
137	    $iindianocean \
138	    $ipacificocean \
139	    $none
140
141	do
142	    case $continent in
143	    '')
144		$PRINTF >&2 "`$GETTEXT "$WARN_ENTER_NUM"`\n";;
145
146	    ?*)
147		case $continent in
148	    $iafrica) continent=Africa;;
149	    $iamerica) continent=America;;
150	    $iantarctica) continent=Antarctica;;
151	    $iarcticocean) continent=Arctic;;
152	    $iasia) continent=Asia;;
153	    $iatlanticocean) continent=Atlantic;;
154	    $iaustralia) continent=Australia;;
155	    $ieurope) continent=Europe;;
156	    $iindianocean) continent=Indian;;
157	    $ipacificocean) continent=Pacific;;
158	    $none) continent=none;;
159		esac
160		break
161	    esac
162	done
163	case $continent in
164	'')
165		exit 1;;
166	none)
167		# Ask the user for a POSIX TZ string.  Check that it conforms.
168		while
169			$PRINTF >&2 "`$GETTEXT "$INFO_ENTER_POSIX"`\n"
170			$PRINTF >&2 "`$GETTEXT "$INFO_POSIX_EX"`\n"
171
172			read TZ
173			env LC_ALL=C $AWK -v TZ="$TZ" 'BEGIN {
174				tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+"
175				time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?"
176				offset = "[-+]?" time
177				date = "(J?[0-9]+|M[0-9]+\.[0-9]+\.[0-9]+)"
178				datetime = "," date "(/" time ")?"
179				tzpattern = "^(:.*|" tzname offset "(" tzname \
180				  "(" offset ")?(" datetime datetime ")?)?)$"
181				if (TZ ~ tzpattern) exit 1
182				exit 0
183			}'
184		do
185			$PRINTF >&2 "`$GETTEXT "$ERR_INV_POSIX"`\n" $TZ
186
187		done
188		TZ_for_date=$TZ;;
189	*)
190		# Get list of names of countries in the continent or ocean.
191		countries=$($AWK -F'\t' \
192			-v continent="$continent" \
193			-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
194		'
195			/^#/ { next }
196			$3 ~ ("^" continent "/") {
197				if (!cc_seen[$1]++) cc_list[++ccs] = $1
198			}
199			END {
200				while (getline <TZ_COUNTRY_TABLE) {
201					if ($0 !~ /^#/) cc_name[$1] = $2
202				}
203				for (i = 1; i <= ccs; i++) {
204					country = cc_list[i]
205					if (cc_name[country]) {
206					  country = cc_name[country]
207					}
208					print country
209				}
210			}
211		' <$TZ_ZONE_TABLE | $SORT -f)
212
213		# i18n country names
214		c=0
215		set -A icountry
216		for country in $countries
217		do
218			if [ $is_C -eq 1 ]; then
219				icountry[c]=$country
220			else
221				icountry[c]=`${GETTEXT} ${DOMAIN2} $country`
222			fi
223			ocountry[c]="$country"
224			c=$(( $c + 1 ))
225		done
226		maxnum=$c
227
228		# If there's more than one country, ask the user which one.
229		case $countries in
230		*"$newline"*)
231			$PRINTF >&2 "`$GETTEXT "$INFO_SELECT_CNTRY"`\n"
232			select xcountry in ${icountry[*]}
233			do
234			    case $xcountry in
235			    '')
236				$PRINTF >&2 "`$GETTEXT "$WARN_ENTER_NUM"`\n"
237				;;
238			    ?*)   c=0
239				  while true; do
240                		    if [ "$xcountry" = "${icountry[$c]}" ];
241				    then
242					    country="${ocountry[$c]}"
243                        		    break
244                		    fi
245                		    if [ $c -lt $maxnum ]; then
246					    c=$(( $c + 1 ))
247                		    else
248                        		    break
249                		    fi
250        		         done
251				 break
252			     esac
253			done
254
255			case $xcountry in
256			'') exit 1
257			esac;;
258		*)
259			country=$countries
260			xcountry=$countries
261		esac
262
263
264		# Get list of names of time zone rule regions in the country.
265		regions=$($AWK -F'\t' \
266			-v country="$country" \
267			-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
268		'
269			BEGIN {
270				cc = country
271				while (getline <TZ_COUNTRY_TABLE) {
272					if ($0 !~ /^#/  &&  country == $2) {
273						cc = $1
274						break
275					}
276				}
277			}
278			$1 == cc { print $5 }
279		' <$TZ_ZONE_TABLE)
280
281		# I18n region names
282		c=0
283		set -A iregion
284		for region in $regions
285		do
286			if [ $is_C -eq 1 ]; then
287				iregion[c]=$region
288			else
289				iregion[c]=`${GETTEXT} ${DOMAIN2} $region`
290			fi
291			oregion[c]="$region"
292			c=$(( $c + 1 ))
293		done
294		maxnum=$c
295
296		# If there's more than one region, ask the user which one.
297		case $regions in
298		*"$newline"*)
299			$PRINTF >&2 "`$GETTEXT "$INFO_SELECT_TZ"`\n"
300
301			select xregion in ${iregion[*]}
302			do
303				case $xregion in
304				'')
305				$PRINTF >&2 "`$GETTEXT "$WARN_ENTER_NUM"`\n"
306				;;
307				?*) c=0
308                                    while true; do
309                                       if [ "$xregion" = "${iregion[$c]}" ];
310				       then
311                                            region="${oregion[$c]}"
312                                            break
313                                       fi
314                                       if [ $c -lt $maxnum ]; then
315					    c=$(( $c + 1 ))
316                                       else
317                                            break
318                                       fi
319                                    done
320				    break
321				esac
322			done
323
324			case $region in
325			'') exit 1
326			esac;;
327		*)
328			region=$regions
329			xregion=$regions
330		esac
331
332		# Determine TZ from country and region.
333		TZ=$($AWK -F'\t' \
334			-v country="$country" \
335			-v region="$region" \
336			-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
337		'
338			BEGIN {
339				cc = country
340				while (getline <TZ_COUNTRY_TABLE) {
341					if ($0 !~ /^#/  &&  country == $2) {
342						cc = $1
343						break
344					}
345				}
346			}
347
348			$1 == cc && $5 == region {
349				# Check if tzname mapped to
350				# backward compatible tzname
351				if ($4 == "-") {
352					print $3
353				} else {
354					print $4
355				}
356			}
357		' <$TZ_ZONE_TABLE)
358
359		# Make sure the corresponding zoneinfo file exists.
360		TZ_for_date=$TZDIR/$TZ
361		<$TZ_for_date || {
362			$PRINTF >&2 "`$GETTEXT "$ERR_NO_SETUP"`\n" $0
363			exit 1
364		}
365		# Absolute path TZ's not supported
366		TZ_for_date=$TZ
367	esac
368
369
370	# Use the proposed TZ to output the current date relative to UTC.
371	# Loop until they agree in seconds.
372	# Give up after 8 unsuccessful tries.
373
374	extra_info1=
375	extra_info2=
376	for i in 1 2 3 4 5 6 7 8
377	do
378		TZdate=$(LANG=C TZ="$TZ_for_date" $DATE)
379		UTdate=$(LANG=C TZ=UTC0 $DATE)
380		TZsec=$($EXPR "$TZdate" : '.*:\([0-5][0-9]\)')
381		UTsec=$($EXPR "$UTdate" : '.*:\([0-5][0-9]\)')
382		case $TZsec in
383		$UTsec)
384			extra_info1=$($PRINTF "`$GETTEXT "$INFO_EXTRA1"`" \
385			"$TZdate")
386			extra_info2=$($PRINTF "`$GETTEXT "$INFO_EXTRA2"`" \
387			"$UTdate")
388			break
389		esac
390	done
391
392
393	# Output TZ info and ask the user to confirm.
394
395	$PRINTF >&2 "\n"
396	$PRINTF >&2 "`$GETTEXT "$INFO_INFO"`\n"
397	$PRINTF >&2 "\n"
398
399	case $country+$region in
400	?*+?*)	$PRINTF >&2 "	$xcountry$newline	$xregion\n";;
401	?*+)	$PRINTF >&2 "	$xcountry\n";;
402	+)	$PRINTF >&2 "	TZ='$TZ'\n"
403	esac
404	$PRINTF >&2 "\n"
405	$PRINTF >&2 "`$GETTEXT "$INFO_TZ"`\n" "$TZ"
406	$PRINTF >&2 "$extra_info1\n"
407	$PRINTF >&2 "$extra_info2\n"
408	$PRINTF >&2 "`$GETTEXT "$INFO_OK"`\n"
409
410	ok=
411	# select ok in Yes No
412	Yes="`$GETTEXT "$INFO_YES"`"
413	No="`$GETTEXT "$INFO_NO"`"
414	select ok in $Yes $No
415	do
416	    case $ok in
417	    '')
418		$PRINTF >&2 "`$GETTEXT "$WARN_ENTER_YORN"`\n"
419		;;
420	    ?*) break
421	    esac
422	done
423	case $ok in
424	'') exit 1;;
425	$Yes) break
426	esac
427do :
428done
429
430$PRINTF >&2 "`$GETTEXT "$INFO_FINE"`\n"
431
432$PRINTF "%s\n" "$TZ"
433