diff --git a/moto/core/utils.py b/moto/core/utils.py index 8f3ddfce0..8e817dda2 100644 --- a/moto/core/utils.py +++ b/moto/core/utils.py @@ -170,10 +170,36 @@ def iso_8601_datetime_without_milliseconds_s3( RFC1123 = "%a, %d %b %Y %H:%M:%S GMT" +EN_WEEKDAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] +EN_MONTHS = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +] def rfc_1123_datetime(src: datetime.datetime) -> str: - return src.strftime(RFC1123) + """ + Returns the datetime in the RFC-1123 format + Names of weekdays/months are in English + """ + # strftime uses the current locale to translate values + # So weekday/month values may not be in English + # For our usecase, we need these values to always be in English, otherwise botocore is unable to parse it + # Having a hardcoded list ensures this always works, even if the user does not have an English locale installed + eng_weekday = EN_WEEKDAYS[src.isoweekday() - 1] + eng_month = EN_MONTHS[src.month - 1] + non_locallized_rfc1123 = RFC1123.replace("%a", "{}").replace("%b", "{}") + return src.strftime(non_locallized_rfc1123).format(eng_weekday, eng_month) def str_to_rfc_1123_datetime(value: str) -> datetime.datetime: diff --git a/tests/test_core/test_utils.py b/tests/test_core/test_utils.py index cc2e86749..7a8915b91 100644 --- a/tests/test_core/test_utils.py +++ b/tests/test_core/test_utils.py @@ -1,3 +1,5 @@ +import datetime + import pytest from freezegun import freeze_time @@ -5,6 +7,8 @@ from moto.core.utils import ( camelcase_to_pascal, camelcase_to_underscores, pascal_to_camelcase, + rfc_1123_datetime, + str_to_rfc_1123_datetime, underscores_to_camelcase, unix_time, ) @@ -50,3 +54,10 @@ def test_camelcase_to_pascal(_input: str, expected: str) -> None: @freeze_time("2015-01-01 12:00:00") def test_unix_time() -> None: # type: ignore[misc] assert unix_time() == 1420113600.0 + + +def test_rfc1123_dates() -> None: + with freeze_time("2012-03-04 05:00:05"): + x = rfc_1123_datetime(datetime.datetime.now()) + assert x == "Sun, 04 Mar 2012 05:00:05 GMT" + assert str_to_rfc_1123_datetime(x) == datetime.datetime.now() diff --git a/tests/test_s3/test_s3_locales.py b/tests/test_s3/test_s3_locales.py new file mode 100644 index 000000000..06aca5c36 --- /dev/null +++ b/tests/test_s3/test_s3_locales.py @@ -0,0 +1,30 @@ +from datetime import datetime +from unittest import SkipTest + +import boto3 +from freezegun import freeze_time + +from moto import mock_s3, settings + + +@mock_s3 +def test_rfc_returns_valid_date_for_every_possible_weekday_and_month(): + if not settings.TEST_DECORATOR_MODE: + raise SkipTest("Freezing time only possible in DecoratorMode") + client = boto3.client("s3", region_name="us-east-1") + client.create_bucket(Bucket="bucket_") + for weekday in range(1, 8): + with freeze_time(f"2023-02-{weekday} 12:00:00"): + client.put_object(Bucket="bucket_", Key="test.txt", Body=b"test") + obj = client.get_object(Bucket="bucket_", Key="test.txt") + assert obj["LastModified"].replace(tzinfo=None) == datetime.now() + # If we get here, the LastModified date will have been successfully parsed + # Regardless of which weekday it is + + for month in range(1, 13): + with freeze_time(f"2023-{month}-02 12:00:00"): + client.put_object(Bucket="bucket_", Key="test.txt", Body=b"test") + obj = client.get_object(Bucket="bucket_", Key="test.txt") + assert obj["LastModified"].replace(tzinfo=None) == datetime.now() + # If we get here, the LastModified date will have been successfully parsed + # Regardless of which month it is