Changeset View
Changeset View
Standalone View
Standalone View
contrib/tzcode/tzselect.ksh
Show All 33 Lines | |||||
# mawk <https://invisible-island.net/mawk/> | # mawk <https://invisible-island.net/mawk/> | ||||
# nawk <https://github.com/onetrueawk/awk> | # nawk <https://github.com/onetrueawk/awk> | ||||
# Specify default values for environment variables if they are unset. | # Specify default values for environment variables if they are unset. | ||||
: ${AWK=awk} | : ${AWK=awk} | ||||
: ${TZDIR=`pwd`} | : ${TZDIR=`pwd`} | ||||
# Output one argument as-is to standard output. | # Output one argument as-is to standard output, with trailing newline. | ||||
# Safer than 'echo', which can mishandle '\' or leading '-'. | # Safer than 'echo', which can mishandle '\' or leading '-'. | ||||
say() { | say() { | ||||
printf '%s\n' "$1" | printf '%s\n' "$1" | ||||
} | } | ||||
# Check for awk Posix compliance. | # Check for awk Posix compliance. | ||||
($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1 | ($AWK -v x=y 'BEGIN { exit 123 }') </dev/null >/dev/null 2>&1 | ||||
[ $? = 123 ] || { | [ $? = 123 ] || { | ||||
Show All 26 Lines | Options: | ||||
--help | --help | ||||
Output this help. | Output this help. | ||||
Report bugs to $REPORT_BUGS_TO." | Report bugs to $REPORT_BUGS_TO." | ||||
# Ask the user to select from the function's arguments, | # Ask the user to select from the function's arguments, | ||||
# and assign the selected argument to the variable 'select_result'. | # 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, | # Exit on EOF or I/O error. Use the shell's nicer 'select' builtin if | ||||
# falling back on a less-nice but portable substitute otherwise. | # available, falling back on a portable substitute otherwise. | ||||
if | if | ||||
case $BASH_VERSION in | case $BASH_VERSION in | ||||
?*) : ;; | ?*) : ;; | ||||
'') | '') | ||||
# '; exit' should be redundant, but Dash doesn't properly fail without it. | # '; exit' should be redundant, but Dash doesn't properly fail without it. | ||||
(eval 'set --; select x; do break; done; exit') </dev/null 2>/dev/null | (eval 'set --; select x; do break; done; exit') </dev/null 2>/dev/null | ||||
esac | esac | ||||
then | then | ||||
▲ Show 20 Lines • Show All 97 Lines • ▼ Show 20 Lines | (umask 77 && mkdir -- "$tmp") | ||||
iconv -f UTF-8 -t //TRANSLIT <"$TZ_ZONE_TABLE" >$tmp/$zonetabtype.tab && | iconv -f UTF-8 -t //TRANSLIT <"$TZ_ZONE_TABLE" >$tmp/$zonetabtype.tab && | ||||
TZ_ZONE_TABLE=$tmp/$zonetabtype.tab | TZ_ZONE_TABLE=$tmp/$zonetabtype.tab | ||||
} | } | ||||
newline=' | newline=' | ||||
' | ' | ||||
IFS=$newline | IFS=$newline | ||||
# Awk script to output a country list. | |||||
output_country_list=' | |||||
BEGIN { FS = "\t" } | |||||
/^#$/ { next } | |||||
/^#[^@]/ { next } | |||||
{ | |||||
commentary = $0 ~ /^#@/ | |||||
if (commentary) { | |||||
col1ccs = substr($1, 3) | |||||
conts = $2 | |||||
} else { | |||||
col1ccs = $1 | |||||
conts = $3 | |||||
} | |||||
ncc = split(col1ccs, cc, /,/) | |||||
ncont = split(conts, cont, /,/) | |||||
for (i = 1; i <= ncc; i++) { | |||||
elsewhere = commentary | |||||
for (ci = 1; ci <= ncont; ci++) { | |||||
if (cont[ci] ~ continent_re) { | |||||
if (!cc_seen[cc[i]]++) cc_list[++ccs] = cc[i] | |||||
elsewhere = 0 | |||||
} | |||||
} | |||||
if (elsewhere) { | |||||
for (i = 1; i <= ncc; i++) { | |||||
cc_elsewhere[cc[i]] = 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_elsewhere[country]) continue | |||||
if (cc_name[country]) { | |||||
country = cc_name[country] | |||||
} | |||||
print country | |||||
} | |||||
} | |||||
' | |||||
# Awk script to read a time zone table and output the same table, | # Awk script to read a time zone table and output the same table, | ||||
# with each column preceded by its distance from 'here'. | # with each row preceded by its distance from 'here'. | ||||
output_distances=' | # If output_times is set, each row is instead preceded by its local time | ||||
# and any apostrophes are escaped for the shell. | |||||
output_distances_or_times=' | |||||
BEGIN { | BEGIN { | ||||
FS = "\t" | FS = "\t" | ||||
if (!output_times) { | |||||
while (getline <TZ_COUNTRY_TABLE) | while (getline <TZ_COUNTRY_TABLE) | ||||
if ($0 ~ /^[^#]/) | if ($0 ~ /^[^#]/) | ||||
country[$1] = $2 | country[$1] = $2 | ||||
country["US"] = "US" # Otherwise the strings get too long. | country["US"] = "US" # Otherwise the strings get too long. | ||||
} | } | ||||
} | |||||
function abs(x) { | function abs(x) { | ||||
return x < 0 ? -x : x; | return x < 0 ? -x : x; | ||||
} | } | ||||
function min(x, y) { | function min(x, y) { | ||||
return x < y ? x : y; | return x < y ? x : y; | ||||
} | } | ||||
function convert_coord(coord, deg, minute, ilen, sign, sec) { | function convert_coord(coord, deg, minute, ilen, sign, sec) { | ||||
if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9][0-9][0-9]([^0-9]|$)/) { | if (coord ~ /^[-+]?[0-9]?[0-9][0-9][0-9][0-9][0-9][0-9]([^0-9]|$)/) { | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | output_distances_or_times=' | ||||
function dist(lat1, long1, lat2, long2) { | function dist(lat1, long1, lat2, long2) { | ||||
return gcdist(lat1, long1, lat2, long2) + pardist(lat1, long1, lat2, long2) | return gcdist(lat1, long1, lat2, long2) + pardist(lat1, long1, lat2, long2) | ||||
} | } | ||||
BEGIN { | BEGIN { | ||||
coord_lat = convert_latitude(coord) | coord_lat = convert_latitude(coord) | ||||
coord_long = convert_longitude(coord) | coord_long = convert_longitude(coord) | ||||
} | } | ||||
/^[^#]/ { | /^[^#]/ { | ||||
here_lat = convert_latitude($2) | inline[inlines++] = $0 | ||||
here_long = convert_longitude($2) | ncc = split($1, cc, /,/) | ||||
for (i = 1; i <= ncc; i++) | |||||
cc_used[cc[i]]++ | |||||
} | |||||
END { | |||||
for (h = 0; h < inlines; h++) { | |||||
$0 = inline[h] | |||||
line = $1 "\t" $2 "\t" $3 | line = $1 "\t" $2 "\t" $3 | ||||
sep = "\t" | sep = "\t" | ||||
ncc = split($1, cc, /,/) | ncc = split($1, cc, /,/) | ||||
split("", item_seen) | |||||
item_seen[""] = 1 | |||||
for (i = 1; i <= ncc; i++) { | for (i = 1; i <= ncc; i++) { | ||||
line = line sep country[cc[i]] | item = cc_used[cc[i]] <= 1 ? country[cc[i]] : $4 | ||||
sep = ", " | if (item_seen[item]++) continue | ||||
line = line sep item | |||||
sep = "; " | |||||
} | } | ||||
if (NF == 4) | if (output_times) { | ||||
line = line " - " $4 | fmt = "TZ='\''%s'\'' date +'\''%d %%Y %%m %%d %%H:%%M %%a %%b\t%s'\''\n" | ||||
gsub(/'\''/, "&\\\\&&", line) | |||||
printf fmt, $3, h, line | |||||
} else { | |||||
here_lat = convert_latitude($2) | |||||
here_long = convert_longitude($2) | |||||
printf "%g\t%s\n", dist(coord_lat, coord_long, here_lat, here_long), line | 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. | # Begin the main loop. We come back here if the user wants to retry. | ||||
while | while | ||||
echo >&2 'Please identify a location' \ | echo >&2 'Please identify a location' \ | ||||
'so that time zone rules can be set correctly.' | 'so that time zone rules can be set correctly.' | ||||
continent= | continent= | ||||
country= | country= | ||||
region= | region= | ||||
case $coord in | case $coord in | ||||
?*) | ?*) | ||||
continent=coord;; | continent=coord;; | ||||
'') | '') | ||||
# Ask the user for continent or ocean. | # Ask the user for continent or ocean. | ||||
echo >&2 'Please select a continent, ocean, "coord", or "TZ".' | echo >&2 'Please select a continent, ocean, "coord", "TZ", or "time".' | ||||
quoted_continents=` | quoted_continents=` | ||||
$AWK ' | $AWK ' | ||||
function handle_entry(entry) { | function handle_entry(entry) { | ||||
entry = substr(entry, 1, index(entry, "/") - 1) | entry = substr(entry, 1, index(entry, "/") - 1) | ||||
if (entry == "America") | if (entry == "America") | ||||
entry = entry "s" | entry = entry "s" | ||||
if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/) | if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/) | ||||
Show All 14 Lines | echo >&2 'Please select a continent, ocean, "coord", "TZ", or "time".' | ||||
sort -u | | sort -u | | ||||
tr '\n' ' ' | tr '\n' ' ' | ||||
echo '' | echo '' | ||||
` | ` | ||||
eval ' | eval ' | ||||
doselect '"$quoted_continents"' \ | doselect '"$quoted_continents"' \ | ||||
"coord - I want to use geographical coordinates." \ | "coord - I want to use geographical coordinates." \ | ||||
"TZ - I want to specify the timezone using the Posix TZ format." | "TZ - I want to specify the timezone using the Posix TZ format." \ | ||||
"time - I know local time already." | |||||
continent=$select_result | continent=$select_result | ||||
case $continent in | case $continent in | ||||
Americas) continent=America;; | Americas) continent=America;; | ||||
*" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''` | *" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''` | ||||
esac | esac | ||||
' | ' | ||||
esac | esac | ||||
Show All 36 Lines | coord) | ||||
echo >&2 'For example, +4042-07403 stands for' | echo >&2 'For example, +4042-07403 stands for' | ||||
echo >&2 '40 degrees 42 minutes north,' \ | echo >&2 '40 degrees 42 minutes north,' \ | ||||
'74 degrees 3 minutes west.' | '74 degrees 3 minutes west.' | ||||
read coord;; | read coord;; | ||||
esac | esac | ||||
distance_table=`$AWK \ | distance_table=`$AWK \ | ||||
-v coord="$coord" \ | -v coord="$coord" \ | ||||
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ | -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ | ||||
"$output_distances" <"$TZ_ZONE_TABLE" | | "$output_distances_or_times" <"$TZ_ZONE_TABLE" | | ||||
sort -n | | sort -n | | ||||
sed "${location_limit}q" | sed "${location_limit}q" | ||||
` | ` | ||||
regions=`say "$distance_table" | $AWK ' | regions=`$AWK \ | ||||
BEGIN { FS = "\t" } | -v distance_table="$distance_table" ' | ||||
{ print $NF } | BEGIN { | ||||
nlines = split(distance_table, line, /\n/) | |||||
for (nr = 1; nr <= nlines; nr++) { | |||||
nf = split(line[nr], f, /\t/) | |||||
print f[nf] | |||||
} | |||||
} | |||||
'` | '` | ||||
echo >&2 'Please select one of the following timezones,' \ | echo >&2 'Please select one of the following timezones,' | ||||
echo >&2 'listed roughly in increasing order' \ | echo >&2 'listed roughly in increasing order' \ | ||||
"of distance from $coord". | "of distance from $coord". | ||||
doselect $regions | doselect $regions | ||||
region=$select_result | region=$select_result | ||||
TZ=`say "$distance_table" | $AWK -v region="$region" ' | TZ=`$AWK \ | ||||
BEGIN { FS="\t" } | -v distance_table="$distance_table" \ | ||||
$NF == region { print $4 } | -v region="$region" ' | ||||
BEGIN { | |||||
nlines = split(distance_table, line, /\n/) | |||||
for (nr = 1; nr <= nlines; nr++) { | |||||
nf = split(line[nr], f, /\t/) | |||||
if (f[nf] == region) { | |||||
print f[4] | |||||
} | |||||
} | |||||
} | |||||
'` | '` | ||||
;; | ;; | ||||
*) | *) | ||||
case $continent in | |||||
time) | |||||
minute_format='%a %b %d %H:%M' | |||||
old_minute=`TZ=UTC0 date +"$minute_format"` | |||||
for i in 1 2 3 | |||||
do | |||||
time_table_command=` | |||||
$AWK -v output_times=1 \ | |||||
"$output_distances_or_times" <"$TZ_ZONE_TABLE" | |||||
` | |||||
time_table=`eval "$time_table_command"` | |||||
new_minute=`TZ=UTC0 date +"$minute_format"` | |||||
case $old_minute in | |||||
"$new_minute") break;; | |||||
esac | |||||
old_minute=$new_minute | |||||
done | |||||
echo >&2 "The system says Universal Time is $new_minute." | |||||
echo >&2 "Assuming that's correct, what is the local time?" | |||||
eval doselect ` | |||||
say "$time_table" | | |||||
sort -k2n -k2,5 -k1n | | |||||
$AWK '{ | |||||
line = $6 " " $7 " " $4 " " $5 | |||||
if (line == oldline) next | |||||
oldline = line | |||||
gsub(/'\''/, "&\\\\&&", line) | |||||
printf "'\''%s'\''\n", line | |||||
}' | |||||
` | |||||
time=$select_result | |||||
zone_table=` | |||||
say "$time_table" | | |||||
$AWK -v time="$time" '{ | |||||
if ($6 " " $7 " " $4 " " $5 == time) { | |||||
sub(/[^\t]*\t/, "") | |||||
} | |||||
}' | |||||
` | |||||
countries=` | |||||
say "$zone_table" | | |||||
$AWK \ | |||||
-v continent_re='' \ | |||||
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ | |||||
"$output_country_list" | | |||||
sort -f | |||||
` | |||||
;; | |||||
*) | |||||
zone_table=file | |||||
# Get list of names of countries in the continent or ocean. | # Get list of names of countries in the continent or ocean. | ||||
countries=`$AWK \ | countries=`$AWK \ | ||||
-v continent_re="^$continent/" \ | -v continent_re="^$continent/" \ | ||||
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ | -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ | ||||
' | "$output_country_list" \ | ||||
BEGIN { FS = "\t" } | <"$TZ_ZONE_TABLE" | sort -f | ||||
/^#$/ { next } | `;; | ||||
/^#[^@]/ { next } | esac | ||||
{ | |||||
commentary = $0 ~ /^#@/ | |||||
if (commentary) { | |||||
col1ccs = substr($1, 3) | |||||
conts = $2 | |||||
} else { | |||||
col1ccs = $1 | |||||
conts = $3 | |||||
} | |||||
ncc = split(col1ccs, cc, /,/) | |||||
ncont = split(conts, cont, /,/) | |||||
for (i = 1; i <= ncc; i++) { | |||||
elsewhere = commentary | |||||
for (ci = 1; ci <= ncont; ci++) { | |||||
if (cont[ci] ~ continent_re) { | |||||
if (!cc_seen[cc[i]]++) cc_list[++ccs] = cc[i] | |||||
elsewhere = 0 | |||||
} | |||||
} | |||||
if (elsewhere) { | |||||
for (i = 1; i <= ncc; i++) { | |||||
cc_elsewhere[cc[i]] = 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_elsewhere[country]) continue | |||||
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. | # If there's more than one country, ask the user which one. | ||||
case $countries in | case $countries in | ||||
*"$newline"*) | *"$newline"*) | ||||
echo >&2 'Please select a country' \ | echo >&2 'Please select a country' \ | ||||
'whose clocks agree with yours.' | 'whose clocks agree with yours.' | ||||
doselect $countries | doselect $countries | ||||
country_result=$select_result | |||||
country=$select_result;; | country=$select_result;; | ||||
*) | *) | ||||
country=$countries | country=$countries | ||||
esac | esac | ||||
# Get list of timezones in the country. | # Get list of timezones in the country. | ||||
regions=`$AWK \ | regions=` | ||||
case $zone_table in | |||||
file) cat -- "$TZ_ZONE_TABLE";; | |||||
*) say "$zone_table";; | |||||
esac | | |||||
$AWK \ | |||||
-v country="$country" \ | -v country="$country" \ | ||||
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ | -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ | ||||
' | ' | ||||
BEGIN { | BEGIN { | ||||
FS = "\t" | FS = "\t" | ||||
cc = country | cc = country | ||||
while (getline <TZ_COUNTRY_TABLE) { | while (getline <TZ_COUNTRY_TABLE) { | ||||
if ($0 !~ /^#/ && country == $2) { | if ($0 !~ /^#/ && country == $2) { | ||||
cc = $1 | cc = $1 | ||||
break | break | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/^#/ { next } | /^#/ { next } | ||||
$1 ~ cc { print $4 } | $1 ~ cc { print $4 } | ||||
' <"$TZ_ZONE_TABLE"` | ' | ||||
` | |||||
# If there's more than one region, ask the user which one. | # If there's more than one region, ask the user which one. | ||||
case $regions in | case $regions in | ||||
*"$newline"*) | *"$newline"*) | ||||
echo >&2 'Please select one of the following timezones.' | echo >&2 'Please select one of the following timezones.' | ||||
doselect $regions | doselect $regions | ||||
region=$select_result;; | region=$select_result | ||||
*) | |||||
region=$regions | |||||
esac | esac | ||||
# Determine TZ from country and region. | # Determine TZ from country and region. | ||||
TZ=`$AWK \ | TZ=` | ||||
case $zone_table in | |||||
file) cat -- "$TZ_ZONE_TABLE";; | |||||
*) say "$zone_table";; | |||||
esac | | |||||
$AWK \ | |||||
-v country="$country" \ | -v country="$country" \ | ||||
-v region="$region" \ | -v region="$region" \ | ||||
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ | -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \ | ||||
' | ' | ||||
BEGIN { | BEGIN { | ||||
FS = "\t" | FS = "\t" | ||||
cc = country | cc = country | ||||
while (getline <TZ_COUNTRY_TABLE) { | while (getline <TZ_COUNTRY_TABLE) { | ||||
if ($0 !~ /^#/ && country == $2) { | if ($0 !~ /^#/ && country == $2) { | ||||
cc = $1 | cc = $1 | ||||
break | break | ||||
} | } | ||||
} | } | ||||
} | } | ||||
/^#/ { next } | /^#/ { next } | ||||
$1 ~ cc && $4 == region { print $3 } | $1 ~ cc && ($4 == region || !region) { print $3 } | ||||
' <"$TZ_ZONE_TABLE"` | ' | ||||
`;; | |||||
esac | esac | ||||
# Make sure the corresponding zoneinfo file exists. | # Make sure the corresponding zoneinfo file exists. | ||||
TZ_for_date=$TZDIR/$TZ | TZ_for_date=$TZDIR/$TZ | ||||
<"$TZ_for_date" || { | <"$TZ_for_date" || { | ||||
say >&2 "$0: time zone files are not set up correctly" | say >&2 "$0: time zone files are not set up correctly" | ||||
exit 1 | exit 1 | ||||
} | } | ||||
Show All 19 Lines | Universal Time is now: $UTdate." | ||||
break | break | ||||
esac | esac | ||||
done | done | ||||
# Output TZ info and ask the user to confirm. | # Output TZ info and ask the user to confirm. | ||||
echo >&2 "" | echo >&2 "" | ||||
echo >&2 "The following information has been given:" | echo >&2 "Based on the following information:" | ||||
echo >&2 "" | echo >&2 "" | ||||
case $country%$region%$coord in | case $time%$country_result%$region%$coord in | ||||
?*%?*%) say >&2 " $country$newline $region";; | ?*%?*%?*%) | ||||
?*%%) say >&2 " $country";; | say >&2 " $time$newline $country_result$newline $region";; | ||||
%?*%?*) say >&2 " coord $coord$newline $region";; | ?*%?*%%|?*%%?*%) say >&2 " $time$newline $country_result$region";; | ||||
%%?*) say >&2 " coord $coord";; | ?*%%%) say >&2 " $time";; | ||||
%?*%?*%) say >&2 " $country_result$newline $region";; | |||||
%?*%%) say >&2 " $country_result";; | |||||
%%?*%?*) say >&2 " coord $coord$newline $region";; | |||||
%%%?*) say >&2 " coord $coord";; | |||||
*) say >&2 " TZ='$TZ'" | *) say >&2 " TZ='$TZ'" | ||||
esac | esac | ||||
say >&2 "" | say >&2 "" | ||||
say >&2 "Therefore TZ='$TZ' will be used.$extra_info" | say >&2 "TZ='$TZ' will be used.$extra_info" | ||||
say >&2 "Is the above information OK?" | say >&2 "Is the above information OK?" | ||||
doselect Yes No | doselect Yes No | ||||
ok=$select_result | ok=$select_result | ||||
case $ok in | case $ok in | ||||
Yes) break | Yes) break | ||||
esac | esac | ||||
do coord= | do coord= | ||||
Show All 16 Lines |