about summary refs log tree commit diff
path: root/sternenblog/timeutil.c
diff options
context:
space:
mode:
Diffstat (limited to 'sternenblog/timeutil.c')
-rw-r--r--sternenblog/timeutil.c97
1 files changed, 97 insertions, 0 deletions
diff --git a/sternenblog/timeutil.c b/sternenblog/timeutil.c
new file mode 100644
index 0000000..187ace0
--- /dev/null
+++ b/sternenblog/timeutil.c
@@ -0,0 +1,97 @@
+#define _POSIX_C_SOURCE 1
+#define _XOPEN_SOURCE 1 // for timezone
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include "timeutil.h"
+#include "stringutil.h"
+
+#include <stdio.h>
+
+char *format_string(enum time_format t) {
+    switch(t) {
+        case RSS_TIME_FORMAT:
+            return "%a, %d %b %Y %T %z";
+        // both remaining cases still need a UTC offset
+        // part at the end which is not supported by
+        // strftime(3), so we do this ourselves in
+        // flocaltime
+        case HTML_TIME_FORMAT_READABLE:
+            return "%Y-%m-%d %T";
+        case ATOM_TIME_FORMAT:
+        default:
+            return "%Y-%m-%dT%T";
+    }
+}
+
+size_t flocaltime(char *b, enum time_format type, size_t size, const time_t *time) {
+    tzset();
+    struct tm *local = localtime(time);
+    char *format = format_string(type);
+
+    size_t res = strftime(b, size, format, local);
+
+    if(res == 0) {
+        return 0;
+    }
+
+    size_t offset_len = 0;
+
+    if(type == ATOM_TIME_FORMAT || type == HTML_TIME_FORMAT_READABLE) {
+        // for these formats we need to append a RFC3339 UTC offset
+        // unfortunately it is *not* exactly provided by strftime,
+        // but in hindsight it might be better to do a little string
+        // manipulation than this madness, since the libc timezone
+        // API is horrible (at least POSIX / glibc)
+        size_t offset_size = 7;
+        char offset[offset_size];
+
+        if(timezone == 0 && !local->tm_isdst) {
+            offset[0] = 'Z';
+            offset[1] = '\0';
+
+            offset_len = 1;
+        } else {
+            // for some reason timezone is seconds *west* of UTC which
+            // is inverse to how UTC offsets are denoted
+            long real_offset = (-1) * timezone;
+
+            if(daylight) {
+                // TODO is this correct in all cases?
+                if(local->tm_isdst == 1) {
+                    real_offset += 3600;
+                }
+            }
+
+            char sign;
+            if(real_offset > 0) {
+                sign = '+';
+            } else {
+                sign = '-';
+            }
+
+            long abso = labs(real_offset);
+            long hour = abso / 3600;
+            long minute = (abso % 3600) / 60;
+
+            offset[0] = sign;
+            offset[1] = nibble_hex((short) hour / 10);
+            offset[2] = nibble_hex((short) hour % 10);
+            offset[3] = ':';
+            offset[4] = nibble_hex((short) minute / 10);
+            offset[5] = nibble_hex((short) minute % 10);
+            offset[6] = '\0';
+
+            offset_len = 6;
+        }
+
+        if(res > 0 && res + offset_size <= size) {
+            memcpy(b + res, offset, offset_size);
+        }
+    }
+
+    // prevent any buffer overflows
+    b[size - 1] = '\0';
+
+    return res + offset_len;
+}