about summary refs log tree commit diff
path: root/sternenblog/timeutil.c
blob: 187ace040618bf1af65645f8cbaf890637806464 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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;
}