diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_datetimez/DTZ007.py b/crates/ruff_linter/resources/test/fixtures/flake8_datetimez/DTZ007.py index 6c7cffdfcf..7897267f1c 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_datetimez/DTZ007.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_datetimez/DTZ007.py @@ -33,3 +33,9 @@ from datetime import datetime # no replace orastimezone unqualified datetime.strptime("something", "something") + +# F-strings +datetime.strptime("something", f"%Y-%m-%dT%H:%M:%S{('.%f' if millis else '')}%z") +datetime.strptime("something", f"%Y-%m-%d %H:%M:%S%z") +# F-string is implicitly concatenated to another string +datetime.strptime("something", f"%Y-%m-%dT%H:%M:%S{('.%f' if millis else '')}" "%z") diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs index 8f9acc166e..1c39b1604f 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_strptime_without_zone.rs @@ -102,11 +102,30 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: & } // Does the `strptime` call contain a format string with a timezone specifier? - if let Some(Expr::StringLiteral(ast::ExprStringLiteral { value: format, .. })) = - call.arguments.args.get(1).as_ref() - { - if format.to_str().contains("%z") { - return; + if let Some(expr) = call.arguments.args.get(1) { + match expr { + Expr::StringLiteral(ast::ExprStringLiteral { value, .. }) => { + if value.to_str().contains("%z") { + return; + } + } + Expr::FString(ast::ExprFString { value, .. }) => { + for f_string_part in value { + match f_string_part { + ast::FStringPart::Literal(string) => { + if string.contains("%z") { + return; + } + } + ast::FStringPart::FString(f_string) => { + if f_string.literals().any(|literal| literal.contains("%z")) { + return; + } + } + } + } + } + _ => {} } }; diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ007_DTZ007.py.snap b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ007_DTZ007.py.snap index 21184a68e7..f2579ea9d2 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ007_DTZ007.py.snap +++ b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ007_DTZ007.py.snap @@ -46,5 +46,7 @@ DTZ007.py:35:1: DTZ007 Naive datetime constructed using `datetime.datetime.strpt 34 | # no replace orastimezone unqualified 35 | datetime.strptime("something", "something") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DTZ007 +36 | +37 | # F-strings | = help: Call `.replace(tzinfo=)` or `.astimezone()` to convert to an aware datetime