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