langgenius / langgenius/dify

parse_time_range mishandles DST spring-forward gaps that aren't exactly one hour (crashes on >1h gaps, wrong instant on <1h gaps)

Open Beginner friendly
#39,230 1 comment 1 reaction 0 assignees View on GitHub
project#dify
Dominant language
TypeScript
Stars
156k
Forks
24.6k
Avg merge
22h 9m
Merged PRs (30d)
610

Description

### Self Checks

- [x] I have searched for existing issues, including closed ones.
- [x] I confirm that I am using English to submit this report.

### Dify version

main (`cb41fb3e76`)

### Cloud or Self Hosted

Self Hosted (Source)

### Steps to reproduce

`libs/datetime_utils.py::parse_time_range` resolves a non-existent (DST spring-forward) wall-clock time by adding a hardcoded one hour:

```python
except pytz.NonExistentTimeError:
dt += datetime.timedelta(hours=1)
return tz.localize(dt, is_dst=None).astimezone(utc)
```

This assumes every spring-forward gap is exactly one hour. Real tzdata (pytz 2025.2 in the repo venv) has gaps of other sizes, giving two failure modes:

**1. Gaps longer than one hour → uncaught crash.** Antarctica/Troll springs forward 2 hours (2024-03-31 01:00 → 03:00), so 01:30 is non-existent. Adding one hour lands at 02:30, still inside the gap, so the retry `tz.localize(..., is_dst=None)` raises `NonExistentTimeError` again — this time uncaught:

```python
>>> from libs.datetime_utils import parse_time_range
>>> parse_time_range('2024-03-31 01:30', None, 'Antarctica/Troll')
pytz.exceptions.NonExistentTimeError: 2024-03-31 02:30:00
```

The docstring says it raises `ValueError` only and "handles ... non-existent times gracefully"; instead a raw pytz exception escapes into the analytics/statistics API path. `Pacific/Apia` (2011-12-30, a fully skipped day) crashes the same way.

**2. Gaps shorter than one hour → wrong UTC instant.** Australia/Lord_Howe springs forward only 30 minutes (2024-10-06 02:00 +10:30 → 02:30 +11:00), so 02:15 is non-existent. The correct instant (zoneinfo fold=0) is 02:15 +10:30 = 2024-10-05 **15:45** UTC, but the code returns **16:15** UTC:

```python
>>> parse_time_range('2024-10-06 02:15', None, 'Australia/Lord_Howe')[0]
datetime.datetime(2024, 10, 5, 16, 15, tzinfo=) # expected 15:45
```

For a statistics start-bound this silently shifts the window by 30 minutes.

### Fix

Resolve the non-existent time with `tz.normalize(tz.localize(dt, is_dst=False))`, which shifts forward by exactly the gap size for any offset change and matches zoneinfo fold=0. Standard one-hour gaps are unchanged (America/New_York 02:30 → 07:30 UTC). PR with real-timezone tests follows.

Contributor guide

Open the contributing guide

Research direction

Start in libs/datetime_utils.py at parse_time_range and reproduce the Antarctica/Troll and Australia/Lord_Howe examples from the issue. Add real-timezone coverage for gaps longer and shorter than one hour, then verify standard America/New_York behavior and that the documented ValueError contract is preserved.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Bug
Difficulty
2/5
Estimated time
Half a day
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.