summaryrefslogtreecommitdiffstats
path: root/libnetdata/datetime
diff options
context:
space:
mode:
Diffstat (limited to 'libnetdata/datetime')
-rw-r--r--libnetdata/datetime/Makefile.am8
-rw-r--r--libnetdata/datetime/README.md11
-rw-r--r--libnetdata/datetime/iso8601.c81
-rw-r--r--libnetdata/datetime/iso8601.h18
-rw-r--r--libnetdata/datetime/rfc3339.c135
-rw-r--r--libnetdata/datetime/rfc3339.h12
-rw-r--r--libnetdata/datetime/rfc7231.c29
-rw-r--r--libnetdata/datetime/rfc7231.h12
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