mirror of
				https://sourceware.org/git/glibc.git
				synced 2025-11-03 20:53:13 +03:00 
			
		
		
		
	This script works fine under bash (which we already require), so drop the legacy ksh munging. Signed-off-by: Mike Frysinger <vapier@gentoo.org>
		
			
				
	
	
		
			505 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			505 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
#!/bin/bash
 | 
						|
 | 
						|
PKGVERSION='(tzcode) '
 | 
						|
TZVERSION=see_Makefile
 | 
						|
REPORT_BUGS_TO=tz@iana.org
 | 
						|
 | 
						|
# Ask the user about the time zone, and output the resulting TZ value to stdout.
 | 
						|
# Interact with the user via stderr and stdin.
 | 
						|
 | 
						|
# Contributed by Paul Eggert.
 | 
						|
 | 
						|
# Porting notes:
 | 
						|
#
 | 
						|
# This script requires a Posix-like shell and prefers the extension of a
 | 
						|
# 'select' statement.  The 'select' statement was introduced in the
 | 
						|
# Korn shell and is available in Bash and other shell implementations.
 | 
						|
# If your host lacks both Bash and the Korn shell, you can get their
 | 
						|
# source from one of these locations:
 | 
						|
#
 | 
						|
#	Bash <http://www.gnu.org/software/bash/bash.html>
 | 
						|
#	Korn Shell <http://www.kornshell.com/>
 | 
						|
#	Public Domain Korn Shell <http://www.cs.mun.ca/~michael/pdksh/>
 | 
						|
#
 | 
						|
# For portability to Solaris 9 /bin/sh this script avoids some POSIX
 | 
						|
# features and common extensions, such as $(...) (which works sometimes
 | 
						|
# but not others), $((...)), and $10.
 | 
						|
#
 | 
						|
# This script also uses several features of modern awk programs.
 | 
						|
# If your host lacks awk, or has an old awk that does not conform to Posix,
 | 
						|
# you can use either of the following free programs instead:
 | 
						|
#
 | 
						|
#	Gawk (GNU awk) <http://www.gnu.org/software/gawk/>
 | 
						|
#	mawk <http://invisible-island.net/mawk/>
 | 
						|
 | 
						|
 | 
						|
# Specify default values for environment variables if they are unset.
 | 
						|
: ${AWK=awk}
 | 
						|
: ${TZDIR=`pwd`}
 | 
						|
 | 
						|
# Check for awk Posix compliance.
 | 
						|
($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1
 | 
						|
[ $? = 123 ] || {
 | 
						|
	echo >&2 "$0: Sorry, your \`$AWK' program is not Posix compatible."
 | 
						|
	exit 1
 | 
						|
}
 | 
						|
 | 
						|
coord=
 | 
						|
location_limit=10
 | 
						|
 | 
						|
usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT]
 | 
						|
Select a time zone interactively.
 | 
						|
 | 
						|
Options:
 | 
						|
 | 
						|
  -c COORD
 | 
						|
    Instead of asking for continent and then country and then city,
 | 
						|
    ask for selection from time zones whose largest cities
 | 
						|
    are closest to the location with geographical coordinates COORD.
 | 
						|
    COORD should use ISO 6709 notation, for example, '-c +4852+00220'
 | 
						|
    for Paris (in degrees and minutes, North and East), or
 | 
						|
    '-c -35-058' for Buenos Aires (in degrees, South and West).
 | 
						|
 | 
						|
  -n LIMIT
 | 
						|
    Display at most LIMIT locations when -c is used (default $location_limit).
 | 
						|
 | 
						|
  --version
 | 
						|
    Output version information.
 | 
						|
 | 
						|
  --help
 | 
						|
    Output this help.
 | 
						|
 | 
						|
Report bugs to $REPORT_BUGS_TO."
 | 
						|
 | 
						|
# Ask the user to select from the function's arguments,
 | 
						|
# and assign the selected argument to the variable 'select_result'.
 | 
						|
# Exit on EOF or I/O error.  Use the shell's 'select' builtin if available,
 | 
						|
# falling back on a less-nice but portable substitute otherwise.
 | 
						|
if
 | 
						|
  case $BASH_VERSION in
 | 
						|
  ?*) : ;;
 | 
						|
  '')
 | 
						|
    # '; exit' should be redundant, but Dash doesn't properly fail without it.
 | 
						|
    (eval 'set --; select x; do break; done; exit') 2>/dev/null
 | 
						|
  esac
 | 
						|
then
 | 
						|
  # Do this inside 'eval', as otherwise the shell might exit when parsing it
 | 
						|
  # even though it is never executed.
 | 
						|
  eval '
 | 
						|
    doselect() {
 | 
						|
      select select_result
 | 
						|
      do
 | 
						|
	case $select_result in
 | 
						|
	"") echo >&2 "Please enter a number in range." ;;
 | 
						|
	?*) break
 | 
						|
	esac
 | 
						|
      done || exit
 | 
						|
    }
 | 
						|
 | 
						|
    # Work around a bug in bash 1.14.7 and earlier, where $PS3 is sent to stdout.
 | 
						|
    case $BASH_VERSION in
 | 
						|
    [01].*)
 | 
						|
      case `echo 1 | (select x in x; do break; done) 2>/dev/null` in
 | 
						|
      ?*) PS3=
 | 
						|
      esac
 | 
						|
    esac
 | 
						|
  '
 | 
						|
else
 | 
						|
  doselect() {
 | 
						|
    # Field width of the prompt numbers.
 | 
						|
    select_width=`expr $# : '.*'`
 | 
						|
 | 
						|
    select_i=
 | 
						|
 | 
						|
    while :
 | 
						|
    do
 | 
						|
      case $select_i in
 | 
						|
      '')
 | 
						|
	select_i=0
 | 
						|
	for select_word
 | 
						|
	do
 | 
						|
	  select_i=`expr $select_i + 1`
 | 
						|
	  printf >&2 "%${select_width}d) %s\\n" $select_i "$select_word"
 | 
						|
	done ;;
 | 
						|
      *[!0-9]*)
 | 
						|
	echo >&2 'Please enter a number in range.' ;;
 | 
						|
      *)
 | 
						|
	if test 1 -le $select_i && test $select_i -le $#; then
 | 
						|
	  shift `expr $select_i - 1`
 | 
						|
	  select_result=$1
 | 
						|
	  break
 | 
						|
	fi
 | 
						|
	echo >&2 'Please enter a number in range.'
 | 
						|
      esac
 | 
						|
 | 
						|
      # Prompt and read input.
 | 
						|
      printf >&2 %s "${PS3-#? }"
 | 
						|
      read select_i || exit
 | 
						|
    done
 | 
						|
  }
 | 
						|
fi
 | 
						|
 | 
						|
while getopts c:n:-: opt
 | 
						|
do
 | 
						|
    case $opt$OPTARG in
 | 
						|
    c*)
 | 
						|
	coord=$OPTARG ;;
 | 
						|
    n*)
 | 
						|
	location_limit=$OPTARG ;;
 | 
						|
    -help)
 | 
						|
	exec echo "$usage" ;;
 | 
						|
    -version)
 | 
						|
	exec echo "tzselect $PKGVERSION$TZVERSION" ;;
 | 
						|
    -*)
 | 
						|
	echo >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1 ;;
 | 
						|
    *)
 | 
						|
	echo >&2 "$0: try '$0 --help'"; exit 1 ;;
 | 
						|
    esac
 | 
						|
done
 | 
						|
 | 
						|
shift `expr $OPTIND - 1`
 | 
						|
case $# in
 | 
						|
0) ;;
 | 
						|
*) echo >&2 "$0: $1: unknown argument"; exit 1 ;;
 | 
						|
esac
 | 
						|
 | 
						|
# Make sure the tables are readable.
 | 
						|
TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab
 | 
						|
TZ_ZONE_TABLE=$TZDIR/zone.tab
 | 
						|
for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
 | 
						|
do
 | 
						|
	<$f || {
 | 
						|
		echo >&2 "$0: time zone files are not set up correctly"
 | 
						|
		exit 1
 | 
						|
	}
 | 
						|
done
 | 
						|
 | 
						|
newline='
 | 
						|
'
 | 
						|
IFS=$newline
 | 
						|
 | 
						|
 | 
						|
# Awk script to read a time zone table and output the same table,
 | 
						|
# with each column preceded by its distance from 'here'.
 | 
						|
output_distances='
 | 
						|
  BEGIN {
 | 
						|
    FS = "\t"
 | 
						|
    while (getline <TZ_COUNTRY_TABLE)
 | 
						|
      if ($0 ~ /^[^#]/)
 | 
						|
        country[$1] = $2
 | 
						|
    country["US"] = "US" # Otherwise the strings get too long.
 | 
						|
  }
 | 
						|
  function convert_coord(coord, deg, min, ilen, sign, sec) {
 | 
						|
    if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9][0-9][0-9]([^0-9]|$)/) {
 | 
						|
      degminsec = coord
 | 
						|
      intdeg = degminsec < 0 ? -int(-degminsec / 10000) : int(degminsec / 10000)
 | 
						|
      minsec = degminsec - intdeg * 10000
 | 
						|
      intmin = minsec < 0 ? -int(-minsec / 100) : int(minsec / 100)
 | 
						|
      sec = minsec - intmin * 100
 | 
						|
      deg = (intdeg * 3600 + intmin * 60 + sec) / 3600
 | 
						|
    } else if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9]([^0-9]|$)/) {
 | 
						|
      degmin = coord
 | 
						|
      intdeg = degmin < 0 ? -int(-degmin / 100) : int(degmin / 100)
 | 
						|
      min = degmin - intdeg * 100
 | 
						|
      deg = (intdeg * 60 + min) / 60
 | 
						|
    } else
 | 
						|
      deg = coord
 | 
						|
    return deg * 0.017453292519943296
 | 
						|
  }
 | 
						|
  function convert_latitude(coord) {
 | 
						|
    match(coord, /..*[-+]/)
 | 
						|
    return convert_coord(substr(coord, 1, RLENGTH - 1))
 | 
						|
  }
 | 
						|
  function convert_longitude(coord) {
 | 
						|
    match(coord, /..*[-+]/)
 | 
						|
    return convert_coord(substr(coord, RLENGTH))
 | 
						|
  }
 | 
						|
  # Great-circle distance between points with given latitude and longitude.
 | 
						|
  # Inputs and output are in radians.  This uses the great-circle special
 | 
						|
  # case of the Vicenty formula for distances on ellipsoids.
 | 
						|
  function dist(lat1, long1, lat2, long2, dlong, x, y, num, denom) {
 | 
						|
    dlong = long2 - long1
 | 
						|
    x = cos (lat2) * sin (dlong)
 | 
						|
    y = cos (lat1) * sin (lat2) - sin (lat1) * cos (lat2) * cos (dlong)
 | 
						|
    num = sqrt (x * x + y * y)
 | 
						|
    denom = sin (lat1) * sin (lat2) + cos (lat1) * cos (lat2) * cos (dlong)
 | 
						|
    return atan2(num, denom)
 | 
						|
  }
 | 
						|
  BEGIN {
 | 
						|
    coord_lat = convert_latitude(coord)
 | 
						|
    coord_long = convert_longitude(coord)
 | 
						|
  }
 | 
						|
  /^[^#]/ {
 | 
						|
    here_lat = convert_latitude($2)
 | 
						|
    here_long = convert_longitude($2)
 | 
						|
    line = $1 "\t" $2 "\t" $3 "\t" country[$1]
 | 
						|
    if (NF == 4)
 | 
						|
      line = line " - " $4
 | 
						|
    printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), line
 | 
						|
  }
 | 
						|
'
 | 
						|
 | 
						|
# Begin the main loop.  We come back here if the user wants to retry.
 | 
						|
while
 | 
						|
 | 
						|
	echo >&2 'Please identify a location' \
 | 
						|
		'so that time zone rules can be set correctly.'
 | 
						|
 | 
						|
	continent=
 | 
						|
	country=
 | 
						|
	region=
 | 
						|
 | 
						|
	case $coord in
 | 
						|
	?*)
 | 
						|
		continent=coord;;
 | 
						|
	'')
 | 
						|
 | 
						|
	# Ask the user for continent or ocean.
 | 
						|
 | 
						|
	echo >&2 'Please select a continent, ocean, "coord", or "TZ".'
 | 
						|
 | 
						|
        quoted_continents=`
 | 
						|
	  $AWK '
 | 
						|
	    BEGIN { FS = "\t" }
 | 
						|
	    /^[^#]/ {
 | 
						|
              entry = substr($3, 1, index($3, "/") - 1)
 | 
						|
              if (entry == "America")
 | 
						|
		entry = entry "s"
 | 
						|
              if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/)
 | 
						|
		entry = entry " Ocean"
 | 
						|
              printf "'\''%s'\''\n", entry
 | 
						|
            }
 | 
						|
          ' $TZ_ZONE_TABLE |
 | 
						|
	  sort -u |
 | 
						|
	  tr '\n' ' '
 | 
						|
	  echo ''
 | 
						|
	`
 | 
						|
 | 
						|
	eval '
 | 
						|
	    doselect '"$quoted_continents"' \
 | 
						|
		"coord - I want to use geographical coordinates." \
 | 
						|
		"TZ - I want to specify the time zone using the Posix TZ format."
 | 
						|
	    continent=$select_result
 | 
						|
	    case $continent in
 | 
						|
	    Americas) continent=America;;
 | 
						|
	    *" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''`
 | 
						|
	    esac
 | 
						|
	'
 | 
						|
	esac
 | 
						|
 | 
						|
	case $continent in
 | 
						|
	TZ)
 | 
						|
		# Ask the user for a Posix TZ string.  Check that it conforms.
 | 
						|
		while
 | 
						|
			echo >&2 'Please enter the desired value' \
 | 
						|
				'of the TZ environment variable.'
 | 
						|
			echo >&2 'For example, GST-10 is a zone named GST' \
 | 
						|
				'that is 10 hours ahead (east) of UTC.'
 | 
						|
			read TZ
 | 
						|
			$AWK -v TZ="$TZ" 'BEGIN {
 | 
						|
				tzname = "[^-+,0-9][^-+,0-9][^-+,0-9]+"
 | 
						|
				time = "[0-2]?[0-9](:[0-5][0-9](:[0-5][0-9])?)?"
 | 
						|
				offset = "[-+]?" time
 | 
						|
				date = "(J?[0-9]+|M[0-9]+\.[0-9]+\.[0-9]+)"
 | 
						|
				datetime = "," date "(/" time ")?"
 | 
						|
				tzpattern = "^(:.*|" tzname offset "(" tzname \
 | 
						|
				  "(" offset ")?(" datetime datetime ")?)?)$"
 | 
						|
				if (TZ ~ tzpattern) exit 1
 | 
						|
				exit 0
 | 
						|
			}'
 | 
						|
		do
 | 
						|
			echo >&2 "\`$TZ' is not a conforming" \
 | 
						|
				'Posix time zone string.'
 | 
						|
		done
 | 
						|
		TZ_for_date=$TZ;;
 | 
						|
	*)
 | 
						|
		case $continent in
 | 
						|
		coord)
 | 
						|
		    case $coord in
 | 
						|
		    '')
 | 
						|
			echo >&2 'Please enter coordinates' \
 | 
						|
				'in ISO 6709 notation.'
 | 
						|
			echo >&2 'For example, +4042-07403 stands for'
 | 
						|
			echo >&2 '40 degrees 42 minutes north,' \
 | 
						|
				'74 degrees 3 minutes west.'
 | 
						|
			read coord;;
 | 
						|
		    esac
 | 
						|
		    distance_table=`$AWK \
 | 
						|
			    -v coord="$coord" \
 | 
						|
			    -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
 | 
						|
			    "$output_distances" <$TZ_ZONE_TABLE |
 | 
						|
		      sort -n |
 | 
						|
		      sed "${location_limit}q"
 | 
						|
		    `
 | 
						|
		    regions=`echo "$distance_table" | $AWK '
 | 
						|
		      BEGIN { FS = "\t" }
 | 
						|
		      { print $NF }
 | 
						|
		    '`
 | 
						|
		    echo >&2 'Please select one of the following' \
 | 
						|
			    'time zone regions,'
 | 
						|
		    echo >&2 'listed roughly in increasing order' \
 | 
						|
			    "of distance from $coord".
 | 
						|
		    doselect $regions
 | 
						|
		    region=$select_result
 | 
						|
		    TZ=`echo "$distance_table" | $AWK -v region="$region" '
 | 
						|
		      BEGIN { FS="\t" }
 | 
						|
		      $NF == region { print $4 }
 | 
						|
		    '`
 | 
						|
		    ;;
 | 
						|
		*)
 | 
						|
		# Get list of names of countries in the continent or ocean.
 | 
						|
		countries=`$AWK \
 | 
						|
			-v continent="$continent" \
 | 
						|
			-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
 | 
						|
		'
 | 
						|
			BEGIN { FS = "\t" }
 | 
						|
			/^#/ { next }
 | 
						|
			$3 ~ ("^" continent "/") {
 | 
						|
				if (!cc_seen[$1]++) cc_list[++ccs] = $1
 | 
						|
			}
 | 
						|
			END {
 | 
						|
				while (getline <TZ_COUNTRY_TABLE) {
 | 
						|
					if ($0 !~ /^#/) cc_name[$1] = $2
 | 
						|
				}
 | 
						|
				for (i = 1; i <= ccs; i++) {
 | 
						|
					country = cc_list[i]
 | 
						|
					if (cc_name[country]) {
 | 
						|
					  country = cc_name[country]
 | 
						|
					}
 | 
						|
					print country
 | 
						|
				}
 | 
						|
			}
 | 
						|
		' <$TZ_ZONE_TABLE | sort -f`
 | 
						|
 | 
						|
 | 
						|
		# If there's more than one country, ask the user which one.
 | 
						|
		case $countries in
 | 
						|
		*"$newline"*)
 | 
						|
			echo >&2 'Please select a country' \
 | 
						|
				'whose clocks agree with yours.'
 | 
						|
			doselect $countries
 | 
						|
			country=$select_result;;
 | 
						|
		*)
 | 
						|
			country=$countries
 | 
						|
		esac
 | 
						|
 | 
						|
 | 
						|
		# Get list of names of time zone rule regions in the country.
 | 
						|
		regions=`$AWK \
 | 
						|
			-v country="$country" \
 | 
						|
			-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
 | 
						|
		'
 | 
						|
			BEGIN {
 | 
						|
				FS = "\t"
 | 
						|
				cc = country
 | 
						|
				while (getline <TZ_COUNTRY_TABLE) {
 | 
						|
					if ($0 !~ /^#/  &&  country == $2) {
 | 
						|
						cc = $1
 | 
						|
						break
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			$1 == cc { print $4 }
 | 
						|
		' <$TZ_ZONE_TABLE`
 | 
						|
 | 
						|
 | 
						|
		# If there's more than one region, ask the user which one.
 | 
						|
		case $regions in
 | 
						|
		*"$newline"*)
 | 
						|
			echo >&2 'Please select one of the following' \
 | 
						|
				'time zone regions.'
 | 
						|
			doselect $regions
 | 
						|
			region=$select_result;;
 | 
						|
		*)
 | 
						|
			region=$regions
 | 
						|
		esac
 | 
						|
 | 
						|
		# Determine TZ from country and region.
 | 
						|
		TZ=`$AWK \
 | 
						|
			-v country="$country" \
 | 
						|
			-v region="$region" \
 | 
						|
			-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
 | 
						|
		'
 | 
						|
			BEGIN {
 | 
						|
				FS = "\t"
 | 
						|
				cc = country
 | 
						|
				while (getline <TZ_COUNTRY_TABLE) {
 | 
						|
					if ($0 !~ /^#/  &&  country == $2) {
 | 
						|
						cc = $1
 | 
						|
						break
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
			$1 == cc && $4 == region { print $3 }
 | 
						|
		' <$TZ_ZONE_TABLE`
 | 
						|
		esac
 | 
						|
 | 
						|
		# Make sure the corresponding zoneinfo file exists.
 | 
						|
		TZ_for_date=$TZDIR/$TZ
 | 
						|
		<$TZ_for_date || {
 | 
						|
			echo >&2 "$0: time zone files are not set up correctly"
 | 
						|
			exit 1
 | 
						|
		}
 | 
						|
	esac
 | 
						|
 | 
						|
 | 
						|
	# Use the proposed TZ to output the current date relative to UTC.
 | 
						|
	# Loop until they agree in seconds.
 | 
						|
	# Give up after 8 unsuccessful tries.
 | 
						|
 | 
						|
	extra_info=
 | 
						|
	for i in 1 2 3 4 5 6 7 8
 | 
						|
	do
 | 
						|
		TZdate=`LANG=C TZ="$TZ_for_date" date`
 | 
						|
		UTdate=`LANG=C TZ=UTC0 date`
 | 
						|
		TZsec=`expr "$TZdate" : '.*:\([0-5][0-9]\)'`
 | 
						|
		UTsec=`expr "$UTdate" : '.*:\([0-5][0-9]\)'`
 | 
						|
		case $TZsec in
 | 
						|
		$UTsec)
 | 
						|
			extra_info="
 | 
						|
Local time is now:	$TZdate.
 | 
						|
Universal Time is now:	$UTdate."
 | 
						|
			break
 | 
						|
		esac
 | 
						|
	done
 | 
						|
 | 
						|
 | 
						|
	# Output TZ info and ask the user to confirm.
 | 
						|
 | 
						|
	echo >&2 ""
 | 
						|
	echo >&2 "The following information has been given:"
 | 
						|
	echo >&2 ""
 | 
						|
	case $country%$region%$coord in
 | 
						|
	?*%?*%)	echo >&2 "	$country$newline	$region";;
 | 
						|
	?*%%)	echo >&2 "	$country";;
 | 
						|
	%?*%?*) echo >&2 "	coord $coord$newline	$region";;
 | 
						|
	%%?*)	echo >&2 "	coord $coord";;
 | 
						|
	+)	echo >&2 "	TZ='$TZ'"
 | 
						|
	esac
 | 
						|
	echo >&2 ""
 | 
						|
	echo >&2 "Therefore TZ='$TZ' will be used.$extra_info"
 | 
						|
	echo >&2 "Is the above information OK?"
 | 
						|
 | 
						|
	doselect Yes No
 | 
						|
	ok=$select_result
 | 
						|
	case $ok in
 | 
						|
	Yes) break
 | 
						|
	esac
 | 
						|
do coord=
 | 
						|
done
 | 
						|
 | 
						|
case $SHELL in
 | 
						|
*csh) file=.login line="setenv TZ '$TZ'";;
 | 
						|
*) file=.profile line="TZ='$TZ'; export TZ"
 | 
						|
esac
 | 
						|
 | 
						|
echo >&2 "
 | 
						|
You can make this change permanent for yourself by appending the line
 | 
						|
	$line
 | 
						|
to the file '$file' in your home directory; then log out and log in again.
 | 
						|
 | 
						|
Here is that TZ value again, this time on standard output so that you
 | 
						|
can use the $0 command in shell scripts:"
 | 
						|
 | 
						|
echo "$TZ"
 |