CLN: no need to pass tz to tzlocal conversion funcs
State: closed


Labels:
name: Clean

Comments:
@pganssle this effectively treats tzlocal as a singleton.  is there any reason not to do that?
> this effectively treats tzlocal as a singleton. is there any reason not to do that?

Unfortunately yes, there is a problem with this, it's why we [explicitly don't use a singleton for "local" time](https://github.com/dateutil/dateutil/blob/43b7838af5d801575e26516e34355eb1ca57beec/dateutil/tz/tz.py#L1558-L1567).

The biggest issue is that it's possible to subtly break `tzlocal` by changing your system time zone, because `tzlocal()` is a wrapper around the system time zone functions, and that's effectively global state. The way `tzlocal()` works, once you call it it will freeze in amber the DST and STD values for `utcoffset()` and `dst()`, but it still needs to query the system for when the DST and STD values change, so you can get stuff like this:

```python
>>> from dateutil import tz
>>> from datetime import *
>>> import os
>>> import time

>>> dt = datetime(2020, 6, 1, tzinfo=tz.tzlocal())
>>> dt.tzname()
'EDT'
>>> os.environ["TZ"] = "UTC"
>>> time.tzset()
>>> dt.tzname()
'EST'
```

A few things are very wrong here:

1. The UTC offset of an existing datetime has now mutated.
2. The new value isn't even the correct current local time — it's the *previous* local time, but following the transition rules of the *new* local time.

The best mitigation for this is to basically create a brand new `tzlocal()` object immediately before you need to get some time zone information about a datetime, which is why it's preferable to avoid singletons for `tzlocal`.

This is a fundamental problem for *any* time zone that purports to be a "local time", which, I think, was a big part of the reasoning behind the semantic shift from "naive datetimes represent abstract datetimes" to "naive datetimes represent local time" — the return value for `.utcoffset()` is built into a bunch of places, and there's no comfortable way to allow it to be anything but a constant, so any "local time" zone would need to either be "this value fixed in amber" or "calculate this value on demand using the current system time". That's why in Python 3, you can do this:

```python
>>> from datetime import *
>>> import os
>>> import time
>>> dt = datetime(2020, 6, 1)

>>> print(dt.astimezone())
2020-06-01 00:00:00-04:00
>>> dt.astimezone()
datetime.datetime(2020, 6, 1, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(days=-1, seconds=72000), 'EDT'))

>>> os.environ["TZ"] = "UTC"
>>> time.tzset()

>>> print(dt.astimezone())
2020-06-01 00:00:00+00:00
>>> dt.astimezone()
datetime.datetime(2020, 6, 1, 0, 0, tzinfo=datetime.timezone(datetime.timedelta(0), 'UTC'))
```

This will do the "freeze the offset in amber" part, but it also allows for an easy way to use "calculate on demand" semantics — just keep the datetime naive and call `.astimezone()` whenever you want to access one of the values for `.utcoffset`, etc.

Anyway, if you don't care about allowing the time zone to change over the course of an interpreter run (something that won't even work on Windows anyway — though it would work with `tz.tzwinlocal`), then `tzlocal`-as-singleton is fine even if it's likely to be subtly broken in some situations. Otherwise I think it's best to do something as close as possible to the "just-in-time offset calculation" that you get with a "store-as-naive-and-use-`astimezone`" workflow.
Thanks @pganssle.  Closing pending a more robust solution.