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