Line data Source code
1 : /**
2 : * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 : * SPDX-License-Identifier: Apache-2.0.
4 : */
5 : #include <aws/common/date_time.h>
6 :
7 : #include <aws/common/array_list.h>
8 : #include <aws/common/byte_buf.h>
9 : #include <aws/common/byte_order.h>
10 : #include <aws/common/clock.h>
11 : #include <aws/common/string.h>
12 : #include <aws/common/time.h>
13 :
14 : #include <ctype.h>
15 :
16 : static const char *RFC822_DATE_FORMAT_STR_MINUS_Z = "%a, %d %b %Y %H:%M:%S GMT";
17 : static const char *RFC822_DATE_FORMAT_STR_WITH_Z = "%a, %d %b %Y %H:%M:%S %Z";
18 : static const char *RFC822_SHORT_DATE_FORMAT_STR = "%a, %d %b %Y";
19 : static const char *ISO_8601_LONG_DATE_FORMAT_STR = "%Y-%m-%dT%H:%M:%SZ";
20 : static const char *ISO_8601_SHORT_DATE_FORMAT_STR = "%Y-%m-%d";
21 : static const char *ISO_8601_LONG_BASIC_DATE_FORMAT_STR = "%Y%m%dT%H%M%SZ";
22 : static const char *ISO_8601_SHORT_BASIC_DATE_FORMAT_STR = "%Y%m%d";
23 :
24 : #define STR_TRIPLET_TO_INDEX(str) \
25 0 : (((uint32_t)tolower((uint8_t)((str)[0])) << 0) | ((uint32_t)tolower((uint8_t)((str)[1])) << 8) | \
26 0 : ((uint32_t)tolower((uint8_t)((str)[2])) << 16))
27 :
28 : static uint32_t s_jan = 0;
29 : static uint32_t s_feb = 0;
30 : static uint32_t s_mar = 0;
31 : static uint32_t s_apr = 0;
32 : static uint32_t s_may = 0;
33 : static uint32_t s_jun = 0;
34 : static uint32_t s_jul = 0;
35 : static uint32_t s_aug = 0;
36 : static uint32_t s_sep = 0;
37 : static uint32_t s_oct = 0;
38 : static uint32_t s_nov = 0;
39 : static uint32_t s_dec = 0;
40 :
41 : static uint32_t s_utc = 0;
42 : static uint32_t s_gmt = 0;
43 :
44 0 : static void s_check_init_str_to_int(void) {
45 0 : if (!s_jan) {
46 0 : s_jan = STR_TRIPLET_TO_INDEX("jan");
47 0 : s_feb = STR_TRIPLET_TO_INDEX("feb");
48 0 : s_mar = STR_TRIPLET_TO_INDEX("mar");
49 0 : s_apr = STR_TRIPLET_TO_INDEX("apr");
50 0 : s_may = STR_TRIPLET_TO_INDEX("may");
51 0 : s_jun = STR_TRIPLET_TO_INDEX("jun");
52 0 : s_jul = STR_TRIPLET_TO_INDEX("jul");
53 0 : s_aug = STR_TRIPLET_TO_INDEX("aug");
54 0 : s_sep = STR_TRIPLET_TO_INDEX("sep");
55 0 : s_oct = STR_TRIPLET_TO_INDEX("oct");
56 0 : s_nov = STR_TRIPLET_TO_INDEX("nov");
57 0 : s_dec = STR_TRIPLET_TO_INDEX("dec");
58 0 : s_utc = STR_TRIPLET_TO_INDEX("utc");
59 0 : s_gmt = STR_TRIPLET_TO_INDEX("gmt");
60 0 : }
61 0 : }
62 :
63 : /* Get the 0-11 monthy number from a string representing Month. Case insensitive and will stop on abbreviation*/
64 0 : static int get_month_number_from_str(const char *time_string, size_t start_index, size_t stop_index) {
65 0 : s_check_init_str_to_int();
66 0 :
67 0 : if (stop_index - start_index < 3) {
68 0 : return -1;
69 0 : }
70 0 :
71 0 : /* This AND forces the string to lowercase (assuming ASCII) */
72 0 : uint32_t comp_val = STR_TRIPLET_TO_INDEX(time_string + start_index);
73 0 :
74 0 : /* this can't be a switch, because I can't make it a constant expression. */
75 0 : if (s_jan == comp_val) {
76 0 : return 0;
77 0 : }
78 0 :
79 0 : if (s_feb == comp_val) {
80 0 : return 1;
81 0 : }
82 0 :
83 0 : if (s_mar == comp_val) {
84 0 : return 2;
85 0 : }
86 0 :
87 0 : if (s_apr == comp_val) {
88 0 : return 3;
89 0 : }
90 0 :
91 0 : if (s_may == comp_val) {
92 0 : return 4;
93 0 : }
94 0 :
95 0 : if (s_jun == comp_val) {
96 0 : return 5;
97 0 : }
98 0 :
99 0 : if (s_jul == comp_val) {
100 0 : return 6;
101 0 : }
102 0 :
103 0 : if (s_aug == comp_val) {
104 0 : return 7;
105 0 : }
106 0 :
107 0 : if (s_sep == comp_val) {
108 0 : return 8;
109 0 : }
110 0 :
111 0 : if (s_oct == comp_val) {
112 0 : return 9;
113 0 : }
114 0 :
115 0 : if (s_nov == comp_val) {
116 0 : return 10;
117 0 : }
118 0 :
119 0 : if (s_dec == comp_val) {
120 0 : return 11;
121 0 : }
122 0 :
123 0 : return -1;
124 0 : }
125 :
126 : /* Detects whether or not the passed in timezone string is a UTC zone. */
127 0 : static bool is_utc_time_zone(const char *str) {
128 0 : s_check_init_str_to_int();
129 0 :
130 0 : size_t len = strlen(str);
131 0 :
132 0 : if (len > 0) {
133 0 : if (str[0] == 'Z') {
134 0 : return true;
135 0 : }
136 0 :
137 0 : /* offsets count since their usable */
138 0 : if (len == 5 && (str[0] == '+' || str[0] == '-')) {
139 0 : return true;
140 0 : }
141 0 :
142 0 : if (len == 2) {
143 0 : return tolower((uint8_t)str[0]) == 'u' && tolower((uint8_t)str[1]) == 't';
144 0 : }
145 0 :
146 0 : if (len < 3) {
147 0 : return false;
148 0 : }
149 0 :
150 0 : uint32_t comp_val = STR_TRIPLET_TO_INDEX(str);
151 0 :
152 0 : if (comp_val == s_utc || comp_val == s_gmt) {
153 0 : return true;
154 0 : }
155 0 : }
156 0 :
157 0 : return false;
158 0 : }
159 :
160 0 : struct tm s_get_time_struct(struct aws_date_time *dt, bool local_time) {
161 0 : struct tm time;
162 0 : AWS_ZERO_STRUCT(time);
163 0 : if (local_time) {
164 0 : aws_localtime(dt->timestamp, &time);
165 0 : } else {
166 0 : aws_gmtime(dt->timestamp, &time);
167 0 : }
168 0 :
169 0 : return time;
170 0 : }
171 :
172 0 : void aws_date_time_init_now(struct aws_date_time *dt) {
173 0 : uint64_t current_time = 0;
174 0 : aws_sys_clock_get_ticks(¤t_time);
175 0 : dt->timestamp = (time_t)aws_timestamp_convert(current_time, AWS_TIMESTAMP_NANOS, AWS_TIMESTAMP_SECS, NULL);
176 0 : dt->gmt_time = s_get_time_struct(dt, false);
177 0 : dt->local_time = s_get_time_struct(dt, true);
178 0 : }
179 :
180 0 : void aws_date_time_init_epoch_millis(struct aws_date_time *dt, uint64_t ms_since_epoch) {
181 0 : dt->timestamp = (time_t)(ms_since_epoch / AWS_TIMESTAMP_MILLIS);
182 0 : dt->gmt_time = s_get_time_struct(dt, false);
183 0 : dt->local_time = s_get_time_struct(dt, true);
184 0 : }
185 :
186 0 : void aws_date_time_init_epoch_secs(struct aws_date_time *dt, double sec_ms) {
187 0 : dt->timestamp = (time_t)sec_ms;
188 0 : dt->gmt_time = s_get_time_struct(dt, false);
189 0 : dt->local_time = s_get_time_struct(dt, true);
190 0 : }
191 :
192 : enum parser_state {
193 : ON_WEEKDAY,
194 : ON_SPACE_DELIM,
195 : ON_YEAR,
196 : ON_MONTH,
197 : ON_MONTH_DAY,
198 : ON_HOUR,
199 : ON_MINUTE,
200 : ON_SECOND,
201 : ON_TZ,
202 : FINISHED,
203 : };
204 :
205 0 : static int s_parse_iso_8601_basic(const struct aws_byte_cursor *date_str_cursor, struct tm *parsed_time) {
206 0 : size_t index = 0;
207 0 : size_t state_start_index = 0;
208 0 : enum parser_state state = ON_YEAR;
209 0 : bool error = false;
210 0 :
211 0 : AWS_ZERO_STRUCT(*parsed_time);
212 0 :
213 0 : while (state < FINISHED && !error && index < date_str_cursor->len) {
214 0 : char c = date_str_cursor->ptr[index];
215 0 : size_t sub_index = index - state_start_index;
216 0 : switch (state) {
217 0 : case ON_YEAR:
218 0 : if (aws_isdigit(c)) {
219 0 : parsed_time->tm_year = parsed_time->tm_year * 10 + (c - '0');
220 0 : if (sub_index == 3) {
221 0 : state = ON_MONTH;
222 0 : state_start_index = index + 1;
223 0 : parsed_time->tm_year -= 1900;
224 0 : }
225 0 : } else {
226 0 : error = true;
227 0 : }
228 0 : break;
229 0 :
230 0 : case ON_MONTH:
231 0 : if (aws_isdigit(c)) {
232 0 : parsed_time->tm_mon = parsed_time->tm_mon * 10 + (c - '0');
233 0 : if (sub_index == 1) {
234 0 : state = ON_MONTH_DAY;
235 0 : state_start_index = index + 1;
236 0 : parsed_time->tm_mon -= 1;
237 0 : }
238 0 : } else {
239 0 : error = true;
240 0 : }
241 0 : break;
242 0 :
243 0 : case ON_MONTH_DAY:
244 0 : if (c == 'T' && sub_index == 2) {
245 0 : state = ON_HOUR;
246 0 : state_start_index = index + 1;
247 0 : } else if (aws_isdigit(c)) {
248 0 : parsed_time->tm_mday = parsed_time->tm_mday * 10 + (c - '0');
249 0 : } else {
250 0 : error = true;
251 0 : }
252 0 : break;
253 0 :
254 0 : case ON_HOUR:
255 0 : if (aws_isdigit(c)) {
256 0 : parsed_time->tm_hour = parsed_time->tm_hour * 10 + (c - '0');
257 0 : if (sub_index == 1) {
258 0 : state = ON_MINUTE;
259 0 : state_start_index = index + 1;
260 0 : }
261 0 : } else {
262 0 : error = true;
263 0 : }
264 0 : break;
265 0 :
266 0 : case ON_MINUTE:
267 0 : if (aws_isdigit(c)) {
268 0 : parsed_time->tm_min = parsed_time->tm_min * 10 + (c - '0');
269 0 : if (sub_index == 1) {
270 0 : state = ON_SECOND;
271 0 : state_start_index = index + 1;
272 0 : }
273 0 : } else {
274 0 : error = true;
275 0 : }
276 0 : break;
277 0 :
278 0 : case ON_SECOND:
279 0 : if (aws_isdigit(c)) {
280 0 : parsed_time->tm_sec = parsed_time->tm_sec * 10 + (c - '0');
281 0 : if (sub_index == 1) {
282 0 : state = ON_TZ;
283 0 : state_start_index = index + 1;
284 0 : }
285 0 : } else {
286 0 : error = true;
287 0 : }
288 0 : break;
289 0 :
290 0 : case ON_TZ:
291 0 : if (c == 'Z' && (sub_index == 0 || sub_index == 3)) {
292 0 : state = FINISHED;
293 0 : } else if (!aws_isdigit(c) || sub_index > 3) {
294 0 : error = true;
295 0 : }
296 0 : break;
297 0 :
298 0 : default:
299 0 : error = true;
300 0 : break;
301 0 : }
302 0 :
303 0 : index++;
304 0 : }
305 0 :
306 0 : /* ISO8601 supports date only with no time portion. state ==ON_MONTH_DAY catches this case. */
307 0 : return (state == FINISHED || state == ON_MONTH_DAY) && !error ? AWS_OP_SUCCESS : AWS_OP_ERR;
308 0 : }
309 :
310 0 : static int s_parse_iso_8601(const struct aws_byte_cursor *date_str_cursor, struct tm *parsed_time) {
311 0 : size_t index = 0;
312 0 : size_t state_start_index = 0;
313 0 : enum parser_state state = ON_YEAR;
314 0 : bool error = false;
315 0 : bool advance = true;
316 0 :
317 0 : AWS_ZERO_STRUCT(*parsed_time);
318 0 :
319 0 : while (state < FINISHED && !error && index < date_str_cursor->len) {
320 0 : char c = date_str_cursor->ptr[index];
321 0 : switch (state) {
322 0 : case ON_YEAR:
323 0 : if (c == '-' && index - state_start_index == 4) {
324 0 : state = ON_MONTH;
325 0 : state_start_index = index + 1;
326 0 : parsed_time->tm_year -= 1900;
327 0 : } else if (aws_isdigit(c)) {
328 0 : parsed_time->tm_year = parsed_time->tm_year * 10 + (c - '0');
329 0 : } else {
330 0 : error = true;
331 0 : }
332 0 : break;
333 0 : case ON_MONTH:
334 0 : if (c == '-' && index - state_start_index == 2) {
335 0 : state = ON_MONTH_DAY;
336 0 : state_start_index = index + 1;
337 0 : parsed_time->tm_mon -= 1;
338 0 : } else if (aws_isdigit(c)) {
339 0 : parsed_time->tm_mon = parsed_time->tm_mon * 10 + (c - '0');
340 0 : } else {
341 0 : error = true;
342 0 : }
343 0 :
344 0 : break;
345 0 : case ON_MONTH_DAY:
346 0 : if (c == 'T' && index - state_start_index == 2) {
347 0 : state = ON_HOUR;
348 0 : state_start_index = index + 1;
349 0 : } else if (aws_isdigit(c)) {
350 0 : parsed_time->tm_mday = parsed_time->tm_mday * 10 + (c - '0');
351 0 : } else {
352 0 : error = true;
353 0 : }
354 0 : break;
355 0 : /* note: no time portion is spec compliant. */
356 0 : case ON_HOUR:
357 0 : /* time parts can be delimited by ':' or just concatenated together, but must always be 2 digits. */
358 0 : if (index - state_start_index == 2) {
359 0 : state = ON_MINUTE;
360 0 : state_start_index = index + 1;
361 0 : if (aws_isdigit(c)) {
362 0 : state_start_index = index;
363 0 : advance = false;
364 0 : } else if (c != ':') {
365 0 : error = true;
366 0 : }
367 0 : } else if (aws_isdigit(c)) {
368 0 : parsed_time->tm_hour = parsed_time->tm_hour * 10 + (c - '0');
369 0 : } else {
370 0 : error = true;
371 0 : }
372 0 :
373 0 : break;
374 0 : case ON_MINUTE:
375 0 : /* time parts can be delimited by ':' or just concatenated together, but must always be 2 digits. */
376 0 : if (index - state_start_index == 2) {
377 0 : state = ON_SECOND;
378 0 : state_start_index = index + 1;
379 0 : if (aws_isdigit(c)) {
380 0 : state_start_index = index;
381 0 : advance = false;
382 0 : } else if (c != ':') {
383 0 : error = true;
384 0 : }
385 0 : } else if (aws_isdigit(c)) {
386 0 : parsed_time->tm_min = parsed_time->tm_min * 10 + (c - '0');
387 0 : } else {
388 0 : error = true;
389 0 : }
390 0 :
391 0 : break;
392 0 : case ON_SECOND:
393 0 : if (c == 'Z' && index - state_start_index == 2) {
394 0 : state = FINISHED;
395 0 : state_start_index = index + 1;
396 0 : } else if (c == '.' && index - state_start_index == 2) {
397 0 : state = ON_TZ;
398 0 : state_start_index = index + 1;
399 0 : } else if (aws_isdigit(c)) {
400 0 : parsed_time->tm_sec = parsed_time->tm_sec * 10 + (c - '0');
401 0 : } else {
402 0 : error = true;
403 0 : }
404 0 :
405 0 : break;
406 0 : case ON_TZ:
407 0 : if (c == 'Z') {
408 0 : state = FINISHED;
409 0 : state_start_index = index + 1;
410 0 : } else if (!aws_isdigit(c)) {
411 0 : error = true;
412 0 : }
413 0 : break;
414 0 : default:
415 0 : error = true;
416 0 : break;
417 0 : }
418 0 :
419 0 : if (advance) {
420 0 : index++;
421 0 : } else {
422 0 : advance = true;
423 0 : }
424 0 : }
425 0 :
426 0 : /* ISO8601 supports date only with no time portion. state ==ON_MONTH_DAY catches this case. */
427 0 : return (state == FINISHED || state == ON_MONTH_DAY) && !error ? AWS_OP_SUCCESS : AWS_OP_ERR;
428 0 : }
429 :
430 : static int s_parse_rfc_822(
431 : const struct aws_byte_cursor *date_str_cursor,
432 : struct tm *parsed_time,
433 0 : struct aws_date_time *dt) {
434 0 : size_t len = date_str_cursor->len;
435 0 :
436 0 : size_t index = 0;
437 0 : size_t state_start_index = 0;
438 0 : int state = ON_WEEKDAY;
439 0 : bool error = false;
440 0 :
441 0 : AWS_ZERO_STRUCT(*parsed_time);
442 0 :
443 0 : while (!error && index < len) {
444 0 : char c = date_str_cursor->ptr[index];
445 0 :
446 0 : switch (state) {
447 0 : /* week day abbr is optional. */
448 0 : case ON_WEEKDAY:
449 0 : if (c == ',') {
450 0 : state = ON_SPACE_DELIM;
451 0 : state_start_index = index + 1;
452 0 : } else if (aws_isdigit(c)) {
453 0 : state = ON_MONTH_DAY;
454 0 : } else if (!aws_isalpha(c)) {
455 0 : error = true;
456 0 : }
457 0 : break;
458 0 : case ON_SPACE_DELIM:
459 0 : if (aws_isspace(c)) {
460 0 : state = ON_MONTH_DAY;
461 0 : state_start_index = index + 1;
462 0 : } else {
463 0 : error = true;
464 0 : }
465 0 : break;
466 0 : case ON_MONTH_DAY:
467 0 : if (aws_isdigit(c)) {
468 0 : parsed_time->tm_mday = parsed_time->tm_mday * 10 + (c - '0');
469 0 : } else if (aws_isspace(c)) {
470 0 : state = ON_MONTH;
471 0 : state_start_index = index + 1;
472 0 : } else {
473 0 : error = true;
474 0 : }
475 0 : break;
476 0 : case ON_MONTH:
477 0 : if (aws_isspace(c)) {
478 0 : int monthNumber =
479 0 : get_month_number_from_str((const char *)date_str_cursor->ptr, state_start_index, index + 1);
480 0 :
481 0 : if (monthNumber > -1) {
482 0 : state = ON_YEAR;
483 0 : state_start_index = index + 1;
484 0 : parsed_time->tm_mon = monthNumber;
485 0 : } else {
486 0 : error = true;
487 0 : }
488 0 : } else if (!aws_isalpha(c)) {
489 0 : error = true;
490 0 : }
491 0 : break;
492 0 : /* year can be 4 or 2 digits. */
493 0 : case ON_YEAR:
494 0 : if (aws_isspace(c) && index - state_start_index == 4) {
495 0 : state = ON_HOUR;
496 0 : state_start_index = index + 1;
497 0 : parsed_time->tm_year -= 1900;
498 0 : } else if (aws_isspace(c) && index - state_start_index == 2) {
499 0 : state = 5;
500 0 : state_start_index = index + 1;
501 0 : parsed_time->tm_year += 2000 - 1900;
502 0 : } else if (aws_isdigit(c)) {
503 0 : parsed_time->tm_year = parsed_time->tm_year * 10 + (c - '0');
504 0 : } else {
505 0 : error = true;
506 0 : }
507 0 : break;
508 0 : case ON_HOUR:
509 0 : if (c == ':' && index - state_start_index == 2) {
510 0 : state = ON_MINUTE;
511 0 : state_start_index = index + 1;
512 0 : } else if (aws_isdigit(c)) {
513 0 : parsed_time->tm_hour = parsed_time->tm_hour * 10 + (c - '0');
514 0 : } else {
515 0 : error = true;
516 0 : }
517 0 : break;
518 0 : case ON_MINUTE:
519 0 : if (c == ':' && index - state_start_index == 2) {
520 0 : state = ON_SECOND;
521 0 : state_start_index = index + 1;
522 0 : } else if (aws_isdigit(c)) {
523 0 : parsed_time->tm_min = parsed_time->tm_min * 10 + (c - '0');
524 0 : } else {
525 0 : error = true;
526 0 : }
527 0 : break;
528 0 : case ON_SECOND:
529 0 : if (aws_isspace(c) && index - state_start_index == 2) {
530 0 : state = ON_TZ;
531 0 : state_start_index = index + 1;
532 0 : } else if (aws_isdigit(c)) {
533 0 : parsed_time->tm_sec = parsed_time->tm_sec * 10 + (c - '0');
534 0 : } else {
535 0 : error = true;
536 0 : }
537 0 : break;
538 0 : case ON_TZ:
539 0 : if ((aws_isalnum(c) || c == '-' || c == '+') && (index - state_start_index) < 5) {
540 0 : dt->tz[index - state_start_index] = c;
541 0 : } else {
542 0 : error = true;
543 0 : }
544 0 :
545 0 : break;
546 0 : default:
547 0 : error = true;
548 0 : break;
549 0 : }
550 0 :
551 0 : index++;
552 0 : }
553 0 :
554 0 : if (dt->tz[0] != 0) {
555 0 : if (is_utc_time_zone(dt->tz)) {
556 0 : dt->utc_assumed = true;
557 0 : } else {
558 0 : error = true;
559 0 : }
560 0 : }
561 0 :
562 0 : return error || state != ON_TZ ? AWS_OP_ERR : AWS_OP_SUCCESS;
563 0 : }
564 :
565 : int aws_date_time_init_from_str_cursor(
566 : struct aws_date_time *dt,
567 : const struct aws_byte_cursor *date_str_cursor,
568 0 : enum aws_date_format fmt) {
569 0 : AWS_ERROR_PRECONDITION(date_str_cursor->len <= AWS_DATE_TIME_STR_MAX_LEN, AWS_ERROR_OVERFLOW_DETECTED);
570 0 :
571 0 : AWS_ZERO_STRUCT(*dt);
572 0 :
573 0 : struct tm parsed_time;
574 0 : bool successfully_parsed = false;
575 0 :
576 0 : time_t seconds_offset = 0;
577 0 : if (fmt == AWS_DATE_FORMAT_ISO_8601 || fmt == AWS_DATE_FORMAT_AUTO_DETECT) {
578 0 : if (!s_parse_iso_8601(date_str_cursor, &parsed_time)) {
579 0 : dt->utc_assumed = true;
580 0 : successfully_parsed = true;
581 0 : }
582 0 : }
583 0 :
584 0 : if (fmt == AWS_DATE_FORMAT_ISO_8601_BASIC || (fmt == AWS_DATE_FORMAT_AUTO_DETECT && !successfully_parsed)) {
585 0 : if (!s_parse_iso_8601_basic(date_str_cursor, &parsed_time)) {
586 0 : dt->utc_assumed = true;
587 0 : successfully_parsed = true;
588 0 : }
589 0 : }
590 0 :
591 0 : if (fmt == AWS_DATE_FORMAT_RFC822 || (fmt == AWS_DATE_FORMAT_AUTO_DETECT && !successfully_parsed)) {
592 0 : if (!s_parse_rfc_822(date_str_cursor, &parsed_time, dt)) {
593 0 : successfully_parsed = true;
594 0 :
595 0 : if (dt->utc_assumed) {
596 0 : if (dt->tz[0] == '+' || dt->tz[0] == '-') {
597 0 : /* in this format, the offset is in format +/-HHMM so convert that to seconds and we'll use
598 0 : * the offset later. */
599 0 : char min_str[3] = {0};
600 0 : char hour_str[3] = {0};
601 0 : hour_str[0] = dt->tz[1];
602 0 : hour_str[1] = dt->tz[2];
603 0 : min_str[0] = dt->tz[3];
604 0 : min_str[1] = dt->tz[4];
605 0 :
606 0 : long hour = strtol(hour_str, NULL, 10);
607 0 : long min = strtol(min_str, NULL, 10);
608 0 : seconds_offset = (time_t)(hour * 3600 + min * 60);
609 0 :
610 0 : if (dt->tz[0] == '-') {
611 0 : seconds_offset = -seconds_offset;
612 0 : }
613 0 : }
614 0 : }
615 0 : }
616 0 : }
617 0 :
618 0 : if (!successfully_parsed) {
619 0 : return aws_raise_error(AWS_ERROR_INVALID_DATE_STR);
620 0 : }
621 0 :
622 0 : if (dt->utc_assumed || seconds_offset) {
623 0 : dt->timestamp = aws_timegm(&parsed_time);
624 0 : } else {
625 0 : dt->timestamp = mktime(&parsed_time);
626 0 : }
627 0 :
628 0 : /* negative means we need to move west (increase the timestamp), positive means head east, so decrease the
629 0 : * timestamp. */
630 0 : dt->timestamp -= seconds_offset;
631 0 :
632 0 : dt->gmt_time = s_get_time_struct(dt, false);
633 0 : dt->local_time = s_get_time_struct(dt, true);
634 0 :
635 0 : return AWS_OP_SUCCESS;
636 0 : }
637 :
638 : int aws_date_time_init_from_str(
639 : struct aws_date_time *dt,
640 : const struct aws_byte_buf *date_str,
641 0 : enum aws_date_format fmt) {
642 0 : AWS_ERROR_PRECONDITION(date_str->len <= AWS_DATE_TIME_STR_MAX_LEN, AWS_ERROR_OVERFLOW_DETECTED);
643 0 :
644 0 : struct aws_byte_cursor date_cursor = aws_byte_cursor_from_buf(date_str);
645 0 : return aws_date_time_init_from_str_cursor(dt, &date_cursor, fmt);
646 0 : }
647 :
648 0 : static inline int s_date_to_str(const struct tm *tm, const char *format_str, struct aws_byte_buf *output_buf) {
649 0 : size_t remaining_space = output_buf->capacity - output_buf->len;
650 0 : size_t bytes_written = strftime((char *)output_buf->buffer + output_buf->len, remaining_space, format_str, tm);
651 0 :
652 0 : if (bytes_written == 0) {
653 0 : return aws_raise_error(AWS_ERROR_SHORT_BUFFER);
654 0 : }
655 0 :
656 0 : output_buf->len += bytes_written;
657 0 :
658 0 : return AWS_OP_SUCCESS;
659 0 : }
660 :
661 : int aws_date_time_to_local_time_str(
662 : const struct aws_date_time *dt,
663 : enum aws_date_format fmt,
664 0 : struct aws_byte_buf *output_buf) {
665 0 : AWS_ASSERT(fmt != AWS_DATE_FORMAT_AUTO_DETECT);
666 0 :
667 0 : switch (fmt) {
668 0 : case AWS_DATE_FORMAT_RFC822:
669 0 : return s_date_to_str(&dt->local_time, RFC822_DATE_FORMAT_STR_WITH_Z, output_buf);
670 0 :
671 0 : case AWS_DATE_FORMAT_ISO_8601:
672 0 : return s_date_to_str(&dt->local_time, ISO_8601_LONG_DATE_FORMAT_STR, output_buf);
673 0 :
674 0 : case AWS_DATE_FORMAT_ISO_8601_BASIC:
675 0 : return s_date_to_str(&dt->local_time, ISO_8601_LONG_BASIC_DATE_FORMAT_STR, output_buf);
676 0 :
677 0 : default:
678 0 : return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
679 0 : }
680 0 : }
681 :
682 : int aws_date_time_to_utc_time_str(
683 : const struct aws_date_time *dt,
684 : enum aws_date_format fmt,
685 0 : struct aws_byte_buf *output_buf) {
686 0 : AWS_ASSERT(fmt != AWS_DATE_FORMAT_AUTO_DETECT);
687 0 :
688 0 : switch (fmt) {
689 0 : case AWS_DATE_FORMAT_RFC822:
690 0 : return s_date_to_str(&dt->gmt_time, RFC822_DATE_FORMAT_STR_MINUS_Z, output_buf);
691 0 :
692 0 : case AWS_DATE_FORMAT_ISO_8601:
693 0 : return s_date_to_str(&dt->gmt_time, ISO_8601_LONG_DATE_FORMAT_STR, output_buf);
694 0 :
695 0 : case AWS_DATE_FORMAT_ISO_8601_BASIC:
696 0 : return s_date_to_str(&dt->gmt_time, ISO_8601_LONG_BASIC_DATE_FORMAT_STR, output_buf);
697 0 :
698 0 : default:
699 0 : return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
700 0 : }
701 0 : }
702 :
703 : int aws_date_time_to_local_time_short_str(
704 : const struct aws_date_time *dt,
705 : enum aws_date_format fmt,
706 0 : struct aws_byte_buf *output_buf) {
707 0 : AWS_ASSERT(fmt != AWS_DATE_FORMAT_AUTO_DETECT);
708 0 :
709 0 : switch (fmt) {
710 0 : case AWS_DATE_FORMAT_RFC822:
711 0 : return s_date_to_str(&dt->local_time, RFC822_SHORT_DATE_FORMAT_STR, output_buf);
712 0 :
713 0 : case AWS_DATE_FORMAT_ISO_8601:
714 0 : return s_date_to_str(&dt->local_time, ISO_8601_SHORT_DATE_FORMAT_STR, output_buf);
715 0 :
716 0 : case AWS_DATE_FORMAT_ISO_8601_BASIC:
717 0 : return s_date_to_str(&dt->local_time, ISO_8601_SHORT_BASIC_DATE_FORMAT_STR, output_buf);
718 0 :
719 0 : default:
720 0 : return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
721 0 : }
722 0 : }
723 :
724 : int aws_date_time_to_utc_time_short_str(
725 : const struct aws_date_time *dt,
726 : enum aws_date_format fmt,
727 0 : struct aws_byte_buf *output_buf) {
728 0 : AWS_ASSERT(fmt != AWS_DATE_FORMAT_AUTO_DETECT);
729 0 :
730 0 : switch (fmt) {
731 0 : case AWS_DATE_FORMAT_RFC822:
732 0 : return s_date_to_str(&dt->gmt_time, RFC822_SHORT_DATE_FORMAT_STR, output_buf);
733 0 :
734 0 : case AWS_DATE_FORMAT_ISO_8601:
735 0 : return s_date_to_str(&dt->gmt_time, ISO_8601_SHORT_DATE_FORMAT_STR, output_buf);
736 0 :
737 0 : case AWS_DATE_FORMAT_ISO_8601_BASIC:
738 0 : return s_date_to_str(&dt->gmt_time, ISO_8601_SHORT_BASIC_DATE_FORMAT_STR, output_buf);
739 0 :
740 0 : default:
741 0 : return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
742 0 : }
743 0 : }
744 :
745 0 : double aws_date_time_as_epoch_secs(const struct aws_date_time *dt) {
746 0 : return (double)dt->timestamp;
747 0 : }
748 :
749 0 : uint64_t aws_date_time_as_nanos(const struct aws_date_time *dt) {
750 0 : return (uint64_t)dt->timestamp * AWS_TIMESTAMP_NANOS;
751 0 : }
752 :
753 0 : uint64_t aws_date_time_as_millis(const struct aws_date_time *dt) {
754 0 : return (uint64_t)dt->timestamp * AWS_TIMESTAMP_MILLIS;
755 0 : }
756 :
757 0 : uint16_t aws_date_time_year(const struct aws_date_time *dt, bool local_time) {
758 0 : const struct tm *time = local_time ? &dt->local_time : &dt->gmt_time;
759 0 :
760 0 : return (uint16_t)(time->tm_year + 1900);
761 0 : }
762 :
763 0 : enum aws_date_month aws_date_time_month(const struct aws_date_time *dt, bool local_time) {
764 0 : const struct tm *time = local_time ? &dt->local_time : &dt->gmt_time;
765 0 :
766 0 : return time->tm_mon;
767 0 : }
768 :
769 0 : uint8_t aws_date_time_month_day(const struct aws_date_time *dt, bool local_time) {
770 0 : const struct tm *time = local_time ? &dt->local_time : &dt->gmt_time;
771 0 :
772 0 : return (uint8_t)time->tm_mday;
773 0 : }
774 :
775 0 : enum aws_date_day_of_week aws_date_time_day_of_week(const struct aws_date_time *dt, bool local_time) {
776 0 : const struct tm *time = local_time ? &dt->local_time : &dt->gmt_time;
777 0 :
778 0 : return time->tm_wday;
779 0 : }
780 :
781 0 : uint8_t aws_date_time_hour(const struct aws_date_time *dt, bool local_time) {
782 0 : const struct tm *time = local_time ? &dt->local_time : &dt->gmt_time;
783 0 :
784 0 : return (uint8_t)time->tm_hour;
785 0 : }
786 :
787 0 : uint8_t aws_date_time_minute(const struct aws_date_time *dt, bool local_time) {
788 0 : const struct tm *time = local_time ? &dt->local_time : &dt->gmt_time;
789 0 :
790 0 : return (uint8_t)time->tm_min;
791 0 : }
792 :
793 0 : uint8_t aws_date_time_second(const struct aws_date_time *dt, bool local_time) {
794 0 : const struct tm *time = local_time ? &dt->local_time : &dt->gmt_time;
795 0 :
796 0 : return (uint8_t)time->tm_sec;
797 0 : }
798 :
799 0 : bool aws_date_time_dst(const struct aws_date_time *dt, bool local_time) {
800 0 : const struct tm *time = local_time ? &dt->local_time : &dt->gmt_time;
801 0 :
802 0 : return (bool)time->tm_isdst;
803 0 : }
804 :
805 0 : time_t aws_date_time_diff(const struct aws_date_time *a, const struct aws_date_time *b) {
806 0 : return a->timestamp - b->timestamp;
807 0 : }
|