Home | History | Annotate | Line # | Download | only in time
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