← Commit list

e7f76c782b

Daniel Stenberg
Daniel Stenberg
2026-09-17
SUCCESS Duration: 415s

Subject

parsedate: find day names, months and time zones faster

Binary

No binary (build failed)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

(log yok)

Commit Diff GitHub →

lib/parsedate.c+196 -69
@@ -114,115 +114,204 @@ struct tzinfo {
#define tDAYZONE (-60) /* offset for daylight savings time */
-/* alpha-sorted list of time zones */
-static const struct tzinfo tz[] = {
+/* alpha-sorted list of single-letter time zones */
+static const struct tzinfo tzone[] = {
{ "A", -1 * 60 }, /* Alpha */
+ { "B", -2 * 60 }, /* Bravo */
+ { "C", -3 * 60 }, /* Charlie */
+ { "D", -4 * 60 }, /* Delta */
+ { "E", -5 * 60 }, /* Echo */
+ { "F", -6 * 60 }, /* Foxtrot */
+ { "G", -7 * 60 }, /* Golf */
+ { "H", -8 * 60 }, /* Hotel */
+ { "I", -9 * 60 }, /* India */
+ { "", -1 }, /* not a timezone */
+ { "K", -10 * 60 }, /* Kilo */
+ { "L", -11 * 60 }, /* Lima */
+ { "M", -12 * 60 }, /* Mike */
+ { "N", 60 }, /* November */
+ { "O", 2 * 60 }, /* Oscar */
+ { "P", 3 * 60 }, /* Papa */
+ { "Q", 4 * 60 }, /* Quebec */
+ { "R", 5 * 60 }, /* Romeo */
+ { "S", 6 * 60 }, /* Sierra */
+ { "T", 7 * 60 }, /* Tango */
+ { "U", 8 * 60 }, /* Uniform */
+ { "V", 9 * 60 }, /* Victor */
+ { "W", 10 * 60 }, /* Whiskey */
+ { "X", 11 * 60 }, /* X-ray */
+ { "Y", 12 * 60 }, /* Yankee */
+ { "Z", 0 }, /* Zulu, zero meridian, a.k.a. UTC */
+};
+
+/* two-letter time zones */
+static const struct tzinfo tztwo[] = {
+ { "NT", 660 }, /* Nome */ /* spellchecker:disable-line */
+ { "UT", 0 }, /* Universal Time */
+};
+
+/* three-letter time zones */
+static const struct tzinfo tzthree[] = {
{ "ADT", 240 + tDAYZONE }, /* Atlantic Daylight */
- { "AHST", 600 }, /* Alaska-Hawaii Standard */
{ "AST", 240 }, /* Atlantic Standard */
- { "B", -2 * 60 }, /* Bravo */
{ "BST", 0 + tDAYZONE }, /* British Summer */
- { "C", -3 * 60 }, /* Charlie */
{ "CAT", 600 }, /* Central Alaska */
{ "CCT", -480 }, /* China Coast, USSR Zone 7 */
{ "CDT", 360 + tDAYZONE }, /* Central Daylight */
- { "CEST", -60 + tDAYZONE }, /* Central European Summer */
{ "CET", -60 }, /* Central European */
{ "CST", 360 }, /* Central Standard */
- { "D", -4 * 60 }, /* Delta */
- { "E", -5 * 60 }, /* Echo */
- { "EADT", -600 + tDAYZONE }, /* Eastern Australian Daylight */
- { "EAST", -600 }, /* Eastern Australian Standard */
{ "EDT", 300 + tDAYZONE }, /* Eastern Daylight */
{ "EET", -120 }, /* Eastern Europe, USSR Zone 1 */
{ "EST", 300 }, /* Eastern Standard */
- { "F", -6 * 60 }, /* Foxtrot */
{ "FST", -60 + tDAYZONE }, /* French Summer */
{ "FWT", -60 }, /* French Winter */
- { "G", -7 * 60 }, /* Golf */
{ "GMT", 0 }, /* Greenwich Mean */
{ "GST", -600 }, /* Guam Standard, USSR Zone 9 */
- { "H", -8 * 60 }, /* Hotel */
{ "HDT", 600 + tDAYZONE }, /* Hawaii Daylight */
{ "HST", 600 }, /* Hawaii Standard */
- { "I", -9 * 60 }, /* India */
- { "IDLE", -720 }, /* International Date Line East */
- { "IDLW", 720 }, /* International Date Line West */
{ "JST", -540 }, /* Japan Standard, USSR Zone 8 */
- { "K", -10 * 60 }, /* Kilo */
- { "L", -11 * 60 }, /* Lima */
- { "M", -12 * 60 }, /* Mike */
{ "MDT", 420 + tDAYZONE }, /* Mountain Daylight */
- { "MEST", -60 + tDAYZONE }, /* Middle European Summer */
- { "MESZ", -60 + tDAYZONE }, /* Middle European Summer */
{ "MET", -60 }, /* Middle European */
- { "MEWT", -60 }, /* Middle European Winter */
{ "MST", 420 }, /* Mountain Standard */
- { "N", 60 }, /* November */
- { "NT", 660 }, /* Nome */ /* spellchecker:disable-line */
- { "NZDT", -720 + tDAYZONE }, /* New Zealand Daylight */
- { "NZST", -720 }, /* New Zealand Standard */
{ "NZT", -720 }, /* New Zealand */
- { "O", 2 * 60 }, /* Oscar */
- { "P", 3 * 60 }, /* Papa */
{ "PDT", 480 + tDAYZONE }, /* Pacific Daylight */
{ "PST", 480 }, /* Pacific Standard */
- { "Q", 4 * 60 }, /* Quebec */
- { "R", 5 * 60 }, /* Romeo */
- { "S", 6 * 60 }, /* Sierra */
- { "T", 7 * 60 }, /* Tango */
- { "U", 8 * 60 }, /* Uniform */
- { "UT", 0 }, /* Universal Time */
{ "UTC", 0 }, /* Universal (Coordinated) */
- { "V", 9 * 60 }, /* Victor */
- { "W", 10 * 60 }, /* Whiskey */
- { "WADT", -420 + tDAYZONE }, /* West Australian Daylight */
- { "WAST", -420 }, /* spellchecker:disable-line */
- /* West Australian Standard */
{ "WAT", 60 }, /* West Africa */
{ "WET", 0 }, /* Western European */
- { "X", 11 * 60 }, /* X-ray */
- { "Y", 12 * 60 }, /* Yankee */
{ "YDT", 540 + tDAYZONE }, /* Yukon Daylight */
{ "YST", 540 }, /* Yukon Standard */
- { "Z", 0 }, /* Zulu, zero meridian, a.k.a. UTC */
};
+/* four-letter time zones */
+static const struct tzinfo tzfoura[] = {
+ { "AHST", 600 }, /* Alaska-Hawaii Standard */
+ { "CEST", -60 + tDAYZONE }, /* Central European Summer */
+ { "EADT", -600 + tDAYZONE }, /* Eastern Australian Daylight */
+ { "EAST", -600 }, /* Eastern Australian Standard */
+ { "IDLE", -720 }, /* International Date Line East */
+ { "IDLW", 720 }, /* International Date Line West */
+ { "MEST", -60 + tDAYZONE }, /* Middle European Summer */
+ { "MESZ", -60 + tDAYZONE }, /* Middle European Summer */
+ { "MEWT", -60 }, /* Middle European Winter */
+ { "NZDT", -720 + tDAYZONE }, /* New Zealand Daylight */
+ { "NZST", -720 }, /* New Zealand Standard */
+ { "WADT", -420 + tDAYZONE }, /* West Australian Daylight */
+ { "WAST", -420 }, /* spellchecker:disable-line */
+ /* West Australian Standard */
+};
+
+#define LOWERCASE(x) ((x) | 0x20)
+
/* returns:
-1 no day
0 monday - 6 sunday
- */
-static int checkday(const char *check, size_t len)
+
+ @unittest 4781
+*/
+UNITTEST int checkday(const char *check, size_t len);
+UNITTEST int checkday(const char *check, size_t len)
{
- int i;
- const char * const *what;
- if(len > 3)
- what = &weekday[0];
- else if(len == 3)
- what = &Curl_wkday[0];
- else
+ int day = -1;
+ if(len < 3)
return -1; /* too short */
- for(i = 0; i < 7; i++) {
- size_t ilen = strlen(what[0]);
- if((ilen == len) &&
- curl_strnequal(check, what[0], len))
- return i;
- what++;
+
+ switch(LOWERCASE(check[1])) {
+ case 'o': /* monday */
+ if((LOWERCASE(check[0]) == 'm') && LOWERCASE(check[2]) == 'n')
+ day = 0;
+ break;
+ case 'u': /* tuesday or sunday */
+ if((LOWERCASE(check[0]) == 't') && LOWERCASE(check[2]) == 'e')
+ day = 1;
+ else if((LOWERCASE(check[0]) == 's') && LOWERCASE(check[2]) == 'n')
+ day = 6;
+ break;
+ case 'e': /* wednesday */
+ if((LOWERCASE(check[0]) == 'w') && LOWERCASE(check[2]) == 'd')
+ day = 2;
+ break;
+ case 'h': /* thursday */
+ if((LOWERCASE(check[0]) == 't') && LOWERCASE(check[2]) == 'u')
+ day = 3;
+ break;
+ case 'r': /* friday */
+ if((LOWERCASE(check[0]) == 'f') && LOWERCASE(check[2]) == 'i')
+ day = 4;
+ break;
+ case 'a': /* saturday */
+ if((LOWERCASE(check[0]) == 's') && LOWERCASE(check[2]) == 't')
+ day = 5;
+ break;
}
- return -1;
+ if((len > 3) && (day != -1)) {
+ /* when more than three letters are provided, verify the full name case
+ insensitively */
+ size_t wlen = strlen(weekday[day]);
+ if((len != wlen) || !curl_strnequal(&check[3], &weekday[day][3], len - 3))
+ return -1;
+ }
+ return day;
}
-static int checkmonth(const char *check, size_t len)
+/* @unittest 4781 */
+
+UNITTEST int checkmonth(const char *check, size_t len);
+UNITTEST int checkmonth(const char *check, size_t len)
{
- int i;
- const char * const *what = &Curl_month[0];
if(len != 3)
return -1; /* not a month */
- for(i = 0; i < 12; i++) {
- if(curl_strnequal(check, what[0], 3))
- return i;
- what++;
+ switch(LOWERCASE(check[2])) {
+ case 'n': /* jan, jun */
+ if(LOWERCASE(check[0]) == 'j') {
+ uint8_t c2 = LOWERCASE(check[1]);
+ if(c2 == 'a')
+ return 0;
+ else if(c2 == 'u')
+ return 5;
+ }
+ break;
+ case 'b': /* feb */
+ if((LOWERCASE(check[0]) == 'f') && LOWERCASE(check[1]) == 'e')
+ return 1;
+ break;
+ case 'r': /* mar, apr */
+ if(LOWERCASE(check[0]) == 'm') {
+ if(LOWERCASE(check[1]) == 'a')
+ return 2;
+ }
+ else if((LOWERCASE(check[0]) == 'a') && LOWERCASE(check[1]) == 'p')
+ return 3;
+ break;
+ case 'y': /* may */
+ if((LOWERCASE(check[0]) == 'm') && LOWERCASE(check[1]) == 'a')
+ return 4;
+ break;
+ case 'l': /* jul */
+ if((LOWERCASE(check[0]) == 'j') && LOWERCASE(check[1]) == 'u')
+ return 6;
+ break;
+ case 'g': /* aug */
+ if((LOWERCASE(check[0]) == 'a') && LOWERCASE(check[1]) == 'u')
+ return 7;
+ break;
+ case 'p': /* sep */
+ if((LOWERCASE(check[0]) == 's') && LOWERCASE(check[1]) == 'e')
+ return 8;
+ break;
+ case 't': /* oct */
+ if((LOWERCASE(check[0]) == 'o') && LOWERCASE(check[1]) == 'c')
+ return 9;
+ break;
+ case 'v': /* nov */
+ if((LOWERCASE(check[0]) == 'n') && LOWERCASE(check[1]) == 'o')
+ return 10;
+ break;
+ case 'c': /* dec */
+ if((LOWERCASE(check[0]) == 'd') && LOWERCASE(check[1]) == 'e')
+ return 11;
+ break;
}
return -1; /* return the offset or -1, no real offset is -1 */
}
@@ -235,14 +324,52 @@ static int tzcompare(const void *m1, const void *m2)
}
/* return the time zone offset between GMT and the input one, in number of
- seconds or -1 if the timezone was not found/legal */
-static int checktz(const char *check, size_t len)
+ seconds or -1 if the timezone was not found/legal
+
+ @unittest 4781
+*/
+UNITTEST int checktz(const char *check, size_t len);
+UNITTEST int checktz(const char *check, size_t len)
{
- if(len <= 4) {
+ if(len > 4)
+ return -1;
+ else if(len == 1) {
+ /* short-cut single letter names */
+ if((check[0] < 'A') || (check[0] > 'Z') || (check[0] == 'J'))
+ return -1; /* no such tz */
+ return tzone[check[0] - 'A'].offset * 60;
+ }
+ else if(len == 2) {
+ /* two-letter names */
+ if(check[1] == 'T') {
+ if(check[0] == 'N')
+ return tztwo[0].offset * 60;
+ else if(check[0] == 'U')
+ return tztwo[1].offset * 60;
+ }
+ return -1; /* nope */
+ }
+ else if(len == 3) {
+ /* three-letter name */
+ const struct tzinfo *what;
+ struct tzinfo find;
+ if((check[0] < 'A') || (check[0] > 'Y'))
+ return -1;
+ curlx_strcopy(find.name, sizeof(find.name), check, len);
+ what = bsearch(&find, tzthree, CURL_ARRAYSIZE(tzthree),
+ sizeof(tzthree[0]), tzcompare);
+ if(what)
+ return what->offset * 60;
+ }
+ else {
+ /* four-letter name */
const struct tzinfo *what;
struct tzinfo find;
+ if((check[0] < 'A') || (check[0] > 'W'))
+ return -1;
curlx_strcopy(find.name, sizeof(find.name), check, len);
- what = bsearch(&find, tz, CURL_ARRAYSIZE(tz), sizeof(tz[0]), tzcompare);
+ what = bsearch(&find, tzfoura, CURL_ARRAYSIZE(tzfoura),
+ sizeof(tzfoura[0]), tzcompare);
if(what)
return what->offset * 60;
}
tests/data/Makefile.am+1 -0
@@ -2116,6 +2116,7 @@ TESTCASES = \
test4644 \
test4656 \
test4657 \
+ test4781 \
test5000 \
test5001 \
test5002 \
tests/data/test4781+21 -0
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+unittest
+weekday
+day
+monthname
+</keywords>
+</info>
+
+<client>
+<features>
+unittest
+</features>
+<name>
+weekday, month name and timezone parsers
+</name>
+</client>
+
+</testcase>
tests/libtest/lib517.c+1 -0
@@ -31,6 +31,7 @@ static CURLcode test_lib517(const char *URL)
};
static const struct dcheck dates[] = {
+ { "Sat May 5 GMT 11:56:27 2035", 2061978987 },
{ "Sun, 06 Nov 1994 08:49:37 GMT", 784111777 },
{ "Sunday, 06-Nov-94 08:49:37 GMT", 784111777 },
{ "Sun Nov 6 08:49:37 1994", 784111777 },
tests/unit/Makefile.inc+2 -1
@@ -108,4 +108,5 @@ TESTS_C = \
unit3303.c \
unit3304.c \
unit3306.c \
- unit3400.c
+ unit3400.c \
+ unit4781.c
tests/unit/unit4781.c+283 -0
@@ -0,0 +1,283 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <[email protected]>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "unitcheck.h"
+
+static CURLcode test_unit4781(const char *arg)
+{
+#ifndef CURL_DISABLE_PARSEDATE
+ unsigned int i;
+ int errors = 0;
+ struct day {
+ const char *name;
+ int val;
+ };
+
+ /* three-letter version or full version, nothing else */
+ static const struct day days[] = {
+ { "wedmesday", -1 },
+ { "monray", -1 },
+ { "mon", 0 },
+ { "tue", 1 },
+ { "wed", 2 },
+ { "thu", 3 },
+ { "fri", 4 },
+ { "sat", 5 },
+ { "sun", 6 },
+ { "monday", 0 },
+ { "tuesday", 1 },
+ { "wednesday", 2 },
+ { "thursday", 3 },
+ { "friday", 4 },
+ { "saturday", 5 },
+ { "sunday", 6 },
+ { "monDAY", 0 },
+ { "tueSDAY", 1 },
+ { "wedneSDAY", 2 },
+ { "thursdAY", 3 },
+ { "frIDAY", 4 },
+ { "satURDAY", 5 },
+ { "suNDAY", 6 },
+ { "monda", -1 },
+ { "tuesda", -1 },
+ { "wednesda", -1 },
+ { "thursda", -1 },
+ { "frida", -1 },
+ { "saturda", -1 },
+ { "sunda", -1 },
+ { "MON", 0 },
+ { "TUE", 1 },
+ { "WED", 2 },
+ { "THU", 3 },
+ { "FRI", 4 },
+ { "SAT", 5 },
+ { "SUN", 6 },
+ { "mo", -1 },
+ { "tu", -1 },
+ { "we", -1 },
+ { "th", -1 },
+ { "fr", -1 },
+ { "sa", -1 },
+ { "su", -1 },
+ { "mom", -1 },
+ { "tuf", -1 },
+ { "wef", -1 },
+ { "thv", -1 },
+ { "frj", -1 },
+ { "sau", -1 },
+ { "sum", -1 },
+ { "abc", -1 },
+ { " monday", -1 },
+ { " tuesday", -1 },
+ { " wednesday", -1 },
+ { " thursday", -1 },
+ { " friday", -1 },
+ { " saturday", -1 },
+ { " sunday", -1 },
+ { "monday ", -1 },
+ { "tuesday ", -1 },
+ { "wednesday ", -1 },
+ { "thursday ", -1 },
+ { "friday ", -1 },
+ { "saturday ", -1 },
+ { "sunday ", -1 },
+ };
+
+ /* three-letter month versions only */
+ static const struct day months[] = {
+ { "monday", -1 },
+ { "tuesday", -1 },
+ { "wednesday", -1 },
+ { "thursday", -1 },
+ { "friday", -1 },
+ { "saturday", -1 },
+ { "jan", 0 },
+ { "feb", 1 },
+ { "mar", 2 },
+ { "apr", 3 },
+ { "may", 4 },
+ { "jun", 5 },
+ { "jul", 6 },
+ { "aug", 7 },
+ { "sep", 8 },
+ { "oct", 9 },
+ { "nov", 10 },
+ { "dec", 11 },
+ { "january", -1 },
+ { "february", -1 },
+ { "march", -1 },
+ { "april", -1 },
+ { "june", -1 },
+ { "july", -1 },
+ { "august", -1 },
+ { "september", -1 },
+ { "october", -1 },
+ { "november", -1 },
+ { "december", -1 },
+ { "janu", -1 },
+ { "febr", -1 },
+ { "marc", -1 },
+ { "apri", -1 },
+ { "june", -1 },
+ { "july", -1 },
+ { "augu", -1 },
+ { "sept", -1 },
+ { "octo", -1 },
+ { "nove", -1 },
+ { "dece", -1 },
+ { "JAN", 0 },
+ { "FEB", 1 },
+ { "MAR", 2 },
+ { "APR", 3 },
+ { "MAY", 4 },
+ { "JUN", 5 },
+ { "JUL", 6 },
+ { "AUG", 7 },
+ { "SEP", 8 },
+ { "OCT", 9 },
+ { "NOV", 10 },
+ { "DEC", 11 },
+ };
+
+ static const struct day timezones[] = {
+ { "A", -3600 },
+ { "B", -7200 },
+ { "a", -1 },
+ { "b", -1 },
+ { "C", -10800 },
+ { "D", -14400 },
+ { "E", -18000 },
+ { "F", -21600 },
+ { "G", -25200 },
+ { "H", -28800 },
+ { "I", -32400 },
+ { "J", -1 },
+ { "K", -36000 },
+ { "L", -39600 },
+ { "M", -43200 },
+ { "N", 3600 },
+ { "O", 7200 },
+ { "P", 10800 },
+ { "Q", 14400 },
+ { "R", 18000 },
+ { "S", 21600 },
+ { "T", 25200 },
+ { "U", 28800 },
+ { "V", 32400 },
+ { "X", 39600 },
+ { "Y", 43200 },
+ { "Z", 0 },
+ { "z", -1 },
+ { "@", -1 },
+
+ { "NT", 39600 },
+ { "UT", 0 },
+ { "nt", -1 },
+ { "ut", -1 },
+ { "PT", -1 },
+ { "CT", -1 },
+
+ { "ADT", 10800 },
+ { "AST", 14400 },
+ { "BST", -3600 },
+ { "CAT", 36000 },
+ { "CCT", -28800 },
+ { "CDT", 18000 },
+ { "CET", -3600 },
+ { "CST", 21600 },
+ { "EDT", 14400 },
+ { "EET", -7200 },
+ { "EST", 18000 },
+ { "FST", -7200 },
+ { "FWT", -3600 },
+ { "GMT", 0 },
+ { "GST", -36000 },
+ { "HDT", 32400 },
+ { "HST", 36000 },
+ { "JST", -32400 },
+ { "MDT", 21600 },
+ { "MET", -3600 },
+ { "MST", 25200 },
+ { "NZT", -43200 },
+ { "PDT", 25200 },
+ { "PST", 28800 },
+ { "UTC", 0 },
+ { "WAT", 3600 },
+ { "WET", 0 },
+ { "YDT", 28800 },
+ { "YST", 32400 },
+ { "yST", -1 },
+ { "ZST", -1 },
+
+ { "AHST", 36000 },
+ { "BHST", -1 },
+ { "ahst", -1 },
+ { "CEST", -7200 },
+ { "cest", -1 },
+ { "EADT", -39600 },
+ { "EAST", -36000 },
+ { "IDLE", -43200 },
+ { "IDLW", 43200 },
+ { "MEST", -7200 },
+ { "MESZ", -7200 },
+ { "MEWT", -3600 },
+ { "NZDT", -46800 },
+ { "NZST", -43200 },
+ { "WADT", -28800 },
+ { "WAST", -25200 } /* spellchecker:disable-line */
+ };
+
+ (void)arg;
+
+ for(i = 0; i < CURL_ARRAYSIZE(days); i++) {
+ int d = checkday(days[i].name, strlen(days[i].name));
+ if(d != days[i].val) {
+ curl_mfprintf(stderr, "Day: %s returned %d, expected %d\n",
+ days[i].name, d, days[i].val);
+ errors++;
+ }
+ }
+ for(i = 0; i < CURL_ARRAYSIZE(months); i++) {
+ int d = checkmonth(months[i].name, strlen(months[i].name));
+ if(d != months[i].val) {
+ curl_mfprintf(stderr, "Month: %s returned %d, expected %d\n",
+ months[i].name, d, months[i].val);
+ errors++;
+ }
+ }
+ for(i = 0; i < CURL_ARRAYSIZE(timezones); i++) {
+ int d = checktz(timezones[i].name, strlen(timezones[i].name));
+ if(d != timezones[i].val) {
+ curl_mfprintf(stderr, "TZ: %s returned %d, expected %d\n",
+ timezones[i].name, d, timezones[i].val);
+ errors++;
+ }
+ }
+ if(errors)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+#else /* CURL_DISABLE_PARSEDATE */
+ /* nothing to do */
+ (void)arg;
+#endif
+ return CURLE_OK;
+}