arrow-py / arrow-py/arrow

incorrect behavior WRT daylight savings for shift/adding timedelta

Open
#1,170 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Python
Stars
9.1k
Forks
784
PR merge metrics
No merged PRs in 30d

Description

## Issue Description
Arrow's `shift` function appears to handle daylight savings time incorrectly, or at least inconsistently. In particular, on spring-forward days, shifting forward by 2 hours from midnight is treated as being the same time as shifting 3 from midnight:
```
Python 3.10.12 | packaged by conda-forge | (main, Jun 23 2023, 22:41:52) [Clang 15.0.7 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import arrow
>>> from datetime import datetime
>>> x = arrow.get(datetime(2023, 3, 12), 'America/Los_Angeles')
>>> x.shift(hours=3) == x.shift(hours=2)
True
>>> print(x.shift(hours=2))
2023-03-12T03:00:00-07:00
>>> print(x.shift(hours=3))
2023-03-12T03:00:00-07:00
```
It looks to me like shifting by 2 hours is correct, but that shifting 3 is not. Perhaps this is the intended semantics of `shift`, but if that's the case then the following example seems inconsistent (shifting by 1 hour 3 times returns the result I expected for shifting 3 hours once):
```
>>> x.shift(hours=1).shift(hours=1).shift(hours=1)

>>> x.shift(hours=1).shift(hours=1).shift(hours=1) == x.shift(hours=3) # I'd expect this to be true for any value of x
False
```

When I try adding `timedelta(hours=1)` I get different unintuitive behavior, this time when it comes to adding 1 hour or 2 hours:
```
>>> (x + timedelta(hours=1)).timestamp() == (x + timedelta(hours=2)).timestamp() # I'd expect this to be false
True
>>> x + timedelta(hours=1)
# this looks right to me
>>> x + timedelta(hours=2)
# I think this should be 2023-03-12T03:00:00-07:00
```

On fall-behind days, there are also problems. In this case using both `shift` and adding a `timedelta` do the same thing : the gap between midnight offsetby 1 hour and offset by 2 hours is 7200 seconds (2 hours):
```
>>> y = arrow.get(datetime(2023, 11, 5), 'America/Los_Angeles')
>>> y.shift(hours=1)

>>> y.shift(hours=2)

>>> y.shift(hours=2).timestamp() - y.shift(hours=1).timestamp() # I'd expect 3600
7200.0
>>> (y + timedelta(hours=2)).timestamp() - (y + timedelta(hours=1)).timestamp() # I'd expect 3600
7200.0
```

## System Info

- 🖥 **OS name and version**: macOs 14.2.1
- 🐍 **Python version**: 3.10.12 
- 🏹 **Arrow version**: 1.3.0

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reproducing the reported spring-forward and fall-back cases with Arrow 1.3.0, using shift and timedelta around America/Los_Angeles transitions. Read the implementations and tests for Arrow shifting, datetime arithmetic, timestamps, and timezone handling. Done means the chosen DST semantics are consistent for the examples and covered by regression tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.