(root)/
coreutils-9.4/
gnulib-tests/
test-parse-datetime.c
       1  /* Test of parse_datetime() function.
       2     Copyright (C) 2008-2023 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    ASSERT (setenv ("TZ", "EST5EDT,M3.2.0,M11.1.0", 1) == 0);
     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    /* ISO 8601 extended date and time of day representation,
     155       ' ' separator, 'J' (local) time zone */
     156    p = "2011-05-01 11:55:18J";
     157    expected.tv_sec = ref_time - gmtoff;
     158    expected.tv_nsec = 0;
     159    ASSERT (parse_datetime (&result, p, 0));
     160    LOG (p, expected, result);
     161    ASSERT (expected.tv_sec == result.tv_sec
     162            && expected.tv_nsec == result.tv_nsec);
     163  
     164  
     165    /* ISO 8601, extended date and time of day representation,
     166       'T' separator, UTC */
     167    p = "2011-05-01T11: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    /* ISO 8601, extended date and time of day representation,
     176       ' ' separator, UTC */
     177    p = "2011-05-01 11:55:18Z";
     178    expected.tv_sec = ref_time;
     179    expected.tv_nsec = 0;
     180    ASSERT (parse_datetime (&result, p, 0));
     181    LOG (p, expected, result);
     182    ASSERT (expected.tv_sec == result.tv_sec
     183            && expected.tv_nsec == result.tv_nsec);
     184  
     185  
     186    /* ISO 8601 extended date and time of day representation,
     187       'T' separator, w/UTC offset */
     188    p = "2011-05-01T11: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    /* ISO 8601 extended date and time of day representation,
     197       ' ' separator, w/UTC offset */
     198    p = "2011-05-01 11:55:18-07:00";
     199    expected.tv_sec = 1304276118;
     200    expected.tv_nsec = 0;
     201    ASSERT (parse_datetime (&result, p, 0));
     202    LOG (p, expected, result);
     203    ASSERT (expected.tv_sec == result.tv_sec
     204            && expected.tv_nsec == result.tv_nsec);
     205  
     206  
     207    /* ISO 8601 extended date and time of day representation,
     208       'T' separator, w/hour only UTC offset */
     209    p = "2011-05-01T11: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    /* ISO 8601 extended date and time of day representation,
     218       ' ' separator, w/hour only UTC offset */
     219    p = "2011-05-01 11:55:18-07";
     220    expected.tv_sec = 1304276118;
     221    expected.tv_nsec = 0;
     222    ASSERT (parse_datetime (&result, p, 0));
     223    LOG (p, expected, result);
     224    ASSERT (expected.tv_sec == result.tv_sec
     225            && expected.tv_nsec == result.tv_nsec);
     226  
     227  
     228    now.tv_sec = 4711;
     229    now.tv_nsec = 1267;
     230    p = "now";
     231    ASSERT (parse_datetime (&result, p, &now));
     232    LOG (p, now, result);
     233    ASSERT (now.tv_sec == result.tv_sec && now.tv_nsec == result.tv_nsec);
     234  
     235    now.tv_sec = 4711;
     236    now.tv_nsec = 1267;
     237    p = "tomorrow";
     238    ASSERT (parse_datetime (&result, p, &now));
     239    LOG (p, now, result);
     240    ASSERT (now.tv_sec + 24 * 60 * 60 == result.tv_sec
     241            && now.tv_nsec == result.tv_nsec);
     242  
     243    now.tv_sec = 4711;
     244    now.tv_nsec = 1267;
     245    p = "yesterday";
     246    ASSERT (parse_datetime (&result, p, &now));
     247    LOG (p, now, result);
     248    ASSERT (now.tv_sec - 24 * 60 * 60 == result.tv_sec
     249            && now.tv_nsec == result.tv_nsec);
     250  
     251    now.tv_sec = 4711;
     252    now.tv_nsec = 1267;
     253    p = "4 hours";
     254    ASSERT (parse_datetime (&result, p, &now));
     255    LOG (p, now, result);
     256    ASSERT (now.tv_sec + 4 * 60 * 60 == result.tv_sec
     257            && now.tv_nsec == result.tv_nsec);
     258  
     259    /* test if timezone is not being ignored for day offset */
     260    now.tv_sec = 4711;
     261    now.tv_nsec = 1267;
     262    p = "UTC+400 +24 hours";
     263    ASSERT (parse_datetime (&result, p, &now));
     264    LOG (p, now, result);
     265    p = "UTC+400 +1 day";
     266    ASSERT (parse_datetime (&result2, p, &now));
     267    LOG (p, now, result2);
     268    ASSERT (result.tv_sec == result2.tv_sec
     269            && result.tv_nsec == result2.tv_nsec);
     270  
     271    /* test if several time zones formats are handled same way */
     272    now.tv_sec = 4711;
     273    now.tv_nsec = 1267;
     274    p = "UTC+14:00";
     275    ASSERT (parse_datetime (&result, p, &now));
     276    LOG (p, now, result);
     277    p = "UTC+14";
     278    ASSERT (parse_datetime (&result2, p, &now));
     279    LOG (p, now, result2);
     280    ASSERT (result.tv_sec == result2.tv_sec
     281            && result.tv_nsec == result2.tv_nsec);
     282    p = "UTC+1400";
     283    ASSERT (parse_datetime (&result2, p, &now));
     284    LOG (p, now, result2);
     285    ASSERT (result.tv_sec == result2.tv_sec
     286            && result.tv_nsec == result2.tv_nsec);
     287  
     288    now.tv_sec = 4711;
     289    now.tv_nsec = 1267;
     290    p = "UTC-14:00";
     291    ASSERT (parse_datetime (&result, p, &now));
     292    LOG (p, now, result);
     293    p = "UTC-14";
     294    ASSERT (parse_datetime (&result2, p, &now));
     295    LOG (p, now, result2);
     296    ASSERT (result.tv_sec == result2.tv_sec
     297            && result.tv_nsec == result2.tv_nsec);
     298    p = "UTC-1400";
     299    ASSERT (parse_datetime (&result2, p, &now));
     300    LOG (p, now, result2);
     301    ASSERT (result.tv_sec == result2.tv_sec
     302            && result.tv_nsec == result2.tv_nsec);
     303  
     304    now.tv_sec = 4711;
     305    now.tv_nsec = 1267;
     306    p = "UTC+0:15";
     307    ASSERT (parse_datetime (&result, p, &now));
     308    LOG (p, now, result);
     309    p = "UTC+0015";
     310    ASSERT (parse_datetime (&result2, p, &now));
     311    LOG (p, now, result2);
     312    ASSERT (result.tv_sec == result2.tv_sec
     313            && result.tv_nsec == result2.tv_nsec);
     314  
     315    now.tv_sec = 4711;
     316    now.tv_nsec = 1267;
     317    p = "UTC-1:30";
     318    ASSERT (parse_datetime (&result, p, &now));
     319    LOG (p, now, result);
     320    p = "UTC-130";
     321    ASSERT (parse_datetime (&result2, p, &now));
     322    LOG (p, now, result2);
     323    ASSERT (result.tv_sec == result2.tv_sec
     324            && result.tv_nsec == result2.tv_nsec);
     325  
     326  
     327    /* TZ out of range should cause parse_datetime failure */
     328    now.tv_sec = 4711;
     329    now.tv_nsec = 1267;
     330    p = "UTC+25:00";
     331    ASSERT (!parse_datetime (&result, p, &now));
     332  
     333          /* Check for several invalid countable dayshifts */
     334    now.tv_sec = 4711;
     335    now.tv_nsec = 1267;
     336    p = "UTC+4:00 +40 yesterday";
     337    ASSERT (!parse_datetime (&result, p, &now));
     338    p = "UTC+4:00 next yesterday";
     339    ASSERT (!parse_datetime (&result, p, &now));
     340    p = "UTC+4:00 tomorrow ago";
     341    ASSERT (!parse_datetime (&result, p, &now));
     342    p = "UTC+4:00 tomorrow hence";
     343    ASSERT (!parse_datetime (&result, p, &now));
     344    p = "UTC+4:00 40 now ago";
     345    ASSERT (!parse_datetime (&result, p, &now));
     346    p = "UTC+4:00 last tomorrow";
     347    ASSERT (!parse_datetime (&result, p, &now));
     348    p = "UTC+4:00 -4 today";
     349    ASSERT (!parse_datetime (&result, p, &now));
     350  
     351    /* And check correct usage of dayshifts */
     352    now.tv_sec = 4711;
     353    now.tv_nsec = 1267;
     354    p = "UTC+400 tomorrow";
     355    ASSERT (parse_datetime (&result, p, &now));
     356    LOG (p, now, result);
     357    p = "UTC+400 +1 day";
     358    ASSERT (parse_datetime (&result2, p, &now));
     359    LOG (p, now, result2);
     360    ASSERT (result.tv_sec == result2.tv_sec
     361            && result.tv_nsec == result2.tv_nsec);
     362    p = "UTC+400 1 day hence";
     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 yesterday";
     370    ASSERT (parse_datetime (&result, p, &now));
     371    LOG (p, now, result);
     372    p = "UTC+400 1 day ago";
     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    now.tv_sec = 4711;
     378    now.tv_nsec = 1267;
     379    p = "UTC+400 now";
     380    ASSERT (parse_datetime (&result, p, &now));
     381    LOG (p, now, result);
     382    p = "UTC+400 +0 minutes"; /* silly, but simple "UTC+400" is different*/
     383    ASSERT (parse_datetime (&result2, p, &now));
     384    LOG (p, now, result2);
     385    ASSERT (result.tv_sec == result2.tv_sec
     386            && result.tv_nsec == result2.tv_nsec);
     387  
     388    /* If this platform has TZDB, check for GNU Bug#48085.  */
     389    ASSERT (setenv ("TZ", "America/Indiana/Indianapolis", 1) == 0);
     390    now.tv_sec = 1619641490;
     391    now.tv_nsec = 0;
     392    struct tm *tm = localtime (&now.tv_sec);
     393    if (tm && tm->tm_year == 2021 - 1900 && tm->tm_mon == 4 - 1
     394        && tm->tm_mday == 28 && tm->tm_hour == 16 && tm->tm_min == 24
     395        && 0 < tm->tm_isdst)
     396      {
     397        int has_leap_seconds = tm->tm_sec != now.tv_sec % 60;
     398        p = "now - 35 years";
     399        ASSERT (parse_datetime (&result, p, &now));
     400        LOG (p, now, result);
     401        ASSERT (result.tv_sec
     402                == 515107490 - 60 * 60 + (has_leap_seconds ? 13 : 0));
     403      }
     404  
     405    /* Check that some "next Monday", "last Wednesday", etc. are correct.  */
     406    ASSERT (setenv ("TZ", "UTC0", 1) == 0);
     407    for (i = 0; day_table[i]; i++)
     408      {
     409        unsigned int thur2 = 7 * 24 * 3600; /* 2nd thursday */
     410        char tmp[32];
     411        sprintf (tmp, "NEXT %s", day_table[i]);
     412        now.tv_sec = thur2 + 4711;
     413        now.tv_nsec = 1267;
     414        ASSERT (parse_datetime (&result, tmp, &now));
     415        LOG (tmp, now, result);
     416        ASSERT (result.tv_nsec == 0);
     417        ASSERT (result.tv_sec == thur2 + (i == 4 ? 7 : (i + 3) % 7) * 24 * 3600);
     418  
     419        sprintf (tmp, "LAST %s", day_table[i]);
     420        now.tv_sec = thur2 + 4711;
     421        now.tv_nsec = 1267;
     422        ASSERT (parse_datetime (&result, tmp, &now));
     423        LOG (tmp, now, result);
     424        ASSERT (result.tv_nsec == 0);
     425        ASSERT (result.tv_sec == thur2 + ((i + 3) % 7 - 7) * 24 * 3600);
     426      }
     427  
     428    p = "1970-12-31T23:59:59+00:00 - 1 year";  /* Bug#50115 */
     429    now.tv_sec = -1;
     430    now.tv_nsec = 0;
     431    ASSERT (parse_datetime (&result, p, &now));
     432    LOG (p, now, result);
     433    ASSERT (result.tv_sec == now.tv_sec
     434            && result.tv_nsec == now.tv_nsec);
     435  
     436    p = "THURSDAY UTC+00";  /* The epoch was on Thursday.  */
     437    now.tv_sec = 0;
     438    now.tv_nsec = 0;
     439    ASSERT (parse_datetime (&result, p, &now));
     440    LOG (p, now, result);
     441    ASSERT (result.tv_sec == now.tv_sec
     442            && result.tv_nsec == now.tv_nsec);
     443  
     444    p = "FRIDAY UTC+00";
     445    now.tv_sec = 0;
     446    now.tv_nsec = 0;
     447    ASSERT (parse_datetime (&result, p, &now));
     448    LOG (p, now, result);
     449    ASSERT (result.tv_sec == 24 * 3600
     450            && result.tv_nsec == now.tv_nsec);
     451  
     452    /* Exercise a sign-extension bug.  Before July 2012, an input
     453       starting with a high-bit-set byte would be treated like "0".  */
     454    ASSERT ( ! parse_datetime (&result, "\xb0", &now));
     455  
     456    /* Exercise TZ="" parsing code.  */
     457    /* These two would infloop or segfault before Feb 2014.  */
     458    ASSERT ( ! parse_datetime (&result, "TZ=\"\"\"", &now));
     459    ASSERT ( ! parse_datetime (&result, "TZ=\"\" \"", &now));
     460    /* Exercise invalid patterns.  */
     461    ASSERT ( ! parse_datetime (&result, "TZ=\"", &now));
     462    ASSERT ( ! parse_datetime (&result, "TZ=\"\\\"", &now));
     463    ASSERT ( ! parse_datetime (&result, "TZ=\"\\n", &now));
     464    ASSERT ( ! parse_datetime (&result, "TZ=\"\\n\"", &now));
     465    /* Exercise valid patterns.  */
     466    ASSERT (   parse_datetime (&result, "TZ=\"\"", &now));
     467    ASSERT (   parse_datetime (&result, "TZ=\"\" ", &now));
     468    ASSERT (   parse_datetime (&result, " TZ=\"\"", &now));
     469    /* Exercise patterns which may be valid or invalid, depending on the
     470       platform.  */
     471  #if !defined __NetBSD__
     472    ASSERT (   parse_datetime (&result, "TZ=\"\\\\\"", &now));
     473    ASSERT (   parse_datetime (&result, "TZ=\"\\\"\"", &now));
     474  #endif
     475  
     476    /* Outlandishly-long time zone abbreviations should not cause problems.  */
     477    {
     478      static char const bufprefix[] = "TZ=\"";
     479      long int tzname_max = -1;
     480      errno = 0;
     481  #ifdef _SC_TZNAME_MAX
     482      tzname_max = sysconf (_SC_TZNAME_MAX);
     483  #endif
     484      enum { tzname_alloc = 2000 };
     485      if (tzname_max < 0)
     486        tzname_max = errno ? 6 : tzname_alloc;
     487      int tzname_len = tzname_alloc < tzname_max ? tzname_alloc : tzname_max;
     488      static char const bufsuffix[] = "0\" 1970-01-01 01:02:03.123456789";
     489      enum { bufsize = sizeof bufprefix - 1 + tzname_alloc + sizeof bufsuffix };
     490      char buf[bufsize];
     491      memcpy (buf, bufprefix, sizeof bufprefix - 1);
     492      memset (buf + sizeof bufprefix - 1, 'X', tzname_len);
     493      strcpy (buf + sizeof bufprefix - 1 + tzname_len, bufsuffix);
     494      ASSERT (parse_datetime (&result, buf, &now));
     495      LOG (buf, now, result);
     496      ASSERT (result.tv_sec == 1 * 60 * 60 + 2 * 60 + 3
     497              && result.tv_nsec == 123456789);
     498    }
     499  
     500    return 0;
     501  }