diff options
Diffstat (limited to 'libnetdata/datetime')
-rw-r--r-- | libnetdata/datetime/Makefile.am | 8 | ||||
-rw-r--r-- | libnetdata/datetime/README.md | 11 | ||||
-rw-r--r-- | libnetdata/datetime/iso8601.c | 81 | ||||
-rw-r--r-- | libnetdata/datetime/iso8601.h | 18 | ||||
-rw-r--r-- | libnetdata/datetime/rfc3339.c | 135 | ||||
-rw-r--r-- | libnetdata/datetime/rfc3339.h | 12 | ||||
-rw-r--r-- | libnetdata/datetime/rfc7231.c | 29 | ||||
-rw-r--r-- | libnetdata/datetime/rfc7231.h | 12 |
8 files changed, 306 insertions, 0 deletions
diff --git a/libnetdata/datetime/Makefile.am b/libnetdata/datetime/Makefile.am new file mode 100644 index 00000000..161784b8 --- /dev/null +++ b/libnetdata/datetime/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/libnetdata/datetime/README.md b/libnetdata/datetime/README.md new file mode 100644 index 00000000..ba236631 --- /dev/null +++ b/libnetdata/datetime/README.md @@ -0,0 +1,11 @@ +<!-- +title: "Datetime" +custom_edit_url: https://github.com/netdata/netdata/edit/master/libnetdata/datetime/README.md +sidebar_label: "Datetime" +learn_topic_type: "Tasks" +learn_rel_path: "Developers/libnetdata" +--> + +# Datetime + +Formatting dates and timestamps. diff --git a/libnetdata/datetime/iso8601.c b/libnetdata/datetime/iso8601.c new file mode 100644 index 00000000..8e3f4e02 --- /dev/null +++ b/libnetdata/datetime/iso8601.c @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.h" + +size_t iso8601_datetime_ut(char *buffer, size_t len, usec_t now_ut, ISO8601_OPTIONS options) { + if(unlikely(!buffer || len == 0)) + return 0; + + time_t t = (time_t)(now_ut / USEC_PER_SEC); + struct tm *tmp, tmbuf; + + if(options & ISO8601_UTC) + // Use gmtime_r for UTC time conversion. + tmp = gmtime_r(&t, &tmbuf); + else + // Use localtime_r for local time conversion. + tmp = localtime_r(&t, &tmbuf); + + if (unlikely(!tmp)) { + buffer[0] = '\0'; + return 0; + } + + // Format the date and time according to the ISO 8601 format. + size_t used_length = strftime(buffer, len, "%Y-%m-%dT%H:%M:%S", tmp); + if (unlikely(used_length == 0)) { + buffer[0] = '\0'; + return 0; + } + + if(options & ISO8601_MILLISECONDS) { + // Calculate the remaining microseconds + int milliseconds = (int) ((now_ut % USEC_PER_SEC) / USEC_PER_MS); + if(milliseconds && len - used_length > 4) + used_length += snprintfz(buffer + used_length, len - used_length, ".%03d", milliseconds); + } + else if(options & ISO8601_MICROSECONDS) { + // Calculate the remaining microseconds + int microseconds = (int) (now_ut % USEC_PER_SEC); + if(microseconds && len - used_length > 7) + used_length += snprintfz(buffer + used_length, len - used_length, ".%06d", microseconds); + } + + if(options & ISO8601_UTC) { + if(used_length + 1 < len) { + buffer[used_length++] = 'Z'; + buffer[used_length] = '\0'; // null-terminate the string. + } + } + else { + // Calculate the timezone offset in hours and minutes from UTC. + long offset = tmbuf.tm_gmtoff; + int hours = (int) (offset / 3600); // Convert offset seconds to hours. + int minutes = (int) ((offset % 3600) / 60); // Convert remainder to minutes (keep the sign for minutes). + + // Check if timezone is UTC. + if(hours == 0 && minutes == 0) { + // For UTC, append 'Z' to the timestamp. + if(used_length + 1 < len) { + buffer[used_length++] = 'Z'; + buffer[used_length] = '\0'; // null-terminate the string. + } + } + else { + // For non-UTC, format the timezone offset. Omit minutes if they are zero. + if(minutes == 0) { + // Check enough space is available for the timezone offset string. + if(used_length + 3 < len) // "+hh\0" + used_length += snprintfz(buffer + used_length, len - used_length, "%+03d", hours); + } + else { + // Check enough space is available for the timezone offset string. + if(used_length + 6 < len) // "+hh:mm\0" + used_length += snprintfz(buffer + used_length, len - used_length, + "%+03d:%02d", hours, abs(minutes)); + } + } + } + + return used_length; +} diff --git a/libnetdata/datetime/iso8601.h b/libnetdata/datetime/iso8601.h new file mode 100644 index 00000000..ce480096 --- /dev/null +++ b/libnetdata/datetime/iso8601.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.h" + +#ifndef NETDATA_ISO8601_H +#define NETDATA_ISO8601_H + +typedef enum __attribute__((__packed__)) { + ISO8601_UTC = (1 << 0), + ISO8601_LOCAL_TIMEZONE = (1 << 1), + ISO8601_MILLISECONDS = (1 << 2), + ISO8601_MICROSECONDS = (1 << 3), +} ISO8601_OPTIONS; + +#define ISO8601_MAX_LENGTH 64 +size_t iso8601_datetime_ut(char *buffer, size_t len, usec_t now_ut, ISO8601_OPTIONS options); + +#endif //NETDATA_ISO8601_H diff --git a/libnetdata/datetime/rfc3339.c b/libnetdata/datetime/rfc3339.c new file mode 100644 index 00000000..157e340d --- /dev/null +++ b/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 >= 0 && 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(s[1]) || !isdigit(s[2]) || s[3] != ':' || + !isdigit(s[4]) || !isdigit(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; +} diff --git a/libnetdata/datetime/rfc3339.h b/libnetdata/datetime/rfc3339.h new file mode 100644 index 00000000..88ebb3ec --- /dev/null +++ b/libnetdata/datetime/rfc3339.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.h" + +#ifndef NETDATA_RFC3339_H +#define NETDATA_RFC3339_H + +#define RFC3339_MAX_LENGTH 36 +size_t rfc3339_datetime_ut(char *buffer, size_t len, usec_t now_ut, size_t fractional_digits, bool utc); +usec_t rfc3339_parse_ut(const char *rfc3339, char **endptr); + +#endif //NETDATA_RFC3339_H diff --git a/libnetdata/datetime/rfc7231.c b/libnetdata/datetime/rfc7231.c new file mode 100644 index 00000000..4925ed2c --- /dev/null +++ b/libnetdata/datetime/rfc7231.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.h" + +inline size_t rfc7231_datetime(char *buffer, size_t len, time_t now_t) { + if (unlikely(!buffer || !len)) + return 0; + + struct tm *tmp, tmbuf; + + // Use gmtime_r for UTC time conversion. + tmp = gmtime_r(&now_t, &tmbuf); + + if (unlikely(!tmp)) { + buffer[0] = '\0'; + return 0; + } + + // Format the date and time according to the RFC 7231 format. + size_t ret = strftime(buffer, len, "%a, %d %b %Y %H:%M:%S GMT", tmp); + if (unlikely(ret == 0)) + buffer[0] = '\0'; + + return ret; +} + +size_t rfc7231_datetime_ut(char *buffer, size_t len, usec_t now_ut) { + return rfc7231_datetime(buffer, len, (time_t) (now_ut / USEC_PER_SEC)); +} diff --git a/libnetdata/datetime/rfc7231.h b/libnetdata/datetime/rfc7231.h new file mode 100644 index 00000000..5ba93053 --- /dev/null +++ b/libnetdata/datetime/rfc7231.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.h" + +#ifndef NETDATA_RFC7231_H +#define NETDATA_RFC7231_H + +#define RFC7231_MAX_LENGTH 30 +size_t rfc7231_datetime(char *buffer, size_t len, time_t now_t); +size_t rfc7231_datetime_ut(char *buffer, size_t len, usec_t now_ut); + +#endif //NETDATA_RFC7231_H |