[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