[tz] [PROPOSED 14/18] tzselect: do not create temporary files
Paul Eggert
eggert at cs.ucla.edu
Tue Dec 19 07:26:03 UTC 2023
* NEWS: Mention this.
* tzselect.ksh (TZ_COUNTRY_TABLE, TZ_ZONE_TABLE): Now the contents
of the files, not the file names. All uses changed. This lets us
avoid the need to create temporary files; instead, we just update
the variable contents.
(read_file): New function, to implement this.
(output_country_list, output_distances_or_times): Adjust to this.
(sorted_table): New var, to help implement this.
(continent_re): New var, to simplify country selection.
---
NEWS | 2 +
tzselect.ksh | 237 +++++++++++++++++++++++++++++----------------------
2 files changed, 135 insertions(+), 104 deletions(-)
diff --git a/NEWS b/NEWS
index a8b7972f..f2d0bc3c 100644
--- a/NEWS
+++ b/NEWS
@@ -32,6 +32,8 @@ Unreleased, experimental changes
DST was in effect before the transition too. (Thanks to Alois
Treindl for debugging help.)
+ tzselect no longer creates temporary files.
+
tzselect no longer mishandles the following:
Spaces and most other special characters in BUGEMAIL, PACKAGE,
diff --git a/tzselect.ksh b/tzselect.ksh
index 43d91799..d2b3ecda 100644
--- a/tzselect.ksh
+++ b/tzselect.ksh
@@ -180,35 +180,23 @@ else
translit=false
fi
-# Make sure the tables are readable.
-TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab
-TZ_ZONE_TABLE=$TZDIR/$zonetabtype.tab
-for f in "$TZ_COUNTRY_TABLE" "$TZ_ZONE_TABLE"
-do
- <"$f" || {
- say >&2 "$0: time zone files are not set up correctly"
- exit 1
- }
-done
-
-# If the current locale does not support UTF-8, convert data to current
-# locale's format if possible, as the shell aligns columns better that way.
-# Check the UTF-8 of U+12345 CUNEIFORM SIGN URU TIMES KI.
-$translit && {
- { tmp=`(mktemp -d) 2>/dev/null` || {
- tmp=${TMPDIR-/tmp}/tzselect.$$ &&
- (umask 77 && mkdir -- "$tmp")
- };} &&
- trap 'status=$?; rm -fr -- "$tmp"; exit $status' 0 HUP INT PIPE TERM &&
- { (iconv -f UTF-8 -t //TRANSLIT <"$TZ_COUNTRY_TABLE" >$tmp/iso3166.tab) \
- 2>/dev/null ||
- (iconv -f UTF-8 <"$TZ_COUNTRY_TABLE" >$tmp/iso3166.tab) \
- 2>/dev/null
- } &&
- TZ_COUNTRY_TABLE=$tmp/iso3166.tab &&
- iconv -f UTF-8 -t //TRANSLIT <"$TZ_ZONE_TABLE" >$tmp/$zonetabtype.tab &&
- TZ_ZONE_TABLE=$tmp/$zonetabtype.tab
+# Read into shell variable $1 the contents of file $2.
+# Convert to the current locale's encoding if possible,
+# as the shell aligns columns better that way.
+# If GNU iconv's //TRANSLIT does not work, fall back on POSIXish iconv;
+# if that does not work, fall back on 'cat'.
+read_file() {
+ { $translit && {
+ eval "$1=\`(iconv -f UTF-8 -t //TRANSLIT) 2>/dev/null <\"\$2\"\`" ||
+ eval "$1=\`(iconv -f UTF-8) 2>/dev/null <\"\$2\"\`"
+ }; } ||
+ eval "$1=\`cat <\"\$2\"\`" || {
+ say >&2 "$0: time zone files are not set up correctly"
+ exit 1
+ }
}
+read_file TZ_COUNTRY_TABLE "$TZDIR/iso3166.tab"
+read_file TZ_ZONE_TABLE "$TZDIR/$zonetabtype.tab"
newline='
'
@@ -219,14 +207,15 @@ output_country_list='
BEGIN {
continent_re = substr(ARGV[1], 2)
TZ_COUNTRY_TABLE = substr(ARGV[2], 2)
- ARGV[1] = ARGV[2] = ""
+ TZ_ZONE_TABLE = substr(ARGV[3], 2)
+ ARGV[1] = ARGV[2] = ARGV[3] = ""
FS = "\t"
- }
- /^#$/ { next }
- /^#[^@]/ { next }
- {
+ nlines = split(TZ_ZONE_TABLE, line, /\n/)
+ for (iline = 1; iline <= nlines; iline++) {
+ $0 = line[iline]
commentary = $0 ~ /^#@/
if (commentary) {
+ if ($0 !~ /^#@/) continue
col1ccs = substr($1, 3)
conts = $2
} else {
@@ -250,8 +239,10 @@ output_country_list='
}
}
}
- END {
- while (getline <TZ_COUNTRY_TABLE) {
+ {
+ nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
+ for (i = 1; i <= nlines; i++) {
+ $0 = line[i]
if ($0 !~ /^#/) cc_name[$1] = $2
}
for (i = 1; i <= ccs; i++) {
@@ -263,9 +254,10 @@ output_country_list='
print country
}
}
+ }
'
-# Awk script to read a time zone table and output the same table,
+# Awk script to process a time zone table and output the same table,
# with each row preceded by its distance from 'here'.
# If output_times is set, each row is instead preceded by its local time
# and any apostrophes are escaped for the shell.
@@ -273,12 +265,16 @@ output_distances_or_times='
BEGIN {
coord = substr(ARGV[1], 2)
TZ_COUNTRY_TABLE = substr(ARGV[2], 2)
- ARGV[1] = ARGV[2] = ""
+ TZ_ZONE_TABLE = substr(ARGV[3], 2)
+ ARGV[1] = ARGV[2] = ARGV[3] = ""
FS = "\t"
if (!output_times) {
- while (getline <TZ_COUNTRY_TABLE)
- if ($0 ~ /^[^#]/)
- country[$1] = $2
+ nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
+ for (i = 1; i <= nlines; i++) {
+ $0 = line[i]
+ if ($0 ~ /^#/) continue
+ country[$1] = $2
+ }
country["US"] = "US" # Otherwise the strings get too long.
}
}
@@ -338,19 +334,20 @@ output_distances_or_times='
return gcdist(lat1, long1, lat2, long2) + pardist(lat1, long1, lat2, long2)
}
BEGIN {
- coord_lat = convert_latitude(coord)
- coord_long = convert_longitude(coord)
- }
- /^[^#]/ {
- inline[inlines++] = $0
- ncc = split($1, cc, /,/)
- for (i = 1; i <= ncc; i++)
- cc_used[cc[i]]++
- }
- END {
+ coord_lat = convert_latitude(coord)
+ coord_long = convert_longitude(coord)
+ nlines = split(TZ_ZONE_TABLE, line, /\n/)
+ for (h = 1; h <= nlines; h++) {
+ $0 = line[h]
+ if ($0 ~ /^#/) continue
+ inline[inlines++] = $0
+ ncc = split($1, cc, /,/)
+ for (i = 1; i <= ncc; i++)
+ cc_used[cc[i]]++
+ }
for (h = 0; h < inlines; h++) {
$0 = inline[h]
- line = $1 "\t" $2 "\t" $3
+ outline = $1 "\t" $2 "\t" $3
sep = "\t"
ncc = split($1, cc, /,/)
split("", item_seen)
@@ -358,17 +355,18 @@ output_distances_or_times='
for (i = 1; i <= ncc; i++) {
item = cc_used[cc[i]] <= 1 ? country[cc[i]] : $4
if (item_seen[item]++) continue
- line = line sep item
+ outline = outline sep item
sep = "; "
}
if (output_times) {
fmt = "TZ='\''%s'\'' date +'\''%d %%Y %%m %%d %%H:%%M %%a %%b\t%s'\''\n"
- gsub(/'\''/, "&\\\\&&", line)
- printf fmt, $3, h, line
+ gsub(/'\''/, "&\\\\&&", outline)
+ printf fmt, $3, h, outline
} 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), \
+ outline
}
}
}
@@ -405,17 +403,23 @@ while
entry = entry " Ocean"
printf "'\''%s'\''\n", entry
}
- BEGIN { FS = "\t" }
- /^[^#]/ {
- handle_entry($3)
- }
- /^#@/ {
- ncont = split($2, cont, /,/)
- for (ci = 1; ci <= ncont; ci++) {
- handle_entry(cont[ci])
+ BEGIN {
+ TZ_ZONE_TABLE = substr(ARGV[1], 2)
+ ARGV[1] = ""
+ FS = "\t"
+ nlines = split(TZ_ZONE_TABLE, line, /\n/)
+ for (i = 1; i <= nlines; i++) {
+ $0 = line[i]
+ if ($0 ~ /^[^#]/)
+ handle_entry($3)
+ else if ($0 ~ /^#@/) {
+ ncont = split($2, cont, /,/)
+ for (ci = 1; ci <= ncont; ci++)
+ handle_entry(cont[ci])
+ }
}
}
- ' <"$TZ_ZONE_TABLE" |
+ ' ="$TZ_ZONE_TABLE" |
sort -u |
tr '\n' ' '
echo ''
@@ -482,7 +486,7 @@ while
esac
distance_table=`$AWK \
"$output_distances_or_times" \
- ="$coord" ="$TZ_COUNTRY_TABLE" <"$TZ_ZONE_TABLE" |
+ ="$coord" ="$TZ_COUNTRY_TABLE" ="$TZ_ZONE_TABLE" |
sort -n |
sed "${location_limit}q"
`
@@ -527,7 +531,7 @@ while
time_table_command=`$AWK \
-v output_times=1 \
"$output_distances_or_times" \
- = = <"$TZ_ZONE_TABLE"
+ = = ="$TZ_ZONE_TABLE"
`
time_table=`eval "$time_table_command"`
new_minute=`TZ=UTC0 date +"$minute_format"`
@@ -538,44 +542,61 @@ while
done
echo >&2 "The system says Universal Time is $new_minute."
echo >&2 "Assuming that's correct, what is the local time?"
+ sorted_table=`say "$time_table" | sort -k2n -k2,5 -k1n` || {
+ say >&2 "$0: cannot sort time table"
+ exit 1
+ }
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
- }'
+ $AWK 'BEGIN {
+ sorted_table = substr(ARGV[1], 2)
+ ARGV[1] = ""
+ nlines = split(sorted_table, line, /\n/)
+ for (i = 1; i <= nlines; i++) {
+ $0 = line[i]
+ outline = $6 " " $7 " " $4 " " $5
+ if (outline == oldline) continue
+ oldline = outline
+ gsub(/'\''/, "&\\\\&&", outline)
+ printf "'\''%s'\''\n", outline
+ }
+ }' ="$sorted_table"
`
time=$select_result
+ continent_re='^'
zone_table=`
- say "$time_table" |
- $AWK 'BEGIN { time = substr(ARGV[1], 2); ARGV[1] = "" } {
+ $AWK 'BEGIN {
+ time = substr(ARGV[1], 2)
+ time_table = substr(ARGV[2], 2)
+ ARGV[1] = ARGV[2] = ""
+ nlines = split(time_table, line, /\n/)
+ for (i = 1; i <= nlines; i++) {
+ $0 = line[i]
if ($6 " " $7 " " $4 " " $5 == time) {
sub(/[^\t]*\t/, "")
print
}
- }' ="$time"
+ }
+ }' ="$time" ="$time_table"
`
countries=`
- say "$zone_table" |
$AWK \
- ="$output_country_list" ='^' ="$TZ_COUNTRY_TABLE" |
+ "$output_country_list" \
+ ="$continent_re" ="$TZ_COUNTRY_TABLE" ="$zone_table" |
sort -f
`
;;
*)
- zone_table=file
- # Get list of names of countries in the continent or ocean.
- countries=`
+ continent_re="^$continent/"
+ zone_table=$TZ_ZONE_TABLE
+ esac
+
+ # Get list of names of countries in the continent or ocean.
+ countries=`
$AWK \
"$output_country_list" \
- "=^$continent/" ="$TZ_COUNTRY_TABLE" <"$TZ_ZONE_TABLE" |
+ ="$continent_re" ="$TZ_COUNTRY_TABLE" ="$zone_table" |
sort -f
- `;;
- esac
+ `
# If there's more than one country, ask the user which one.
case $countries in
@@ -592,28 +613,32 @@ while
# Get list of timezones in the country.
regions=`
- case $zone_table in
- file) cat -- "$TZ_ZONE_TABLE";;
- *) say "$zone_table";;
- esac |
$AWK \
'
BEGIN {
country = substr(ARGV[1], 2)
TZ_COUNTRY_TABLE = substr(ARGV[2], 2)
- ARGV[1] = ARGV[2] = ""
+ TZ_ZONE_TABLE = substr(ARGV[3], 2)
+ ARGV[1] = ARGV[2] = ARGV[3] = ""
FS = "\t"
cc = country
- while (getline <TZ_COUNTRY_TABLE) {
+ nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
+ for (i = 1; i <= nlines; i++) {
+ $0 = line[i]
if ($0 !~ /^#/ && country == $2) {
cc = $1
break
}
}
+ nlines = split(TZ_ZONE_TABLE, line, /\n/)
+ for (i = 1; i <= nlines; i++) {
+ $0 = line[i]
+ if ($0 ~ /^#/) continue
+ if ($1 ~ cc)
+ print $4
+ }
}
- /^#/ { next }
- $1 ~ cc { print $4 }
- ' ="$country" ="$TZ_COUNTRY_TABLE"
+ ' ="$country" ="$TZ_COUNTRY_TABLE" ="$zone_table"
`
@@ -627,29 +652,33 @@ while
# Determine tz from country and region.
tz=`
- case $zone_table in
- file) cat -- "$TZ_ZONE_TABLE";;
- *) say "$zone_table";;
- esac |
$AWK \
'
BEGIN {
country = substr(ARGV[1], 2)
region = substr(ARGV[2], 2)
TZ_COUNTRY_TABLE = substr(ARGV[3], 2)
- ARGV[1] = ARGV[2] = ARGV[3] = ""
+ TZ_ZONE_TABLE = substr(ARGV[4], 2)
+ ARGV[1] = ARGV[2] = ARGV[3] = ARGV[4] = ""
FS = "\t"
cc = country
- while (getline <TZ_COUNTRY_TABLE) {
+ nlines = split(TZ_COUNTRY_TABLE, line, /\n/)
+ for (i = 1; i <= nlines; i++) {
+ $0 = line[i]
if ($0 !~ /^#/ && country == $2) {
cc = $1
break
}
}
+ nlines = split(TZ_ZONE_TABLE, line, /\n/)
+ for (i = 1; i <= nlines; i++) {
+ $0 = line[i]
+ if ($0 ~ /^#/) continue
+ if ($1 ~ cc && ($4 == region || !region))
+ print $3
+ }
}
- /^#/ { next }
- $1 ~ cc && ($4 == region || !region) { print $3 }
- ' ="$country" ="$region" ="$TZ_COUNTRY_TABLE"
+ ' ="$country" ="$region" ="$TZ_COUNTRY_TABLE" ="$zone_table"
`;;
esac
--
2.40.1
More information about the tz
mailing list