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