fix: preserve local time when parsing naive datetime strings (#1294)

to_datetime() previously interpreted naive strings differently depending on their precision. For example, 2026-01-15 23:45 in Europe/Berlin became 2026-01-16 00:45+01:00, while the equivalent string with seconds kept the intended date and time. Minutes, seconds, and fractional seconds now consistently represent 2026-01-15 23:45+01:00.

Pass the resolved target/local timezone to Pendulum's fallback parser. Explicit Z and numeric offsets continue to determine the input instant, and Unix timestamps retain their UTC semantics. Update the affected docstring examples to distinguish local wall time from UTC conversion.

Regression coverage includes space and T separators, explicit and default Europe/Berlin timezones, winter/summer dates near midnight, fractional-second precision, explicit offsets, and Unix timestamps. The Open-Meteo integration test now independently checks the timestamps and all three irradiance series for all 72 fixture hours, with both UTC and Europe/Berlin as the host timezone. This replaces an expectation that incorrectly associated the 08:00 values with 09:00. The previous parser misplaces all 72 source hours; the corrected parser maps all 72 correctly.
This commit is contained in:
dr-dimitri
2026-09-14 08:24:55 +02:00
committed by GitHub
parent 3b9eecc52b
commit 446ea3da9d
3 changed files with 173 additions and 41 deletions
+20 -4
View File
@@ -32,7 +32,7 @@ Usage Examples:
>>> to_time("15:30:00", in_timezone="Europe/Berlin")
Time(17, 30, 0, tzinfo=Timezone('Europe/Berlin'))
>>> to_datetime("2024-10-13T15:30:00", in_timezone="Europe/Berlin")
>>> to_datetime("2024-10-13T15:30:00Z", in_timezone="Europe/Berlin")
DateTime(2024, 10, 13, 17, 30, 0, tzinfo=Timezone('Europe/Berlin'))
>>> to_duration("2 days 5 hours")
@@ -875,7 +875,16 @@ def to_datetime(
) -> Union[DateTime, str]:
"""Convert a date input into a Pendulum DateTime object or a formatted string, with optional timezone handling.
This function handles various date input formats, adjusts for timezones, and provides flexibility for formatting and time adjustments. For date strings without explicit timezone information, the local timezone is assumed. Be aware that Pendulum DateTime objects created without a timezone default to UTC.
This function handles various date input formats, adjusts for timezones, and provides
flexibility for formatting and time adjustments. Date strings without explicit timezone
information represent wall-clock time in `in_timezone`, or in the local timezone if it is
omitted, regardless of whether they include minutes, seconds, or fractional seconds. Pass the
configured location's timezone as `in_timezone` to interpret its local date and time. For
example, `2026-01-15 23:45` in `Europe/Berlin` means `2026-01-15T23:45:00+01:00`.
Supplying this timezone during parsing preserves the local date and clock components instead
of first assuming UTC and shifting them during conversion. Explicit offsets and Unix
timestamps retain their instant when converted to the target timezone. Be aware that Pendulum
DateTime objects created without a timezone default to UTC.
Args:
date_input (Optional[Any]): The date input to convert. Supported types include:
@@ -896,6 +905,7 @@ def to_datetime(
in_timezone (Optional[Union[str, Timezone]]): Specifies the target timezone for the result.
- Can be a timezone string (e.g., "UTC", "Europe/Berlin") or a `pendulum.Timezone` object.
- Defaults to the local timezone if not provided.
- Also defines the assumed timezone for strings without timezone information.
to_naiv (Optional[bool]): If `True`, removes timezone information from the resulting datetime object.
- Defaults to `False`.
@@ -918,6 +928,9 @@ def to_datetime(
'2024-10-13T00:00:00+00:00'
>>> to_datetime("2024-10-13T15:30:00", in_timezone="Europe/Berlin")
DateTime(2024, 10, 13, 15, 30, 0, tzinfo=Timezone('Europe/Berlin'))
>>> to_datetime("2024-10-13T15:30:00Z", in_timezone="Europe/Berlin")
DateTime(2024, 10, 13, 17, 30, 0, tzinfo=Timezone('Europe/Berlin'))
>>> to_datetime(date(2024, 10, 13), to_maxtime=True)
@@ -974,9 +987,12 @@ def to_datetime(
logger.trace(f"{date_input}, {fmt}, {e}")
dt = None
else:
# DateTime input with timezone info
# The fallback also handles naive strings with omitted or fractional seconds.
# Interpret their clock components directly in the resolved location/local timezone.
# Parsing as UTC first and then converting would shift the clock and possibly the date.
# Explicit offsets take precedence over tz in Pendulum, preserving the input instant.
try:
dt = cast(pendulum.DateTime, pendulum.parse(date_input))
dt = cast(pendulum.DateTime, pendulum.parse(date_input, tz=timezone))
logger.trace(
f"Pendulum Fmt converted: {dt}, tz={dt.tz} from {date_input}, tz={timezone}"
)