From 6f9d5da45d58e6f8253583130506e69b36e6faf8 Mon Sep 17 00:00:00 2001 From: Anish Mehta Date: Fri, 30 Oct 2026 22:01:08 +0530 Subject: [PATCH] fix(feature_flags): match days of week regardless of locale --- .../utilities/feature_flags/comparators.py | 16 ++++++- .../_boto3/test_time_based_actions.py | 42 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/aws_lambda_powertools/utilities/feature_flags/comparators.py b/aws_lambda_powertools/utilities/feature_flags/comparators.py index 2888c1cb14b..a7f4617f71e 100644 --- a/aws_lambda_powertools/utilities/feature_flags/comparators.py +++ b/aws_lambda_powertools/utilities/feature_flags/comparators.py @@ -19,11 +19,23 @@ def _get_now_from_timezone(timezone: tzinfo | None) -> datetime: return datetime.now(timezone) +# Indexed by datetime.weekday(), where Monday is 0 +_DAYS_OF_WEEK = ( + TimeValues.MONDAY.value, + TimeValues.TUESDAY.value, + TimeValues.WEDNESDAY.value, + TimeValues.THURSDAY.value, + TimeValues.FRIDAY.value, + TimeValues.SATURDAY.value, + TimeValues.SUNDAY.value, +) + + def compare_days_of_week(context_value: Any, condition_value: dict) -> bool: timezone_name = condition_value.get(TimeValues.TIMEZONE.value, "UTC") - # %A = Weekday as locale’s full name. - current_day = _get_now_from_timezone(gettz(timezone_name)).strftime("%A").upper() + # Not strftime("%A"): it names the day in the process locale, e.g. "FREITAG" under a German LC_TIME + current_day = _DAYS_OF_WEEK[_get_now_from_timezone(gettz(timezone_name)).weekday()] days = condition_value.get(TimeValues.DAYS.value, []) return current_day in days diff --git a/tests/functional/feature_flags/_boto3/test_time_based_actions.py b/tests/functional/feature_flags/_boto3/test_time_based_actions.py index 36ea41cf8f6..48fcb9767fe 100644 --- a/tests/functional/feature_flags/_boto3/test_time_based_actions.py +++ b/tests/functional/feature_flags/_boto3/test_time_based_actions.py @@ -416,6 +416,48 @@ def test_time_based_utc_days_range_rule_match(mocker): ) +def test_time_based_days_range_rule_match_with_non_english_locale(mocker): + # GIVEN a process whose LC_TIME names the days in German, so strftime("%A") on a Friday is "Freitag" + class GermanDatetime(dt.datetime): + def strftime(self, fmt): + return super().strftime(fmt).replace("Friday", "Freitag") + + friday = GermanDatetime(2022, 11, 18, 10, 0, 0, tzinfo=dt.timezone.utc) + mocker.patch( + "aws_lambda_powertools.utilities.feature_flags.comparators._get_now_from_timezone", + return_value=friday, + ) + mocked_get_conf = mocker.patch("aws_lambda_powertools.utilities.parameters.AppConfigProvider.get") + mocked_get_conf.return_value = { + "my_feature": { + FEATURE_DEFAULT_VAL_KEY: False, + RULES_KEY: { + "match only on fridays": { + RULE_MATCH_VALUE: True, + CONDITIONS_KEY: [ + { + CONDITION_ACTION: RuleAction.SCHEDULE_BETWEEN_DAYS_OF_WEEK.value, + CONDITION_KEY: TimeKeys.CURRENT_DAY_OF_WEEK.value, + CONDITION_VALUE: {TimeValues.DAYS.value: [TimeValues.FRIDAY.value]}, + }, + ], + }, + }, + }, + } + store = AppConfigStore( + environment="test_env", + application="test_app", + name="test_conf_name", + max_age=600, + sdk_config=Config(region_name="us-east-1"), + ) + + # WHEN evaluating the rule + # THEN the day still matches FRIDAY + assert FeatureFlags(store=store).evaluate(name="my_feature", context={}, default=False) + + def test_time_based_utc_days_range_no_rule_match(mocker): assert not evaluate_mocked_schema( mocker=mocker,