diff --git a/airbyte_cdk/sources/declarative/datetime/datetime_parser.py b/airbyte_cdk/sources/declarative/datetime/datetime_parser.py index 93122e29c..130406fcc 100644 --- a/airbyte_cdk/sources/declarative/datetime/datetime_parser.py +++ b/airbyte_cdk/sources/declarative/datetime/datetime_parser.py @@ -31,7 +31,8 @@ def parse(self, date: Union[str, int], format: str) -> datetime.datetime: return datetime.datetime.fromtimestamp(float(date), tz=datetime.timezone.utc) elif format == "%ms": return self._UNIX_EPOCH + datetime.timedelta(milliseconds=int(date)) - + elif "%_ms" in format: + format = format.replace("%_ms", "%f") parsed_datetime = datetime.datetime.strptime(str(date), format) if self._is_naive(parsed_datetime): return parsed_datetime.replace(tzinfo=datetime.timezone.utc) @@ -48,6 +49,11 @@ def format(self, dt: datetime.datetime, format: str) -> str: if format == "%ms": # timstamp() returns a float representing the number of seconds since the unix epoch return str(int(dt.timestamp() * 1000)) + if "%_ms" in format: + _format = format.replace("%_ms", "%f") + milliseconds = int(dt.microsecond / 1000) + formatted_dt = dt.strftime(_format).replace(dt.strftime("%f"), "%03d" % milliseconds) + return formatted_dt else: return dt.strftime(format) diff --git a/airbyte_cdk/sources/declarative/declarative_component_schema.yaml b/airbyte_cdk/sources/declarative/declarative_component_schema.yaml index c664e237a..35244aedc 100644 --- a/airbyte_cdk/sources/declarative/declarative_component_schema.yaml +++ b/airbyte_cdk/sources/declarative/declarative_component_schema.yaml @@ -844,6 +844,7 @@ definitions: * **%M**: Minute (zero-padded) - `00`, `01`, ..., `59` * **%S**: Second (zero-padded) - `00`, `01`, ..., `59` * **%f**: Microsecond (zero-padded to 6 digits) - `000000` + * **%_ms**: Millisecond (zero-padded to 3 digits) - `000` * **%z**: UTC offset - `(empty)`, `+0000`, `-04:00` * **%Z**: Time zone name - `(empty)`, `UTC`, `GMT` * **%j**: Day of the year (zero-padded) - `001`, `002`, ..., `366` @@ -2401,6 +2402,7 @@ definitions: * **%M**: Minute (zero-padded) - `00`, `01`, ..., `59` * **%S**: Second (zero-padded) - `00`, `01`, ..., `59` * **%f**: Microsecond (zero-padded to 6 digits) - `000000`, `000001`, ..., `999999` + * **%_ms**: Millisecond (zero-padded to 3 digits) - `000`, `001`, ..., `999` * **%z**: UTC offset - `(empty)`, `+0000`, `-04:00` * **%Z**: Time zone name - `(empty)`, `UTC`, `GMT` * **%j**: Day of the year (zero-padded) - `001`, `002`, ..., `366` diff --git a/unit_tests/sources/declarative/datetime/test_datetime_parser.py b/unit_tests/sources/declarative/datetime/test_datetime_parser.py index 7a1ba951b..640abd6c2 100644 --- a/unit_tests/sources/declarative/datetime/test_datetime_parser.py +++ b/unit_tests/sources/declarative/datetime/test_datetime_parser.py @@ -50,6 +50,12 @@ "%Y%m%d", datetime.datetime(2021, 1, 1, 0, 0, tzinfo=datetime.timezone.utc), ), + ( + "test_parse_format_datetime_with__ms", + "2021-11-22T08:41:55.640Z", + "%Y-%m-%dT%H:%M:%S.%_msZ", + datetime.datetime(2021, 11, 22, 8, 41, 55, 640000, tzinfo=datetime.timezone.utc), + ), ], ) def test_parse_date(test_name, input_date, date_format, expected_output_date): @@ -91,6 +97,12 @@ def test_parse_date(test_name, input_date, date_format, expected_output_date): "%Y%m%d", "20210101", ), + ( + "test_parse_format_datetime_with__ms", + datetime.datetime(2021, 11, 22, 8, 41, 55, 640000, tzinfo=datetime.timezone.utc), + "%Y-%m-%dT%H:%M:%S.%_msZ", + "2021-11-22T08:41:55.640Z", + ), ], ) def test_format_datetime(test_name, input_dt, datetimeformat, expected_output):