Coverage Report

Created: 2026-01-09 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/parsedate.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#include "parsedate.h"
27
#include "curlx/strparse.h"
28
29
/*
30
  A brief summary of the date string formats this parser groks:
31
32
  RFC 2616 3.3.1
33
34
  Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
35
  Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
36
  Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format
37
38
  we support dates without week day name:
39
40
  06 Nov 1994 08:49:37 GMT
41
  06-Nov-94 08:49:37 GMT
42
  Nov  6 08:49:37 1994
43
44
  without the time zone:
45
46
  06 Nov 1994 08:49:37
47
  06-Nov-94 08:49:37
48
49
  weird order:
50
51
  1994 Nov 6 08:49:37  (GNU date fails)
52
  GMT 08:49:37 06-Nov-94 Sunday
53
  94 6 Nov 08:49:37    (GNU date fails)
54
55
  time left out:
56
57
  1994 Nov 6
58
  06-Nov-94
59
  Sun Nov 6 94
60
61
  unusual separators:
62
63
  1994.Nov.6
64
  Sun/Nov/6/94/GMT
65
66
  commonly used time zone names:
67
68
  Sun, 06 Nov 1994 08:49:37 CET
69
  06 Nov 1994 08:49:37 EST
70
71
  time zones specified using RFC822 style:
72
73
  Sun, 12 Sep 2004 15:05:58 -0700
74
  Sat, 11 Sep 2004 21:32:11 +0200
75
76
  compact numerical date strings:
77
78
  20040912 15:05:58 -0700
79
  20040911 +0200
80
81
*/
82
83
/*
84
 * parsedate()
85
 *
86
 * Returns:
87
 *
88
 * PARSEDATE_OK     - a fine conversion
89
 * PARSEDATE_FAIL   - failed to convert
90
 * PARSEDATE_LATER  - time overflow at the far end of time_t
91
 * PARSEDATE_SOONER - time underflow at the low end of time_t
92
 */
93
94
static int parsedate(const char *date, time_t *output);
95
96
245
#define PARSEDATE_OK     0
97
24.8k
#define PARSEDATE_FAIL   -1
98
0
#define PARSEDATE_LATER  1
99
#if defined(HAVE_TIME_T_UNSIGNED) || (SIZEOF_TIME_T < 5)
100
#define PARSEDATE_SOONER 2
101
#endif
102
103
#if !defined(CURL_DISABLE_PARSEDATE) || !defined(CURL_DISABLE_FTP) || \
104
  !defined(CURL_DISABLE_FILE) || defined(USE_GNUTLS)
105
/* These names are also used by FTP and FILE code */
106
const char * const Curl_wkday[] = {
107
  "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
108
};
109
const char * const Curl_month[] = {
110
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
111
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
112
};
113
#endif
114
115
#ifndef CURL_DISABLE_PARSEDATE
116
static const char * const weekday[] = {
117
  "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"
118
};
119
120
struct tzinfo {
121
  char name[5];
122
  int offset; /* +/- in minutes */
123
};
124
125
/* Here's a bunch of frequently used time zone names. These were supported
126
   by the old getdate parser. */
127
#define tDAYZONE -60       /* offset for daylight savings time */
128
static const struct tzinfo tz[] = {
129
  { "GMT",     0 },          /* Greenwich Mean */
130
  { "UT",      0 },          /* Universal Time */
131
  { "UTC",     0 },          /* Universal (Coordinated) */
132
  { "WET",     0 },          /* Western European */
133
  { "BST",     0 tDAYZONE }, /* British Summer */
134
  { "WAT",    60 },          /* West Africa */
135
  { "AST",   240 },          /* Atlantic Standard */
136
  { "ADT",   240 tDAYZONE }, /* Atlantic Daylight */
137
  { "EST",   300 },          /* Eastern Standard */
138
  { "EDT",   300 tDAYZONE }, /* Eastern Daylight */
139
  { "CST",   360 },          /* Central Standard */
140
  { "CDT",   360 tDAYZONE }, /* Central Daylight */
141
  { "MST",   420 },          /* Mountain Standard */
142
  { "MDT",   420 tDAYZONE }, /* Mountain Daylight */
143
  { "PST",   480 },          /* Pacific Standard */
144
  { "PDT",   480 tDAYZONE }, /* Pacific Daylight */
145
  { "YST",   540 },          /* Yukon Standard */
146
  { "YDT",   540 tDAYZONE }, /* Yukon Daylight */
147
  { "HST",   600 },          /* Hawaii Standard */
148
  { "HDT",   600 tDAYZONE }, /* Hawaii Daylight */
149
  { "CAT",   600 },          /* Central Alaska */
150
  { "AHST",  600 },          /* Alaska-Hawaii Standard */
151
  { "NT",    660 },          /* Nome */ /* spellchecker:disable-line */
152
  { "IDLW",  720 },          /* International Date Line West */
153
  { "CET",   -60 },          /* Central European */
154
  { "MET",   -60 },          /* Middle European */
155
  { "MEWT",  -60 },          /* Middle European Winter */
156
  { "MEST",  -60 tDAYZONE }, /* Middle European Summer */
157
  { "CEST",  -60 tDAYZONE }, /* Central European Summer */
158
  { "MESZ",  -60 tDAYZONE }, /* Middle European Summer */
159
  { "FWT",   -60 },          /* French Winter */
160
  { "FST",   -60 tDAYZONE }, /* French Summer */
161
  { "EET",  -120 },          /* Eastern Europe, USSR Zone 1 */
162
  { "WAST", -420 }, /* spellchecker:disable-line */
163
                             /* West Australian Standard */
164
  { "WADT", -420 tDAYZONE }, /* West Australian Daylight */
165
  { "CCT",  -480 },          /* China Coast, USSR Zone 7 */
166
  { "JST",  -540 },          /* Japan Standard, USSR Zone 8 */
167
  { "EAST", -600 },          /* Eastern Australian Standard */
168
  { "EADT", -600 tDAYZONE }, /* Eastern Australian Daylight */
169
  { "GST",  -600 },          /* Guam Standard, USSR Zone 9 */
170
  { "NZT",  -720 },          /* New Zealand */
171
  { "NZST", -720 },          /* New Zealand Standard */
172
  { "NZDT", -720 tDAYZONE }, /* New Zealand Daylight */
173
  { "IDLE", -720 },          /* International Date Line East */
174
  /* Next up: Military timezone names. RFC822 allowed these, but (as noted in
175
     RFC 1123) had their signs wrong. Here we use the correct signs to match
176
     actual military usage.
177
   */
178
  { "A",   1 * 60 },         /* Alpha */
179
  { "B",   2 * 60 },         /* Bravo */
180
  { "C",   3 * 60 },         /* Charlie */
181
  { "D",   4 * 60 },         /* Delta */
182
  { "E",   5 * 60 },         /* Echo */
183
  { "F",   6 * 60 },         /* Foxtrot */
184
  { "G",   7 * 60 },         /* Golf */
185
  { "H",   8 * 60 },         /* Hotel */
186
  { "I",   9 * 60 },         /* India */
187
  /* "J", Juliet is not used as a timezone, to indicate the observer's local
188
     time */
189
  { "K",  10 * 60 },         /* Kilo */
190
  { "L",  11 * 60 },         /* Lima */
191
  { "M",  12 * 60 },         /* Mike */
192
  { "N",  -1 * 60 },         /* November */
193
  { "O",  -2 * 60 },         /* Oscar */
194
  { "P",  -3 * 60 },         /* Papa */
195
  { "Q",  -4 * 60 },         /* Quebec */
196
  { "R",  -5 * 60 },         /* Romeo */
197
  { "S",  -6 * 60 },         /* Sierra */
198
  { "T",  -7 * 60 },         /* Tango */
199
  { "U",  -8 * 60 },         /* Uniform */
200
  { "V",  -9 * 60 },         /* Victor */
201
  { "W", -10 * 60 },         /* Whiskey */
202
  { "X", -11 * 60 },         /* X-ray */
203
  { "Y", -12 * 60 },         /* Yankee */
204
  { "Z", 0 },                /* Zulu, zero meridian, a.k.a. UTC */
205
};
206
207
/* returns:
208
   -1 no day
209
   0 monday - 6 sunday
210
*/
211
212
static int checkday(const char *check, size_t len)
213
10.6k
{
214
10.6k
  int i;
215
10.6k
  const char * const *what;
216
10.6k
  if(len > 3)
217
1.04k
    what = &weekday[0];
218
9.63k
  else if(len == 3)
219
3.53k
    what = &Curl_wkday[0];
220
6.09k
  else
221
6.09k
    return -1; /* too short */
222
34.3k
  for(i = 0; i < 7; i++) {
223
30.5k
    size_t ilen = strlen(what[0]);
224
30.5k
    if((ilen == len) &&
225
23.9k
       curl_strnequal(check, what[0], len))
226
789
      return i;
227
29.7k
    what++;
228
29.7k
  }
229
3.78k
  return -1;
230
4.57k
}
231
232
static int checkmonth(const char *check, size_t len)
233
8.71k
{
234
8.71k
  int i;
235
8.71k
  const char * const *what = &Curl_month[0];
236
8.71k
  if(len != 3)
237
5.87k
    return -1; /* not a month */
238
239
23.7k
  for(i = 0; i < 12; i++) {
240
22.9k
    if(curl_strnequal(check, what[0], 3))
241
2.08k
      return i;
242
20.8k
    what++;
243
20.8k
  }
244
758
  return -1; /* return the offset or -1, no real offset is -1 */
245
2.84k
}
246
247
/* return the time zone offset between GMT and the input one, in number
248
   of seconds or -1 if the timezone was not found/legal */
249
250
static int checktz(const char *check, size_t len)
251
6.84k
{
252
6.84k
  unsigned int i;
253
6.84k
  const struct tzinfo *what = tz;
254
6.84k
  if(len > 4) /* longer than any valid timezone */
255
508
    return -1;
256
257
367k
  for(i = 0; i < CURL_ARRAYSIZE(tz); i++) {
258
365k
    size_t ilen = strlen(what->name);
259
365k
    if((ilen == len) &&
260
74.4k
       curl_strnequal(check, what->name, len))
261
4.83k
      return what->offset * 60;
262
360k
    what++;
263
360k
  }
264
1.50k
  return -1;
265
6.33k
}
266
267
static void skip(const char **date)
268
31.9k
{
269
  /* skip everything that are not letters or digits */
270
55.2k
  while(**date && !ISALNUM(**date))
271
23.3k
    (*date)++;
272
31.9k
}
273
274
enum assume {
275
  DATE_MDAY,
276
  DATE_YEAR,
277
  DATE_TIME
278
};
279
280
/*
281
 * time2epoch: time stamp to seconds since epoch in GMT time zone. Similar to
282
 * mktime but for GMT only.
283
 */
284
static time_t time2epoch(int sec, int min, int hour,
285
                         int mday, int mon, int year)
286
245
{
287
245
  static const int month_days_cumulative[12] = {
288
245
    0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
289
245
  };
290
245
  int leap_days = year - (mon <= 1);
291
245
  leap_days = ((leap_days / 4) - (leap_days / 100) + (leap_days / 400)
292
245
               - (1969 / 4) + (1969 / 100) - (1969 / 400));
293
245
  return ((((time_t)(year - 1970) * 365
294
245
            + leap_days + month_days_cumulative[mon] + mday - 1) * 24
295
245
           + hour) * 60 + min) * 60 + sec;
296
245
}
297
298
/* Returns the value of a single-digit or two-digit decimal number, return
299
   then pointer to after the number. The 'date' pointer is known to point to a
300
   digit. */
301
static int oneortwodigit(const char *date, const char **endp)
302
20.9k
{
303
20.9k
  int num = date[0] - '0';
304
20.9k
  if(ISDIGIT(date[1])) {
305
9.61k
    *endp = &date[2];
306
9.61k
    return num * 10 + (date[1] - '0');
307
9.61k
  }
308
11.3k
  *endp = &date[1];
309
11.3k
  return num;
310
20.9k
}
311
312
/* HH:MM:SS or HH:MM and accept single-digits too */
313
static bool match_time(const char *date,
314
                       int *h, int *m, int *s, char **endp)
315
16.9k
{
316
16.9k
  const char *p;
317
16.9k
  int hh, mm, ss = 0;
318
16.9k
  hh = oneortwodigit(date, &p);
319
16.9k
  if((hh < 24) && (*p == ':') && ISDIGIT(p[1])) {
320
2.89k
    mm = oneortwodigit(&p[1], &p);
321
2.89k
    if(mm < 60) {
322
2.10k
      if((*p == ':') && ISDIGIT(p[1])) {
323
1.09k
        ss = oneortwodigit(&p[1], &p);
324
1.09k
        if(ss <= 60) {
325
          /* valid HH:MM:SS */
326
693
          goto match;
327
693
        }
328
1.09k
      }
329
1.00k
      else {
330
        /* valid HH:MM */
331
1.00k
        goto match;
332
1.00k
      }
333
2.10k
    }
334
2.89k
  }
335
15.2k
  return FALSE; /* not a time string */
336
1.69k
match:
337
1.69k
  *h = hh;
338
1.69k
  *m = mm;
339
1.69k
  *s = ss;
340
1.69k
  *endp = (char *)CURL_UNCONST(p);
341
1.69k
  return TRUE;
342
16.9k
}
343
344
/*
345
 * parsedate()
346
 *
347
 * Returns:
348
 *
349
 * PARSEDATE_OK     - a fine conversion
350
 * PARSEDATE_FAIL   - failed to convert
351
 * PARSEDATE_LATER  - time overflow at the far end of time_t
352
 * PARSEDATE_SOONER - time underflow at the low end of time_t
353
 */
354
355
/* Wednesday is the longest name this parser knows about */
356
39.8k
#define NAME_LEN 12
357
358
static int parsedate(const char *date, time_t *output)
359
12.5k
{
360
12.5k
  time_t t = 0;
361
12.5k
  int wdaynum = -1;  /* day of the week number, 0-6 (mon-sun) */
362
12.5k
  int monnum = -1;   /* month of the year number, 0-11 */
363
12.5k
  int mdaynum = -1;  /* day of month, 1 - 31 */
364
12.5k
  int hournum = -1;
365
12.5k
  int minnum = -1;
366
12.5k
  int secnum = -1;
367
12.5k
  int yearnum = -1;
368
12.5k
  int tzoff = -1;
369
12.5k
  enum assume dignext = DATE_MDAY;
370
12.5k
  const char *indate = date; /* save the original pointer */
371
12.5k
  int part = 0; /* max 6 parts */
372
373
37.5k
  while(*date && (part < 6)) {
374
31.9k
    bool found = FALSE;
375
376
31.9k
    skip(&date);
377
378
31.9k
    if(ISALPHA(*date)) {
379
      /* a name coming up */
380
11.8k
      size_t len = 0;
381
11.8k
      const char *p = date;
382
39.6k
      while(ISALPHA(*p) && (len < NAME_LEN)) {
383
27.8k
        p++;
384
27.8k
        len++;
385
27.8k
      }
386
387
11.8k
      if(len != NAME_LEN) {
388
11.5k
        if(wdaynum == -1) {
389
10.6k
          wdaynum = checkday(date, len);
390
10.6k
          if(wdaynum != -1)
391
789
            found = TRUE;
392
10.6k
        }
393
11.5k
        if(!found && (monnum == -1)) {
394
8.71k
          monnum = checkmonth(date, len);
395
8.71k
          if(monnum != -1)
396
2.08k
            found = TRUE;
397
8.71k
        }
398
399
11.5k
        if(!found && (tzoff == -1)) {
400
          /* this just must be a time zone string */
401
6.84k
          tzoff = checktz(date, len);
402
6.84k
          if(tzoff != -1)
403
4.83k
            found = TRUE;
404
6.84k
        }
405
11.5k
      }
406
11.8k
      if(!found)
407
4.10k
        return PARSEDATE_FAIL; /* bad string */
408
409
7.70k
      date += len;
410
7.70k
    }
411
20.0k
    else if(ISDIGIT(*date)) {
412
      /* a digit */
413
18.0k
      unsigned int val;
414
18.0k
      char *end;
415
18.0k
      if((secnum == -1) &&
416
16.9k
         match_time(date, &hournum, &minnum, &secnum, &end)) {
417
        /* time stamp */
418
1.69k
        date = end;
419
1.69k
      }
420
16.3k
      else {
421
16.3k
        curl_off_t lval;
422
16.3k
        int num_digits = 0;
423
16.3k
        const char *p = date;
424
16.3k
        if(curlx_str_number(&p, &lval, 99999999))
425
299
          return PARSEDATE_FAIL;
426
427
        /* we know num_digits cannot be larger than 8 */
428
16.0k
        num_digits = (int)(p - date);
429
16.0k
        val = (unsigned int)lval;
430
431
16.0k
        if((tzoff == -1) &&
432
13.0k
           (num_digits == 4) &&
433
1.61k
           (val <= 1400) &&
434
1.25k
           (indate < date) &&
435
885
           ((date[-1] == '+' || date[-1] == '-'))) {
436
          /* four digits and a value less than or equal to 1400 (to take into
437
             account all sorts of funny time zone diffs) and it is preceded
438
             with a plus or minus. This is a time zone indication. 1400 is
439
             picked since +1300 is frequently used and +1400 is mentioned as
440
             an edge number in the document "ISO C 200X Proposal: Timezone
441
             Functions" at http://david.tribble.com/text/c0xtimezone.html If
442
             anyone has a more authoritative source for the exact maximum time
443
             zone offsets, please speak up! */
444
640
          found = TRUE;
445
640
          tzoff = (val / 100 * 60 + val % 100) * 60;
446
447
          /* the + and - prefix indicates the local time compared to GMT,
448
             this we need their reversed math to get what we want */
449
640
          tzoff = date[-1] == '+' ? -tzoff : tzoff;
450
640
        }
451
452
15.3k
        else if((num_digits == 8) &&
453
1.99k
                (yearnum == -1) &&
454
1.76k
                (monnum == -1) &&
455
1.44k
                (mdaynum == -1)) {
456
          /* 8 digits, no year, month or day yet. This is YYYYMMDD */
457
1.23k
          found = TRUE;
458
1.23k
          yearnum = val / 10000;
459
1.23k
          monnum = (val % 10000) / 100 - 1; /* month is 0 - 11 */
460
1.23k
          mdaynum = val % 100;
461
1.23k
        }
462
463
16.0k
        if(!found && (dignext == DATE_MDAY) && (mdaynum == -1)) {
464
9.98k
          if((val > 0) && (val < 32)) {
465
5.33k
            mdaynum = val;
466
5.33k
            found = TRUE;
467
5.33k
          }
468
9.98k
          dignext = DATE_YEAR;
469
9.98k
        }
470
471
16.0k
        if(!found && (dignext == DATE_YEAR) && (yearnum == -1)) {
472
6.29k
          yearnum = val;
473
6.29k
          found = TRUE;
474
6.29k
          if(yearnum < 100) {
475
4.11k
            if(yearnum > 70)
476
662
              yearnum += 1900;
477
3.45k
            else
478
3.45k
              yearnum += 2000;
479
4.11k
          }
480
6.29k
          if(mdaynum == -1)
481
3.93k
            dignext = DATE_MDAY;
482
6.29k
        }
483
484
16.0k
        if(!found)
485
2.53k
          return PARSEDATE_FAIL;
486
487
13.5k
        date = p;
488
13.5k
      }
489
18.0k
    }
490
491
24.9k
    part++;
492
24.9k
  }
493
494
5.61k
  if(-1 == secnum)
495
4.77k
    secnum = minnum = hournum = 0; /* no time, make it zero */
496
497
5.61k
  if((-1 == mdaynum) ||
498
3.59k
     (-1 == monnum) ||
499
1.75k
     (-1 == yearnum))
500
    /* lacks vital info, fail */
501
4.34k
    return PARSEDATE_FAIL;
502
503
#ifdef HAVE_TIME_T_UNSIGNED
504
  if(yearnum < 1970) {
505
    /* only positive numbers cannot return earlier */
506
    *output = TIME_T_MIN;
507
    return PARSEDATE_SOONER;
508
  }
509
#endif
510
511
#if (SIZEOF_TIME_T < 5)
512
513
#ifdef HAVE_TIME_T_UNSIGNED
514
  /* an unsigned 32-bit time_t can only hold dates to 2106 */
515
  if(yearnum > 2105) {
516
    *output = TIME_T_MAX;
517
    return PARSEDATE_LATER;
518
  }
519
#else
520
  /* a signed 32-bit time_t can only hold dates to the beginning of 2038 */
521
  if(yearnum > 2037) {
522
    *output = TIME_T_MAX;
523
    return PARSEDATE_LATER;
524
  }
525
  if(yearnum < 1903) {
526
    *output = TIME_T_MIN;
527
    return PARSEDATE_SOONER;
528
  }
529
#endif
530
531
#else
532
  /* The Gregorian calendar was introduced 1582 */
533
1.27k
  if(yearnum < 1583)
534
306
    return PARSEDATE_FAIL;
535
968
#endif
536
537
968
  if((mdaynum > 31) || (monnum > 11) ||
538
245
     (hournum > 23) || (minnum > 59) || (secnum > 60))
539
723
    return PARSEDATE_FAIL; /* clearly an illegal date */
540
541
  /* time2epoch() returns a time_t. time_t is often 32 bits, sometimes even on
542
     architectures that feature a 64 bits 'long' but ultimately time_t is the
543
     correct data type to use.
544
  */
545
245
  t = time2epoch(secnum, minnum, hournum, mdaynum, monnum, yearnum);
546
547
  /* Add the time zone diff between local time zone and GMT. */
548
245
  if(tzoff == -1)
549
193
    tzoff = 0;
550
551
245
  if((tzoff > 0) && (t > (time_t)(TIME_T_MAX - tzoff))) {
552
0
    *output = TIME_T_MAX;
553
0
    return PARSEDATE_LATER; /* time_t overflow */
554
0
  }
555
556
245
  t += tzoff;
557
558
245
  *output = t;
559
560
245
  return PARSEDATE_OK;
561
245
}
562
#else
563
/* disabled */
564
static int parsedate(const char *date, time_t *output)
565
{
566
  (void)date;
567
  *output = 0;
568
  return PARSEDATE_OK; /* a lie */
569
}
570
#endif
571
572
time_t curl_getdate(const char *p, const time_t *now)
573
0
{
574
0
  time_t parsed = -1;
575
0
  int rc = parsedate(p, &parsed);
576
0
  (void)now; /* legacy argument from the past that we ignore */
577
578
0
  if(rc == PARSEDATE_OK) {
579
0
    if(parsed == (time_t)-1)
580
      /* avoid returning -1 for a working scenario */
581
0
      parsed++;
582
0
    return parsed;
583
0
  }
584
  /* everything else is fail */
585
0
  return -1;
586
0
}
587
588
/* Curl_getdate_capped() differs from curl_getdate() in that this will return
589
   TIME_T_MAX in case the parsed time value was too big, instead of an
590
   error. Returns non-zero on error. */
591
592
int Curl_getdate_capped(const char *p, time_t *tp)
593
12.5k
{
594
12.5k
  int rc = parsedate(p, tp);
595
12.5k
  return (rc == PARSEDATE_FAIL);
596
12.5k
}