Further eta/thousands update

Hrvoje Niksic <[email protected]> Sun, 26 Jun 2005 23:02:41 +0200
Newsgroups gmane.comp.web.wget.patches
Message-ID <[email protected]>
According to http://tinyurl.com/86r83, minute, hour, and day can be
treated as SI units (with a minor deviation that we abbreviated "min"
to "m" for brevity, but the surrounding context makes the meaning
obvious) and don't require translation.

Although the thousand separators are no longer widely used, they are
still used in the progress bar.  This fixes them to be fully
localized.


2005-06-26  Hrvoje Niksic  <[email protected]>

	* utils.c (with_thousand_seps): Correctly implement thousand seps
	consisting of more than one character.

2005-06-26  Hrvoje Niksic  <[email protected]>

	* main.c (secs_to_human_time): Ditto.

	* progress.c (create_image): Print more exact duration of very
	short downloads.

	* main.c (secs_to_human_time): Don't translate time suffixes "h",
	"m", and "s", which are not strictly SI, but are "accepted for use
	with SI".
	(secs_to_human_time): Print really small intervals as 0s, not
	0.00s.

Index: src/utils.c
===================================================================
--- src/utils.c	(revision 1831)
+++ src/utils.c	(revision 1834)
@@ -1164,6 +1164,35 @@
 }
 
 
+static void
+get_grouping_data (const char **sep, const char **grouping)
+{
+  static const char *cached_sep;
+  static const char *cached_grouping;
+  static bool initialized;
+  if (!initialized)
+    {
+#ifdef LC_NUMERIC
+      /* Get the grouping info from the locale. */
+      struct lconv *lconv;
+      const char *oldlocale = setlocale (LC_NUMERIC, "");
+      lconv = localeconv ();
+      cached_sep = xstrdup (lconv->thousands_sep);
+      cached_grouping = xstrdup (lconv->grouping);
+      /* Restore the locale to previous settings. */
+      setlocale (LC_NUMERIC, oldlocale);
+      if (!cached_sep)
+#endif
+	/* Force separator for locales that specify no separators
+	   ("C", "hr", and probably many more.) */
+	cached_sep = ",", cached_grouping = "\x03";
+      initialized = true;
+    }
+  *sep = cached_sep;
+  *grouping = cached_grouping;
+}
+
+
 /* Return a printed representation of N with thousand separators.
    This should respect locale settings, with the exception of the "C"
    locale which mandates no separator, but we use one anyway.
@@ -1177,47 +1206,38 @@
 with_thousand_seps (wgint n)
 {
   static char outbuf[48];
+  char *p = outbuf + sizeof outbuf;
 
-  static char loc_sepchar;
-  static const char *loc_grouping;
+  /* Info received from locale */
+  const char *grouping, *sep;
+  int seplen;
 
+  /* State information */
   int i = 0, groupsize;
-  char *p;
   const char *atgroup;
 
-  if (!loc_sepchar)
-    {
-#ifdef LC_NUMERIC
-      /* Get the grouping character from the locale. */
-      struct lconv *lconv;
-      const char *oldlocale = setlocale (LC_NUMERIC, "");
-      lconv = localeconv ();
-      loc_sepchar = *lconv->thousands_sep;
-      loc_grouping = xstrdup (lconv->grouping);
-      /* Restore the C locale semantics of printing and reading numbers */
-      setlocale (LC_NUMERIC, oldlocale);
-      if (!loc_sepchar)
-#endif
-	/* defaults for C locale or no locale */
-	loc_sepchar = ',', loc_grouping = "\x03";
-    }
-  atgroup = loc_grouping;
+  /* Initialize grouping data. */
+  get_grouping_data (&sep, &grouping);
+  seplen = strlen (sep);
+  atgroup = grouping;
+  groupsize = *atgroup++;
 
-  p = outbuf + sizeof outbuf;
+  /* Write the number into the buffer, backwards, inserting the
+     separators as necessary.  */
   *--p = '\0';
-  groupsize = *atgroup++;
-
   while (1)
     {
       *--p = n % 10 + '0';
       n /= 10;
       if (n == 0)
 	break;
-      /* Insert the separator on every groupsize'd digit, and get the
-	 new groupsize.  */
+      /* Prepend SEP to every groupsize'd digit and get new groupsize.  */
       if (++i == groupsize)
 	{
-	  *--p = loc_sepchar;
+	  if (seplen == 1)
+	    *--p = *sep;
+	  else
+	    memcpy (p -= seplen, sep, seplen);
 	  i = 0;
 	  if (*atgroup)
 	    groupsize = *atgroup++;
Index: src/ChangeLog
===================================================================
--- src/ChangeLog	(revision 1831)
+++ src/ChangeLog	(revision 1834)
@@ -1,5 +1,23 @@
Index: src/progress.c
===================================================================
--- src/progress.c	(revision 1831)
+++ src/progress.c	(revision 1834)
@@ -707,6 +707,11 @@
   p += sizeof (s) - 1;				\
 } while (0)
 
+/* Use move_to_end (s) to get S to point the end of the string (the
+   terminating \0).  This is faster than s+=strlen(s), but some people
+   are confused when they see strchr (s, '\0') in the code.  */
+#define move_to_end(s) s = strchr (s, '\0');
+
 #ifndef MAX
 # define MAX(a, b) ((a) >= (b) ? (a) : (b))
 #endif
@@ -824,7 +829,7 @@
 
   /* " 234,567,890" */
   sprintf (p, " %-11s", size_grouped);
-  p += strlen (p);
+  move_to_end (p);
 
   /* " 1012.45K/s" */
   if (hist->total_time && hist->total_bytes)
@@ -837,7 +842,7 @@
       double dltime = hist->total_time + (dl_total_time - bp->recent_start);
       double dlspeed = calc_rate (dlquant, dltime, &units);
       sprintf (p, " %7.2f%s", dlspeed, short_units[units]);
-      p += strlen (p);
+      move_to_end (p);
     }
   else
     APPEND_LITERAL ("   --.--K/s");
@@ -873,8 +878,10 @@
 	      bp->last_eta_time = dl_total_time;
 	    }
 
+	  /* Translation note: "ETA" is English-centric, but this must
+	     be short, ideally 3 chars.  Abbreviate if necessary.  */
 	  sprintf (p, "  eta %s", eta_to_human_short (eta));
-	  p += strlen (p);
+	  move_to_end (p);
 	}
       else if (bp->total_length > 0)
 	{
@@ -884,8 +891,22 @@
   else
     {
       /* When the download is done, print the elapsed time.  */
-      sprintf (p, _("   in %s"), eta_to_human_short (dl_total_time / 1000 + 0.5));
-      p += strlen (p);
+      double secs = dl_total_time / 1000;
+      /* Note to translators: this should not take up more room than
+	 available here.  Abbreviate if necessary.  */
+      strcpy (p, _("   in "));
+      move_to_end (p);		/* not p+=6, think translations! */
+      if (secs >= 10)
+	strcpy (p, eta_to_human_short ((int) (secs + 0.5)));
+      else
+	/* For very quick downloads show more exact timing information. */
+	sprintf (p, _("%.*fs"),
+		 secs < 0.001 ? 0 : /* 0s instead of 0.000s */
+		 secs < 0.01 ? 3 :  /* 0.00x */
+		 secs < 0.1 ? 2 :   /* 0.0x */
+		 1,                 /* 0.x, 1.x, ..., 9.x */
+		 secs);
+      move_to_end (p);
     }
 
   assert (p - bp->buffer <= bp->width);
@@ -951,13 +972,20 @@
 }
 #endif
 
-/* Provide a short human-readable rendition of the ETA.  It never
-   occupies more than 7 characters of screen space.  */
+/* Provide a short human-readable rendition of the ETA.  This is like
+   secs_to_human_time in main.c, except the output doesn't include
+   fractions (which would look silly in by nature imprecise ETA) and
+   takes less room.  If the time is measured in hours, hours and
+   minutes (but not seconds) are shown; if measured in days, then days
+   and hours are shown.  This ensures brevity while still displaying
+   as much as possible.
 
+   It never occupies more than 7 characters of screen space.  */
+
 static const char *
 eta_to_human_short (int secs)
 {
-  static char buf[10];		/* 8 is enough, but just in case */
+  static char buf[10];		/* 8 should be enough, but just in case */
   static int last = -1;
 
   /* Trivial optimization.  This function can be called every 200
@@ -968,16 +996,16 @@
   last = secs;
 
   if (secs < 100)
-    sprintf (buf, _("%ds"), secs);
+    sprintf (buf, "%ds", secs);
   else if (secs < 100 * 60)
-    sprintf (buf, _("%dm %ds"), secs / 60, secs % 60);
+    sprintf (buf, "%dm %ds", secs / 60, secs % 60);
   else if (secs < 100 * 3600)
-    sprintf (buf, _("%dh %dm"), secs / 3600, (secs / 60) % 60);
+    sprintf (buf, "%dh %dm", secs / 3600, (secs / 60) % 60);
   else if (secs < 100 * 86400)
-    sprintf (buf, _("%dd %dh"), secs / 86400, (secs / 3600) % 60);
+    sprintf (buf, "%dd %dh", secs / 86400, (secs / 3600) % 60);
   else
-    /* (2^31-1)/86400 doesn't overflow BUF. */
-    sprintf (buf, _("%dd"), secs / 86400);
+    /* even (2^31-1)/86400 doesn't overflow BUF. */
+    sprintf (buf, "%dd", secs / 86400);
 
   return buf;
 }
Index: src/main.c
===================================================================
--- src/main.c	(revision 1831)
+++ src/main.c	(revision 1834)
@@ -637,11 +637,14 @@
   exit (0);
 }
 
+/* Return a human-readable printed representation of INTERVAL,
+   measured in seconds.  */
+
 static char *
-secs_to_human_time (double secs_decimal)
+secs_to_human_time (double interval)
 {
   static char buf[32];
-  int secs = (int) (secs_decimal + 0.5);
+  int secs = (int) (interval + 0.5);
   int hours, mins, days;
 
   days = secs / 86400, secs %= 86400;
@@ -649,15 +652,21 @@
   mins = secs / 60, secs %= 60;
 
   if (days)
-    sprintf (buf, _("%dd %dh %dm %ds"), days, hours, mins, secs);
+    sprintf (buf, "%dd %dh %dm %ds", days, hours, mins, secs);
   else if (hours)
-    sprintf (buf, _("%dh %dm %ds"), hours, mins, secs);
+    sprintf (buf, "%dh %dm %ds", hours, mins, secs);
   else if (mins)
-    sprintf (buf, _("%dm %ds"), mins, secs);
-  else if (secs_decimal >= 1)
-    sprintf (buf, _("%ds"), secs);
+    sprintf (buf, "%dm %ds", mins, secs);
+  else if (interval >= 10)
+    sprintf (buf, "%ds", secs);
   else
-    sprintf (buf, _("%.2fs"), secs_decimal);
+    /* For very quick downloads show more exact timing information. */
+    sprintf (buf, "%.*fs",
+	     interval < 0.001 ? 0 : /* 0s instead of 0.000s */
+	     interval < 0.01 ? 3 :  /* 0.00x */
+	     interval < 0.1 ? 2 :   /* 0.0x */
+	     1,                     /* 0.x, 1.x, ..., 9.x */
+	     interval);
 
   return buf;
 }
Index: TODO
===================================================================
--- TODO	(revision 1831)
+++ TODO	(revision 1834)
@@ -1,4 +1,4 @@
-                                 Hey Emacs, this is -*- outline -*- mode
+                                                       -*- outline -*-
 
 This is the to-do list for GNU Wget.  There is no timetable of when we
 plan to implement these features -- this is just a list of features