1 #! /usr/bin/ksh
   2 #
   3 # Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
   4 # Use is subject to license terms.
   5 #
   6 # ident "%Z%%M% %I%     %E% SMI"
   7 #
   8 # '@(#)tzselect.ksh     1.8'
   9 
  10 # Ask the user about the time zone, and output the resulting TZ value to stdout.
  11 # Interact with the user via stderr and stdin.
  12 
  13 # Contributed by Paul Eggert
  14 
  15 # Porting notes:
  16 #
  17 # This script requires several features of the Korn shell.
  18 # If your host lacks the Korn shell,
  19 # you can use either of the following free programs instead:
  20 #
  21 #       <a href=ftp://ftp.gnu.org/pub/gnu/>
  22 #       Bourne-Again shell (bash)
  23 #       </a>
  24 #
  25 #       <a href=ftp://ftp.cs.mun.ca/pub/pdksh/pdksh.tar.gz>
  26 #       Public domain ksh
  27 #       </a>
  28 #
  29 # This script also uses several features of modern awk programs.
  30 # If your host lacks awk, or has an old awk that does not conform to POSIX.2,
  31 # you can use either of the following free programs instead:
  32 #
  33 #       <a href=ftp://ftp.gnu.org/pub/gnu/>
  34 #       GNU awk (gawk)
  35 #       </a>
  36 #
  37 #       <a href=ftp://ftp.whidbey.net/pub/brennan/>
  38 #       mawk
  39 #       </a>
  40 
  41 AWK=/usr/bin/nawk
  42 GREP=/usr/bin/grep
  43 EXPR=/usr/bin/expr
  44 LOCALE=/usr/bin/locale
  45 SORT=/usr/bin/sort
  46 PRINTF=/usr/bin/printf
  47 DATE=/usr/bin/date
  48 GETTEXT=/usr/bin/gettext
  49 
  50 TZDIR=/usr/share/lib/zoneinfo
  51 
  52 # Messages
  53 ERR_NO_SETUP="%s: time zone files are not set up correctly"
  54 INFO_LOCATION="Please identify a location so that time zone rules \
  55 can be set correctly."
  56 INFO_SELECT_CONT="Please select a continent or ocean."
  57 INFO_POSIX="none - I want to specify the time zone using the POSIX \
  58 TZ format."
  59 WARN_ENTER_NUM="Please enter a number in range."
  60 INFO_ENTER_POSIX="Please enter the desired value of the TZ environment \
  61 variable."
  62 INFO_POSIX_EX="For example, GST-10 is a zone named GST that is 10 hours \
  63 ahead (east) of UTC."
  64 ERR_INV_POSIX="\`%s\' is not a conforming POSIX time zone string."
  65 INFO_SELECT_CNTRY="Please select a country or region."
  66 INFO_SELECT_TZ="Please select one of the following time zone regions."
  67 INFO_EXTRA1="Local time is now:       %s"
  68 INFO_EXTRA2="Universal Time is now:   %s"
  69 INFO_INFO="The following information has been given:"
  70 INFO_TZ="Therefore TZ='%s' will be used."
  71 INFO_OK="Is the above information OK?"
  72 INFO_YES="Yes"
  73 INFO_NO="No"
  74 WARN_ENTER_YORN="Please enter 1 for Yes, or 2 for No."
  75 INFO_FINE="Here is the TZ value again, this time on standard output:"
  76 
  77 # I18n support
  78 TEXTDOMAINDIR=/usr/lib/locale; export TEXTDOMAINDIR
  79 TEXTDOMAIN=SUNW_OST_OSCMD; export TEXTDOMAIN
  80 DOMAIN2=SUNW_OST_ZONEINFO
  81 
  82 # Make sure the tables are readable.
  83 TZ_COUNTRY_TABLE=$TZDIR/tab/country.tab
  84 TZ_ZONE_TABLE=$TZDIR/tab/zone_sun.tab
  85 for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
  86 do
  87         <$f || {
  88                 $PRINTF >&2 "`$GETTEXT "$ERR_NO_SETUP"`\n"  $0
  89                 exit 1
  90         }
  91 done
  92 
  93 newline='
  94 '
  95 IFS=$newline
  96 
  97 # For C locale, don't need to call gettext(1)
  98 loc_messages=`$LOCALE | $GREP LC_MESSAGES | $AWK -F"=" '{print $2}`
  99 if [ "$loc_messages" = "\"C\""  -o "$loc_messages" = "C" ]; then
 100         is_C=1
 101 else
 102         is_C=0
 103 fi
 104 
 105 iafrica=`$GETTEXT $DOMAIN2 Africa`
 106 iamerica=`$GETTEXT $DOMAIN2 Americas`
 107 iantarctica=`$GETTEXT $DOMAIN2 Antarctica`
 108 iarcticocean=`$GETTEXT $DOMAIN2 "Arctic Ocean"`
 109 iasia=`$GETTEXT $DOMAIN2 Asia`
 110 iatlanticocean=`$GETTEXT $DOMAIN2 "Atlantic Ocean"`
 111 iaustralia=`$GETTEXT $DOMAIN2 Australia`
 112 ieurope=`$GETTEXT $DOMAIN2 Europe`
 113 ipacificocean=`$GETTEXT $DOMAIN2 "Pacific Ocean"`
 114 iindianocean=`$GETTEXT $DOMAIN2 "Indian Ocean"`
 115 none=`$GETTEXT "$INFO_POSIX"`
 116 
 117 # Begin the main loop.  We come back here if the user wants to retry.
 118 while
 119         $PRINTF >&2 "`$GETTEXT "$INFO_LOCATION"`\n"
 120 
 121         continent=
 122         country=
 123         region=
 124 
 125         # Ask the user for continent or ocean.
 126         $PRINTF >&2 "`$GETTEXT "$INFO_SELECT_CONT"`\n"
 127 
 128         select continent in \
 129             $iafrica \
 130             $iamerica \
 131             $iantarctica \
 132             $iarcticocean \
 133             $iasia \
 134             $iatlanticocean \
 135             $iaustralia \
 136             $ieurope \
 137             $iindianocean \
 138             $ipacificocean \
 139             $none
 140 
 141         do
 142             case $continent in
 143             '')
 144                 $PRINTF >&2 "`$GETTEXT "$WARN_ENTER_NUM"`\n";;
 145 
 146             ?*)
 147                 case $continent in
 148             $iafrica) continent=Africa;;
 149             $iamerica) continent=America;;
 150             $iantarctica) continent=Antarctica;;
 151             $iarcticocean) continent=Arctic;;
 152             $iasia) continent=Asia;;
 153             $iatlanticocean) continent=Atlantic;;
 154             $iaustralia) continent=Australia;;
 155             $ieurope) continent=Europe;;
 156             $iindianocean) continent=Indian;;
 157             $ipacificocean) continent=Pacific;;
 158             $none) continent=none;;
 159                 esac
 160                 break
 161             esac
 162         done
 163         case $continent in
 164         '')
 165                 exit 1;;
 166         none)
 167                 # Ask the user for a POSIX TZ string.  Check that it conforms.
 168                 while
 169                         $PRINTF >&2 "`$GETTEXT "$INFO_ENTER_POSIX"`\n"
 170                         $PRINTF >&2 "`$GETTEXT "$INFO_POSIX_EX"`\n"
 171 
 172                         read TZ
 173                         env LC_ALL=C $AWK -v TZ="$TZ" 'BEGIN {
 174                                 tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+"
 175                                 time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?"
 176                                 offset = "[-+]?" time
 177                                 date = "(J?[0-9]+|M[0-9]+\.[0-9]+\.[0-9]+)"
 178                                 datetime = "," date "(/" time ")?"
 179                                 tzpattern = "^(:.*|" tzname offset "(" tzname \
 180                                   "(" offset ")?(" datetime datetime ")?)?)$"
 181                                 if (TZ ~ tzpattern) exit 1
 182                                 exit 0
 183                         }'
 184                 do
 185                         $PRINTF >&2 "`$GETTEXT "$ERR_INV_POSIX"`\n" $TZ
 186 
 187                 done
 188                 TZ_for_date=$TZ;;
 189         *)
 190                 # Get list of names of countries in the continent or ocean.
 191                 countries=$($AWK -F'\t' \
 192                         -v continent="$continent" \
 193                         -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
 194                 '
 195                         /^#/ { next }
 196                         $3 ~ ("^" continent "/") {
 197                                 if (!cc_seen[$1]++) cc_list[++ccs] = $1
 198                         }
 199                         END {
 200                                 while (getline <TZ_COUNTRY_TABLE) {
 201                                         if ($0 !~ /^#/) cc_name[$1] = $2
 202                                 }
 203                                 for (i = 1; i <= ccs; i++) {
 204                                         country = cc_list[i]
 205                                         if (cc_name[country]) {
 206                                           country = cc_name[country]
 207                                         }
 208                                         print country
 209                                 }
 210                         }
 211                 ' <$TZ_ZONE_TABLE | $SORT -f)
 212 
 213                 # i18n country names
 214                 c=0
 215                 set -A icountry
 216                 for country in $countries
 217                 do
 218                         if [ $is_C -eq 1 ]; then
 219                                 icountry[c]=$country
 220                         else
 221                                 icountry[c]=`${GETTEXT} ${DOMAIN2} $country`
 222                         fi
 223                         ocountry[c]="$country"
 224                         c=$(( $c + 1 ))
 225                 done
 226                 maxnum=$c
 227 
 228                 # If there's more than one country, ask the user which one.
 229                 case $countries in
 230                 *"$newline"*)
 231                         $PRINTF >&2 "`$GETTEXT "$INFO_SELECT_CNTRY"`\n"
 232                         select xcountry in ${icountry[*]}
 233                         do
 234                             case $xcountry in
 235                             '')
 236                                 $PRINTF >&2 "`$GETTEXT "$WARN_ENTER_NUM"`\n"
 237                                 ;;
 238                             ?*)   c=0
 239                                   while true; do
 240                                     if [ "$xcountry" = "${icountry[$c]}" ];
 241                                     then
 242                                             country="${ocountry[$c]}"
 243                                             break
 244                                     fi
 245                                     if [ $c -lt $maxnum ]; then
 246                                             c=$(( $c + 1 ))
 247                                     else
 248                                             break
 249                                     fi
 250                                  done
 251                                  break
 252                              esac
 253                         done
 254 
 255                         case $xcountry in
 256                         '') exit 1
 257                         esac;;
 258                 *)
 259                         country=$countries
 260                         xcountry=$countries
 261                 esac
 262 
 263 
 264                 # Get list of names of time zone rule regions in the country.
 265                 regions=$($AWK -F'\t' \
 266                         -v country="$country" \
 267                         -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
 268                 '
 269                         BEGIN {
 270                                 cc = country
 271                                 while (getline <TZ_COUNTRY_TABLE) {
 272                                         if ($0 !~ /^#/  &&  country == $2) {
 273                                                 cc = $1
 274                                                 break
 275                                         }
 276                                 }
 277                         }
 278                         $1 == cc { print $5 }
 279                 ' <$TZ_ZONE_TABLE)
 280 
 281                 # I18n region names
 282                 c=0
 283                 set -A iregion
 284                 for region in $regions
 285                 do
 286                         if [ $is_C -eq 1 ]; then
 287                                 iregion[c]=$region
 288                         else
 289                                 iregion[c]=`${GETTEXT} ${DOMAIN2} $region`
 290                         fi
 291                         oregion[c]="$region"
 292                         c=$(( $c + 1 ))
 293                 done
 294                 maxnum=$c
 295 
 296                 # If there's more than one region, ask the user which one.
 297                 case $regions in
 298                 *"$newline"*)
 299                         $PRINTF >&2 "`$GETTEXT "$INFO_SELECT_TZ"`\n"
 300 
 301                         select xregion in ${iregion[*]}
 302                         do
 303                                 case $xregion in
 304                                 '') 
 305                                 $PRINTF >&2 "`$GETTEXT "$WARN_ENTER_NUM"`\n"
 306                                 ;;
 307                                 ?*) c=0
 308                                     while true; do
 309                                        if [ "$xregion" = "${iregion[$c]}" ];
 310                                        then
 311                                             region="${oregion[$c]}"
 312                                             break
 313                                        fi
 314                                        if [ $c -lt $maxnum ]; then
 315                                             c=$(( $c + 1 ))
 316                                        else
 317                                             break
 318                                        fi
 319                                     done
 320                                     break
 321                                 esac
 322                         done
 323 
 324                         case $region in
 325                         '') exit 1
 326                         esac;;
 327                 *)
 328                         region=$regions
 329                         xregion=$regions
 330                 esac
 331 
 332                 # Determine TZ from country and region.
 333                 TZ=$($AWK -F'\t' \
 334                         -v country="$country" \
 335                         -v region="$region" \
 336                         -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
 337                 '
 338                         BEGIN {
 339                                 cc = country
 340                                 while (getline <TZ_COUNTRY_TABLE) {
 341                                         if ($0 !~ /^#/  &&  country == $2) {
 342                                                 cc = $1
 343                                                 break
 344                                         }
 345                                 }
 346                         }
 347 
 348                         $1 == cc && $5 == region { 
 349                                 # Check if tzname mapped to 
 350                                 # backward compatible tzname
 351                                 if ($4 == "-") {
 352                                         print $3
 353                                 } else {
 354                                         print $4
 355                                 }
 356                         }
 357                 ' <$TZ_ZONE_TABLE)
 358 
 359                 # Make sure the corresponding zoneinfo file exists.
 360                 TZ_for_date=$TZDIR/$TZ
 361                 <$TZ_for_date || {
 362                         $PRINTF >&2 "`$GETTEXT "$ERR_NO_SETUP"`\n" $0
 363                         exit 1
 364                 }
 365                 # Absolute path TZ's not supported
 366                 TZ_for_date=$TZ
 367         esac
 368 
 369 
 370         # Use the proposed TZ to output the current date relative to UTC.
 371         # Loop until they agree in seconds.
 372         # Give up after 8 unsuccessful tries.
 373 
 374         extra_info1=
 375         extra_info2=
 376         for i in 1 2 3 4 5 6 7 8
 377         do
 378                 TZdate=$(LANG=C TZ="$TZ_for_date" $DATE)
 379                 UTdate=$(LANG=C TZ=UTC0 $DATE)
 380                 TZsec=$($EXPR "$TZdate" : '.*:\([0-5][0-9]\)')
 381                 UTsec=$($EXPR "$UTdate" : '.*:\([0-5][0-9]\)')
 382                 case $TZsec in
 383                 $UTsec)
 384                         extra_info1=$($PRINTF "`$GETTEXT "$INFO_EXTRA1"`" \
 385                         "$TZdate")
 386                         extra_info2=$($PRINTF "`$GETTEXT "$INFO_EXTRA2"`" \
 387                         "$UTdate")
 388                         break
 389                 esac
 390         done
 391 
 392 
 393         # Output TZ info and ask the user to confirm.
 394 
 395         $PRINTF >&2 "\n"
 396         $PRINTF >&2 "`$GETTEXT "$INFO_INFO"`\n"
 397         $PRINTF >&2 "\n"
 398 
 399         case $country+$region in
 400         ?*+?*)  $PRINTF >&2 "    $xcountry$newline       $xregion\n";;
 401         ?*+)    $PRINTF >&2 "    $xcountry\n";;
 402         +)      $PRINTF >&2 "    TZ='$TZ'\n"
 403         esac
 404         $PRINTF >&2 "\n"
 405         $PRINTF >&2 "`$GETTEXT "$INFO_TZ"`\n" "$TZ"
 406         $PRINTF >&2 "$extra_info1\n"
 407         $PRINTF >&2 "$extra_info2\n"
 408         $PRINTF >&2 "`$GETTEXT "$INFO_OK"`\n"
 409 
 410         ok=
 411         # select ok in Yes No
 412         Yes="`$GETTEXT "$INFO_YES"`"
 413         No="`$GETTEXT "$INFO_NO"`"
 414         select ok in $Yes $No
 415         do
 416             case $ok in
 417             '') 
 418                 $PRINTF >&2 "`$GETTEXT "$WARN_ENTER_YORN"`\n"
 419                 ;;
 420             ?*) break
 421             esac
 422         done
 423         case $ok in
 424         '') exit 1;;
 425         $Yes) break
 426         esac
 427 do :
 428 done
 429 
 430 $PRINTF >&2 "`$GETTEXT "$INFO_FINE"`\n"
 431 
 432 $PRINTF "%s\n" "$TZ"