From fcadc0dd5d8a26134c8bbf08c58e30eff50d177b Mon Sep 17 00:00:00 2001 From: Michael Graeb Date: Tue, 5 Mar 2024 09:16:27 -0800 Subject: [PATCH] Accept all RFC3339-compliant timestamps (#1093) **Issue:** `aws_date_time` did not accept "2024-02-23 23:06:27+00:00", despite that being a valid RFC 3339 timestamp. **Background:** - [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601): is a standard for date and time, but has a lot of options in it. - `aws_date_time` has enums for ISO_8601, but does not claim to support every single option - One option it did support though, was Basic vs Extended format. - Basic: no separators between "YYYYMMDD" and "HHMMSS" - Extended: has separators between "YYYY-MM-DD" and "HH-MM-SS" - [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339): details a subset of ISO 8601 for use in internet timestamps - [Smithy 2.0](https://smithy.io/2.0/spec/protocol-traits.html#timestamp-formats) says that timestamps should use RFC 3339 - `aws_date_time` does not fully support RFC 3339. The unsupported features in the string above were: 1) Space instead of "T" between date and time 2) "+00:00" offset instead of "Z" after time - I personally found the [parsing code for ISO_8601](https://github.com/awslabs/aws-c-common/blob/15a25349d59852e2655c0920835644f2eb948d77/source/date_time.c#L326) hard to follow, and the [ISO_8601_BASIC code](https://github.com/awslabs/aws-c-common/blob/15a25349d59852e2655c0920835644f2eb948d77/source/date_time.c#L221) was a big copy/paste of that. **Description of changes:** - The code is rewritten to be simpler. It is no longer a state machine. - Combine parsing code for `ISO_8601` and `ISO_8601_BASIC` into 1 function. - The parse is lenient now, accepting Basic or Extended timestamps, regardless of whether the `ISO_8601` or `ISO_8601_BASIC` enum is passed in. - Our `ISO_8601` code already allowed separators to be omitted from time, which shows evidence that lenience is good. - This matches the leniency of Python's [dateutil.parser.isoparse(str)](https://dateutil.readthedocs.io/en/stable/parser.html) which allows "2024-02-23T23:06:27Z" or "20240223T230627Z" or "2024-02-23T230627Z" or "20240223T23:06:27Z" - Allow space instead of "T" - Allows lowercase "t" and "z" - Support offsets like "+12:34", instead of just "Z" - Allow ",123" for fractional seconds, not just ".123" - The Basic parse code had a bug where did not expect any characters between seconds and fractional-seconds. This is wrong. Python's dateutil rejects this. --- include/aws/common/date_time.h | 7 +- source/date_time.c | 424 +++++++++++++++------------------ tests/CMakeLists.txt | 3 +- tests/date_time_test.c | 113 ++++----- 4 files changed, 243 insertions(+), 304 deletions(-) diff --git a/include/aws/common/date_time.h b/include/aws/common/date_time.h index 7e13740bd..72ca09a86 100644 --- a/include/aws/common/date_time.h +++ b/include/aws/common/date_time.h @@ -80,11 +80,14 @@ AWS_COMMON_API void aws_date_time_init_epoch_secs(struct aws_date_time *dt, doub * Initializes dt to be the time represented by date_str in format 'fmt'. Returns AWS_OP_SUCCESS if the * string was successfully parsed, returns AWS_OP_ERR if parsing failed. * + * The parser is lenient regarding AWS_DATE_FORMAT_ISO_8601 vs AWS_DATE_FORMAT_ISO_8601_BASIC. + * Regardless of which you pass in, both "2002-10-02T08:05:09Z" and "20021002T080509Z" would be accepted. + * * Notes for AWS_DATE_FORMAT_RFC822: * If no time zone information is provided, it is assumed to be local time (please don't do this). * - * If the time zone is something other than something indicating Universal Time (e.g. Z, UT, UTC, or GMT) or an offset - * from UTC (e.g. +0100, -0700), parsing will fail. + * Only time zones indicating Universal Time (e.g. Z, UT, UTC, or GMT), + * or offsets from UTC (e.g. +0100, -0700), are accepted. * * Really, it's just better if you always use Universal Time. */ diff --git a/source/date_time.c b/source/date_time.c index 49793074c..0c1869bce 100644 --- a/source/date_time.c +++ b/source/date_time.c @@ -131,7 +131,7 @@ static bool is_utc_time_zone(const char *str) { size_t len = strlen(str); if (len > 0) { - if (str[0] == 'Z') { + if (tolower((uint8_t)str[0]) == 'z') { return true; } @@ -207,232 +207,7 @@ enum parser_state { FINISHED, }; -static int s_parse_iso_8601_basic(const struct aws_byte_cursor *date_str_cursor, struct tm *parsed_time) { - size_t index = 0; - size_t state_start_index = 0; - enum parser_state state = ON_YEAR; - bool error = false; - - AWS_ZERO_STRUCT(*parsed_time); - - while (state < FINISHED && !error && index < date_str_cursor->len) { - char c = (char)date_str_cursor->ptr[index]; - size_t sub_index = index - state_start_index; - switch (state) { - case ON_YEAR: - if (aws_isdigit(c)) { - parsed_time->tm_year = parsed_time->tm_year * 10 + (c - '0'); - if (sub_index == 3) { - state = ON_MONTH; - state_start_index = index + 1; - parsed_time->tm_year -= 1900; - } - } else { - error = true; - } - break; - - case ON_MONTH: - if (aws_isdigit(c)) { - parsed_time->tm_mon = parsed_time->tm_mon * 10 + (c - '0'); - if (sub_index == 1) { - state = ON_MONTH_DAY; - state_start_index = index + 1; - parsed_time->tm_mon -= 1; - } - } else { - error = true; - } - break; - - case ON_MONTH_DAY: - if (c == 'T' && sub_index == 2) { - state = ON_HOUR; - state_start_index = index + 1; - } else if (aws_isdigit(c)) { - parsed_time->tm_mday = parsed_time->tm_mday * 10 + (c - '0'); - } else { - error = true; - } - break; - - case ON_HOUR: - if (aws_isdigit(c)) { - parsed_time->tm_hour = parsed_time->tm_hour * 10 + (c - '0'); - if (sub_index == 1) { - state = ON_MINUTE; - state_start_index = index + 1; - } - } else { - error = true; - } - break; - - case ON_MINUTE: - if (aws_isdigit(c)) { - parsed_time->tm_min = parsed_time->tm_min * 10 + (c - '0'); - if (sub_index == 1) { - state = ON_SECOND; - state_start_index = index + 1; - } - } else { - error = true; - } - break; - - case ON_SECOND: - if (aws_isdigit(c)) { - parsed_time->tm_sec = parsed_time->tm_sec * 10 + (c - '0'); - if (sub_index == 1) { - state = ON_TZ; - state_start_index = index + 1; - } - } else { - error = true; - } - break; - - case ON_TZ: - if (c == 'Z' && (sub_index == 0 || sub_index == 3)) { - state = FINISHED; - } else if (!aws_isdigit(c) || sub_index > 3) { - error = true; - } - break; - - default: - error = true; - break; - } - - index++; - } - - /* ISO8601 supports date only with no time portion. state ==ON_MONTH_DAY catches this case. */ - return (state == FINISHED || state == ON_MONTH_DAY) && !error ? AWS_OP_SUCCESS : AWS_OP_ERR; -} - -static int s_parse_iso_8601(const struct aws_byte_cursor *date_str_cursor, struct tm *parsed_time) { - size_t index = 0; - size_t state_start_index = 0; - enum parser_state state = ON_YEAR; - bool error = false; - bool advance = true; - - AWS_ZERO_STRUCT(*parsed_time); - - while (state < FINISHED && !error && index < date_str_cursor->len) { - char c = (char)date_str_cursor->ptr[index]; - switch (state) { - case ON_YEAR: - if (c == '-' && index - state_start_index == 4) { - state = ON_MONTH; - state_start_index = index + 1; - parsed_time->tm_year -= 1900; - } else if (aws_isdigit(c)) { - parsed_time->tm_year = parsed_time->tm_year * 10 + (c - '0'); - } else { - error = true; - } - break; - case ON_MONTH: - if (c == '-' && index - state_start_index == 2) { - state = ON_MONTH_DAY; - state_start_index = index + 1; - parsed_time->tm_mon -= 1; - } else if (aws_isdigit(c)) { - parsed_time->tm_mon = parsed_time->tm_mon * 10 + (c - '0'); - } else { - error = true; - } - - break; - case ON_MONTH_DAY: - if (c == 'T' && index - state_start_index == 2) { - state = ON_HOUR; - state_start_index = index + 1; - } else if (aws_isdigit(c)) { - parsed_time->tm_mday = parsed_time->tm_mday * 10 + (c - '0'); - } else { - error = true; - } - break; - /* note: no time portion is spec compliant. */ - case ON_HOUR: - /* time parts can be delimited by ':' or just concatenated together, but must always be 2 digits. */ - if (index - state_start_index == 2) { - state = ON_MINUTE; - state_start_index = index + 1; - if (aws_isdigit(c)) { - state_start_index = index; - advance = false; - } else if (c != ':') { - error = true; - } - } else if (aws_isdigit(c)) { - parsed_time->tm_hour = parsed_time->tm_hour * 10 + (c - '0'); - } else { - error = true; - } - - break; - case ON_MINUTE: - /* time parts can be delimited by ':' or just concatenated together, but must always be 2 digits. */ - if (index - state_start_index == 2) { - state = ON_SECOND; - state_start_index = index + 1; - if (aws_isdigit(c)) { - state_start_index = index; - advance = false; - } else if (c != ':') { - error = true; - } - } else if (aws_isdigit(c)) { - parsed_time->tm_min = parsed_time->tm_min * 10 + (c - '0'); - } else { - error = true; - } - - break; - case ON_SECOND: - if (c == 'Z' && index - state_start_index == 2) { - state = FINISHED; - state_start_index = index + 1; - } else if (c == '.' && index - state_start_index == 2) { - state = ON_TZ; - state_start_index = index + 1; - } else if (aws_isdigit(c)) { - parsed_time->tm_sec = parsed_time->tm_sec * 10 + (c - '0'); - } else { - error = true; - } - - break; - case ON_TZ: - if (c == 'Z') { - state = FINISHED; - state_start_index = index + 1; - } else if (!aws_isdigit(c)) { - error = true; - } - break; - default: - error = true; - break; - } - - if (advance) { - index++; - } else { - advance = true; - } - } - - /* ISO8601 supports date only with no time portion. state ==ON_MONTH_DAY catches this case. */ - return (state == FINISHED || state == ON_MONTH_DAY) && !error ? AWS_OP_SUCCESS : AWS_OP_ERR; -} - -static int s_parse_rfc_822( +static bool s_parse_rfc_822( const struct aws_byte_cursor *date_str_cursor, struct tm *parsed_time, struct aws_date_time *dt) { @@ -564,7 +339,186 @@ static int s_parse_rfc_822( } } - return error || state != ON_TZ ? AWS_OP_ERR : AWS_OP_SUCCESS; + return error || state != ON_TZ ? false : true; +} + +/* Returns true if the next N characters are digits, advancing the string and getting their numeric value */ +static bool s_read_n_digits(struct aws_byte_cursor *str, size_t n, int *out_val) { + int val = 0; + if (str->len < n) { + return false; + } + + for (size_t i = 0; i < n; ++i) { + uint8_t c = str->ptr[i]; + if (aws_isdigit(c)) { + val = val * 10 + (c - '0'); + } else { + return false; + } + } + + aws_byte_cursor_advance(str, n); + *out_val = val; + return true; +} + +/* Returns true if there's 1 more character, advancing the string and getting the character's value. */ +static bool s_read_1_char(struct aws_byte_cursor *str, uint8_t *out_c) { + if (str->len == 0) { + return false; + } + + *out_c = str->ptr[0]; + aws_byte_cursor_advance(str, 1); + return true; +} + +/* Returns true (and advances str) if next character is c */ +static bool s_advance_if_next_char_is(struct aws_byte_cursor *str, uint8_t c) { + if (str->len == 0 || str->ptr[0] != c) { + return false; + } + + aws_byte_cursor_advance(str, 1); + return true; +} + +/* If the (optional) fractional seconds (".123" or ",123") are next, str is advanced. + * Returns false if there was an error */ +static bool s_skip_optional_fractional_seconds(struct aws_byte_cursor *str) { + if (str->len == 0) { + return true; + } + + uint8_t c = str->ptr[0]; + if (c != '.' && c != ',') { + return true; + } + + size_t num_digits = 0; + for (size_t i = 1; i < str->len; ++i) { + if (aws_isdigit(str->ptr[i])) { + ++num_digits; + } else { + break; + } + } + + if (num_digits == 0) { + return false; + } + + aws_byte_cursor_advance(str, 1 + num_digits); + return true; +} + +/* Parses ISO 8601, both extended and basic format are accepted. + * Returns true if successful. */ +static bool s_parse_iso_8601(struct aws_byte_cursor str, struct tm *parsed_time, time_t *seconds_offset) { + AWS_ZERO_STRUCT(*parsed_time); + *seconds_offset = 0; + uint8_t c = 0; + + /* read year */ + if (!s_read_n_digits(&str, 4, &parsed_time->tm_year)) { + return false; + } + parsed_time->tm_year -= 1900; + + /* be lenient, allow date with separator or not */ + bool has_date_separator = s_advance_if_next_char_is(&str, '-'); + + /* read month */ + if (!s_read_n_digits(&str, 2, &parsed_time->tm_mon)) { + return false; + } + parsed_time->tm_mon -= 1; + + if (has_date_separator) { + if (!s_read_1_char(&str, &c) || c != '-') { + return false; + } + } + + /* read month-day */ + if (!s_read_n_digits(&str, 2, &parsed_time->tm_mday)) { + return false; + } + + /* ISO8601 supports date only with no time portion */ + if (str.len == 0) { + return true; + } + + /* followed by T or space (allowed by rfc3339#section-5.6) */ + if (!s_read_1_char(&str, &c) || (tolower(c) != 't' && c != ' ')) { + return false; + } + + /* read hours */ + if (!s_read_n_digits(&str, 2, &parsed_time->tm_hour)) { + return false; + } + + /* be lenient, allow time with separator or not */ + bool has_time_separator = s_advance_if_next_char_is(&str, ':'); + + /* read minutes */ + if (!s_read_n_digits(&str, 2, &parsed_time->tm_min)) { + return false; + } + + if (has_time_separator) { + if (!s_read_1_char(&str, &c) || c != ':') { + return false; + } + } + + /* read seconds */ + if (!s_read_n_digits(&str, 2, &parsed_time->tm_sec)) { + return false; + } + + /* fractional seconds are optional (discard value since tm struct has no corresponding field) */ + if (!s_skip_optional_fractional_seconds(&str)) { + return false; + } + + /* read final Z, or (+/-) indicating there will be an offset */ + if (!s_read_1_char(&str, &c)) { + return false; + } + + if (tolower(c) == 'z') { + /* Success! */ + return true; + } + + if (c != '+' && c != '-') { + return false; + } + + bool negative_offset = c == '-'; + + /* read hours offset */ + int hours_offset = 0; + if (!s_read_n_digits(&str, 2, &hours_offset)) { + return false; + } + + /* be lenient, allow offset with separator or not */ + s_advance_if_next_char_is(&str, ':'); + + /* read minutes offset */ + int minutes_offset = 0; + if (!s_read_n_digits(&str, 2, &minutes_offset)) { + return false; + } + + /* Success! */ + *seconds_offset = (time_t)(hours_offset * 3600 + minutes_offset * 60) * (negative_offset ? -1 : 1); + return true; } int aws_date_time_init_from_str_cursor( @@ -579,22 +533,16 @@ int aws_date_time_init_from_str_cursor( bool successfully_parsed = false; time_t seconds_offset = 0; - if (fmt == AWS_DATE_FORMAT_ISO_8601 || fmt == AWS_DATE_FORMAT_AUTO_DETECT) { - if (!s_parse_iso_8601(date_str_cursor, &parsed_time)) { - dt->utc_assumed = true; - successfully_parsed = true; - } - } - - if (fmt == AWS_DATE_FORMAT_ISO_8601_BASIC || (fmt == AWS_DATE_FORMAT_AUTO_DETECT && !successfully_parsed)) { - if (!s_parse_iso_8601_basic(date_str_cursor, &parsed_time)) { + if (fmt == AWS_DATE_FORMAT_ISO_8601 || fmt == AWS_DATE_FORMAT_ISO_8601_BASIC || + fmt == AWS_DATE_FORMAT_AUTO_DETECT) { + if (s_parse_iso_8601(*date_str_cursor, &parsed_time, &seconds_offset)) { dt->utc_assumed = true; successfully_parsed = true; } } if (fmt == AWS_DATE_FORMAT_RFC822 || (fmt == AWS_DATE_FORMAT_AUTO_DETECT && !successfully_parsed)) { - if (!s_parse_rfc_822(date_str_cursor, &parsed_time, dt)) { + if (s_parse_rfc_822(date_str_cursor, &parsed_time, dt)) { successfully_parsed = true; if (dt->utc_assumed) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 331e0504c..391f98fac 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -360,13 +360,12 @@ add_test_case(rfc822_utc_dos_prevented) add_test_case(rfc822_invalid_format) add_test_case(rfc822_invalid_tz) add_test_case(rfc822_invalid_auto_format) -add_test_case(iso8601_utc_parsing) +add_test_case(iso8601_parsing) add_test_case(iso8601_basic_utc_parsing) add_test_case(iso8601_utc_parsing_auto_detect) add_test_case(iso8601_basic_utc_parsing_auto_detect) add_test_case(iso8601_date_only_parsing) add_test_case(iso8601_basic_date_only_parsing) -add_test_case(iso8601_utc_no_colon_parsing) add_test_case(iso8601_utc_dos_prevented) add_test_case(iso8601_invalid_format) add_test_case(iso8601_invalid_auto_format) diff --git a/tests/date_time_test.c b/tests/date_time_test.c index 8272a4fd5..69f0417f0 100644 --- a/tests/date_time_test.c +++ b/tests/date_time_test.c @@ -19,7 +19,7 @@ static int s_test_rfc822_utc_parsing_fn(struct aws_allocator *allocator, void *c "Wed, 02 Oct 2002 08:05:09 UTC", }; - for (size_t i = 0; i < 4; ++i) { + for (size_t i = 0; i < AWS_ARRAY_SIZE(valid_utc_dates); ++i) { struct aws_date_time date_time; const char *date_str = valid_utc_dates[i]; struct aws_byte_buf date_buf = aws_byte_buf_from_c_str(date_str); @@ -284,52 +284,73 @@ static int s_test_rfc822_invalid_auto_format_fn(struct aws_allocator *allocator, AWS_TEST_CASE(rfc822_invalid_auto_format, s_test_rfc822_invalid_auto_format_fn) -static int s_test_iso8601_utc_parsing_fn(struct aws_allocator *allocator, void *ctx) { +static int s_test_iso8601_parsing_fn(struct aws_allocator *allocator, void *ctx) { (void)allocator; (void)ctx; - struct aws_date_time date_time; - const char *date_str = "2002-10-02T08:05:09.000Z"; - struct aws_byte_buf date_buf = aws_byte_buf_from_c_str(date_str); + /* array of {"date", "description"} */ + const char *valid_dates[][2] = { + {"2002-10-02T08:05:09.000Z", "extended format"}, + {"2002-10-02t08:05:09.000z", "lowercase T and Z"}, + {"2002-10-02 08:05:09Z", "space instead of T"}, /*allowed per NOTE in RFC3339#section-5.6 */ + {"2002-10-02T08:05:09+00:00", "offset instead of Z"}, + {"2002-10-01T19:31:09-12:34", "western offset"}, + {"2002-10-02T20:39:09+12:34", "eastern offset"}, + {"2002-10-02T08:05:09.000+00:00", "fractional seconds and offset"}, + {"20021002T080509.000Z", "basic format"}, + {"20021002T203909+1234", "basic format with fractional seconds"}, + {"2002-10-02T080509.000Z", "weird mix of extended date but basic time"}, + {"20021002T08:05:09.000Z", "weird mix of basic date but extended time"}, + {"2002-10-02T20:39:09+1234", "weird mix of extended date and time but basic fractional seconds"}, + }; - ASSERT_SUCCESS(aws_date_time_init_from_str(&date_time, &date_buf, AWS_DATE_FORMAT_ISO_8601)); - ASSERT_INT_EQUALS(AWS_DATE_DAY_OF_WEEK_WEDNESDAY, aws_date_time_day_of_week(&date_time, false)); - ASSERT_UINT_EQUALS(2, aws_date_time_month_day(&date_time, false)); - ASSERT_UINT_EQUALS(AWS_DATE_MONTH_OCTOBER, aws_date_time_month(&date_time, false)); - ASSERT_UINT_EQUALS(2002, aws_date_time_year(&date_time, false)); - ASSERT_UINT_EQUALS(8, aws_date_time_hour(&date_time, false)); - ASSERT_UINT_EQUALS(5, aws_date_time_minute(&date_time, false)); - ASSERT_UINT_EQUALS(9, aws_date_time_second(&date_time, false)); + for (size_t i = 0; i < AWS_ARRAY_SIZE(valid_dates); ++i) { + const char *date_str = valid_dates[i][0]; + const char *description = valid_dates[i][1]; + printf("checking date[%zu] \"%s\" (%s)\n", i, date_str, description); - uint8_t date_output[AWS_DATE_TIME_STR_MAX_LEN]; - AWS_ZERO_ARRAY(date_output); - struct aws_byte_buf str_output = aws_byte_buf_from_array(date_output, sizeof(date_output)); - str_output.len = 0; - ASSERT_SUCCESS(aws_date_time_to_utc_time_str(&date_time, AWS_DATE_FORMAT_ISO_8601, &str_output)); + struct aws_date_time date_time; + struct aws_byte_buf date_buf = aws_byte_buf_from_c_str(date_str); - const char *expected_date_str = "2002-10-02T08:05:09Z"; - struct aws_byte_buf expected_date_buf = aws_byte_buf_from_c_str(expected_date_str); - ASSERT_BIN_ARRAYS_EQUALS(expected_date_buf.buffer, expected_date_buf.len, str_output.buffer, str_output.len); + ASSERT_SUCCESS(aws_date_time_init_from_str(&date_time, &date_buf, AWS_DATE_FORMAT_ISO_8601)); + ASSERT_INT_EQUALS(AWS_DATE_DAY_OF_WEEK_WEDNESDAY, aws_date_time_day_of_week(&date_time, false)); + ASSERT_UINT_EQUALS(2, aws_date_time_month_day(&date_time, false)); + ASSERT_UINT_EQUALS(AWS_DATE_MONTH_OCTOBER, aws_date_time_month(&date_time, false)); + ASSERT_UINT_EQUALS(2002, aws_date_time_year(&date_time, false)); + ASSERT_UINT_EQUALS(8, aws_date_time_hour(&date_time, false)); + ASSERT_UINT_EQUALS(5, aws_date_time_minute(&date_time, false)); + ASSERT_UINT_EQUALS(9, aws_date_time_second(&date_time, false)); - AWS_ZERO_ARRAY(date_output); - str_output.len = 0; - ASSERT_SUCCESS(aws_date_time_to_utc_time_short_str(&date_time, AWS_DATE_FORMAT_ISO_8601, &str_output)); + uint8_t date_output[AWS_DATE_TIME_STR_MAX_LEN]; + AWS_ZERO_ARRAY(date_output); + struct aws_byte_buf str_output = aws_byte_buf_from_array(date_output, sizeof(date_output)); + str_output.len = 0; + ASSERT_SUCCESS(aws_date_time_to_utc_time_str(&date_time, AWS_DATE_FORMAT_ISO_8601, &str_output)); - const char *expected_short_str = "2002-10-02"; - struct aws_byte_buf expected_short_buf = aws_byte_buf_from_c_str(expected_short_str); + const char *expected_date_str = "2002-10-02T08:05:09Z"; + struct aws_byte_buf expected_date_buf = aws_byte_buf_from_c_str(expected_date_str); + ASSERT_BIN_ARRAYS_EQUALS(expected_date_buf.buffer, expected_date_buf.len, str_output.buffer, str_output.len); - ASSERT_BIN_ARRAYS_EQUALS(expected_short_buf.buffer, expected_short_buf.len, str_output.buffer, str_output.len); + AWS_ZERO_ARRAY(date_output); + str_output.len = 0; + ASSERT_SUCCESS(aws_date_time_to_utc_time_short_str(&date_time, AWS_DATE_FORMAT_ISO_8601, &str_output)); + + const char *expected_short_str = "2002-10-02"; + struct aws_byte_buf expected_short_buf = aws_byte_buf_from_c_str(expected_short_str); + + ASSERT_BIN_ARRAYS_EQUALS(expected_short_buf.buffer, expected_short_buf.len, str_output.buffer, str_output.len); + } return AWS_OP_SUCCESS; } -AWS_TEST_CASE(iso8601_utc_parsing, s_test_iso8601_utc_parsing_fn) +AWS_TEST_CASE(iso8601_parsing, s_test_iso8601_parsing_fn) static int s_test_iso8601_basic_utc_parsing_fn(struct aws_allocator *allocator, void *ctx) { (void)allocator; (void)ctx; struct aws_date_time date_time; - const char *date_str = "20021002T080509000Z"; + const char *date_str = "20021002T080509.000Z"; struct aws_byte_buf date_buf = aws_byte_buf_from_c_str(date_str); ASSERT_SUCCESS(aws_date_time_init_from_str(&date_time, &date_buf, AWS_DATE_FORMAT_ISO_8601_BASIC)); @@ -401,7 +422,7 @@ static int s_test_iso8601_basic_utc_parsing_auto_detect_fn(struct aws_allocator (void)ctx; struct aws_date_time date_time; - const char *date_str = "20021002T080509000Z"; + const char *date_str = "20021002T080509,000Z"; struct aws_byte_buf date_buf = aws_byte_buf_from_c_str(date_str); ASSERT_SUCCESS(aws_date_time_init_from_str(&date_time, &date_buf, AWS_DATE_FORMAT_AUTO_DETECT)); @@ -428,38 +449,6 @@ static int s_test_iso8601_basic_utc_parsing_auto_detect_fn(struct aws_allocator AWS_TEST_CASE(iso8601_basic_utc_parsing_auto_detect, s_test_iso8601_basic_utc_parsing_auto_detect_fn) -static int s_test_iso8601_utc_no_colon_parsing_fn(struct aws_allocator *allocator, void *ctx) { - (void)allocator; - (void)ctx; - - struct aws_date_time date_time; - const char *date_str = "2002-10-02T080509.000Z"; - struct aws_byte_buf date_buf = aws_byte_buf_from_c_str(date_str); - - ASSERT_SUCCESS(aws_date_time_init_from_str(&date_time, &date_buf, AWS_DATE_FORMAT_ISO_8601)); - ASSERT_INT_EQUALS(AWS_DATE_DAY_OF_WEEK_WEDNESDAY, aws_date_time_day_of_week(&date_time, false)); - ASSERT_UINT_EQUALS(2, aws_date_time_month_day(&date_time, false)); - ASSERT_UINT_EQUALS(AWS_DATE_MONTH_OCTOBER, aws_date_time_month(&date_time, false)); - ASSERT_UINT_EQUALS(2002, aws_date_time_year(&date_time, false)); - ASSERT_UINT_EQUALS(8, aws_date_time_hour(&date_time, false)); - ASSERT_UINT_EQUALS(5, aws_date_time_minute(&date_time, false)); - ASSERT_UINT_EQUALS(9, aws_date_time_second(&date_time, false)); - - uint8_t date_output[AWS_DATE_TIME_STR_MAX_LEN]; - AWS_ZERO_ARRAY(date_output); - struct aws_byte_buf str_output = aws_byte_buf_from_array(date_output, sizeof(date_output)); - str_output.len = 0; - ASSERT_SUCCESS(aws_date_time_to_utc_time_str(&date_time, AWS_DATE_FORMAT_ISO_8601, &str_output)); - - const char *expected_date_str = "2002-10-02T08:05:09Z"; - struct aws_byte_buf expected_date_buf = aws_byte_buf_from_c_str(expected_date_str); - ASSERT_BIN_ARRAYS_EQUALS(expected_date_buf.buffer, expected_date_buf.len, str_output.buffer, str_output.len); - - return AWS_OP_SUCCESS; -} - -AWS_TEST_CASE(iso8601_utc_no_colon_parsing, s_test_iso8601_utc_no_colon_parsing_fn) - static int s_test_iso8601_date_only_parsing_fn(struct aws_allocator *allocator, void *ctx) { (void)allocator; (void)ctx;