summaryrefslogtreecommitdiffstats
path: root/src/libnetdata/datetime/rfc3339.c
blob: 5c4e990ddb6a2cebcb9b88c64e27dc3191076851 (plain)
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// SPDX-License-Identifier: GPL-3.0-or-later

#include "../libnetdata.h"

#include "rfc3339.h"

size_t rfc3339_datetime_ut(char *buffer, size_t len, usec_t now_ut, size_t fractional_digits, bool utc) {
    if (!buffer || len == 0)
        return 0;

    time_t t = (time_t)(now_ut / USEC_PER_SEC);
    struct tm *tmp, tmbuf;

    if (utc)
        tmp = gmtime_r(&t, &tmbuf);
    else
        tmp = localtime_r(&t, &tmbuf);

    if (!tmp) {
        buffer[0] = '\0';
        return 0;
    }

    size_t used_length = strftime(buffer, len, "%Y-%m-%dT%H:%M:%S", tmp);
    if (used_length == 0) {
        buffer[0] = '\0';
        return 0;
    }

    if (fractional_digits >= 1 && fractional_digits <= 9) {
        int fractional_part = (int)(now_ut % USEC_PER_SEC);
        if (fractional_part && len - used_length > fractional_digits + 1) {
            char format[] = ".%01d";
            format[3] = (char)('0' + fractional_digits);

            // Adjust fractional part
            fractional_part /= (int)pow(10, 6 - fractional_digits);

            used_length += snprintf(buffer + used_length, len - used_length,
                                    format, fractional_part);
        }
    }

    if (utc) {
        if (used_length + 1 < len) {
            buffer[used_length++] = 'Z';
            buffer[used_length] = '\0';
        }
    }
    else {
        long offset = tmbuf.tm_gmtoff;
        int hours = (int)(offset / 3600);
        int minutes = abs((int)((offset % 3600) / 60));

        if (used_length + 7 < len) { // Space for "+HH:MM\0"
            used_length += snprintf(buffer + used_length, len - used_length, "%+03d:%02d", hours, minutes);
        }
    }

    return used_length;
}

usec_t rfc3339_parse_ut(const char *rfc3339, char **endptr) {
    struct tm tm = { 0 };
    int tz_hours = 0, tz_mins = 0;
    char *s;
    usec_t timestamp, usec = 0;

    // Use strptime to parse up to seconds
    s = strptime(rfc3339, "%Y-%m-%dT%H:%M:%S", &tm);
    if (!s)
        return 0; // Parsing error

    // Parse fractional seconds if present
    if (*s == '.') {
        char *next;
        usec = strtoul(s + 1, &next, 10);
        int digits_parsed = (int)(next - (s + 1));

        if (digits_parsed < 1 || digits_parsed > 9)
            return 0; // parsing error

        static const usec_t fix_usec[] = {
                1000000,  // 0 digits (not used)
                100000,   // 1 digit
                10000,    // 2 digits
                1000,     // 3 digits
                100,      // 4 digits
                10,       // 5 digits
                1,        // 6 digits
                10,       // 7 digits
                100,      // 8 digits
                1000,     // 9 digits
        };
        usec = digits_parsed <= 6 ? usec * fix_usec[digits_parsed] : usec / fix_usec[digits_parsed];

        s = next;
    }

    // Check and parse timezone if present
    int tz_offset = 0;
    if (*s == '+' || *s == '-') {
        // Parse the hours:mins part of the timezone

        if (!isdigit((uint8_t)s[1]) || !isdigit((uint8_t)s[2]) || s[3] != ':' ||
            !isdigit((uint8_t)s[4]) || !isdigit((uint8_t)s[5]))
            return 0; // Parsing error

        char tz_sign = *s;
        tz_hours = (s[1] - '0') * 10 + (s[2] - '0');
        tz_mins = (s[4] - '0') * 10 + (s[5] - '0');

        tz_offset = tz_hours * 3600 + tz_mins * 60;
        tz_offset *= (tz_sign == '+' ? 1 : -1);

        s += 6; // Move past the timezone part
    }
    else if (*s == 'Z')
        s++;
    else
        return 0; // Invalid RFC 3339 format

    // Convert to time_t (assuming local time, then adjusting for timezone later)
    time_t epoch_s = mktime(&tm);
    if (epoch_s == -1)
        return 0; // Error in time conversion

    timestamp = (usec_t)epoch_s * USEC_PER_SEC + usec;
    timestamp -= tz_offset * USEC_PER_SEC;

    if(endptr)
        *endptr = s;

    return timestamp;
}