diff options
Diffstat (limited to 'src/libnetdata/datetime/rfc3339.c')
-rw-r--r-- | src/libnetdata/datetime/rfc3339.c | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/src/libnetdata/datetime/rfc3339.c b/src/libnetdata/datetime/rfc3339.c new file mode 100644 index 000000000..5c4e990dd --- /dev/null +++ b/src/libnetdata/datetime/rfc3339.c @@ -0,0 +1,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; +} |