master
c 308 lines 8.59 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "../libnetdata.h"
4
5 #include "rfc3339.h"
6
7 // Helper functions for safe printing of date/time components
8 // These functions don't add a null terminator and return only the exact count of bytes written
9
10 static inline size_t print_4digit_year(char *buffer, size_t size, int year) {
11 if (!buffer || size < 4) return 0; // Need at least 4 digits
12
13 // Ensure year is in valid range (0-9999)
14 year = year < 0 ? 0 : (year > 9999 ? 9999 : year);
15
16 buffer[0] = '0' + (year / 1000) % 10;
17 buffer[1] = '0' + (year / 100) % 10;
18 buffer[2] = '0' + (year / 10) % 10;
19 buffer[3] = '0' + year % 10;
20
21 return 4;
22 }
23
24 static inline size_t print_2digit(char *buffer, size_t size, int value) {
25 if (!buffer || size < 2) return 0; // Need at least 2 digits
26
27 // Ensure value is in valid range (0-99)
28 value = value < 0 ? 0 : (value > 99 ? 99 : value);
29
30 buffer[0] = '0' + (value / 10) % 10;
31 buffer[1] = '0' + value % 10;
32
33 return 2;
34 }
35
36 static inline size_t print_fraction(char *buffer, size_t size, usec_t fraction, size_t digits) {
37 if (!buffer) return 0;
38
39 // Validate and cap the number of digits
40 digits = digits < 1 ? 1 : (digits > 9 ? 9 : digits);
41 if (size < digits) return 0;
42
43 // Scale microseconds to the requested precision
44 if (digits < 6) {
45 usec_t divisor = 1;
46 for (size_t i = digits; i < 6; i++)
47 divisor *= 10;
48
49 fraction /= divisor;
50 }
51 else if (digits > 6) {
52 for (size_t i = 6; i < digits; i++)
53 fraction *= 10;
54 }
55
56 // Ensure fraction won't exceed the requested number of digits
57 usec_t max_value = 1;
58 for (size_t i = 0; i < digits; i++)
59 max_value *= 10;
60 max_value--;
61
62 fraction = fraction > max_value ? max_value : fraction;
63
64 // Print the fraction with leading zeros
65 usec_t remaining = fraction;
66
67 // Setup working backwards from least significant digit
68 for (int i = digits - 1; i >= 0; i--) {
69 buffer[i] = '0' + (remaining % 10);
70 remaining /= 10;
71 }
72
73 return digits;
74 }
75
76 size_t rfc3339_datetime_ut(char *buffer, size_t len, usec_t now_ut, size_t fractional_digits, bool utc) {
77 if (!buffer || len < 20) // Minimum size for YYYY-MM-DDThh:mm:ssZ
78 return 0;
79
80 time_t t = (time_t)(now_ut / USEC_PER_SEC);
81 struct tm *tmp, tmbuf;
82
83 if (utc)
84 tmp = gmtime_r(&t, &tmbuf);
85 else
86 tmp = localtime_r(&t, &tmbuf);
87
88 if (!tmp) {
89 buffer[0] = '\0';
90 return 0;
91 }
92
93 size_t pos = 0;
94
95 // Year (4 digits)
96 if (len - pos < 4) goto finish;
97 pos += print_4digit_year(&buffer[pos], len - pos, tmp->tm_year + 1900);
98
99 // Month separator
100 if (len - pos < 1) goto finish;
101 buffer[pos++] = '-';
102
103 // Month (2 digits)
104 if (len - pos < 2) goto finish;
105 pos += print_2digit(&buffer[pos], len - pos, tmp->tm_mon + 1);
106
107 // Day separator
108 if (len - pos < 1) goto finish;
109 buffer[pos++] = '-';
110
111 // Day (2 digits)
112 if (len - pos < 2) goto finish;
113 pos += print_2digit(&buffer[pos], len - pos, tmp->tm_mday);
114
115 // T separator
116 if (len - pos < 1) goto finish;
117 buffer[pos++] = 'T';
118
119 // Hour (2 digits)
120 if (len - pos < 2) goto finish;
121 pos += print_2digit(&buffer[pos], len - pos, tmp->tm_hour);
122
123 // Minute separator
124 if (len - pos < 1) goto finish;
125 buffer[pos++] = ':';
126
127 // Minute (2 digits)
128 if (len - pos < 2) goto finish;
129 pos += print_2digit(&buffer[pos], len - pos, tmp->tm_min);
130
131 // Second separator
132 if (len - pos < 1) goto finish;
133 buffer[pos++] = ':';
134
135 // Second (2 digits)
136 if (len - pos < 2) goto finish;
137 pos += print_2digit(&buffer[pos], len - pos, tmp->tm_sec);
138
139 // Add fractional part if requested
140 if (fractional_digits > 9) fractional_digits = 9;
141 if (fractional_digits) {
142 usec_t fractional_part = now_ut % USEC_PER_SEC;
143
144 if (fractional_part > 0) {
145 // Need space for decimal point and digits
146 if (len - pos < fractional_digits + 1) goto finish;
147
148 buffer[pos++] = '.';
149 pos += print_fraction(&buffer[pos], len - pos, fractional_part, fractional_digits);
150 }
151 }
152
153 // Add timezone information
154 if (utc) {
155 if (len - pos < 1) goto finish;
156 buffer[pos++] = 'Z';
157 }
158 else {
159 long offset = tmbuf.tm_gmtoff;
160 int hours = (int)(offset / 3600);
161 int minutes = abs((int)((offset % 3600) / 60));
162
163 // Check if timezone is UTC
164 if (hours == 0 && minutes == 0) {
165 if (len - pos < 1) goto finish;
166 buffer[pos++] = 'Z';
167 }
168 else {
169 // Need space for sign, hours, colon, minutes (6 chars total)
170 if (len - pos < 6) goto finish;
171
172 // Add timezone offset
173 buffer[pos++] = (hours >= 0) ? '+' : '-';
174 hours = abs(hours);
175
176 // Hours with leading zero
177 pos += print_2digit(&buffer[pos], len - pos, hours);
178
179 // Colon
180 buffer[pos++] = ':';
181
182 // Minutes with leading zero
183 pos += print_2digit(&buffer[pos], len - pos, minutes);
184 }
185 }
186
187 finish:
188 // Ensure null termination
189 if (pos < len)
190 buffer[pos] = '\0';
191 else
192 buffer[len - 1] = '\0';
193
194 return pos;
195 }
196
197 usec_t rfc3339_parse_ut(const char *rfc3339, char **endptr) {
198 struct tm tm = { 0 };
199 int tz_hours = 0, tz_mins = 0;
200 char *s;
201 usec_t timestamp, usec = 0;
202
203 // Parse date and time (up to seconds)
204 s = strptime(rfc3339, "%Y-%m-%dT%H:%M:%S", &tm);
205 if (!s)
206 return 0; // Parsing error
207
208 // Parse fractional seconds if present
209 if (*s == '.') {
210 char *next;
211 usec = strtoul(s + 1, &next, 10);
212 int digits_parsed = (int)(next - (s + 1));
213
214 if (digits_parsed < 1 || digits_parsed > 9)
215 return 0; // Parsing error
216
217 static const usec_t fix_usec[] = {
218 1000000, // 0 digits (not used)
219 100000, // 1 digit
220 10000, // 2 digits
221 1000, // 3 digits
222 100, // 4 digits
223 10, // 5 digits
224 1, // 6 digits
225 10, // 7 digits
226 100, // 8 digits
227 1000 // 9 digits
228 };
229
230 if (digits_parsed <= 6)
231 usec = usec * fix_usec[digits_parsed];
232 else
233 usec = usec / fix_usec[digits_parsed];
234
235 s = next;
236 }
237
238 // Parse timezone specification
239 int tz_offset = 0;
240 if (*s == '+' || *s == '-') {
241 // Ensure format is correct: e.g. +02:00 or -05:30
242
243 if (!isdigit((uint8_t)s[1]) || !isdigit((uint8_t)s[2]) || s[3] != ':' ||
244 !isdigit((uint8_t)s[4]) || !isdigit((uint8_t)s[5]))
245 return 0; // Parsing error
246
247 char tz_sign = *s;
248 tz_hours = (s[1] - '0') * 10 + (s[2] - '0');
249 tz_mins = (s[4] - '0') * 10 + (s[5] - '0');
250 tz_offset = tz_hours * 3600 + tz_mins * 60;
251 tz_offset *= (tz_sign == '+' ? 1 : -1);
252
253 s += 6; // Move past the timezone part
254 }
255 else if (*s == 'Z')
256 s++;
257 else
258 return 0; // Invalid RFC 3339 timezone specification
259
260 // Convert struct tm to time_t in UTC
261 time_t epoch_s;
262
263 #if defined(HAVE_TIMEGM)
264 // If available, use timegm() which interprets tm as UTC.
265 epoch_s = timegm(&tm);
266 #else
267 // Use mktime(), which assumes tm is local time, then adjust.
268 epoch_s = mktime(&tm);
269 if (epoch_s == -1)
270 return 0; // Error in time conversion
271
272 # if defined(HAVE_TM_GMTOFF)
273 // tm.tm_gmtoff is the offset (in seconds) of local time from UTC.
274 epoch_s -= tm.tm_gmtoff;
275 # else
276 // Fallback: compute the difference between localtime and gmtime.
277 {
278 struct tm local_tm, utc_tm;
279 #if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && !defined(__APPLE__)
280 localtime_r(&epoch_s, &local_tm);
281 gmtime_r(&epoch_s, &utc_tm);
282 #else
283 // If thread-safe functions are not available, use localtime() and gmtime()
284 struct tm *lt = localtime(&epoch_s);
285 struct tm *gt = gmtime(&epoch_s);
286 if (!lt || !gt)
287 return 0;
288 local_tm = *lt;
289 utc_tm = *gt;
290 #endif
291 int local_offset = (local_tm.tm_hour - utc_tm.tm_hour) * 3600 +
292 (local_tm.tm_min - utc_tm.tm_min) * 60;
293 int day_diff = local_tm.tm_yday - utc_tm.tm_yday;
294 local_offset += day_diff * 86400;
295 epoch_s -= local_offset;
296 }
297 # endif
298 #endif
299
300 // Combine seconds with fractional microseconds, then adjust for the RFC 3339 timezone.
301 timestamp = (usec_t)epoch_s * USEC_PER_SEC + usec;
302 timestamp -= tz_offset * USEC_PER_SEC;
303
304 if (endptr)
305 *endptr = s;
306
307 return timestamp;
308 }