support time zone conversion and date-time parsing - ics2txt - convert icalendar .ics file to plain text
 (HTM) git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ics2txt
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Tags
 (DIR) README
       ---
 (DIR) commit 58a1a9df90b5751ae05fba076cd9e664e3d9f3c1
 (DIR) parent b72092250747c7443e20fee06bee232b236f441e
 (HTM) Author: Josuah Demangeon <me@josuah.net>
       Date:   Mon, 14 Jun 2021 00:08:10 +0200
       
       support time zone conversion and date-time parsing
       
       Convert dates from DT* fields to epoch on sample program.
       
       Diffstat:
         M ical.c                              |     168 ++++++++++++++++++++++++++------
         M ical.h                              |      19 ++++++++++++++-----
         M ics2tree.c                          |      28 +++++++++++++++++++++-------
         M util.c                              |      81 ++++++++++++++++++++++---------
         M util.h                              |      21 +++++++++++++--------
       
       5 files changed, 245 insertions(+), 72 deletions(-)
       ---
 (DIR) diff --git a/ical.c b/ical.c
       @@ -11,6 +11,12 @@
        #include "util.h"
        #include "base64.h"
        
       +#define Xstrlcpy(d, s) (strlcpy((d), (s), sizeof(d)) < sizeof(d))
       +#define Xstrlcat(d, s) (strlcat((d), (s), sizeof(d)) < sizeof(d))
       +
       +/* helpers: common utilities to call within the p->fn() callbacks as
       + * well as in the code below */
       +
        int
        ical_error(IcalParser *p, char const *msg)
        {
       @@ -19,6 +25,12 @@ ical_error(IcalParser *p, char const *msg)
        }
        
        int
       +ical_get_level(IcalParser *p)
       +{
       +        return p->current - p->stack;
       +}
       +
       +int
        ical_get_value(IcalParser *p, char *s, size_t *len)
        {
                *len = strlen(s);
       @@ -31,9 +43,111 @@ ical_get_value(IcalParser *p, char *s, size_t *len)
        int
        ical_get_time(IcalParser *p, char *s, time_t *t)
        {
       -        return -1;
       +        struct tm tm = {0};
       +        char const *tzid;
       +
       +        tzid = (p->tzid) ? p->tzid :
       +            (p->current && p->current->tzid[0] != '\0') ? p->current->tzid :
       +            "";
       +
       +        /* date */
       +        for (int i = 0; i < 8; i++)
       +                if (!isdigit(s[i]))
       +                        return ical_error(p, "invalid date format");
       +        tm.tm_year = s[0] * 1000 + s[1] * 100 + s[2] * 10 + s[3];
       +        tm.tm_mon = s[4] * 10 + s[5] - 1;
       +        tm.tm_mday = s[6] * 10 + s[7];
       +        s += 8;
       +
       +        if (*s == 'T') {
       +                /* time */
       +                s++;
       +                for (int i = 0; i < 6; i++)
       +                        if (!isdigit(s[i]))
       +                                return ical_error(p, "invalid time format");
       +                tm.tm_hour = s[0] * 10 + s[1];
       +                tm.tm_min = s[2] * 10 + s[3];
       +                tm.tm_sec = s[4] * 10 + s[5];
       +                if (s[6] == 'Z')
       +                        tzid = "UTC";
       +        }
       +
       +        if ((*t = tztime(&tm, tzid)) == (time_t)-1)
       +                return ical_error(p, "could not convert time");
       +
       +        return 0;
       +}
       +
       +/* hooks: called just before user functions to do extra work such as
       + * processing time zones definition or prepare base64 decoding, and
       + * permit to only have parsing code left to parsing functions */
       +
       +int
       +hook_entry_name(IcalParser *p, char *name)
       +{
       +        (void)p; (void)name;
       +        return 0;
       +}
       +
       +int
       +hook_param_name(IcalParser *p, char *name)
       +{
       +        (void)p; (void)name;
       +        return 0;
       +}
       +
       +int
       +hook_param_value(IcalParser *p, char *name, char *value)
       +{
       +        if (strcasecmp(name, "ENCODING") == 0)
       +                p->base64 = (strcasecmp(value, "BASE64") == 0);
       +
       +        if (strcasecmp(name, "TZID") == 0)
       +                p->tzid = value;
       +
       +        return 0;
       +}
       +
       +int
       +hook_entry_value(IcalParser *p, char *name, char *value)
       +{
       +        if (strcasecmp(name, "TZID") == 0)
       +                if (!Xstrlcpy(p->current->tzid, value))
       +                        return ical_error(p, "TZID: name too large");
       +
       +        p->tzid = NULL;
       +
       +        return 0;
       +}
       +
       +int
       +hook_block_begin(IcalParser *p, char *name)
       +{
       +        p->current++;
       +        memset(p->current, 0, sizeof(*p->current));
       +        if (ical_get_level(p) >= ICAL_STACK_SIZE)
       +                return ical_error(p, "max recurion reached");
       +        if (!Xstrlcpy(p->current->name, name))
       +                return ical_error(p, "value too large");
       +
       +        return 0;
       +}
       +
       +int
       +hook_block_end(IcalParser *p, char *name)
       +{
       +        if (strcasecmp(p->current->name, name) != 0)
       +                return ical_error(p, "mismatching BEGIN: and END:");
       +        p->current--;
       +        if (p->current < p->stack)
       +                return ical_error(p, "more END: than BEGIN:");
       +
       +        return 0;
        }
        
       +/* parsers: in charge of reading from `fp`, splitting text into
       + * fields, and call hooks and user functions. */
       +
        #define CALL(p, fn, ...) ((p)->fn ? (p)->fn((p), __VA_ARGS__) : 0)
        
        static int
       @@ -43,24 +157,23 @@ ical_parse_value(IcalParser *p, char **sp, char *name)
                char *s, c, *val;
        
                s = *sp;
       -
                if (*s == '"') {
       -                ++s;
       -                for (val = s; !iscntrl(*s) && !strchr(",;:\"", *s); s++);
       +                val = ++s;
       +                while (!iscntrl(*s) && *s != '"')
       +                        s++;
                        if (*s != '"')
                                return ical_error(p, "missing '\"'");
                        *s++ = '\0';
                } else {
       -                for (val = s; !iscntrl(*s) && !strchr(",;:'\"", *s); s++);
       +                val = s;
       +                while (!iscntrl(*s) && !strchr(",;:'\"", *s))
       +                        s++;
                }
       -
                c = *s, *s = '\0';
       -        if ((err = CALL(p, fn_param_value, name, val)) != 0)
       +        if ((err = hook_param_value(p, name, val)) != 0 ||
       +            (err = CALL(p, fn_param_value, name, val)) != 0)
                        return err;
       -        if (strcasecmp(name, "ENCODING") == 0)
       -                p->base64 = (strcasecmp(val, "BASE64") == 0);
                *s = c;
       -
                *sp = s;
                return 0;
        }
       @@ -72,42 +185,39 @@ ical_parse_param(IcalParser *p, char **sp)
                char *s, *name;
        
                s = *sp;
       -
                do {
                        for (name = s; isalnum(*s) || *s == '-'; s++);
                        if (s == name || (*s != '='))
                                return ical_error(p, "invalid parameter name");
                        *s++ = '\0';
       -                if ((err = CALL(p, fn_param_name, name)) != 0)
       +                if ((err = hook_param_name(p, name)) != 0 ||
       +                    (err = CALL(p, fn_param_name, name)) != 0)
                                return err;
       -
                        do {
                                if ((err = ical_parse_value(p, &s, name)) != 0)
                                        return err;
                        } while (*s == ',' && s++);
                } while (*s == ';' && s++);
       -
                *sp = s;
                return 0;
        }
        
        static int
       -ical_parse_contentline(IcalParser *p, char *line)
       +ical_parse_contentline(IcalParser *p, char *s)
        {
                int err;
       -        char *s, c, *name, *end;
       -
       -        s = line;
       +        char c, *name, *sep;
        
                for (name = s; isalnum(*s) || *s == '-'; s++);
                if (s == name || (*s != ';' && *s != ':'))
                        return ical_error(p, "invalid entry name");
                c = *s, *s = '\0';
                if (strcasecmp(name, "BEGIN") != 0 && strcasecmp(name, "END") != 0)
       -                if ((err = CALL(p, fn_entry_name, name)) != 0)
       +                if ((err = hook_entry_name(p, name)) != 0 ||
       +                    (err = CALL(p, fn_entry_name, name)) != 0)
                                return err;
                *s = c;
       -        end = s;
       +        sep = s;
        
                p->base64 = 0;
                while (*s == ';') {
       @@ -120,20 +230,20 @@ ical_parse_contentline(IcalParser *p, char *line)
                        return ical_error(p, "expected ':' delimiter");
                s++;
        
       -        *end = '\0';
       +        *sep = '\0';
                if (strcasecmp(name, "BEGIN") == 0) {
       -                if ((err = CALL(p, fn_block_begin, s)) != 0)
       +                if ((err = hook_block_begin(p, s)) != 0 ||
       +                    (err = CALL(p, fn_block_begin, s)) != 0)
                                return err;
       -                p->level++;
                } else if (strcasecmp(name, "END") == 0) {
       -                if ((err = CALL(p, fn_block_end, s)) != 0)
       +                if ((err = hook_block_end(p, s)) != 0 ||
       +                    (err = CALL(p, fn_block_end, s)) != 0)
                                return err;
       -                p->level--;
                } else {
       -                if ((err = CALL(p, fn_entry_value, name, s)) != 0)
       +                if ((err = hook_entry_value(p, name, s)) != 0 ||
       +                    (err = CALL(p, fn_entry_value, name, s)) != 0)
                                return err;
                }
       -
                return 0;
        }
        
       @@ -144,6 +254,8 @@ ical_parse(IcalParser *p, FILE *fp)
                size_t sz = 0;
                int err, c;
        
       +        p->current = p->stack;
       +
                while (!feof(fp)) {
                        if ((contentline = realloc(contentline, 1)) == NULL)
                                return -1;
       @@ -151,7 +263,7 @@ ical_parse(IcalParser *p, FILE *fp)
        
                        do {
                                do {
       -                                p->line++;
       +                                p->linenum++;
                                        if (getline(&ln, &sz, fp) <= 0)
                                                return -1;
                                        strchomp(ln);
 (DIR) diff --git a/ical.h b/ical.h
       @@ -4,9 +4,18 @@
        #include <stdio.h>
        #include <time.h>
        
       +#define ICAL_STACK_SIZE 10
       +
        typedef struct IcalParser IcalParser;
       +typedef struct IcalStack IcalStack;
       +
       +struct IcalStack {
       +        char         name[32];
       +        char         tzid[32];
       +};
       +
        struct IcalParser {
       -        /* function called on content */
       +        /* function called while parsing in this order */
                int (*fn_entry_name)(IcalParser *, char *);
                int (*fn_param_name)(IcalParser *, char *);
                int (*fn_param_value)(IcalParser *, char *, char *);
       @@ -17,14 +26,14 @@ struct IcalParser {
        
                int         base64;
                char const *errmsg;
       -        size_t         line;
       +        size_t         linenum;
       +        char        *tzid;
        
       -        /* stack of blocks names: "name1\0name2\0...nameN\0\0" */
       -        int         level;
       -        char         stack[1024];
       +        IcalStack stack[ICAL_STACK_SIZE], *current;
        };
        
        int        ical_parse(IcalParser *, FILE *);
       +int        ical_get_level(IcalParser *);
        int        ical_get_time(IcalParser *, char *, time_t *);
        int        ical_get_value(IcalParser *, char *, size_t *);
        int        ical_error(IcalParser *, char const *);
 (DIR) diff --git a/ics2tree.c b/ics2tree.c
       @@ -1,6 +1,7 @@
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
       +#include <strings.h>
        
        #include "ical.h"
        #include "util.h"
       @@ -15,7 +16,7 @@ print_ruler(int level)
        static int
        fn_entry_name(IcalParser *p, char *name)
        {
       -        print_ruler(p->level);
       +        print_ruler(ical_get_level(p));
                printf("name %s\n", name);
                return 0;
        }
       @@ -23,7 +24,7 @@ fn_entry_name(IcalParser *p, char *name)
        static int
        fn_block_begin(IcalParser *p, char *name)
        {
       -        print_ruler(p->level);
       +        print_ruler(ical_get_level(p) - 1);
                printf("begin %s\n", name);
                return 0;
        }
       @@ -31,7 +32,7 @@ fn_block_begin(IcalParser *p, char *name)
        static int
        fn_param_value(IcalParser *p, char *name, char *value)
        {
       -        print_ruler(p->level + 1);
       +        print_ruler(ical_get_level(p) + 1);
                printf("param %s=%s\n", name, value);
                return 0;
        }
       @@ -44,8 +45,21 @@ fn_entry_value(IcalParser *p, char *name, char *value)
        
                if (ical_get_value(p, value, &len) < 0)
                        return -1;
       -        print_ruler(p->level + 1);
       -        printf("value %s\n", value);
       +
       +        print_ruler(ical_get_level(p) + 1);
       +
       +        if (strcasecmp(name, "DTSTART") == 0 ||
       +            strcasecmp(name, "DTSTAMP") == 0 ||
       +            strcasecmp(name, "DTEND") == 0) {
       +                time_t t;
       +
       +                if (ical_get_time(p, value, &t) != 0)
       +                        warn("%s: %s", p->errmsg, value);
       +                printf("epoch %ld\n", t);
       +        } else {        
       +                printf("value %s\n", value);
       +        }
       +
                return 0;
        }
        
       @@ -62,7 +76,7 @@ main(int argc, char **argv)
        
                if (*argv == NULL) {
                        if (ical_parse(&p, stdin) < 0)
       -                        err("parsing stdin:%d: %s", p.line, p.errmsg);
       +                        err("parsing stdin:%d: %s", p.linenum, p.errmsg);
                }
        
                for (; *argv != NULL; argv++, argc--) {
       @@ -72,7 +86,7 @@ main(int argc, char **argv)
                        if ((fp = fopen(*argv, "r")) == NULL)
                                err("opening %s", *argv);
                        if (ical_parse(&p, fp) < 0)
       -                        err("parsing %s:%d: %s", *argv, p.line, p.errmsg);
       +                        err("parsing %s:%d: %s", *argv, p.linenum, p.errmsg);
                        fclose(fp);
                }
                return 0;
 (DIR) diff --git a/util.c b/util.c
       @@ -5,20 +5,18 @@
        #include <stdlib.h>
        #include <string.h>
        #include <stdio.h>
       +#include <time.h>
        
        char *arg0;
        
       -/* logging */
       +/** logging **/
        
        static void
       -_log(char const *tag, char const *fmt, va_list va)
       +_log(char const *fmt, va_list va)
        {
                if (arg0 != NULL)
                        fprintf(stderr, "%s: ", arg0);
       -        fprintf(stderr, "%s: ", tag);
                vfprintf(stderr, fmt, va);
       -        if (errno != 0)
       -                fprintf(stderr, ": %s", strerror(errno));
                fprintf(stderr, "\n");
                fflush(stderr);
        }
       @@ -29,7 +27,7 @@ err(char const *fmt, ...)
                va_list va;
        
                va_start(va, fmt);
       -        _log("error", fmt, va);
       +        _log( fmt, va);
                exit(1);
        }
        
       @@ -39,7 +37,7 @@ warn(char const *fmt, ...)
                va_list va;
        
                va_start(va, fmt);
       -        _log("warning", fmt, va);
       +        _log(fmt, va);
        }
        
        void
       @@ -53,23 +51,34 @@ debug(char const *fmt, ...)
                if (!verbose)
                        return;
                va_start(va, fmt);
       -        _log("debug", fmt, va);
       +        _log(fmt, va);
        }
        
       -/* strings */
       +/** strings **/
        
        size_t
       -strlcpy(char *buf, char const *str, size_t sz)
       +strlcpy(char *d, char const *s, size_t sz)
        {
                size_t len, cpy;
        
       -        len = strlen(str);
       +        len = strlen(s);
                cpy = (len > sz) ? (sz) : (len);
       -        memcpy(buf, str, cpy + 1);
       -        buf[sz - 1] = '\0';
       +        memcpy(d, s, cpy + 1);
       +        d[sz - 1] = '\0';
                return len;
        }
        
       +size_t
       +strlcat(char *d, char const *s, size_t dsz)
       +{
       +        size_t dlen;
       +
       +        dlen = strlen(d);
       +        if (dlen >= dsz)
       +                return dlen + strlen(s);
       +        return dlen + strlcpy(d + dlen, s, dsz - dlen);
       +}
       +
        char *
        strsep(char **sp, char const *sep)
        {
       @@ -102,28 +111,52 @@ strchomp(char *line)
        }
        
        int
       -strappend(char **dstp, char const *src)
       +strappend(char **dp, char const *s)
        {
       -        size_t dstlen, srclen;
       +        size_t dlen, slen;
                void *mem;
        
       -        dstlen = (*dstp == NULL) ? 0 : strlen(*dstp);
       -        srclen = strlen(src);
       +        dlen = (*dp == NULL) ? 0 : strlen(*dp);
       +        slen = strlen(s);
        
       -        if ((mem = realloc(*dstp, dstlen + srclen + 1)) == NULL)
       +        if ((mem = realloc(*dp, dlen + slen + 1)) == NULL)
                        return -1;
       -        *dstp = mem;
       +        *dp = mem;
        
       -        memcpy(*dstp + dstlen, src, srclen + 1);
       +        memcpy(*dp + dlen, s, slen + 1);
                return 0;
        }
        
       -/* memory */
       +/** memory **/
        
        void *
       -reallocarray(void *buf, size_t len, size_t sz)
       +reallocarray(void *mem, size_t n, size_t sz)
        {
       -        if (SIZE_MAX / len < sz)
       +        if (SIZE_MAX / n < sz)
                        return errno=ERANGE, NULL;
       -        return realloc(buf, len * sz);
       +        return realloc(mem, n * sz);
       +}
       +
       +/** time **/
       +
       +time_t
       +tztime(struct tm *tm, char const *tz)
       +{
       +        char *env, old[32];
       +        time_t t;
       +
       +        env = getenv("TZ");
       +        if (strlcpy(old, env ? env : "", sizeof old) >= sizeof old)
       +                return -1;
       +        if (setenv("TZ", tz, 1) < 0)
       +                return -1;
       +
       +        tzset();
       +        t = mktime(tm);
       +
       +        if (env == NULL)
       +                unsetenv("TZ");
       +        else if (setenv("TZ", old, 1) < 0)
       +                return -1;
       +        return t;
        }
 (DIR) diff --git a/util.h b/util.h
       @@ -3,20 +3,25 @@
        
        #include <stddef.h>
        #include <stdarg.h>
       +#include <time.h>
        
       -/* logging */
       +/** logging **/
        extern char *arg0;
        void         err(char const *fmt, ...);
        void         warn(char const *fmt, ...);
        void         debug(char const *fmt, ...);
        
       -/* strings */
       -size_t         strlcpy(char *buf, char const *str, size_t sz);
       -char        *strsep(char **str_p, char const *sep);
       -void         strchomp(char *line);
       -int         strappend(char **base_p, char const *s);
       +/** strings **/
       +size_t         strlcpy(char *, char const *, size_t);
       +char        *strsep(char **, char const *);
       +void         strchomp(char *);
       +int         strappend(char **, char const *);
       +size_t         strlcat(char *, char const *, size_t);
        
       -/* memory */
       -void        *reallocarray(void *buf, size_t len, size_t sz);
       +/** memory **/
       +void        *reallocarray(void *, size_t, size_t);
       +
       +/** time **/
       +time_t         tztime(struct tm *, char const *);
        
        #endif