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