root/maint/gnulib/tests/test-parse-datetime.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. tm_diff
  2. gmt_offset
  3. main

   1 /* Test of parse_datetime() function.
   2    Copyright (C) 2008-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, or (at your option)
   7    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 Simon Josefsson <simon@josefsson.org>, 2008.  */
  18 
  19 #include <config.h>
  20 
  21 #include "parse-datetime.h"
  22 
  23 #include <errno.h>
  24 #include <stdio.h>
  25 #include <stdlib.h>
  26 #include <string.h>
  27 #include <unistd.h>
  28 
  29 #include "macros.h"
  30 
  31 #ifdef DEBUG
  32 #define LOG(str, now, res)                                              \
  33   printf ("string '%s' diff %d %d\n",                                 \
  34           str, res.tv_sec - now.tv_sec, res.tv_nsec - now.tv_nsec);
  35 #else
  36 #define LOG(str, now, res) (void) 0
  37 #endif
  38 
  39 static const char *const day_table[] =
  40 {
  41   "SUNDAY",
  42   "MONDAY",
  43   "TUESDAY",
  44   "WEDNESDAY",
  45   "THURSDAY",
  46   "FRIDAY",
  47   "SATURDAY",
  48   NULL
  49 };
  50 
  51 
  52 #if ! HAVE_TM_GMTOFF
  53 /* Shift A right by B bits portably, by dividing A by 2**B and
  54    truncating towards minus infinity.  A and B should be free of side
  55    effects, and B should be in the range 0 <= B <= INT_BITS - 2, where
  56    INT_BITS is the number of useful bits in an int.  GNU code can
  57    assume that INT_BITS is at least 32.
  58 
  59    ISO C99 says that A >> B is implementation-defined if A < 0.  Some
  60    implementations (e.g., UNICOS 9.0 on a Cray Y-MP EL) don't shift
  61    right in the usual way when A < 0, so SHR falls back on division if
  62    ordinary A >> B doesn't seem to be the usual signed shift.  */
  63 #define SHR(a, b)       \
  64   (-1 >> 1 == -1        \
  65    ? (a) >> (b)         \
  66    : (a) / (1 << (b)) - ((a) % (1 << (b)) < 0))
  67 
  68 #define TM_YEAR_BASE 1900
  69 
  70 /* Yield the difference between *A and *B,
  71    measured in seconds, ignoring leap seconds.
  72    The body of this function is taken directly from the GNU C Library;
  73    see src/strftime.c.  */
  74 static long int
  75 tm_diff (struct tm const *a, struct tm const *b)
     /* [previous][next][first][last][top][bottom][index][help] */
  76 {
  77   /* Compute intervening leap days correctly even if year is negative.
  78      Take care to avoid int overflow in leap day calculations.  */
  79   int a4 = SHR (a->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (a->tm_year & 3);
  80   int b4 = SHR (b->tm_year, 2) + SHR (TM_YEAR_BASE, 2) - ! (b->tm_year & 3);
  81   int a100 = a4 / 25 - (a4 % 25 < 0);
  82   int b100 = b4 / 25 - (b4 % 25 < 0);
  83   int a400 = SHR (a100, 2);
  84   int b400 = SHR (b100, 2);
  85   int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
  86   long int ayear = a->tm_year;
  87   long int years = ayear - b->tm_year;
  88   long int days = (365 * years + intervening_leap_days
  89                    + (a->tm_yday - b->tm_yday));
  90   return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
  91                 + (a->tm_min - b->tm_min))
  92           + (a->tm_sec - b->tm_sec));
  93 }
  94 #endif /* ! HAVE_TM_GMTOFF */
  95 
  96 static long
  97 gmt_offset (time_t s)
     /* [previous][next][first][last][top][bottom][index][help] */
  98 {
  99   long gmtoff;
 100 
 101 #if !HAVE_TM_GMTOFF
 102   struct tm tm_local = *localtime (&s);
 103   struct tm tm_gmt   = *gmtime (&s);
 104 
 105   gmtoff = tm_diff (&tm_local, &tm_gmt);
 106 #else
 107   gmtoff = localtime (&s)->tm_gmtoff;
 108 #endif
 109 
 110   return gmtoff;
 111 }
 112 
 113 int
 114 main (_GL_UNUSED int argc, char **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 115 {
 116   struct timespec result;
 117   struct timespec result2;
 118   struct timespec expected;
 119   struct timespec now;
 120   const char *p;
 121   int i;
 122   long gmtoff;
 123   time_t ref_time = 1304250918;
 124 
 125   /* Set the time zone to US Eastern time with the 2012 rules.  This
 126      should disable any leap second support.  Otherwise, there will be
 127      a problem with glibc on sites that default to leap seconds; see
 128      <https://bugs.gnu.org/12206>.  */
 129   setenv ("TZ", "EST5EDT,M3.2.0,M11.1.0", 1);
 130 
 131   gmtoff = gmt_offset (ref_time);
 132 
 133 
 134   /* ISO 8601 extended date and time of day representation,
 135      'T' separator, local time zone */
 136   p = "2011-05-01T11:55:18";
 137   expected.tv_sec = ref_time - gmtoff;
 138   expected.tv_nsec = 0;
 139   ASSERT (parse_datetime (&result, p, 0));
 140   LOG (p, expected, result);
 141   ASSERT (expected.tv_sec == result.tv_sec
 142           && expected.tv_nsec == result.tv_nsec);
 143 
 144   /* ISO 8601 extended date and time of day representation,
 145      ' ' separator, local time zone */
 146   p = "2011-05-01 11:55:18";
 147   expected.tv_sec = ref_time - gmtoff;
 148   expected.tv_nsec = 0;
 149   ASSERT (parse_datetime (&result, p, 0));
 150   LOG (p, expected, result);
 151   ASSERT (expected.tv_sec == result.tv_sec
 152           && expected.tv_nsec == result.tv_nsec);
 153 
 154 
 155   /* ISO 8601, extended date and time of day representation,
 156      'T' separator, UTC */
 157   p = "2011-05-01T11:55:18Z";
 158   expected.tv_sec = ref_time;
 159   expected.tv_nsec = 0;
 160   ASSERT (parse_datetime (&result, p, 0));
 161   LOG (p, expected, result);
 162   ASSERT (expected.tv_sec == result.tv_sec
 163           && expected.tv_nsec == result.tv_nsec);
 164 
 165   /* ISO 8601, extended date and time of day representation,
 166      ' ' separator, UTC */
 167   p = "2011-05-01 11:55:18Z";
 168   expected.tv_sec = ref_time;
 169   expected.tv_nsec = 0;
 170   ASSERT (parse_datetime (&result, p, 0));
 171   LOG (p, expected, result);
 172   ASSERT (expected.tv_sec == result.tv_sec
 173           && expected.tv_nsec == result.tv_nsec);
 174 
 175 
 176   /* ISO 8601 extended date and time of day representation,
 177      'T' separator, w/UTC offset */
 178   p = "2011-05-01T11:55:18-07:00";
 179   expected.tv_sec = 1304276118;
 180   expected.tv_nsec = 0;
 181   ASSERT (parse_datetime (&result, p, 0));
 182   LOG (p, expected, result);
 183   ASSERT (expected.tv_sec == result.tv_sec
 184           && expected.tv_nsec == result.tv_nsec);
 185 
 186   /* ISO 8601 extended date and time of day representation,
 187      ' ' separator, w/UTC offset */
 188   p = "2011-05-01 11:55:18-07:00";
 189   expected.tv_sec = 1304276118;
 190   expected.tv_nsec = 0;
 191   ASSERT (parse_datetime (&result, p, 0));
 192   LOG (p, expected, result);
 193   ASSERT (expected.tv_sec == result.tv_sec
 194           && expected.tv_nsec == result.tv_nsec);
 195 
 196 
 197   /* ISO 8601 extended date and time of day representation,
 198      'T' separator, w/hour only UTC offset */
 199   p = "2011-05-01T11:55:18-07";
 200   expected.tv_sec = 1304276118;
 201   expected.tv_nsec = 0;
 202   ASSERT (parse_datetime (&result, p, 0));
 203   LOG (p, expected, result);
 204   ASSERT (expected.tv_sec == result.tv_sec
 205           && expected.tv_nsec == result.tv_nsec);
 206 
 207   /* ISO 8601 extended date and time of day representation,
 208      ' ' separator, w/hour only UTC offset */
 209   p = "2011-05-01 11:55:18-07";
 210   expected.tv_sec = 1304276118;
 211   expected.tv_nsec = 0;
 212   ASSERT (parse_datetime (&result, p, 0));
 213   LOG (p, expected, result);
 214   ASSERT (expected.tv_sec == result.tv_sec
 215           && expected.tv_nsec == result.tv_nsec);
 216 
 217 
 218   now.tv_sec = 4711;
 219   now.tv_nsec = 1267;
 220   p = "now";
 221   ASSERT (parse_datetime (&result, p, &now));
 222   LOG (p, now, result);
 223   ASSERT (now.tv_sec == result.tv_sec && now.tv_nsec == result.tv_nsec);
 224 
 225   now.tv_sec = 4711;
 226   now.tv_nsec = 1267;
 227   p = "tomorrow";
 228   ASSERT (parse_datetime (&result, p, &now));
 229   LOG (p, now, result);
 230   ASSERT (now.tv_sec + 24 * 60 * 60 == result.tv_sec
 231           && now.tv_nsec == result.tv_nsec);
 232 
 233   now.tv_sec = 4711;
 234   now.tv_nsec = 1267;
 235   p = "yesterday";
 236   ASSERT (parse_datetime (&result, p, &now));
 237   LOG (p, now, result);
 238   ASSERT (now.tv_sec - 24 * 60 * 60 == result.tv_sec
 239           && now.tv_nsec == result.tv_nsec);
 240 
 241   now.tv_sec = 4711;
 242   now.tv_nsec = 1267;
 243   p = "4 hours";
 244   ASSERT (parse_datetime (&result, p, &now));
 245   LOG (p, now, result);
 246   ASSERT (now.tv_sec + 4 * 60 * 60 == result.tv_sec
 247           && now.tv_nsec == result.tv_nsec);
 248 
 249   /* test if timezone is not being ignored for day offset */
 250   now.tv_sec = 4711;
 251   now.tv_nsec = 1267;
 252   p = "UTC+400 +24 hours";
 253   ASSERT (parse_datetime (&result, p, &now));
 254   LOG (p, now, result);
 255   p = "UTC+400 +1 day";
 256   ASSERT (parse_datetime (&result2, p, &now));
 257   LOG (p, now, result2);
 258   ASSERT (result.tv_sec == result2.tv_sec
 259           && result.tv_nsec == result2.tv_nsec);
 260 
 261   /* test if several time zones formats are handled same way */
 262   now.tv_sec = 4711;
 263   now.tv_nsec = 1267;
 264   p = "UTC+14:00";
 265   ASSERT (parse_datetime (&result, p, &now));
 266   LOG (p, now, result);
 267   p = "UTC+14";
 268   ASSERT (parse_datetime (&result2, p, &now));
 269   LOG (p, now, result2);
 270   ASSERT (result.tv_sec == result2.tv_sec
 271           && result.tv_nsec == result2.tv_nsec);
 272   p = "UTC+1400";
 273   ASSERT (parse_datetime (&result2, p, &now));
 274   LOG (p, now, result2);
 275   ASSERT (result.tv_sec == result2.tv_sec
 276           && result.tv_nsec == result2.tv_nsec);
 277 
 278   now.tv_sec = 4711;
 279   now.tv_nsec = 1267;
 280   p = "UTC-14:00";
 281   ASSERT (parse_datetime (&result, p, &now));
 282   LOG (p, now, result);
 283   p = "UTC-14";
 284   ASSERT (parse_datetime (&result2, p, &now));
 285   LOG (p, now, result2);
 286   ASSERT (result.tv_sec == result2.tv_sec
 287           && result.tv_nsec == result2.tv_nsec);
 288   p = "UTC-1400";
 289   ASSERT (parse_datetime (&result2, p, &now));
 290   LOG (p, now, result2);
 291   ASSERT (result.tv_sec == result2.tv_sec
 292           && result.tv_nsec == result2.tv_nsec);
 293 
 294   now.tv_sec = 4711;
 295   now.tv_nsec = 1267;
 296   p = "UTC+0:15";
 297   ASSERT (parse_datetime (&result, p, &now));
 298   LOG (p, now, result);
 299   p = "UTC+0015";
 300   ASSERT (parse_datetime (&result2, p, &now));
 301   LOG (p, now, result2);
 302   ASSERT (result.tv_sec == result2.tv_sec
 303           && result.tv_nsec == result2.tv_nsec);
 304 
 305   now.tv_sec = 4711;
 306   now.tv_nsec = 1267;
 307   p = "UTC-1:30";
 308   ASSERT (parse_datetime (&result, p, &now));
 309   LOG (p, now, result);
 310   p = "UTC-130";
 311   ASSERT (parse_datetime (&result2, p, &now));
 312   LOG (p, now, result2);
 313   ASSERT (result.tv_sec == result2.tv_sec
 314           && result.tv_nsec == result2.tv_nsec);
 315 
 316 
 317   /* TZ out of range should cause parse_datetime failure */
 318   now.tv_sec = 4711;
 319   now.tv_nsec = 1267;
 320   p = "UTC+25:00";
 321   ASSERT (!parse_datetime (&result, p, &now));
 322 
 323         /* Check for several invalid countable dayshifts */
 324   now.tv_sec = 4711;
 325   now.tv_nsec = 1267;
 326   p = "UTC+4:00 +40 yesterday";
 327   ASSERT (!parse_datetime (&result, p, &now));
 328   p = "UTC+4:00 next yesterday";
 329   ASSERT (!parse_datetime (&result, p, &now));
 330   p = "UTC+4:00 tomorrow ago";
 331   ASSERT (!parse_datetime (&result, p, &now));
 332   p = "UTC+4:00 tomorrow hence";
 333   ASSERT (!parse_datetime (&result, p, &now));
 334   p = "UTC+4:00 40 now ago";
 335   ASSERT (!parse_datetime (&result, p, &now));
 336   p = "UTC+4:00 last tomorrow";
 337   ASSERT (!parse_datetime (&result, p, &now));
 338   p = "UTC+4:00 -4 today";
 339   ASSERT (!parse_datetime (&result, p, &now));
 340 
 341   /* And check correct usage of dayshifts */
 342   now.tv_sec = 4711;
 343   now.tv_nsec = 1267;
 344   p = "UTC+400 tomorrow";
 345   ASSERT (parse_datetime (&result, p, &now));
 346   LOG (p, now, result);
 347   p = "UTC+400 +1 day";
 348   ASSERT (parse_datetime (&result2, p, &now));
 349   LOG (p, now, result2);
 350   ASSERT (result.tv_sec == result2.tv_sec
 351           && result.tv_nsec == result2.tv_nsec);
 352   p = "UTC+400 1 day hence";
 353   ASSERT (parse_datetime (&result2, p, &now));
 354   LOG (p, now, result2);
 355   ASSERT (result.tv_sec == result2.tv_sec
 356           && result.tv_nsec == result2.tv_nsec);
 357   now.tv_sec = 4711;
 358   now.tv_nsec = 1267;
 359   p = "UTC+400 yesterday";
 360   ASSERT (parse_datetime (&result, p, &now));
 361   LOG (p, now, result);
 362   p = "UTC+400 1 day ago";
 363   ASSERT (parse_datetime (&result2, p, &now));
 364   LOG (p, now, result2);
 365   ASSERT (result.tv_sec == result2.tv_sec
 366           && result.tv_nsec == result2.tv_nsec);
 367   now.tv_sec = 4711;
 368   now.tv_nsec = 1267;
 369   p = "UTC+400 now";
 370   ASSERT (parse_datetime (&result, p, &now));
 371   LOG (p, now, result);
 372   p = "UTC+400 +0 minutes"; /* silly, but simple "UTC+400" is different*/
 373   ASSERT (parse_datetime (&result2, p, &now));
 374   LOG (p, now, result2);
 375   ASSERT (result.tv_sec == result2.tv_sec
 376           && result.tv_nsec == result2.tv_nsec);
 377 
 378   /* Check that some "next Monday", "last Wednesday", etc. are correct.  */
 379   setenv ("TZ", "UTC0", 1);
 380   for (i = 0; day_table[i]; i++)
 381     {
 382       unsigned int thur2 = 7 * 24 * 3600; /* 2nd thursday */
 383       char tmp[32];
 384       sprintf (tmp, "NEXT %s", day_table[i]);
 385       now.tv_sec = thur2 + 4711;
 386       now.tv_nsec = 1267;
 387       ASSERT (parse_datetime (&result, tmp, &now));
 388       LOG (tmp, now, result);
 389       ASSERT (result.tv_nsec == 0);
 390       ASSERT (result.tv_sec == thur2 + (i == 4 ? 7 : (i + 3) % 7) * 24 * 3600);
 391 
 392       sprintf (tmp, "LAST %s", day_table[i]);
 393       now.tv_sec = thur2 + 4711;
 394       now.tv_nsec = 1267;
 395       ASSERT (parse_datetime (&result, tmp, &now));
 396       LOG (tmp, now, result);
 397       ASSERT (result.tv_nsec == 0);
 398       ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600);
 399     }
 400 
 401   p = "THURSDAY UTC+00";  /* The epoch was on Thursday.  */
 402   now.tv_sec = 0;
 403   now.tv_nsec = 0;
 404   ASSERT (parse_datetime (&result, p, &now));
 405   LOG (p, now, result);
 406   ASSERT (result.tv_sec == now.tv_sec
 407           && result.tv_nsec == now.tv_nsec);
 408 
 409   p = "FRIDAY UTC+00";
 410   now.tv_sec = 0;
 411   now.tv_nsec = 0;
 412   ASSERT (parse_datetime (&result, p, &now));
 413   LOG (p, now, result);
 414   ASSERT (result.tv_sec == 24 * 3600
 415           && result.tv_nsec == now.tv_nsec);
 416 
 417   /* Exercise a sign-extension bug.  Before July 2012, an input
 418      starting with a high-bit-set byte would be treated like "0".  */
 419   ASSERT ( ! parse_datetime (&result, "\xb0", &now));
 420 
 421   /* Exercise TZ="" parsing code.  */
 422   /* These two would infloop or segfault before Feb 2014.  */
 423   ASSERT ( ! parse_datetime (&result, "TZ=\"\"\"", &now));
 424   ASSERT ( ! parse_datetime (&result, "TZ=\"\" \"", &now));
 425   /* Exercise invalid patterns.  */
 426   ASSERT ( ! parse_datetime (&result, "TZ=\"", &now));
 427   ASSERT ( ! parse_datetime (&result, "TZ=\"\\\"", &now));
 428   ASSERT ( ! parse_datetime (&result, "TZ=\"\\n", &now));
 429   ASSERT ( ! parse_datetime (&result, "TZ=\"\\n\"", &now));
 430   /* Exercise valid patterns.  */
 431   ASSERT (   parse_datetime (&result, "TZ=\"\"", &now));
 432   ASSERT (   parse_datetime (&result, "TZ=\"\" ", &now));
 433   ASSERT (   parse_datetime (&result, " TZ=\"\"", &now));
 434   /* Exercise patterns which may be valid or invalid, depending on the
 435      platform.  */
 436 #if !defined __NetBSD__
 437   ASSERT (   parse_datetime (&result, "TZ=\"\\\\\"", &now));
 438   ASSERT (   parse_datetime (&result, "TZ=\"\\\"\"", &now));
 439 #endif
 440 
 441   /* Outlandishly-long time zone abbreviations should not cause problems.  */
 442   {
 443     static char const bufprefix[] = "TZ=\"";
 444     long int tzname_max = -1;
 445     errno = 0;
 446 #ifdef _SC_TZNAME_MAX
 447     tzname_max = sysconf (_SC_TZNAME_MAX);
 448 #endif
 449     enum { tzname_alloc = 2000 };
 450     if (tzname_max < 0)
 451       tzname_max = errno ? 6 : tzname_alloc;
 452     int tzname_len = tzname_alloc < tzname_max ? tzname_alloc : tzname_max;
 453     static char const bufsuffix[] = "0\" 1970-01-01 01:02:03.123456789";
 454     enum { bufsize = sizeof bufprefix - 1 + tzname_alloc + sizeof bufsuffix };
 455     char buf[bufsize];
 456     memcpy (buf, bufprefix, sizeof bufprefix - 1);
 457     memset (buf + sizeof bufprefix - 1, 'X', tzname_len);
 458     strcpy (buf + sizeof bufprefix - 1 + tzname_len, bufsuffix);
 459     ASSERT (parse_datetime (&result, buf, &now));
 460     LOG (buf, now, result);
 461     ASSERT (result.tv_sec == 1 * 60 * 60 + 2 * 60 + 3
 462             && result.tv_nsec == 123456789);
 463   }
 464 
 465   return 0;
 466 }

/* [previous][next][first][last][top][bottom][index][help] */