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