Introduction
Introduction Statistics Contact Development Disclaimer Help
support time zone conversion and date-time parsing - ics2txt - convert icalenda…
git clone git://bitreich.org/ics2txt git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws…
Log
Files
Refs
Tags
README
---
commit 58a1a9df90b5751ae05fba076cd9e664e3d9f3c1
parent b72092250747c7443e20fee06bee232b236f441e
Author: Josuah Demangeon <[email protected]>
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(-)
---
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);
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 *);
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;
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;
}
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
You are viewing proxied material from bitreich.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.