(root)/
findutils-4.9.0/
gnulib-tests/
test-parse-datetime.c
       1  /* Test of parse_datetime() function.
       2     Copyright (C) 2008-2022 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)
      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)
      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)
     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  }