Skip to content

Replace pytz with standard library's zoneinfo #2343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/requirements-py3.10.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies:
- pytest-rerunfailures
- conda-forge::pytest-remotedata # version in default channel is old
- python=3.10
- pytz
# - pytz
- requests
- scipy >= 1.6.0
- statsmodels
Expand Down
2 changes: 1 addition & 1 deletion ci/requirements-py3.11.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies:
- pytest-rerunfailures
- conda-forge::pytest-remotedata # version in default channel is old
- python=3.11
- pytz
# - pytz
- requests
- scipy >= 1.6.0
- statsmodels
Expand Down
2 changes: 1 addition & 1 deletion ci/requirements-py3.12.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies:
- pytest-rerunfailures
- conda-forge::pytest-remotedata # version in default channel is old
- python=3.12
- pytz
# - pytz
- requests
- scipy >= 1.6.0
- statsmodels
Expand Down
2 changes: 1 addition & 1 deletion ci/requirements-py3.9-min.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ dependencies:
- pytest-mock
- pytest-timeout
- python=3.9
- pytz
# - pytz
- requests
- pip:
- h5py==3.0.0
Expand Down
2 changes: 1 addition & 1 deletion ci/requirements-py3.9.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ dependencies:
- pytest-rerunfailures
- conda-forge::pytest-remotedata # version in default channel is old
- python=3.9
- pytz
# - pytz
- requests
- scipy >= 1.6.0
- statsmodels
Expand Down
4 changes: 2 additions & 2 deletions docs/sphinx/source/whatsnew/v0.11.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Documentation

Testing
~~~~~~~
* Add tests for time conversions in tools package. (:issue:`2340`, :pull:`2341`)


Requirements
Expand All @@ -26,5 +27,4 @@ Requirements

Contributors
~~~~~~~~~~~~


* Mark Campanellli (:ghuser:`markcampanelli`)
32 changes: 16 additions & 16 deletions docs/tutorials/solarposition.ipynb

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions pvlib/iotools/pvgis.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@
import io
import json
from pathlib import Path
from zoneinfo import ZoneInfo

import requests
import numpy as np
import pandas as pd
import pytz

from pvlib.iotools import read_epw, parse_epw

URL = 'https://re.jrc.ec.europa.eu/api/'
Expand Down Expand Up @@ -398,10 +400,10 @@ def _coerce_and_roll_tmy(tmy_data, tz, year):
re-interpreted as zero / UTC.
"""
if tz:
tzname = pytz.timezone(f'Etc/GMT{-tz:+d}')
tzname = ZoneInfo(f'Etc/GMT{-tz:+d}')
else:
tz = 0
tzname = pytz.timezone('UTC')
tzname = ZoneInfo('UTC')
new_index = pd.DatetimeIndex([
timestamp.replace(year=year, tzinfo=tzname)
for timestamp in tmy_data.index],
Expand Down
77 changes: 46 additions & 31 deletions pvlib/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@

# Will Holmgren, University of Arizona, 2014-2016.

import pathlib
import datetime
import pathlib
from zoneinfo import ZoneInfo

import pandas as pd
import pytz
import h5py
import pandas as pd
# import pytz

from pvlib import solarposition, clearsky, atmosphere, irradiance
from pvlib import atmosphere, clearsky, irradiance, solarposition
# from pvlib._deprecation import warn_deprecated
from pvlib.tools import _degrees_to_index


Expand All @@ -21,10 +23,12 @@ class Location:
timezone, and altitude data associated with a particular
geographic location. You can also assign a name to a location object.

Location objects have two timezone attributes:
Location objects have one timezone attribute:

* ``tz`` is a IANA-compatible standard-library zoneinfo.ZoneInfo.

* ``tz`` is a IANA timezone string.
* ``pytz`` is a pytz timezone object.
Thus, the passed timezone must be representable by one of the values in
zoneinfo.available_timezones().

Location objects support the print method.

Expand All @@ -38,12 +42,13 @@ class Location:
Positive is east of the prime meridian.
Use decimal degrees notation.

tz : str, int, float, or pytz.timezone, default 'UTC'.
See
http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
for a list of valid time zones.
pytz.timezone objects will be converted to strings.
ints and floats must be in hours from UTC.
tz : str, int, float, zoneinfo.ZoneInfo, datetime.timezone, or pytz
timezone (deprecated), default 'UTC'.
See http://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a
list of valid time zone strings, or use the function
zoneinfo.available_timezones().
ints and floats must be in hours N from UTC, and are converted to the
Etc/GMT+N or Etc/GMT-N format depending on the sign of N.

altitude : float, optional
Altitude from sea level in meters.
Expand All @@ -59,33 +64,43 @@ class Location:
pvlib.pvsystem.PVSystem
"""

def __init__(self, latitude, longitude, tz='UTC', altitude=None,
name=None):
def __init__(
self, latitude, longitude, tz='UTC', altitude=None, name=None
):

if name is None:
name = ""

self.name = name

self.latitude = latitude
self.longitude = longitude

if isinstance(tz, str):
self.tz = tz
self.pytz = pytz.timezone(tz)
elif isinstance(tz, datetime.timezone):
self.tz = 'UTC'
self.pytz = pytz.UTC
elif isinstance(tz, datetime.tzinfo):
self.tz = tz.zone
self.pytz = tz
elif isinstance(tz, (int, float)):
self.tz = tz
self.pytz = pytz.FixedOffset(tz*60)
else:
raise TypeError('Invalid tz specification')

if altitude is None:
altitude = lookup_altitude(latitude, longitude)

self.altitude = altitude

self.name = name
if isinstance(tz, str):
self.tz = ZoneInfo(tz)
elif isinstance(tz, int):
self.tz = ZoneInfo(f"Etc/GMT{-tz:+d}")
elif isinstance(tz, float):
self.tz = ZoneInfo(f"Etc/GMT{int(-tz):+d}")
elif isinstance(tz, ZoneInfo):
self.tz = tz
elif isinstance(tz, datetime.timezone):
self.tz = ZoneInfo(str(tz))
# elif isinstance(tz, pytz.BaseTzInfo):
# warn_deprecated(
# "0.11.3",
# message='pytz-based timezones are deprecated for locations',
# alternative='use zoneinfo.ZoneInfo from standard library',
# obj_type='function argument type',
# )
# self.tz = ZoneInfo(tz.zone)
else:
raise TypeError(f'Invalid tz specification: {tz}')

def __repr__(self):
attrs = ['name', 'latitude', 'longitude', 'altitude', 'tz']
Expand Down
1 change: 0 additions & 1 deletion pvlib/temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import numpy as np
import pandas as pd
from pvlib.tools import sind
from pvlib._deprecation import warn_deprecated
from pvlib.tools import _get_sample_intervals
import scipy
import scipy.constants
Expand Down
3 changes: 1 addition & 2 deletions pvlib/tests/iotools/test_midc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import pandas as pd
import pytest
import pytz

from pvlib.iotools import midc
from ..conftest import DATA_DIR, RERUNS, RERUNS_DELAY
Expand Down Expand Up @@ -43,7 +42,7 @@ def test_midc__format_index_tz_conversion():
data = pd.read_csv(MIDC_TESTFILE)
data = data.rename(columns={'MST': 'PST'})
data = midc._format_index(data)
assert data.index[0].tz == pytz.timezone('Etc/GMT+8')
assert str(data.index[0].tz) == 'Etc/GMT+8'


def test_midc__format_index_raw():
Expand Down
4 changes: 2 additions & 2 deletions pvlib/tests/test_clearsky.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from collections import OrderedDict
from zoneinfo import ZoneInfo

import numpy as np
from numpy import nan
import pandas as pd
import pytz
from scipy.linalg import hankel

import pytest
Expand Down Expand Up @@ -758,7 +758,7 @@ def test_bird():
times = pd.date_range(start='1/1/2015 0:00', end='12/31/2015 23:00',
freq='h')
tz = -7 # test timezone
gmt_tz = pytz.timezone('Etc/GMT%+d' % -(tz))
gmt_tz = ZoneInfo(f"Etc/GMT{-tz:+}")
times = times.tz_localize(gmt_tz) # set timezone
times_utc = times.tz_convert('UTC')
# match test data from BIRD_08_16_2012.xls
Expand Down
32 changes: 19 additions & 13 deletions pvlib/tests/test_location.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import datetime
from unittest.mock import ANY
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

import numpy as np
from numpy import nan
import pandas as pd
from .conftest import assert_frame_equal, assert_index_equal

import pytest

import pytz
from pytz.exceptions import UnknownTimeZoneError
# import pytz

import pvlib
from pvlib import location
Expand All @@ -27,16 +26,23 @@ def test_location_all():
Location(32.2, -111, 'US/Arizona', 700, 'Tucson')


@pytest.mark.parametrize('tz', [
pytz.timezone('US/Arizona'), 'America/Phoenix', -7, -7.0,
datetime.timezone.utc
])
@pytest.mark.parametrize(
'tz',
[
# pytz.timezone('US/Arizona'),
ZoneInfo('US/Arizona'),
'America/Phoenix',
-7,
-7.0,
datetime.timezone.utc,
]
)
def test_location_tz(tz):
Location(32.2, -111, tz)


def test_location_invalid_tz():
with pytest.raises(UnknownTimeZoneError):
with pytest.raises(ZoneInfoNotFoundError):
Location(32.2, -111, 'invalid')


Expand All @@ -58,8 +64,8 @@ def test_location_print_all():
assert tus.__str__() == expected_str


def test_location_print_pytz():
tus = Location(32.2, -111, pytz.timezone('US/Arizona'), 700, 'Tucson')
def test_location_print_zoneinfo():
tus = Location(32.2, -111, ZoneInfo('US/Arizona'), 700, 'Tucson')
expected_str = '\n'.join([
'Location: ',
' name: Tucson',
Expand Down Expand Up @@ -215,7 +221,7 @@ def test_from_tmy_3():
from pvlib.iotools import read_tmy3
data, meta = read_tmy3(TMY3_TESTFILE, map_variables=True)
loc = Location.from_tmy(meta, data)
assert loc.name is not None
assert loc.name != ""
assert loc.altitude != 0
assert loc.tz != 'UTC'
assert_frame_equal(loc.weather, data)
Expand All @@ -226,7 +232,7 @@ def test_from_tmy_2():
from pvlib.iotools import read_tmy2
data, meta = read_tmy2(TMY2_TESTFILE)
loc = Location.from_tmy(meta, data)
assert loc.name is not None
assert loc.name != ""
assert loc.altitude != 0
assert loc.tz != 'UTC'
assert_frame_equal(loc.weather, data)
Expand All @@ -237,7 +243,7 @@ def test_from_epw():
from pvlib.iotools import read_epw
data, meta = read_epw(epw_testfile)
loc = Location.from_epw(meta, data)
assert loc.name is not None
assert loc.name != ""
assert loc.altitude != 0
assert loc.tz != 'UTC'
assert_frame_equal(loc.weather, data)
Expand Down
Loading
Loading