diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs index 1a022a3010fb0..cf3397a4771ac 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_fromtimestamp.rs @@ -18,7 +18,7 @@ use crate::checkers::ast::Checker; /// always use timezone-aware objects. /// /// `datetime.date.fromtimestamp(ts)` returns a naive datetime object. -/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=)` to return a +/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=...)` to create a /// timezone-aware object. /// /// ## Example @@ -50,10 +50,11 @@ pub struct CallDateFromtimestamp; impl Violation for CallDateFromtimestamp { #[derive_message_formats] fn message(&self) -> String { - format!( - "The use of `datetime.date.fromtimestamp()` is not allowed, use \ - `datetime.datetime.fromtimestamp(ts, tz=).date()` instead" - ) + format!("`datetime.date.fromtimestamp()` used") + } + + fn fix_title(&self) -> Option { + Some("Use `datetime.datetime.fromtimestamp(ts, tz=...).date()` instead".to_string()) } } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs index 9e0bacbff8921..3dd1d967b2f92 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_date_today.rs @@ -18,7 +18,7 @@ use crate::checkers::ast::Checker; /// always use timezone-aware objects. /// /// `datetime.date.today` returns a naive datetime object. Instead, use -/// `datetime.datetime.now(tz=).date()` to return a timezone-aware object. +/// `datetime.datetime.now(tz=...).date()` to create a timezone-aware object. /// /// ## Example /// ```python @@ -49,10 +49,11 @@ pub struct CallDateToday; impl Violation for CallDateToday { #[derive_message_formats] fn message(&self) -> String { - format!( - "The use of `datetime.date.today()` is not allowed, use \ - `datetime.datetime.now(tz=).date()` instead" - ) + format!("`datetime.date.today()` used") + } + + fn fix_title(&self) -> Option { + Some("Use `datetime.datetime.now(tz=...).date()` instead".to_string()) } } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs index 30e67d827e06e..26b5682f91e0e 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_fromtimestamp.rs @@ -3,16 +3,14 @@ use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast}; use ruff_python_semantic::Modules; -use ruff_text_size::Ranged; use crate::checkers::ast::Checker; -use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; -use super::helpers; +use super::helpers::{self, DatetimeModuleAntipattern}; /// ## What it does -/// Checks for usage of `datetime.datetime.fromtimestamp()` without a `tz` -/// argument. +/// Checks for usage of `datetime.datetime.fromtimestamp()` that do not specify +/// a timezone. /// /// ## Why is this bad? /// Python datetime objects can be naive or timezone-aware. While an aware @@ -21,9 +19,10 @@ use super::helpers; /// datetime objects. Since this can lead to errors, it is recommended to /// always use timezone-aware objects. /// -/// `datetime.datetime.fromtimestamp(ts)` returns a naive datetime object. -/// Instead, use `datetime.datetime.fromtimestamp(ts, tz=)` to return a -/// timezone-aware object. +/// `datetime.datetime.fromtimestamp(ts)` or +/// `datetime.datetime.fromtimestampe(ts, tz=None)` returns a naive datetime +/// object. Instead, use `datetime.datetime.fromtimestamp(ts, tz=)` +/// to create a timezone-aware object. /// /// ## Example /// ```python @@ -39,7 +38,7 @@ use super::helpers; /// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc) /// ``` /// -/// Or, for Python 3.11 and later: +/// Or, on Python 3.11 and later: /// ```python /// import datetime /// @@ -49,14 +48,24 @@ use super::helpers; /// ## References /// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects) #[violation] -pub struct CallDatetimeFromtimestamp; +pub struct CallDatetimeFromtimestamp(DatetimeModuleAntipattern); impl Violation for CallDatetimeFromtimestamp { #[derive_message_formats] fn message(&self) -> String { - format!( - "The use of `datetime.datetime.fromtimestamp()` without `tz` argument is not allowed" - ) + let CallDatetimeFromtimestamp(antipattern) = self; + match antipattern { + DatetimeModuleAntipattern::NoTzArgumentPassed => { + format!("`datetime.datetime.fromtimestamp()` called without a `tz` argument") + } + DatetimeModuleAntipattern::NonePassedToTzArgument => { + format!("`tz=None` passed to `datetime.datetime.fromtimestamp()`") + } + } + } + + fn fix_title(&self) -> Option { + Some("Pass a `datetime.timezone` object to the `tz` parameter".to_string()) } } @@ -82,26 +91,14 @@ pub(crate) fn call_datetime_fromtimestamp(checker: &mut Checker, call: &ast::Exp return; } - // no args / no args unqualified - if call.arguments.args.len() < 2 && call.arguments.keywords.is_empty() { - checker - .diagnostics - .push(Diagnostic::new(CallDatetimeFromtimestamp, call.range())); - return; - } - - // none args - if call.arguments.args.len() > 1 && call.arguments.args[1].is_none_literal_expr() { - checker - .diagnostics - .push(Diagnostic::new(CallDatetimeFromtimestamp, call.range())); - return; - } + let antipattern = match call.arguments.find_argument("tz", 1) { + Some(ast::Expr::NoneLiteral(_)) => DatetimeModuleAntipattern::NonePassedToTzArgument, + Some(_) => return, + None => DatetimeModuleAntipattern::NoTzArgumentPassed, + }; - // wrong keywords / none keyword - if !call.arguments.keywords.is_empty() && !has_non_none_keyword(&call.arguments, "tz") { - checker - .diagnostics - .push(Diagnostic::new(CallDatetimeFromtimestamp, call.range())); - } + checker.diagnostics.push(Diagnostic::new( + CallDatetimeFromtimestamp(antipattern), + call.range, + )); } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs index 6a40aa2734b40..603f9ac018576 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_now_without_tzinfo.rs @@ -1,17 +1,15 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::{self as ast, Expr}; +use ruff_python_ast as ast; use ruff_python_semantic::Modules; -use ruff_text_size::Ranged; use crate::checkers::ast::Checker; -use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; -use super::helpers; +use super::helpers::{self, DatetimeModuleAntipattern}; /// ## What it does -/// Checks for usage of `datetime.datetime.now()` without a `tz` argument. +/// Checks for usages of `datetime.datetime.now()` that do not specify a timezone. /// /// ## Why is this bad? /// Python datetime objects can be naive or timezone-aware. While an aware @@ -20,8 +18,9 @@ use super::helpers; /// datetime objects. Since this can lead to errors, it is recommended to /// always use timezone-aware objects. /// -/// `datetime.datetime.now()` returns a naive datetime object. Instead, use -/// `datetime.datetime.now(tz=)` to return a timezone-aware object. +/// `datetime.datetime.now()` or `datetime.datetime.now(tz=None)` returns a naive +/// datetime object. Instead, use `datetime.datetime.now(tz=)` to create +/// a timezone-aware object. /// /// ## Example /// ```python @@ -47,12 +46,24 @@ use super::helpers; /// ## References /// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects) #[violation] -pub struct CallDatetimeNowWithoutTzinfo; +pub struct CallDatetimeNowWithoutTzinfo(DatetimeModuleAntipattern); impl Violation for CallDatetimeNowWithoutTzinfo { #[derive_message_formats] fn message(&self) -> String { - format!("The use of `datetime.datetime.now()` without `tz` argument is not allowed") + let CallDatetimeNowWithoutTzinfo(antipattern) = self; + match antipattern { + DatetimeModuleAntipattern::NoTzArgumentPassed => { + format!("`datetime.datetime.now()` called without a `tz` argument") + } + DatetimeModuleAntipattern::NonePassedToTzArgument => { + format!("`tz=None` passed to `datetime.datetime.now()`") + } + } + } + + fn fix_title(&self) -> Option { + Some("Pass a `datetime.timezone` object to the `tz` parameter".to_string()) } } @@ -75,31 +86,14 @@ pub(crate) fn call_datetime_now_without_tzinfo(checker: &mut Checker, call: &ast return; } - // no args / no args unqualified - if call.arguments.args.is_empty() && call.arguments.keywords.is_empty() { - checker - .diagnostics - .push(Diagnostic::new(CallDatetimeNowWithoutTzinfo, call.range())); - return; - } + let antipattern = match call.arguments.find_argument("tz", 0) { + Some(ast::Expr::NoneLiteral(_)) => DatetimeModuleAntipattern::NonePassedToTzArgument, + Some(_) => return, + None => DatetimeModuleAntipattern::NoTzArgumentPassed, + }; - // none args - if call - .arguments - .args - .first() - .is_some_and(Expr::is_none_literal_expr) - { - checker - .diagnostics - .push(Diagnostic::new(CallDatetimeNowWithoutTzinfo, call.range())); - return; - } - - // wrong keywords / none keyword - if !call.arguments.keywords.is_empty() && !has_non_none_keyword(&call.arguments, "tz") { - checker - .diagnostics - .push(Diagnostic::new(CallDatetimeNowWithoutTzinfo, call.range())); - } + checker.diagnostics.push(Diagnostic::new( + CallDatetimeNowWithoutTzinfo(antipattern), + call.range, + )); } 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 9fdce6ee34829..8f9acc166e2e2 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 @@ -2,10 +2,10 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; use ruff_python_ast::{self as ast, Expr}; use ruff_python_semantic::Modules; -use ruff_text_size::Ranged; use crate::checkers::ast::Checker; -use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; + +use super::helpers::DatetimeModuleAntipattern; /// ## What it does /// Checks for uses of `datetime.datetime.strptime()` that lead to naive @@ -19,7 +19,7 @@ use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; /// always use timezone-aware objects. /// /// `datetime.datetime.strptime()` without `%z` returns a naive datetime -/// object. Follow it with `.replace(tzinfo=)` or `.astimezone()`. +/// object. Follow it with `.replace(tzinfo=)` or `.astimezone()`. /// /// ## Example /// ```python @@ -28,7 +28,7 @@ use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; /// datetime.datetime.strptime("2022/01/31", "%Y/%m/%d") /// ``` /// -/// Instead, use `.replace(tzinfo=)`: +/// Instead, use `.replace(tzinfo=)`: /// ```python /// import datetime /// @@ -51,15 +51,34 @@ use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; /// - [Python documentation: Aware and Naive Objects](https://docs.python.org/3/library/datetime.html#aware-and-naive-objects) /// - [Python documentation: `strftime()` and `strptime()` Behavior](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior) #[violation] -pub struct CallDatetimeStrptimeWithoutZone; +pub struct CallDatetimeStrptimeWithoutZone(DatetimeModuleAntipattern); impl Violation for CallDatetimeStrptimeWithoutZone { #[derive_message_formats] fn message(&self) -> String { - format!( - "The use of `datetime.datetime.strptime()` without %z must be followed by \ - `.replace(tzinfo=)` or `.astimezone()`" - ) + let CallDatetimeStrptimeWithoutZone(antipattern) = self; + match antipattern { + DatetimeModuleAntipattern::NoTzArgumentPassed => format!( + "Naive datetime constructed using `datetime.datetime.strptime()` without %z" + ), + DatetimeModuleAntipattern::NonePassedToTzArgument => { + format!("`datetime.datetime.strptime(...).replace(tz=None)` used") + } + } + } + + fn fix_title(&self) -> Option { + let CallDatetimeStrptimeWithoutZone(antipattern) = self; + match antipattern { + DatetimeModuleAntipattern::NoTzArgumentPassed => Some( + "Call `.replace(tzinfo=)` or `.astimezone()` \ + to convert to an aware datetime" + .to_string(), + ), + DatetimeModuleAntipattern::NonePassedToTzArgument => { + Some("Pass a `datetime.timezone` object to the `tzinfo` parameter".to_string()) + } + } } } @@ -91,36 +110,44 @@ pub(crate) fn call_datetime_strptime_without_zone(checker: &mut Checker, call: & } }; - let (Some(grandparent), Some(parent)) = ( - checker.semantic().current_expression_grandparent(), - checker.semantic().current_expression_parent(), - ) else { + let semantic = checker.semantic(); + if let Some(antipattern) = find_antipattern( + semantic.current_expression_grandparent(), + semantic.current_expression_parent(), + ) { checker.diagnostics.push(Diagnostic::new( - CallDatetimeStrptimeWithoutZone, - call.range(), + CallDatetimeStrptimeWithoutZone(antipattern), + call.range, )); - return; - }; - - if let Expr::Call(ast::ExprCall { arguments, .. }) = grandparent { - if let Expr::Attribute(ast::ExprAttribute { attr, .. }) = parent { - let attr = attr.as_str(); - // Ex) `datetime.strptime(...).astimezone()` - if attr == "astimezone" { - return; - } - - // Ex) `datetime.strptime(...).replace(tzinfo=UTC)` - if attr == "replace" { - if has_non_none_keyword(arguments, "tzinfo") { - return; - } - } - } } +} - checker.diagnostics.push(Diagnostic::new( - CallDatetimeStrptimeWithoutZone, - call.range(), - )); +fn find_antipattern( + grandparent: Option<&Expr>, + parent: Option<&Expr>, +) -> Option { + let Some(Expr::Call(ast::ExprCall { arguments, .. })) = grandparent else { + return Some(DatetimeModuleAntipattern::NoTzArgumentPassed); + }; + let Some(Expr::Attribute(ast::ExprAttribute { attr, .. })) = parent else { + return Some(DatetimeModuleAntipattern::NoTzArgumentPassed); + }; + // Ex) `datetime.strptime(...).astimezone()` + if attr == "astimezone" { + return None; + } + if attr != "replace" { + return Some(DatetimeModuleAntipattern::NoTzArgumentPassed); + } + match arguments.find_keyword("tzinfo") { + // Ex) `datetime.strptime(...).replace(tzinfo=None)` + Some(ast::Keyword { + value: Expr::NoneLiteral(_), + .. + }) => Some(DatetimeModuleAntipattern::NonePassedToTzArgument), + // Ex) `datetime.strptime(...).replace(tzinfo=...)` + Some(_) => None, + // Ex) `datetime.strptime(...).replace(...)` with no `tzinfo` argument + None => Some(DatetimeModuleAntipattern::NoTzArgumentPassed), + } } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs index d712f604ec1d7..b22a7e84c2ddb 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_today.rs @@ -20,7 +20,7 @@ use super::helpers; /// time, unlike "naive" objects. /// /// `datetime.datetime.today()` creates a "naive" object; instead, use -/// `datetime.datetime.now(tz=)` to create a timezone-aware object. +/// `datetime.datetime.now(tz=...)` to create a timezone-aware object. /// /// ## Example /// ```python @@ -48,10 +48,11 @@ pub struct CallDatetimeToday; impl Violation for CallDatetimeToday { #[derive_message_formats] fn message(&self) -> String { - format!( - "The use of `datetime.datetime.today()` is not allowed, use \ - `datetime.datetime.now(tz=)` instead" - ) + format!("`datetime.datetime.today()` used") + } + + fn fix_title(&self) -> Option { + Some("Use `datetime.datetime.now(tz=...)` instead".to_string()) } } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs index 2db2649e73793..f24b54158d845 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcfromtimestamp.rs @@ -19,15 +19,15 @@ use super::helpers; /// datetime objects. Since this can lead to errors, it is recommended to /// always use timezone-aware objects. /// -/// `datetime.datetime.utcfromtimestamp()` returns a naive datetime object; -/// instead, use `datetime.datetime.fromtimestamp(ts, tz=)` to return a -/// timezone-aware object. +/// `datetime.datetime.utcfromtimestamp()` returns a naive datetime +/// object; instead, use `datetime.datetime.fromtimestamp(ts, tz=...)` +/// to create a timezone-aware object. /// /// ## Example /// ```python /// import datetime /// -/// datetime.datetime.utcfromtimestamp() +/// datetime.datetime.utcfromtimestamp(946684800) /// ``` /// /// Use instead: @@ -37,7 +37,7 @@ use super::helpers; /// datetime.datetime.fromtimestamp(946684800, tz=datetime.timezone.utc) /// ``` /// -/// Or, for Python 3.11 and later: +/// Or, on Python 3.11 and later: /// ```python /// import datetime /// @@ -52,10 +52,11 @@ pub struct CallDatetimeUtcfromtimestamp; impl Violation for CallDatetimeUtcfromtimestamp { #[derive_message_formats] fn message(&self) -> String { - format!( - "The use of `datetime.datetime.utcfromtimestamp()` is not allowed, use \ - `datetime.datetime.fromtimestamp(ts, tz=)` instead" - ) + format!("`datetime.datetime.utcfromtimestamp()` used") + } + + fn fix_title(&self) -> Option { + Some("Use `datetime.datetime.fromtimestamp(ts, tz=...)` instead".to_string()) } } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs index 9ff0de0549cb3..059f9b80df572 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_utcnow.rs @@ -20,7 +20,7 @@ use super::helpers; /// always use timezone-aware objects. /// /// `datetime.datetime.utcnow()` returns a naive datetime object; instead, use -/// `datetime.datetime.now(tz=)` to return a timezone-aware object. +/// `datetime.datetime.now(tz=...)` to create a timezone-aware object. /// /// ## Example /// ```python @@ -51,10 +51,11 @@ pub struct CallDatetimeUtcnow; impl Violation for CallDatetimeUtcnow { #[derive_message_formats] fn message(&self) -> String { - format!( - "The use of `datetime.datetime.utcnow()` is not allowed, use \ - `datetime.datetime.now(tz=)` instead" - ) + format!("`datetime.datetime.utcnow()` used") + } + + fn fix_title(&self) -> Option { + Some("Use `datetime.datetime.now(tz=...)` instead".to_string()) } } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs index 5c3bc9cec39a4..264950d937eff 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/call_datetime_without_tzinfo.rs @@ -1,17 +1,15 @@ use ruff_diagnostics::{Diagnostic, Violation}; use ruff_macros::{derive_message_formats, violation}; -use ruff_python_ast::{self as ast, Expr}; +use ruff_python_ast as ast; use ruff_python_semantic::Modules; -use ruff_text_size::Ranged; use crate::checkers::ast::Checker; -use crate::rules::flake8_datetimez::rules::helpers::has_non_none_keyword; -use super::helpers; +use super::helpers::{self, DatetimeModuleAntipattern}; /// ## What it does -/// Checks for `datetime` instantiations that lack a `tzinfo` argument. +/// Checks for `datetime` instantiations that do not specify a timezone. /// /// ## Why is this bad? /// `datetime` objects are "naive" by default, in that they do not include @@ -20,7 +18,8 @@ use super::helpers; /// `datetime` objects are preferred, as they represent a specific moment in /// time, unlike "naive" objects. /// -/// By providing a `tzinfo` value, a `datetime` can be made timezone-aware. +/// By providing a non-`None` value for `tzinfo`, a `datetime` can be made +/// timezone-aware. /// /// ## Example /// ```python @@ -36,19 +35,31 @@ use super::helpers; /// datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc) /// ``` /// -/// Or, for Python 3.11 and later: +/// Or, on Python 3.11 and later: /// ```python /// import datetime /// /// datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.UTC) /// ``` #[violation] -pub struct CallDatetimeWithoutTzinfo; +pub struct CallDatetimeWithoutTzinfo(DatetimeModuleAntipattern); impl Violation for CallDatetimeWithoutTzinfo { #[derive_message_formats] fn message(&self) -> String { - format!("The use of `datetime.datetime()` without `tzinfo` argument is not allowed") + let CallDatetimeWithoutTzinfo(antipattern) = self; + match antipattern { + DatetimeModuleAntipattern::NoTzArgumentPassed => { + format!("`datetime.datetime()` called without a `tzinfo` argument") + } + DatetimeModuleAntipattern::NonePassedToTzArgument => { + format!("`tzinfo=None` passed to `datetime.datetime()`") + } + } + } + + fn fix_title(&self) -> Option { + Some("Pass a `datetime.timezone` object to the `tzinfo` parameter".to_string()) } } @@ -69,23 +80,14 @@ pub(crate) fn call_datetime_without_tzinfo(checker: &mut Checker, call: &ast::Ex return; } - // No positional arg: keyword is missing or constant None. - if call.arguments.args.len() < 8 && !has_non_none_keyword(&call.arguments, "tzinfo") { - checker - .diagnostics - .push(Diagnostic::new(CallDatetimeWithoutTzinfo, call.range())); - return; - } + let antipattern = match call.arguments.find_argument("tzinfo", 7) { + Some(ast::Expr::NoneLiteral(_)) => DatetimeModuleAntipattern::NonePassedToTzArgument, + Some(_) => return, + None => DatetimeModuleAntipattern::NoTzArgumentPassed, + }; - // Positional arg: is constant None. - if call - .arguments - .args - .get(7) - .is_some_and(Expr::is_none_literal_expr) - { - checker - .diagnostics - .push(Diagnostic::new(CallDatetimeWithoutTzinfo, call.range())); - } + checker.diagnostics.push(Diagnostic::new( + CallDatetimeWithoutTzinfo(antipattern), + call.range, + )); } diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/rules/helpers.rs b/crates/ruff_linter/src/rules/flake8_datetimez/rules/helpers.rs index 21912044d13bb..a580c9bc95e76 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/rules/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_datetimez/rules/helpers.rs @@ -1,7 +1,13 @@ -use ruff_python_ast::{Arguments, Expr, ExprAttribute}; +use ruff_python_ast::{Expr, ExprAttribute}; use crate::checkers::ast::Checker; +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub(super) enum DatetimeModuleAntipattern { + NoTzArgumentPassed, + NonePassedToTzArgument, +} + /// Check if the parent expression is a call to `astimezone`. This assumes that /// the current expression is a `datetime.datetime` object. pub(super) fn parent_expr_is_astimezone(checker: &Checker) -> bool { @@ -9,10 +15,3 @@ pub(super) fn parent_expr_is_astimezone(checker: &Checker) -> bool { matches!(parent, Expr::Attribute(ExprAttribute { attr, .. }) if attr.as_str() == "astimezone") }) } - -/// Return `true` if a keyword argument is present with a non-`None` value. -pub(super) fn has_non_none_keyword(arguments: &Arguments, keyword: &str) -> bool { - arguments - .find_keyword(keyword) - .is_some_and(|keyword| !keyword.value.is_none_literal_expr()) -} diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ001_DTZ001.py.snap b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ001_DTZ001.py.snap index 45f6dc665784a..e4e8e1f901ef0 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ001_DTZ001.py.snap +++ b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ001_DTZ001.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_datetimez/mod.rs --- -DTZ001.py:4:1: DTZ001 The use of `datetime.datetime()` without `tzinfo` argument is not allowed +DTZ001.py:4:1: DTZ001 `datetime.datetime()` called without a `tzinfo` argument | 3 | # no args 4 | datetime.datetime(2000, 1, 1, 0, 0, 0) @@ -9,8 +9,9 @@ DTZ001.py:4:1: DTZ001 The use of `datetime.datetime()` without `tzinfo` argument 5 | 6 | # none args | + = help: Pass a `datetime.timezone` object to the `tzinfo` parameter -DTZ001.py:7:1: DTZ001 The use of `datetime.datetime()` without `tzinfo` argument is not allowed +DTZ001.py:7:1: DTZ001 `tzinfo=None` passed to `datetime.datetime()` | 6 | # none args 7 | datetime.datetime(2000, 1, 1, 0, 0, 0, 0, None) @@ -18,8 +19,9 @@ DTZ001.py:7:1: DTZ001 The use of `datetime.datetime()` without `tzinfo` argument 8 | 9 | # not none arg | + = help: Pass a `datetime.timezone` object to the `tzinfo` parameter -DTZ001.py:13:1: DTZ001 The use of `datetime.datetime()` without `tzinfo` argument is not allowed +DTZ001.py:13:1: DTZ001 `datetime.datetime()` called without a `tzinfo` argument | 12 | # no kwargs 13 | datetime.datetime(2000, 1, 1, fold=1) @@ -27,8 +29,9 @@ DTZ001.py:13:1: DTZ001 The use of `datetime.datetime()` without `tzinfo` argumen 14 | 15 | # none kwargs | + = help: Pass a `datetime.timezone` object to the `tzinfo` parameter -DTZ001.py:16:1: DTZ001 The use of `datetime.datetime()` without `tzinfo` argument is not allowed +DTZ001.py:16:1: DTZ001 `tzinfo=None` passed to `datetime.datetime()` | 15 | # none kwargs 16 | datetime.datetime(2000, 1, 1, tzinfo=None) @@ -36,8 +39,9 @@ DTZ001.py:16:1: DTZ001 The use of `datetime.datetime()` without `tzinfo` argumen 17 | 18 | from datetime import datetime | + = help: Pass a `datetime.timezone` object to the `tzinfo` parameter -DTZ001.py:21:1: DTZ001 The use of `datetime.datetime()` without `tzinfo` argument is not allowed +DTZ001.py:21:1: DTZ001 `datetime.datetime()` called without a `tzinfo` argument | 20 | # no args unqualified 21 | datetime(2000, 1, 1, 0, 0, 0) @@ -45,5 +49,4 @@ DTZ001.py:21:1: DTZ001 The use of `datetime.datetime()` without `tzinfo` argumen 22 | 23 | # uses `astimezone` method | - - + = help: Pass a `datetime.timezone` object to the `tzinfo` parameter diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ002_DTZ002.py.snap b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ002_DTZ002.py.snap index d7ff15f3f5898..0f29ecf110394 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ002_DTZ002.py.snap +++ b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ002_DTZ002.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_datetimez/mod.rs --- -DTZ002.py:4:1: DTZ002 The use of `datetime.datetime.today()` is not allowed, use `datetime.datetime.now(tz=)` instead +DTZ002.py:4:1: DTZ002 `datetime.datetime.today()` used | 3 | # qualified 4 | datetime.datetime.today() @@ -9,8 +9,9 @@ DTZ002.py:4:1: DTZ002 The use of `datetime.datetime.today()` is not allowed, use 5 | 6 | from datetime import datetime | + = help: Use `datetime.datetime.now(tz=...)` instead -DTZ002.py:9:1: DTZ002 The use of `datetime.datetime.today()` is not allowed, use `datetime.datetime.now(tz=)` instead +DTZ002.py:9:1: DTZ002 `datetime.datetime.today()` used | 8 | # unqualified 9 | datetime.today() @@ -18,5 +19,4 @@ DTZ002.py:9:1: DTZ002 The use of `datetime.datetime.today()` is not allowed, use 10 | 11 | # uses `astimezone` method | - - + = help: Use `datetime.datetime.now(tz=...)` instead diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ003_DTZ003.py.snap b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ003_DTZ003.py.snap index 0c1da8d49b8e6..2b7953a7148aa 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ003_DTZ003.py.snap +++ b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ003_DTZ003.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_datetimez/mod.rs --- -DTZ003.py:4:1: DTZ003 The use of `datetime.datetime.utcnow()` is not allowed, use `datetime.datetime.now(tz=)` instead +DTZ003.py:4:1: DTZ003 `datetime.datetime.utcnow()` used | 3 | # qualified 4 | datetime.datetime.utcnow() @@ -9,8 +9,9 @@ DTZ003.py:4:1: DTZ003 The use of `datetime.datetime.utcnow()` is not allowed, us 5 | 6 | from datetime import datetime | + = help: Use `datetime.datetime.now(tz=...)` instead -DTZ003.py:9:1: DTZ003 The use of `datetime.datetime.utcnow()` is not allowed, use `datetime.datetime.now(tz=)` instead +DTZ003.py:9:1: DTZ003 `datetime.datetime.utcnow()` used | 8 | # unqualified 9 | datetime.utcnow() @@ -18,5 +19,4 @@ DTZ003.py:9:1: DTZ003 The use of `datetime.datetime.utcnow()` is not allowed, us 10 | 11 | # uses `astimezone` method | - - + = help: Use `datetime.datetime.now(tz=...)` instead diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ004_DTZ004.py.snap b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ004_DTZ004.py.snap index d0a0184fe3eda..0efe06454de4b 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ004_DTZ004.py.snap +++ b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ004_DTZ004.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_datetimez/mod.rs --- -DTZ004.py:4:1: DTZ004 The use of `datetime.datetime.utcfromtimestamp()` is not allowed, use `datetime.datetime.fromtimestamp(ts, tz=)` instead +DTZ004.py:4:1: DTZ004 `datetime.datetime.utcfromtimestamp()` used | 3 | # qualified 4 | datetime.datetime.utcfromtimestamp(1234) @@ -9,8 +9,9 @@ DTZ004.py:4:1: DTZ004 The use of `datetime.datetime.utcfromtimestamp()` is not a 5 | 6 | from datetime import datetime | + = help: Use `datetime.datetime.fromtimestamp(ts, tz=...)` instead -DTZ004.py:9:1: DTZ004 The use of `datetime.datetime.utcfromtimestamp()` is not allowed, use `datetime.datetime.fromtimestamp(ts, tz=)` instead +DTZ004.py:9:1: DTZ004 `datetime.datetime.utcfromtimestamp()` used | 8 | # unqualified 9 | datetime.utcfromtimestamp(1234) @@ -18,5 +19,4 @@ DTZ004.py:9:1: DTZ004 The use of `datetime.datetime.utcfromtimestamp()` is not a 10 | 11 | # uses `astimezone` method | - - + = help: Use `datetime.datetime.fromtimestamp(ts, tz=...)` instead diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ005_DTZ005.py.snap b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ005_DTZ005.py.snap index a20ccd8a4606c..ee6f711e0bfc3 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ005_DTZ005.py.snap +++ b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ005_DTZ005.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_datetimez/mod.rs --- -DTZ005.py:4:1: DTZ005 The use of `datetime.datetime.now()` without `tz` argument is not allowed +DTZ005.py:4:1: DTZ005 `datetime.datetime.now()` called without a `tz` argument | 3 | # no args 4 | datetime.datetime.now() @@ -9,8 +9,9 @@ DTZ005.py:4:1: DTZ005 The use of `datetime.datetime.now()` without `tz` argument 5 | 6 | # wrong keywords | + = help: Pass a `datetime.timezone` object to the `tz` parameter -DTZ005.py:7:1: DTZ005 The use of `datetime.datetime.now()` without `tz` argument is not allowed +DTZ005.py:7:1: DTZ005 `datetime.datetime.now()` called without a `tz` argument | 6 | # wrong keywords 7 | datetime.datetime.now(bad=datetime.timezone.utc) @@ -18,8 +19,9 @@ DTZ005.py:7:1: DTZ005 The use of `datetime.datetime.now()` without `tz` argument 8 | 9 | # none args | + = help: Pass a `datetime.timezone` object to the `tz` parameter -DTZ005.py:10:1: DTZ005 The use of `datetime.datetime.now()` without `tz` argument is not allowed +DTZ005.py:10:1: DTZ005 `tz=None` passed to `datetime.datetime.now()` | 9 | # none args 10 | datetime.datetime.now(None) @@ -27,8 +29,9 @@ DTZ005.py:10:1: DTZ005 The use of `datetime.datetime.now()` without `tz` argumen 11 | 12 | # none keywords | + = help: Pass a `datetime.timezone` object to the `tz` parameter -DTZ005.py:13:1: DTZ005 The use of `datetime.datetime.now()` without `tz` argument is not allowed +DTZ005.py:13:1: DTZ005 `tz=None` passed to `datetime.datetime.now()` | 12 | # none keywords 13 | datetime.datetime.now(tz=None) @@ -36,8 +39,9 @@ DTZ005.py:13:1: DTZ005 The use of `datetime.datetime.now()` without `tz` argumen 14 | 15 | from datetime import datetime | + = help: Pass a `datetime.timezone` object to the `tz` parameter -DTZ005.py:18:1: DTZ005 The use of `datetime.datetime.now()` without `tz` argument is not allowed +DTZ005.py:18:1: DTZ005 `datetime.datetime.now()` called without a `tz` argument | 17 | # no args unqualified 18 | datetime.now() @@ -45,5 +49,4 @@ DTZ005.py:18:1: DTZ005 The use of `datetime.datetime.now()` without `tz` argumen 19 | 20 | # uses `astimezone` method | - - + = help: Pass a `datetime.timezone` object to the `tz` parameter diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ006_DTZ006.py.snap b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ006_DTZ006.py.snap index 671c428306698..e708ed9d52404 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ006_DTZ006.py.snap +++ b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ006_DTZ006.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_datetimez/mod.rs --- -DTZ006.py:4:1: DTZ006 The use of `datetime.datetime.fromtimestamp()` without `tz` argument is not allowed +DTZ006.py:4:1: DTZ006 `datetime.datetime.fromtimestamp()` called without a `tz` argument | 3 | # no args 4 | datetime.datetime.fromtimestamp(1234) @@ -9,8 +9,9 @@ DTZ006.py:4:1: DTZ006 The use of `datetime.datetime.fromtimestamp()` without `tz 5 | 6 | # wrong keywords | + = help: Pass a `datetime.timezone` object to the `tz` parameter -DTZ006.py:7:1: DTZ006 The use of `datetime.datetime.fromtimestamp()` without `tz` argument is not allowed +DTZ006.py:7:1: DTZ006 `datetime.datetime.fromtimestamp()` called without a `tz` argument | 6 | # wrong keywords 7 | datetime.datetime.fromtimestamp(1234, bad=datetime.timezone.utc) @@ -18,8 +19,9 @@ DTZ006.py:7:1: DTZ006 The use of `datetime.datetime.fromtimestamp()` without `tz 8 | 9 | # none args | + = help: Pass a `datetime.timezone` object to the `tz` parameter -DTZ006.py:10:1: DTZ006 The use of `datetime.datetime.fromtimestamp()` without `tz` argument is not allowed +DTZ006.py:10:1: DTZ006 `tz=None` passed to `datetime.datetime.fromtimestamp()` | 9 | # none args 10 | datetime.datetime.fromtimestamp(1234, None) @@ -27,8 +29,9 @@ DTZ006.py:10:1: DTZ006 The use of `datetime.datetime.fromtimestamp()` without `t 11 | 12 | # none keywords | + = help: Pass a `datetime.timezone` object to the `tz` parameter -DTZ006.py:13:1: DTZ006 The use of `datetime.datetime.fromtimestamp()` without `tz` argument is not allowed +DTZ006.py:13:1: DTZ006 `tz=None` passed to `datetime.datetime.fromtimestamp()` | 12 | # none keywords 13 | datetime.datetime.fromtimestamp(1234, tz=None) @@ -36,8 +39,9 @@ DTZ006.py:13:1: DTZ006 The use of `datetime.datetime.fromtimestamp()` without `t 14 | 15 | from datetime import datetime | + = help: Pass a `datetime.timezone` object to the `tz` parameter -DTZ006.py:18:1: DTZ006 The use of `datetime.datetime.fromtimestamp()` without `tz` argument is not allowed +DTZ006.py:18:1: DTZ006 `datetime.datetime.fromtimestamp()` called without a `tz` argument | 17 | # no args unqualified 18 | datetime.fromtimestamp(1234) @@ -45,5 +49,4 @@ DTZ006.py:18:1: DTZ006 The use of `datetime.datetime.fromtimestamp()` without `t 19 | 20 | # uses `astimezone` method | - - + = help: Pass a `datetime.timezone` object to the `tz` parameter 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 d47324ece6d1d..21184a68e755c 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 @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_datetimez/mod.rs --- -DTZ007.py:4:1: DTZ007 The use of `datetime.datetime.strptime()` without %z must be followed by `.replace(tzinfo=)` or `.astimezone()` +DTZ007.py:4:1: DTZ007 Naive datetime constructed using `datetime.datetime.strptime()` without %z | 3 | # bad format 4 | datetime.datetime.strptime("something", "%H:%M:%S%Z") @@ -9,8 +9,9 @@ DTZ007.py:4:1: DTZ007 The use of `datetime.datetime.strptime()` without %z must 5 | 6 | # no replace or astimezone | + = help: Call `.replace(tzinfo=)` or `.astimezone()` to convert to an aware datetime -DTZ007.py:7:1: DTZ007 The use of `datetime.datetime.strptime()` without %z must be followed by `.replace(tzinfo=)` or `.astimezone()` +DTZ007.py:7:1: DTZ007 Naive datetime constructed using `datetime.datetime.strptime()` without %z | 6 | # no replace or astimezone 7 | datetime.datetime.strptime("something", "something") @@ -18,8 +19,9 @@ DTZ007.py:7:1: DTZ007 The use of `datetime.datetime.strptime()` without %z must 8 | 9 | # wrong replace | + = help: Call `.replace(tzinfo=)` or `.astimezone()` to convert to an aware datetime -DTZ007.py:10:1: DTZ007 The use of `datetime.datetime.strptime()` without %z must be followed by `.replace(tzinfo=)` or `.astimezone()` +DTZ007.py:10:1: DTZ007 Naive datetime constructed using `datetime.datetime.strptime()` without %z | 9 | # wrong replace 10 | datetime.datetime.strptime("something", "something").replace(hour=1) @@ -27,8 +29,9 @@ DTZ007.py:10:1: DTZ007 The use of `datetime.datetime.strptime()` without %z must 11 | 12 | # none replace | + = help: Call `.replace(tzinfo=)` or `.astimezone()` to convert to an aware datetime -DTZ007.py:13:1: DTZ007 The use of `datetime.datetime.strptime()` without %z must be followed by `.replace(tzinfo=)` or `.astimezone()` +DTZ007.py:13:1: DTZ007 `datetime.datetime.strptime(...).replace(tz=None)` used | 12 | # none replace 13 | datetime.datetime.strptime("something", "something").replace(tzinfo=None) @@ -36,12 +39,12 @@ DTZ007.py:13:1: DTZ007 The use of `datetime.datetime.strptime()` without %z must 14 | 15 | # OK | + = help: Pass a `datetime.timezone` object to the `tzinfo` parameter -DTZ007.py:35:1: DTZ007 The use of `datetime.datetime.strptime()` without %z must be followed by `.replace(tzinfo=)` or `.astimezone()` +DTZ007.py:35:1: DTZ007 Naive datetime constructed using `datetime.datetime.strptime()` without %z | 34 | # no replace orastimezone unqualified 35 | datetime.strptime("something", "something") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DTZ007 | - - + = help: Call `.replace(tzinfo=)` or `.astimezone()` to convert to an aware datetime diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ011_DTZ011.py.snap b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ011_DTZ011.py.snap index 546f8e9bbfc3b..2a19a7162f01b 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ011_DTZ011.py.snap +++ b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ011_DTZ011.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_datetimez/mod.rs --- -DTZ011.py:4:1: DTZ011 The use of `datetime.date.today()` is not allowed, use `datetime.datetime.now(tz=).date()` instead +DTZ011.py:4:1: DTZ011 `datetime.date.today()` used | 3 | # qualified 4 | datetime.date.today() @@ -9,12 +9,12 @@ DTZ011.py:4:1: DTZ011 The use of `datetime.date.today()` is not allowed, use `da 5 | 6 | from datetime import date | + = help: Use `datetime.datetime.now(tz=...).date()` instead -DTZ011.py:9:1: DTZ011 The use of `datetime.date.today()` is not allowed, use `datetime.datetime.now(tz=).date()` instead +DTZ011.py:9:1: DTZ011 `datetime.date.today()` used | 8 | # unqualified 9 | date.today() | ^^^^^^^^^^^^ DTZ011 | - - + = help: Use `datetime.datetime.now(tz=...).date()` instead diff --git a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ012_DTZ012.py.snap b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ012_DTZ012.py.snap index cce6418b93b0c..fe0bffe0f4d3c 100644 --- a/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ012_DTZ012.py.snap +++ b/crates/ruff_linter/src/rules/flake8_datetimez/snapshots/ruff_linter__rules__flake8_datetimez__tests__DTZ012_DTZ012.py.snap @@ -1,7 +1,7 @@ --- source: crates/ruff_linter/src/rules/flake8_datetimez/mod.rs --- -DTZ012.py:4:1: DTZ012 The use of `datetime.date.fromtimestamp()` is not allowed, use `datetime.datetime.fromtimestamp(ts, tz=).date()` instead +DTZ012.py:4:1: DTZ012 `datetime.date.fromtimestamp()` used | 3 | # qualified 4 | datetime.date.fromtimestamp(1234) @@ -9,12 +9,12 @@ DTZ012.py:4:1: DTZ012 The use of `datetime.date.fromtimestamp()` is not allowed, 5 | 6 | from datetime import date | + = help: Use `datetime.datetime.fromtimestamp(ts, tz=...).date()` instead -DTZ012.py:9:1: DTZ012 The use of `datetime.date.fromtimestamp()` is not allowed, use `datetime.datetime.fromtimestamp(ts, tz=).date()` instead +DTZ012.py:9:1: DTZ012 `datetime.date.fromtimestamp()` used | 8 | # unqualified 9 | date.fromtimestamp(1234) | ^^^^^^^^^^^^^^^^^^^^^^^^ DTZ012 | - - + = help: Use `datetime.datetime.fromtimestamp(ts, tz=...).date()` instead