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