]> diplodocus.org Git - nmh/blobdiff - sbr/datetime.c
new.c: Order two return statements to match comment.
[nmh] / sbr / datetime.c
index 8b2513621e8ce4ea4cf805e21dab2c3a6465cf2b..a7906a52063bd5c23ad7b12adf509be5dabd5943 100644 (file)
@@ -1,5 +1,4 @@
-/*
- * datetime.c -- functions for manipulating RFC 5545 date-time values
+/* datetime.c -- functions for manipulating RFC 5545 date-time values
  *
  * This code is Copyright (c) 2014, by the authors of nmh.
  * See the COPYRIGHT file in the root directory of the nmh
  *
  * This code is Copyright (c) 2014, by the authors of nmh.
  * See the COPYRIGHT file in the root directory of the nmh
@@ -11,6 +10,7 @@
 #include <h/fmt_scan.h>
 #include "h/tws.h"
 #include "h/utils.h"
 #include <h/fmt_scan.h>
 #include "h/tws.h"
 #include "h/utils.h"
+#include "unquote.h"
 
 /*
  * This doesn't try to support all of the myriad date-time formats
 
 /*
  * This doesn't try to support all of the myriad date-time formats
@@ -58,38 +58,45 @@ struct tzdesc {
  * struct tws.
  */
 static int
  * struct tws.
  */
 static int
-parse_datetime (const char *datetime, const char *zone, int dst,
+parse_datetime (const char *datetime, const char *zone, bool dst,
                 struct tws *tws) {
     char utc_indicator;
                 struct tws *tws) {
     char utc_indicator;
-    int form_1 = 0;
-    int items_matched =
+    bool form_1;
+    int items_matched;
+
+    ZERO(tws);
+    items_matched =
         sscanf (datetime, "%4d%2d%2dT%2d%2d%2d%c",
                 &tws->tw_year, &tws->tw_mon, &tws->tw_mday,
                 &tws->tw_hour, &tws->tw_min, &tws->tw_sec,
                 &utc_indicator);
     tws->tw_flags = TW_NULL;
 
         sscanf (datetime, "%4d%2d%2dT%2d%2d%2d%c",
                 &tws->tw_year, &tws->tw_mon, &tws->tw_mday,
                 &tws->tw_hour, &tws->tw_min, &tws->tw_sec,
                 &utc_indicator);
     tws->tw_flags = TW_NULL;
 
+    form_1 = false;
     if (items_matched == 7) {
         /* The 'Z' must be capital according to RFC 5545 Sec. 3.3.5. */
         if (utc_indicator != 'Z') {
     if (items_matched == 7) {
         /* The 'Z' must be capital according to RFC 5545 Sec. 3.3.5. */
         if (utc_indicator != 'Z') {
-            advise (NULL, "%s has invalid timezone indicator of 0x%x",
+            inform("%s has invalid timezone-indicator byte: %#x",
                     datetime, utc_indicator);
             return NOTOK;
         }
     } else if (zone == NULL) {
                     datetime, utc_indicator);
             return NOTOK;
         }
     } else if (zone == NULL) {
-        form_1 = 1;
+        form_1 = true;
     }
 
     }
 
-    if (items_matched >= 6) {
-        int offset = atoi (zone ? zone : "0");
-
+    /* items_matched of 3 is for, e.g., 20151230.  Assume that means
+       the entire day.  The time fields of the tws struct were
+       initialized to 0 by the memset() above. */
+    if (items_matched >= 6  ||  items_matched == 3) {
         /* struct tws defines tw_mon over [0, 11]. */
         --tws->tw_mon;
 
         /* struct tws defines tw_mon over [0, 11]. */
         --tws->tw_mon;
 
+        /* Fill out rest of tws, i.e., its tw_wday and tw_flags. */
         set_dotw (tws);
         /* set_dotw() sets TW_SIMP.  Replace that with TW_SEXP so that
            dasctime() outputs the dotw before the date instead of after. */
         set_dotw (tws);
         /* set_dotw() sets TW_SIMP.  Replace that with TW_SEXP so that
            dasctime() outputs the dotw before the date instead of after. */
-        tws->tw_flags &= ~TW_SDAY, tws->tw_flags |= TW_SEXP;
+        tws->tw_flags &= ~TW_SDAY;
+        tws->tw_flags |= TW_SEXP;
 
         /* For the call to dmktime():
            - don't need tw_yday
 
         /* For the call to dmktime():
            - don't need tw_yday
@@ -97,7 +104,11 @@ parse_datetime (const char *datetime, const char *zone, int dst,
            - the only flag in tw_flags used is TW_DST
          */
         tws->tw_yday = tws->tw_clock = 0;
            - the only flag in tw_flags used is TW_DST
          */
         tws->tw_yday = tws->tw_clock = 0;
-        tws->tw_zone = 60 * (offset / 100)  +  offset % 100;
+        if (zone) {
+            int offset = atoi(zone);
+            tws->tw_zone = 60 * (offset / 100)  +  offset % 100;
+        } else
+            tws->tw_zone = 0;
         if (dst) {
             tws->tw_zone -= 60;  /* per dlocaltime() */
             tws->tw_flags |= TW_DST;
         if (dst) {
             tws->tw_zone -= 60;  /* per dlocaltime() */
             tws->tw_flags |= TW_DST;
@@ -136,20 +147,20 @@ parse_datetime (const char *datetime, const char *zone, int dst,
         }
 
         return OK;
         }
 
         return OK;
-    } else {
-        return NOTOK;
     }
     }
+
+    return NOTOK;
 }
 
 tzdesc_t
 load_timezones (const contentline *clines) {
     tzdesc_t timezones = NULL, timezone = NULL;
 }
 
 tzdesc_t
 load_timezones (const contentline *clines) {
     tzdesc_t timezones = NULL, timezone = NULL;
-    int in_vtimezone, in_standard, in_daylight;
+    bool in_vtimezone, in_standard, in_daylight;
     tzparams *params = NULL;
     const contentline *node;
 
     /* Interpret each VTIMEZONE section. */
     tzparams *params = NULL;
     const contentline *node;
 
     /* Interpret each VTIMEZONE section. */
-    in_vtimezone = in_standard = in_daylight = 0;
+    in_vtimezone = in_standard = in_daylight = false;
     for (node = clines; node; node = node->next) {
         /* node->name will be NULL if the line was "deleted". */
         if (! node->name) { continue; }
     for (node = clines; node; node = node->next) {
         /* node->name will be NULL if the line was "deleted". */
         if (! node->name) { continue; }
@@ -160,21 +171,21 @@ load_timezones (const contentline *clines) {
                  (in_daylight  &&  ! strcasecmp ("DAYLIGHT", node->value)))) {
                 struct tws tws;
 
                  (in_daylight  &&  ! strcasecmp ("DAYLIGHT", node->value)))) {
                 struct tws tws;
 
-                if (in_standard) { in_standard = 0; }
-                else if (in_daylight) { in_daylight = 0; }
-                if (parse_datetime (params->dtstart, params->offsetfrom,
-                                    in_daylight ? 1 : 0,
-                                    &tws) == OK) {
-                    if (tws.tw_year >= 1970) {
-                        /* dmktime() falls apart for, e.g., the year 1601. */
-                        params->start_dt = tws.tw_clock;
-                    }
-                } else {
-                    advise (NULL, "failed to parse start time %s for %s",
-                            params->dtstart,
-                            in_standard ? "standard" : "daylight");
+                if (in_standard) { in_standard = false; }
+                else if (in_daylight) { in_daylight = false; }
+
+                if (parse_datetime(params->dtstart, params->offsetfrom,
+                    in_daylight, &tws) != OK) {
+                    inform("failed to parse start time %s for %s",
+                        params->dtstart,
+                        in_daylight ? "daylight" : "standard");
                     return NULL;
                 }
                     return NULL;
                 }
+
+                if (tws.tw_year >= 1970) {
+                    /* dmktime() falls apart for, e.g., the year 1601. */
+                    params->start_dt = tws.tw_clock;
+                }
                 params = NULL;
             } else if (! strcasecmp ("DTSTART", node->name)) {
                 /* Save DTSTART for use after getting TZOFFSETFROM. */
                 params = NULL;
             } else if (! strcasecmp ("DTSTART", node->name)) {
                 /* Save DTSTART for use after getting TZOFFSETFROM. */
@@ -189,24 +200,28 @@ load_timezones (const contentline *clines) {
         } else if (in_vtimezone) {
             if (! strcasecmp ("END", node->name)  &&
                 ! strcasecmp ("VTIMEZONE", node->value)) {
         } else if (in_vtimezone) {
             if (! strcasecmp ("END", node->name)  &&
                 ! strcasecmp ("VTIMEZONE", node->value)) {
-                in_vtimezone = 0;
+                in_vtimezone = false;
             } else if (! strcasecmp ("BEGIN", node->name)  &&
                 ! strcasecmp ("STANDARD", node->value)) {
             } else if (! strcasecmp ("BEGIN", node->name)  &&
                 ! strcasecmp ("STANDARD", node->value)) {
-                in_standard = 1;
+                in_standard = true;
                 params = &timezone->standard_params;
             } else if (! strcasecmp ("BEGIN", node->name)  &&
                 ! strcasecmp ("DAYLIGHT", node->value)) {
                 params = &timezone->standard_params;
             } else if (! strcasecmp ("BEGIN", node->name)  &&
                 ! strcasecmp ("DAYLIGHT", node->value)) {
-                in_daylight = 1;
+                in_daylight = true;
                 params = &timezone->daylight_params;
             } else if (! strcasecmp ("TZID", node->name)) {
                 params = &timezone->daylight_params;
             } else if (! strcasecmp ("TZID", node->name)) {
-                timezone->tzid = strdup (node->value);
+                /* See comment below in format_datetime() about removing any enclosing quotes from a
+                   timezone identifier. */
+                char *buf = mh_xmalloc(strlen(node->value) + 1);
+                unquote_string(node->value, buf);
+                timezone->tzid = buf;
             }
         } else {
             if (! strcasecmp ("BEGIN", node->name)  &&
                 ! strcasecmp ("VTIMEZONE", node->value)) {
 
             }
         } else {
             if (! strcasecmp ("BEGIN", node->name)  &&
                 ! strcasecmp ("VTIMEZONE", node->value)) {
 
-                in_vtimezone = 1;
-                timezone = mh_xcalloc (1, sizeof (struct tzdesc));
+                in_vtimezone = true;
+                NEW0(timezone);
                 if (timezones) {
                     tzdesc_t t;
 
                 if (timezones) {
                     tzdesc_t t;
 
@@ -258,7 +273,8 @@ rrule_clock (const char *rrule, const char *starttime, const char *zone,
              unsigned int year) {
     time_t clock = 0;
 
              unsigned int year) {
     time_t clock = 0;
 
-    if (nmh_strcasestr (rrule, "FREQ=YEARLY;INTERVAL=1")) {
+    if (nmh_strcasestr (rrule, "FREQ=YEARLY;INTERVAL=1")  ||
+        (nmh_strcasestr (rrule, "FREQ=YEARLY")  &&  nmh_strcasestr(rrule, "INTERVAL") == NULL)) {
         struct tws *tws;
         const char *cp;
         int wday = -1, month = -1;
         struct tws *tws;
         const char *cp;
         int wday = -1, month = -1;
@@ -309,9 +325,8 @@ rrule_clock (const char *rrule, const char *starttime, const char *zone,
 
 fail:
     if (clock == 0) {
 
 fail:
     if (clock == 0) {
-        admonish (NULL,
-                  "Unsupported RRULE format: %s, assume local timezone",
-                  rrule);
+        inform("Unsupported RRULE format: %s, assume local timezone, continuing...",
+           rrule);
     }
 
     return clock;
     }
 
     return clock;
@@ -329,7 +344,15 @@ format_datetime (tzdesc_t timezones, const contentline *node) {
     /* Extract the timezone, if specified (RFC 5545 Sec. 3.3.5 Form #3). */
     for (p = node->params; p && p->param_name; p = p->next) {
         if (! strcasecmp (p->param_name, "TZID")  &&  p->values) {
     /* Extract the timezone, if specified (RFC 5545 Sec. 3.3.5 Form #3). */
     for (p = node->params; p && p->param_name; p = p->next) {
         if (! strcasecmp (p->param_name, "TZID")  &&  p->values) {
-            dt_timezone = p->values->value;
+            /* Remove any enclosing quotes from the timezone identifier.  I don't believe that it's
+               legal for it to be quoted, according to RFC 5545 ยง 3.2.19:
+                   tzidparam  = "TZID" "=" [tzidprefix] paramtext
+                   tzidprefix = "/"
+               where paramtext includes SAFE-CHAR, which specifically excludes DQUOTE.  But we'll
+               be generous and strip quotes. */
+            char *buf = mh_xmalloc(strlen(p->values->value) + 1);
+            unquote_string(p->values->value, buf);
+            dt_timezone = buf;
             break;
         }
     }
             break;
         }
     }
@@ -337,12 +360,11 @@ format_datetime (tzdesc_t timezones, const contentline *node) {
     if (! dt_timezone) {
         /* Form #1: DATE WITH LOCAL TIME, i.e., no time zone, or
            Form #2: DATE WITH UTC TIME */
     if (! dt_timezone) {
         /* Form #1: DATE WITH LOCAL TIME, i.e., no time zone, or
            Form #2: DATE WITH UTC TIME */
-        if (parse_datetime (node->value, NULL, 0, &tws[0]) == OK) {
-            return strdup (dasctime (&tws[0], 0));
-        } else {
-            advise (NULL, "unable to parse datetime %s", node->value);
+        if (parse_datetime(node->value, NULL, false, &tws[0]) != OK) {
+            inform("unable to parse datetime %s", node->value);
             return NULL;
         }
             return NULL;
         }
+        return strdup (dasctime (&tws[0], 0));
     }
 
     /*
     }
 
     /*
@@ -352,7 +374,7 @@ format_datetime (tzdesc_t timezones, const contentline *node) {
 
     /* Find the corresponding tzdesc. */
     for (tz = timezones; dt_timezone && tz; tz = tz->next) {
 
     /* Find the corresponding tzdesc. */
     for (tz = timezones; dt_timezone && tz; tz = tz->next) {
-        /* Property parameter values are case insenstive (RFC 5545
+        /* Property parameter values are case insensitive (RFC 5545
            Sec. 2) and time zone identifiers are property parameters
            (RFC 5545 Sec. 3.8.2.4), though it would seem odd to use
            different case in the same file for identifiers that are
            Sec. 2) and time zone identifiers are property parameters
            (RFC 5545 Sec. 3.8.2.4), though it would seem odd to use
            different case in the same file for identifiers that are
@@ -360,10 +382,12 @@ format_datetime (tzdesc_t timezones, const contentline *node) {
         if (tz->tzid  &&  ! strcasecmp (dt_timezone, tz->tzid)) { break; }
     }
 
         if (tz->tzid  &&  ! strcasecmp (dt_timezone, tz->tzid)) { break; }
     }
 
-    if (! tz) {
-        advise (NULL, "did not find VTIMEZONE section for %s", dt_timezone);
+    if (!tz) {
+        inform("did not find VTIMEZONE section for %s", dt_timezone);
+        free(dt_timezone);
         return NULL;
     }
         return NULL;
     }
+    free(dt_timezone);
 
     /* Determine if it's Daylight Saving. */
     tp_std = strchr (tz->standard_params.dtstart, 'T');
 
     /* Determine if it's Daylight Saving. */
     tp_std = strchr (tz->standard_params.dtstart, 'T');
@@ -402,42 +426,37 @@ format_datetime (tzdesc_t timezones, const contentline *node) {
         }
 
         if (transition[0] < transition[1]) {
         }
 
         if (transition[0] < transition[1]) {
-            advise (NULL, "format_datetime() requires that daylight "
+            inform("format_datetime() requires that daylight "
                     "saving time transition precede standard time "
                     "transition");
             return NULL;
         }
 
                     "saving time transition precede standard time "
                     "transition");
             return NULL;
         }
 
-        if (parse_datetime (node->value, tz->standard_params.offsetto,
-                            0, &tws[0]) == OK) {
-            dt[0] = tws[0].tw_clock;
-        } else {
-            advise (NULL, "unable to parse datetime %s", node->value);
+        if (parse_datetime(node->value, tz->standard_params.offsetto,
+            false, &tws[0]) != OK) {
+            inform("unable to parse datetime %s", node->value);
             return NULL;
         }
             return NULL;
         }
+        dt[0] = tws[0].tw_clock;
 
         if (tp_dst) {
             if (dt[0] < transition[1]) {
                 dst = 0;
             } else {
 
         if (tp_dst) {
             if (dt[0] < transition[1]) {
                 dst = 0;
             } else {
-                if (parse_datetime (node->value,
-                                    tz->daylight_params.offsetto, 1,
-                                    &tws[1]) == OK) {
-                    dt[1] = tws[1].tw_clock;
-                } else {
-                    advise (NULL, "unable to parse datetime %s",
-                            node->value);
+                if (parse_datetime(node->value,
+                    tz->daylight_params.offsetto, true, &tws[1]) != OK) {
+                    inform("unable to parse datetime %s", node->value);
                     return NULL;
                 }
                     return NULL;
                 }
-
-                dst = dt[1] > transition[0]  ?  0  :  1;
+                dt[1] = tws[1].tw_clock;
+                dst = dt[1] <= transition[0];
             }
         }
 
         if (dst) {
             if (tz->daylight_params.start_dt > 0  &&
                 dt[dst] < tz->daylight_params.start_dt) {
             }
         }
 
         if (dst) {
             if (tz->daylight_params.start_dt > 0  &&
                 dt[dst] < tz->daylight_params.start_dt) {
-                advise (NULL, "date-time of %s is before VTIMEZONE start "
+                inform("date-time of %s is before VTIMEZONE start "
                         "of %s", node->value,
                         tz->daylight_params.dtstart);
                 return NULL;
                         "of %s", node->value,
                         tz->daylight_params.dtstart);
                 return NULL;
@@ -445,7 +464,7 @@ format_datetime (tzdesc_t timezones, const contentline *node) {
         } else {
             if (tz->standard_params.start_dt > 0  &&
                 dt[dst] < tz->standard_params.start_dt) {
         } else {
             if (tz->standard_params.start_dt > 0  &&
                 dt[dst] < tz->standard_params.start_dt) {
-                advise (NULL, "date-time of %s is before VTIMEZONE start "
+                inform("date-time of %s is before VTIMEZONE start "
                         "of %s", node->value,
                         tz->standard_params.dtstart);
                 return NULL;
                         "of %s", node->value,
                         tz->standard_params.dtstart);
                 return NULL;
@@ -453,12 +472,12 @@ format_datetime (tzdesc_t timezones, const contentline *node) {
         }
     } else {
         if (! tp_std) {
         }
     } else {
         if (! tp_std) {
-            advise (NULL, "unsupported date-time format: %s",
+            inform("unsupported date-time format: %s",
                     tz->standard_params.dtstart);
             return NULL;
         }
         if (! tp_dt) {
                     tz->standard_params.dtstart);
             return NULL;
         }
         if (! tp_dt) {
-            advise (NULL, "unsupported date-time format: %s", node->value);
+            inform("unsupported date-time format: %s", node->value);
             return NULL;
         }
     }
             return NULL;
         }
     }