1 /* Test that nstrftime works as required.
2    Copyright (C) 2011-2021 Free Software Foundation, Inc.
3 
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
16 
17 /* Written by Jim Meyering.  */
18 
19 #include <config.h>
20 
21 #include "strftime.h"
22 
23 #include "intprops.h"
24 
25 #include <errno.h>
26 #include <limits.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <time.h>
30 #include <unistd.h>
31 
32 #include "macros.h"
33 #define STREQ(a, b) (strcmp (a, b) == 0)
34 
35 /* Support for settings like TZ='<+00>0' was added in IEEE Std 1003.1-2001.  */
36 #define TZ_ANGLE_BRACKETS_SHOULD_WORK (200112 <= _POSIX_VERSION)
37 
38 struct posixtm_test
39 {
40   time_t in;
41   int in_ns;
42   char const *fmt;
43   char const *exp;
44 };
45 
46 static struct posixtm_test const T[] =
47   {
48     { 1300000000, 0,            "%F", "2011-03-13" },
49     { 0,          10,           "%T.%N", "00:00:00.000000010" },
50     { 56,         123456789,    "%T.%12N", "00:00:56.123456789000" },
51     { 0,          0,            NULL, NULL }
52   };
53 
54 static int
posixtm_test(void)55 posixtm_test (void)
56 {
57   int fail = 0;
58   unsigned int i;
59 
60   for (i = 0; T[i].fmt; i++)
61     {
62       char buf[1000];
63       time_t t = T[i].in;
64       struct tm *tm = gmtime (&t);
65       size_t n;
66 
67       ASSERT (tm);
68 
69       n = nstrftime (buf, sizeof buf, T[i].fmt, tm, 0, T[i].in_ns);
70       if (n == 0)
71         {
72           fail = 1;
73           printf ("nstrftime failed with format %s\n", T[i].fmt);
74         }
75 
76       if (! STREQ (buf, T[i].exp))
77         {
78           fail = 1;
79           printf ("%s: result mismatch: got %s, expected %s\n",
80                   T[i].fmt, buf, T[i].exp);
81         }
82     }
83 
84   return fail;
85 }
86 
87 struct tzalloc_test
88 {
89   timezone_t tz;
90   char const *setting;
91 };
92 
93 static struct tzalloc_test TZ[] =
94   {
95 #define Pacific 0
96     { 0, "PST8PDT,M3.2.0,M11.1.0"      },
97 #define Arizona 1
98     { 0, "MST7"                        },
99 #define UTC 2
100     { 0, 0                             },
101 #define CentEur 3
102     { 0, "CET-1CEST,M3.5.0,M10.5.0/3"  },
103 #define Japan 4
104     { 0, "JST-9"                       },
105 #define NZ 5
106     { 0, "NZST-12NZDT,M9.5.0,M4.1.0/3" },
107 #define Unknown 6
108     { 0, "<-00>0" },
109     { 0 }
110   };
111 
112 struct localtime_rz_test
113 {
114   /* Input parameters.  */
115   struct tzalloc_test *tza;
116   time_t t;
117 
118   /* Expected result.  */
119   char const *exp;
120 
121   /* Determines if an incorrectly unset tm_isdst
122      results in failure or just a warning.  */
123   int ahistorical;
124 };
125 
126 static struct localtime_rz_test LT[] =
127   {
128     { TZ+Pacific,          0, "1969-12-31 16:00:00 -0800 (PST)",  0 },
129     { TZ+Arizona,          0, "1969-12-31 17:00:00 -0700 (MST)",  0 },
130     { TZ+UTC    ,          0, "1970-01-01 00:00:00 +0000 (UTC)",  0 },
131     { TZ+CentEur,          0, "1970-01-01 01:00:00 +0100 (CET)",  0 },
132     { TZ+Japan  ,          0, "1970-01-01 09:00:00 +0900 (JST)",  0 },
133     { TZ+NZ     ,          0, "1970-01-01 13:00:00 +1300 (NZDT)", 1 },
134     { TZ+Pacific,  500000001, "1985-11-04 16:53:21 -0800 (PST)",  0 },
135     { TZ+Arizona,  500000001, "1985-11-04 17:53:21 -0700 (MST)",  0 },
136     { TZ+UTC    ,  500000001, "1985-11-05 00:53:21 +0000 (UTC)",  0 },
137     { TZ+CentEur,  500000001, "1985-11-05 01:53:21 +0100 (CET)",  1 },
138     { TZ+Japan  ,  500000001, "1985-11-05 09:53:21 +0900 (JST)",  0 },
139     { TZ+NZ     ,  500000001, "1985-11-05 13:53:21 +1300 (NZDT)", 0 },
140     { TZ+Pacific, 1000000002, "2001-09-08 18:46:42 -0700 (PDT)",  0 },
141     { TZ+Arizona, 1000000002, "2001-09-08 18:46:42 -0700 (MST)",  0 },
142     { TZ+UTC    , 1000000002, "2001-09-09 01:46:42 +0000 (UTC)",  0 },
143     { TZ+CentEur, 1000000002, "2001-09-09 03:46:42 +0200 (CEST)", 0 },
144     { TZ+Japan  , 1000000002, "2001-09-09 10:46:42 +0900 (JST)",  0 },
145     { TZ+NZ     , 1000000002, "2001-09-09 13:46:42 +1200 (NZST)", 0 },
146 #if TZ_ANGLE_BRACKETS_SHOULD_WORK
147     { TZ+Unknown,          0, "1970-01-01 00:00:00 -0000 (-00)",  0 },
148     { TZ+Unknown,  500000001, "1985-11-05 00:53:21 -0000 (-00)",  0 },
149     { TZ+Unknown, 1000000002, "2001-09-09 01:46:42 -0000 (-00)",  0 },
150 #endif
151     { 0 }
152   };
153 
154 static int
tzalloc_test(void)155 tzalloc_test (void)
156 {
157   int fail = 0;
158   int i;
159 
160   for (i = 0; LT[i].tza; i++)
161     {
162       struct tzalloc_test *tza = LT[i].tza;
163       long lt = LT[i].t;
164       timezone_t tz = tza->tz;
165       char const *setting;
166       static char const format[] = "%Y-%m-%d %H:%M:%S %z (%Z)";
167       char buf[1000];
168       struct tm tm;
169       size_t n;
170 
171       if (!tz && tza->setting)
172         {
173           tz = tzalloc (tza->setting);
174           if (!tz)
175             {
176               fail = 1;
177               printf ("%s: tzalloc: %s\n", TZ[i].setting, strerror (errno));
178               continue;
179             }
180           tza->tz = tz;
181         }
182 
183       setting = tza->setting ? tza->setting : "UTC0";
184 
185       if (!localtime_rz (tz, &LT[i].t, &tm))
186         {
187           fail = 1;
188           printf ("%s: %ld: localtime_rz: %s\n", setting, lt,
189                   strerror (errno));
190           continue;
191         }
192 
193       n = nstrftime (buf, sizeof buf, format, &tm, tz, 0);
194       if (n == 0)
195         {
196           fail = 1;
197           printf ("%s: %ld: nstrftime failed\n", setting, lt);
198           continue;
199         }
200 
201       if (! (STREQ (buf, LT[i].exp)
202              || (!tz && n == strlen (LT[i].exp)
203                  && memcmp (buf, LT[i].exp, n - sizeof "(GMT)" + 1) == 0
204                  && STREQ (buf + n - sizeof "(GMT)" + 1, "(GMT)"))))
205         {
206           /* Don't fail for unhandled dst in ahistorical entries,
207              as gnulib doesn't currently fix that issue, seen on Darwin 14.  */
208           if (!LT[i].ahistorical || tm.tm_isdst)
209             fail = 1;
210           printf ("%s: expected \"%s\", got \"%s\"\n",
211                   setting, LT[i].exp, buf);
212         }
213     }
214 
215   return fail;
216 }
217 
218 
219 static int
quarter_test(void)220 quarter_test (void)
221 {
222   int result = 0;
223   size_t mon;
224 
225   /* Check %q.  */
226   for (mon = 1; mon <= 12; mon++)
227     {
228       char out[2];
229       char exp[2] = {0,};
230       struct tm qtm = { .tm_mon = mon - 1 };
231       char fmt[3] = {'%','q','\0'};
232 
233       size_t r = nstrftime (out, sizeof (out), fmt, &qtm, 0, 0);
234       if (r == 0)
235         {
236           puts ("nstrftime(\"%q\") failed");
237           result = 1;
238           break;
239         }
240 
241       exp[0] = mon < 4 ? '1' : mon < 7 ? '2' : mon < 10 ? '3' : '4';
242       if (strcmp (out, exp) != 0)
243         {
244           printf ("nstrftime %%q: expected \"%s\", got \"%s\"\n", exp, out);
245           result = 1;
246           break;
247         }
248     }
249 
250   return result;
251 }
252 
253 static int
errno_test(void)254 errno_test (void)
255 {
256   int fail = 0;
257   struct tm tm = { .tm_year = 2020 - 1900, .tm_mday = 1 };
258   char buf[INT_BUFSIZE_BOUND (time_t)];
259   size_t n;
260   int bigyear = LLONG_MAX - 1900 < INT_MAX ? LLONG_MAX - 1900 : INT_MAX;
261 
262   errno = 0;
263   n = nstrftime (buf, 0, "%m", &tm, 0, 0);
264   if (! (n == 0 && errno == ERANGE))
265     {
266       fail = 1;
267       printf ("nstrftime failed to set errno = ERANGE\n");
268     }
269 
270   errno = 0;
271   n = nstrftime (buf, sizeof buf, "", &tm, 0, 0);
272   if (! (n == 0 && errno == 0))
273     {
274       fail = 1;
275       printf ("nstrftime failed to leave errno alone\n");
276     }
277 
278 
279   tm.tm_year = bigyear;
280   errno = 0;
281   n = nstrftime (buf, sizeof buf, "%s", &tm, 0, 0);
282   if (n == 0)
283     {
284       if (errno != EOVERFLOW)
285         {
286           fail = 1;
287           printf ("nstrftime failed to set errno = EOVERFLOW\n");
288         }
289 
290       if (mktime_z (0, &tm) != (time_t) -1)
291         {
292           fail = 1;
293           printf ("nstrftime %%s failed but mktime_z worked for tm_year=%d\n",
294                   bigyear);
295         }
296     }
297   else
298     {
299       long long int text_seconds = atoll (buf);
300       if (text_seconds <= (LLONG_MAX - 1 < TYPE_MAXIMUM (time_t)
301                            ? LLONG_MAX - 1 : TYPE_MAXIMUM (time_t)))
302         {
303           time_t bigtime = text_seconds;
304           struct tm *tmp = gmtime (&bigtime);
305           if (!tmp)
306             {
307               fail = 1;
308               printf ("gmtime failed on nstrftime result\n");
309             }
310           else
311             {
312               char buf1[sizeof buf];
313               size_t n1 = nstrftime (buf1, sizeof buf1, "%s", tmp, 0, 0);
314               buf1[n1] = '\0';
315               if (! STREQ (buf, buf1))
316                 {
317                   fail = 1;
318                   printf ("nstrftime %%s first returned '%s', then '%s'\n",
319                           buf, buf1);
320                 }
321             }
322         }
323     }
324 
325   return fail;
326 }
327 
328 int
main(void)329 main (void)
330 {
331   int fail = 0;
332   fail |= posixtm_test ();
333   fail |= tzalloc_test ();
334   fail |= quarter_test ();
335   fail |= errno_test ();
336   return fail;
337 }
338 
339 /*
340 Local Variables:
341 indent-tabs-mode: nil
342 End:
343 */
344