Skip to content

Commit a842ddf

Browse files
Remove pytz from tools
1 parent 6f3cf67 commit a842ddf

File tree

9 files changed

+75
-64
lines changed

9 files changed

+75
-64
lines changed

pvlib/iotools/pvgis.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
import io
1818
import json
1919
from pathlib import Path
20+
from zoneinfo import ZoneInfo
21+
2022
import requests
2123
import numpy as np
2224
import pandas as pd
23-
import pytz
25+
2426
from pvlib.iotools import read_epw, parse_epw
2527

2628
URL = 'https://re.jrc.ec.europa.eu/api/'
@@ -398,10 +400,10 @@ def _coerce_and_roll_tmy(tmy_data, tz, year):
398400
re-interpreted as zero / UTC.
399401
"""
400402
if tz:
401-
tzname = pytz.timezone(f'Etc/GMT{-tz:+d}')
403+
tzname = ZoneInfo(f'Etc/GMT{-tz:+d}')
402404
else:
403405
tz = 0
404-
tzname = pytz.timezone('UTC')
406+
tzname = ZoneInfo('UTC')
405407
new_index = pd.DatetimeIndex([
406408
timestamp.replace(year=year, tzinfo=tzname)
407409
for timestamp in tmy_data.index],

pvlib/location.py

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44

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

7-
import pathlib
87
import datetime
8+
import pathlib
9+
from zoneinfo import ZoneInfo
910

11+
import h5py
1012
import pandas as pd
1113
import pytz
12-
import h5py
1314

14-
from pvlib import solarposition, clearsky, atmosphere, irradiance
15+
from pvlib import atmosphere, clearsky, irradiance, solarposition
16+
from pvlib._deprecation import warn_deprecated
1517
from pvlib.tools import _degrees_to_index
1618

1719

@@ -21,10 +23,12 @@ class Location:
2123
timezone, and altitude data associated with a particular
2224
geographic location. You can also assign a name to a location object.
2325
24-
Location objects have two timezone attributes:
26+
Location objects have one timezone attribute:
27+
28+
* ``tz`` is a IANA-compatible standard-library zoneinfo.ZoneInfo.
2529
26-
* ``tz`` is a IANA timezone string.
27-
* ``pytz`` is a pytz timezone object.
30+
Thus, the passed timezone must be representable by one of the values in
31+
zoneinfo.available_timezones().
2832
2933
Location objects support the print method.
3034
@@ -38,12 +42,12 @@ class Location:
3842
Positive is east of the prime meridian.
3943
Use decimal degrees notation.
4044
41-
tz : str, int, float, or pytz.timezone, default 'UTC'.
42-
See
43-
http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
44-
for a list of valid time zones.
45-
pytz.timezone objects will be converted to strings.
46-
ints and floats must be in hours from UTC.
45+
tz : str, int, float, zoneinfo.ZoneInfo, datetime.timezone, or pytz timezone (deprecated), default 'UTC'.
46+
See http://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a
47+
list of valid time zone strings, or use the function
48+
zoneinfo.available_timezones().
49+
ints and floats must be in hours N from UTC, and are converted to the
50+
Etc/GMT+N or Etc/GMT-N format depending on the sign of N.
4751
4852
altitude : float, optional
4953
Altitude from sea level in meters.
@@ -59,33 +63,43 @@ class Location:
5963
pvlib.pvsystem.PVSystem
6064
"""
6165

62-
def __init__(self, latitude, longitude, tz='UTC', altitude=None,
63-
name=None):
66+
def __init__(
67+
self, latitude, longitude, tz='UTC', altitude=None, name=None
68+
):
69+
70+
if name is None:
71+
name = ""
72+
73+
self.name = name
6474

6575
self.latitude = latitude
6676
self.longitude = longitude
6777

68-
if isinstance(tz, str):
69-
self.tz = tz
70-
self.pytz = pytz.timezone(tz)
71-
elif isinstance(tz, datetime.timezone):
72-
self.tz = 'UTC'
73-
self.pytz = pytz.UTC
74-
elif isinstance(tz, datetime.tzinfo):
75-
self.tz = tz.zone
76-
self.pytz = tz
77-
elif isinstance(tz, (int, float)):
78-
self.tz = tz
79-
self.pytz = pytz.FixedOffset(tz*60)
80-
else:
81-
raise TypeError('Invalid tz specification')
82-
8378
if altitude is None:
8479
altitude = lookup_altitude(latitude, longitude)
8580

8681
self.altitude = altitude
8782

88-
self.name = name
83+
if isinstance(tz, str):
84+
self.tz = ZoneInfo(tz)
85+
elif isinstance(tz, int):
86+
self.tz = ZoneInfo(f"Etc/GMT{-tz:+d}")
87+
elif isinstance(tz, float):
88+
self.tz = ZoneInfo(f"Etc/GMT{int(-tz):+d}")
89+
elif isinstance(tz, ZoneInfo):
90+
self.tz = tz
91+
elif isinstance(tz, datetime.timezone):
92+
self.tz = ZoneInfo(str(tz))
93+
elif isinstance(tz, pytz.BaseTzInfo):
94+
warn_deprecated(
95+
"0.11.3",
96+
message='pytz timezones are deprecated',
97+
alternative='use zoneinfo.ZoneInfo from the standard library',
98+
obj_type='function argument type',
99+
)
100+
self.tz = ZoneInfo(tz.zone)
101+
else:
102+
raise TypeError(f'Invalid tz specification: {tz}')
89103

90104
def __repr__(self):
91105
attrs = ['name', 'latitude', 'longitude', 'altitude', 'tz']

pvlib/temperature.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import numpy as np
77
import pandas as pd
88
from pvlib.tools import sind
9-
from pvlib._deprecation import warn_deprecated
109
from pvlib.tools import _get_sample_intervals
1110
import scipy
1211
import scipy.constants

pvlib/tests/iotools/test_midc.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import pandas as pd
22
import pytest
3-
import pytz
43

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

4847

4948
def test_midc__format_index_raw():

pvlib/tests/test_clearsky.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from collections import OrderedDict
2+
from zoneinfo import ZoneInfo
23

34
import numpy as np
45
from numpy import nan
56
import pandas as pd
6-
import pytz
77
from scipy.linalg import hankel
88

99
import pytest
@@ -758,7 +758,7 @@ def test_bird():
758758
times = pd.date_range(start='1/1/2015 0:00', end='12/31/2015 23:00',
759759
freq='h')
760760
tz = -7 # test timezone
761-
gmt_tz = pytz.timezone('Etc/GMT%+d' % -(tz))
761+
gmt_tz = ZoneInfo(f"Etc/GMT{-tz:+}")
762762
times = times.tz_localize(gmt_tz) # set timezone
763763
times_utc = times.tz_convert('UTC')
764764
# match test data from BIRD_08_16_2012.xls

pvlib/tests/test_location.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import datetime
22
from unittest.mock import ANY
3+
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
34

45
import numpy as np
56
from numpy import nan
67
import pandas as pd
78
from .conftest import assert_frame_equal, assert_index_equal
89

910
import pytest
10-
1111
import pytz
12-
from pytz.exceptions import UnknownTimeZoneError
1312

1413
import pvlib
1514
from pvlib import location
@@ -28,15 +27,15 @@ def test_location_all():
2827

2928

3029
@pytest.mark.parametrize('tz', [
31-
pytz.timezone('US/Arizona'), 'America/Phoenix', -7, -7.0,
32-
datetime.timezone.utc
30+
pytz.timezone('US/Arizona'), ZoneInfo('US/Arizona'), 'America/Phoenix',
31+
-7, -7.0, datetime.timezone.utc
3332
])
3433
def test_location_tz(tz):
3534
Location(32.2, -111, tz)
3635

3736

3837
def test_location_invalid_tz():
39-
with pytest.raises(UnknownTimeZoneError):
38+
with pytest.raises(ZoneInfoNotFoundError):
4039
Location(32.2, -111, 'invalid')
4140

4241

@@ -58,8 +57,8 @@ def test_location_print_all():
5857
assert tus.__str__() == expected_str
5958

6059

61-
def test_location_print_pytz():
62-
tus = Location(32.2, -111, pytz.timezone('US/Arizona'), 700, 'Tucson')
60+
def test_location_print_zoneinfo():
61+
tus = Location(32.2, -111, ZoneInfo('US/Arizona'), 700, 'Tucson')
6362
expected_str = '\n'.join([
6463
'Location: ',
6564
' name: Tucson',
@@ -215,7 +214,7 @@ def test_from_tmy_3():
215214
from pvlib.iotools import read_tmy3
216215
data, meta = read_tmy3(TMY3_TESTFILE, map_variables=True)
217216
loc = Location.from_tmy(meta, data)
218-
assert loc.name is not None
217+
assert loc.name != ""
219218
assert loc.altitude != 0
220219
assert loc.tz != 'UTC'
221220
assert_frame_equal(loc.weather, data)
@@ -226,7 +225,7 @@ def test_from_tmy_2():
226225
from pvlib.iotools import read_tmy2
227226
data, meta = read_tmy2(TMY2_TESTFILE)
228227
loc = Location.from_tmy(meta, data)
229-
assert loc.name is not None
228+
assert loc.name != ""
230229
assert loc.altitude != 0
231230
assert loc.tz != 'UTC'
232231
assert_frame_equal(loc.weather, data)
@@ -237,7 +236,7 @@ def test_from_epw():
237236
from pvlib.iotools import read_epw
238237
data, meta = read_epw(epw_testfile)
239238
loc = Location.from_epw(meta, data)
240-
assert loc.name is not None
239+
assert loc.name != ""
241240
assert loc.altitude != 0
242241
assert loc.tz != 'UTC'
243242
assert_frame_equal(loc.weather, data)

pvlib/tests/test_solarposition.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,15 +344,13 @@ def test_pyephem_physical_dst(expected_solpos, golden):
344344

345345
@requires_ephem
346346
def test_calc_time():
347-
import pytz
348347
import math
349348
# validation from USNO solar position calculator online
350349

351350
epoch = datetime.datetime(1970, 1, 1)
352351
epoch_dt = pytz.utc.localize(epoch)
353352

354353
loc = tus
355-
loc.pressure = 0
356354
actual_time = pytz.timezone(loc.tz).localize(
357355
datetime.datetime(2014, 10, 10, 8, 30))
358356
lb = pytz.timezone(loc.tz).localize(datetime.datetime(2014, 10, 10, tol))

pvlib/tests/test_tools.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,7 @@ def test_localize_to_utc(input, expected):
298298
),
299299
(
300300
{
301-
"time": datetime(
302-
1974, 6, 22, 23, 30, 15, tzinfo=ZoneInfo("UTC")
303-
)
301+
"time": datetime(1974, 6, 22, 23, 30, 15)
304302
},
305303
27201.47934027778,
306304
),

pvlib/tools.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
"""
44

55
import datetime as dt
6+
import warnings
7+
import zoneinfo
8+
69
import numpy as np
710
import pandas as pd
8-
import pytz
9-
import warnings
1011

1112

1213
def cosd(angle):
@@ -133,13 +134,14 @@ def localize_to_utc(time, location):
133134
"""
134135
if isinstance(time, dt.datetime):
135136
if time.tzinfo is None:
136-
time = pytz.timezone(location.tz).localize(time)
137-
time_utc = time.astimezone(pytz.utc)
137+
time = time.replace(tzinfo=location.tz)
138+
139+
time_utc = time.astimezone(zoneinfo.ZoneInfo("UTC"))
138140
else:
139141
try:
140142
time_utc = time.tz_convert('UTC')
141143
except TypeError:
142-
time_utc = time.tz_localize(location.tz).tz_convert('UTC')
144+
time_utc = time.tz_localize(str(location.tz)).tz_convert('UTC')
143145

144146
return time_utc
145147

@@ -160,11 +162,11 @@ def datetime_to_djd(time):
160162
"""
161163

162164
if time.tzinfo is None:
163-
time_utc = pytz.utc.localize(time)
165+
time_utc = time.replace(tzinfo=zoneinfo.ZoneInfo("UTC"))
164166
else:
165-
time_utc = time.astimezone(pytz.utc)
167+
time_utc = time.astimezone(zoneinfo.ZoneInfo("UTC"))
166168

167-
djd_start = pytz.utc.localize(dt.datetime(1899, 12, 31, 12))
169+
djd_start = dt.datetime(1899, 12, 31, 12, tzinfo=zoneinfo.ZoneInfo("UTC"))
168170
djd = (time_utc - djd_start).total_seconds() * 1.0/(60 * 60 * 24)
169171

170172
return djd
@@ -187,10 +189,10 @@ def djd_to_datetime(djd, tz='UTC'):
187189
The resultant datetime localized to tz
188190
"""
189191

190-
djd_start = pytz.utc.localize(dt.datetime(1899, 12, 31, 12))
191-
192+
djd_start = dt.datetime(1899, 12, 31, 12, tzinfo=zoneinfo.ZoneInfo("UTC"))
192193
utc_time = djd_start + dt.timedelta(days=djd)
193-
return utc_time.astimezone(pytz.timezone(tz))
194+
195+
return utc_time.astimezone(zoneinfo.ZoneInfo(tz))
194196

195197

196198
def _pandas_to_doy(pd_object):

0 commit comments

Comments
 (0)