[v3] strftime,strptime: have %q represent the quarter of year

Message ID 2f5f737e-2e42-08c6-ae2b-33aab798a1d9@draigBrady.com
State New
Headers show

Commit Message

Pádraig Brady Nov. 4, 2016, 2:27 p.m.
On 04/11/16 11:33, Rafal Luzynski wrote:
> Hi,

> 

> Shouldn't strptime() family also support the same?


I was debating that, but you're right,
strptime() should support that for consistency.

Updated patch is attached, which also adjusts
to Paul Eggert's optimization.

thanks,
Pádraig

Patch

From ad0ed0247612bf7d85afe1af0c8a61e804db274c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P@draigBrady.com>
Date: Thu, 3 Nov 2016 19:36:59 +0000
Subject: [PATCH] strftime,strptime: have %q represent the quarter of year

This is already supported by gnulib, and for example
simplifies quarter determination at the shell, which
currently needs to use "$(( ($(date +%-m)-1)/3+1 ))".

* manual/time.texi: Document %q for str[fp]time.
* time/strftime_l.c: Implement %q.
* time/strptime_l.c: Likewise.
* time/tst-strftime.c: Add a test case.
* time/tst-strptime3.c: Likewise.
* NEWS: Mention the new feature.
---
 NEWS                 |  3 +++
 manual/time.texi     | 12 ++++++++++++
 time/strftime_l.c    |  7 +++++++
 time/strptime_l.c    | 18 ++++++++++++++++++
 time/tst-strftime.c  | 25 +++++++++++++++++++++++++
 time/tst-strptime3.c | 20 ++++++++++++++++++++
 6 files changed, 85 insertions(+)

diff --git a/NEWS b/NEWS
index 65184b1..e08f9ed 100644
--- a/NEWS
+++ b/NEWS
@@ -119,6 +119,9 @@  Version 2.25
   variable for a particular architecture in the GCC source file
   'gcc/config.gcc'.
 
+* strftime and strptime now support the %q directive to represent
+  the quarter of the year, with January starting the first quarter.
+
 Security related changes:
 
   On ARM EABI (32-bit), generating a backtrace for execution contexts which
diff --git a/manual/time.texi b/manual/time.texi
index 6a899b7..5ba0558 100644
--- a/manual/time.texi
+++ b/manual/time.texi
@@ -1510,6 +1510,12 @@  most locales @samp{AM}/@samp{PM} format is not supported, in such cases
 
 This format is a GNU extension.
 
+@item %q
+Quarter of the year (@samp{1}@dots{}@samp{4}),
+with January starting the first quarter.
+
+This format is a GNU extension.
+
 @item %r
 The complete calendar time using the AM/PM format of the current locale.
 
@@ -1926,6 +1932,12 @@  all and therefore the conversion fails.
 
 @code{%P} is a GNU extension following a GNU extension to @code{strftime}.
 
+@item %q
+Quarter of the year (@samp{1}@dots{}@samp{4}),
+with January starting the first quarter.
+
+This format is a GNU extension.
+
 @item %r
 The complete time using the AM/PM format of the current locale.
 
diff --git a/time/strftime_l.c b/time/strftime_l.c
index 1205035..09d9373 100644
--- a/time/strftime_l.c
+++ b/time/strftime_l.c
@@ -1085,6 +1085,13 @@  __strftime_internal (CHAR_T *s, size_t maxsize, const CHAR_T *format,
 	  goto underlying_strftime;
 #endif
 
+	case L_('q'):           /* Quarter of year.  GNU extension.  */
+	  if (modifier == L_('E'))
+	    goto bad_format;
+
+	  DO_NUMBER (1, ((tp->tm_mon * 11) >> 5) + 1);
+	  break;
+
 	case L_('R'):
 	  subfmt = L_("%H:%M");
 	  goto subformat;
diff --git a/time/strptime_l.c b/time/strptime_l.c
index 3a56947..f957e40 100644
--- a/time/strptime_l.c
+++ b/time/strptime_l.c
@@ -593,6 +593,15 @@  __strptime_internal (const char *rp, const char *fmt, struct tm *tmp,
 	  else
 	    s.is_pm = 0;
 	  break;
+	case 'q':
+	  /* Match quarter of year.  GNU extension.  */
+	  get_number (1, 4, 1);
+	  tm->tm_mon = (val - 1) * 3;
+	  tm->tm_mday = 1;
+	  s.have_mon = 1;
+	  s.have_mday = 1;
+	  s.want_xday = 1;
+	  break;
 	case 'r':
 #ifdef _NL_CURRENT
 	  if (s.decided != raw)
@@ -1050,6 +1059,15 @@  __strptime_internal (const char *rp, const char *fmt, struct tm *tmp,
 	      get_alt_number (0, 59, 2);
 	      tm->tm_min = val;
 	      break;
+	    case 'q':
+	      /* Match quarter using alternate numeric symbols.  */
+	      get_alt_number (1, 4, 1);
+	      tm->tm_mon = (val - 1) * 3;
+	      tm->tm_mday = 1;
+	      s.have_mon = 1;
+	      s.have_mday = 1;
+	      s.want_xday = 1;
+	      break;
 	    case 'S':
 	      /* Match seconds using alternate numeric symbols.  */
 	      get_alt_number (0, 61, 2);
diff --git a/time/tst-strftime.c b/time/tst-strftime.c
index af3ff72..62ee6c0 100644
--- a/time/tst-strftime.c
+++ b/time/tst-strftime.c
@@ -154,6 +154,31 @@  do_test (void)
 	}
     }
 
+  /* Check %q.  */
+  for (size_t mon = 1; mon <= 12; mon++)
+    {
+      char out[2];
+      char exp[2] = {0,};
+      struct tm qtm = { .tm_mon = mon - 1 };
+      char fmt[3] = {'%','q','\0'};
+
+      size_t r = strftime (out, sizeof (out), fmt, &qtm);
+      if (r == 0)
+        {
+          puts ("strftime(\"%q\") failed");
+          result = 1;
+          break;
+        }
+
+      exp[0] = mon < 4 ? '1' : mon < 7 ? '2' : mon < 10 ? '3' : '4';
+      if (strcmp (out, exp) != 0)
+        {
+          printf ("strftime %%q: expected \"%s\", got \"%s\"\n", exp, out);
+          result = 1;
+          break;
+        }
+    }
+
   return result + do_bz18985 ();
 }
 
diff --git a/time/tst-strptime3.c b/time/tst-strptime3.c
index d53f51e..b08f6f4 100644
--- a/time/tst-strptime3.c
+++ b/time/tst-strptime3.c
@@ -48,6 +48,26 @@  do_test (void)
       result = 1;
     }
 
+  memset (&tm, 0xaa, sizeof (tm));
+  for (size_t q = 1; q <= 4; q++)
+    {
+      char in[2] = {0,};
+      in[0] = q + '0';
+      int exp_mon = q == 1 ? 0 : q == 2 ? 3 : q == 3 ? 6 : 9;
+      char fmt[3] = {'%','q','\0'};
+
+      if (strptime (in, fmt, &tm) == NULL)
+        {
+          puts ("strptime failed");
+          result = 1;
+        }
+      if (tm.tm_mon != exp_mon || tm.tm_mday != 1)
+        {
+          puts ("unexpected tm content");
+          result = 1;
+        }
+    }
+
   return result;
 }
 
-- 
2.5.5