Skip to content

Commit

Permalink
Add localtime docs about timezones (inc. pitfalls)
Browse files Browse the repository at this point in the history
In response to [^1].

[^1]: #178 (comment)
  • Loading branch information
benthorner committed Dec 20, 2024
1 parent 7f34d7c commit 11a881e
Showing 1 changed file with 67 additions and 0 deletions.
67 changes: 67 additions & 0 deletions docs/xocto/localtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,73 @@ from xocto import localtime

See [xocto.localtime](https://github.com/octoenergy/xocto/blob/master/xocto/localtime.py) for more details, including examples and in depth technical details.

## Localising datetimes between different timezones

`localtime` can be used to change the timezone of a datetime e.g.

```python
london_tz = zoneinfo.ZoneInfo("Europe/London")
dt = datetime.datetime(2024, 1, 1, tzinfo=london_tz)

sydney_tz = zoneinfo.ZoneInfo("Australia/Sydney")
localtime.as_localtime(dt, tz=sydney_tz)
=> datetime.datetime(2024, 1, 1, 11, 0, tzinfo=zoneinfo.ZoneInfo(key='Australia/Sydney'))

localtime.as_utc(dt)
=> datetime.datetime(2024, 1, 1, 0, 0, tzinfo=zoneinfo.ZoneInfo(key='UTC'))
```

Behaviour around Daylight Savings Time (DST) boundaries - where
clocks go forward or back - can be surprising e.g.

```python
# Normally "2AM - 0AM" should be "2 hours". However, in
# GBR clocks go forward on this day at 1AM => 2AM. This
# means after "as_utc" we are actually doing "1AM - 0AM".
localtime.as_utc(datetime(2020, 3, 29, hour=2, tzinfo=london_tz)) -
localtime.as_utc(datetime(2020, 3, 29, hour=0, tzinfo=london_tz))
=> timedelta(seconds=3600)
```

You should consider the implications of DST when localizing to a
different timezone - especially when working with ranges.

### Non-existent datetimes

Python datetimes can be constructed for times that don't
actually exist in certain timezones, due to DST changes e.g.

```python
# Doesn't exist in GBR as at this time the clocks went
# forwards one hour - to 2AM.
datetime(2020, 3, 29, hours=1, tzinfo=london_tz)

# Python datetime operations do not consider DST when
# doing calculations on datetimes. Although "2AM - 1AM"
# is normally "1 hour", the example below is wrong as
# in GBR the clocks went forward here: 1AM => 2AM - so
# the time that actually elapsed was "0".
datetime(2020, 3, 29, hour=2, tzinfo=london_tz) -
datetime(2020, 3, 29, hour=1, tzinfo=london_tz)
=> datetime.timedelta(seconds=3600) # <== WRONG (should be 0)
```

To ensure a datetime exists in its timezone, convert it to UTC
(a timezone without DST) and back again e.g.

```python
localtime.as_localtime(localtime.as_utc(my_dt), tz=target_tz)
```

Although such datetimes are unlikely to be created directly, they
may be created from other code in `datetime` e.g.

```python
datetime(2020, 3, 29, tzinfo=london_tz) + timedelta(hours=1)
=> datetime(2020, 3, 29, hours=1, tzinfo=london_tz)
```


## Variables

```{eval-rst}
Expand Down

0 comments on commit 11a881e

Please sign in to comment.