Skip to content
Open
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
8 changes: 4 additions & 4 deletions benchmarks/benchmarks/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@


def set_solar_position(obj):
obj.location = pvlib.location.Location(32, -110, altitude=700,
tz='Etc/GMT+7')
tz = 'Etc/GMT+7'
obj.location = pvlib.location.Location(32, -110, altitude=700)
obj.times = pd.date_range(start='20180601', freq='3min',
periods=1440)
periods=1440, tz=tz)
obj.days = pd.date_range(start='20180101', freq='d', periods=365,
tz=obj.location.tz)
tz=tz)
obj.solar_position = obj.location.get_solarposition(obj.times)


Expand Down
2 changes: 1 addition & 1 deletion docs/examples/bifacial/plot_bifi_model_mc.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
cec_inverter = cec_inverters['ABB__MICRO_0_25_I_OUTD_US_208__208V_']

# create a location for site, and get solar position and clearsky data
site_location = location.Location(lat, lon, tz=tz, name='Greensboro, NC')
site_location = location.Location(lat, lon, name='Greensboro, NC')
solar_position = site_location.get_solarposition(times)
cs = site_location.get_clearsky(times)

Expand Down
2 changes: 1 addition & 1 deletion docs/examples/bifacial/plot_bifi_model_pvwatts.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
times = pd.date_range('2021-06-21', '2021-06-22', freq='1min', tz=tz)

# create location object and get clearsky data
site_location = location.Location(lat, lon, tz=tz, name='Greensboro, NC')
site_location = location.Location(lat, lon, name='Greensboro, NC')
cs = site_location.get_clearsky(times)

# get solar position data
Expand Down
2 changes: 1 addition & 1 deletion docs/examples/bifacial/plot_pvfactors_fixed_tilt.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
# First, generate the usual modeling inputs:

times = pd.date_range('2021-06-21', '2021-06-22', freq='1min', tz='Etc/GMT+5')
loc = location.Location(latitude=40, longitude=-80, tz=times.tz)
loc = location.Location(latitude=40, longitude=-80)
sp = loc.get_solarposition(times)
cs = loc.get_clearsky(times)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
tz = 'MST'
lat, lon = 39.755, -105.221

# Create location object to store lat, lon, timezone
site = location.Location(lat, lon, tz=tz)
# Create location object to store lat and lon
site = location.Location(lat, lon)


# Calculate clear-sky GHI and transpose to plane of array
Expand All @@ -31,7 +31,7 @@
def get_irradiance(site_location, date, tilt, surface_azimuth):
# Creates one day's worth of 10 min intervals
times = pd.date_range(date, freq='10min', periods=6*24,
tz=site_location.tz)
tz=tz)
# Generate clearsky data using the Ineichen model, which is the default
# The get_clearsky method returns a dataframe with values for GHI, DNI,
# and DHI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def transpose(irradiance, timeshift):
# is negligible.

# baseline: all calculations done at 1-second scale
location = pvlib.location.Location(40, -80, tz='Etc/GMT+5')
location = pvlib.location.Location(40, -80)
times = pd.date_range('2019-06-01 05:00', '2019-06-01 19:00',
freq='1s', tz='Etc/GMT+5')
solpos = location.get_solarposition(times)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
)

# Create location object, and get solar position and clearsky irradiance data.
location = pvlib.location.Location(latitude, longitude, tz)
location = pvlib.location.Location(latitude, longitude)
solar_position = location.get_solarposition(times)
clearsky = location.get_clearsky(times)

Expand Down
2 changes: 1 addition & 1 deletion docs/examples/spectrum/spectral_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
lat, lon = metadata['latitude'], metadata['longitude']
alt = altitude = metadata['altitude']
tz = 'Etc/GMT+5'
loc = location.Location(lat, lon, tz=tz, name='Greensboro, NC')
loc = location.Location(lat, lon, name='Greensboro, NC')

# Calculate solar position parameters
solpos = loc.get_solarposition(
Expand Down
5 changes: 5 additions & 0 deletions docs/sphinx/source/whatsnew/v0.15.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ Breaking Changes
* Changed output type of :py:func:`pvlib.irradiance.get_total_irradiance`,
:py:func:`pvlib.irradiance.get_sky_diffuse`, and :py:func:`pvlib.irradiance.poa_components`
from ``OrderedDict`` to ``dict``. (:issue:`2750`, :pull:`2800`)
* Removed timezone-related APIs from :py:class:`pvlib.location.Location`:
``tz``, ``pytz``, and ``_zoneinfo``. Removed
:py:func:`pvlib.location.lookup_timezone` and
:py:func:`pvlib.tools.localize_to_utc`. Use timezone-aware timestamps
instead of storing timezone state on ``Location``. (:pull:`2800`)


Deprecations
Expand Down
130 changes: 33 additions & 97 deletions pvlib/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,25 @@
# Will Holmgren, University of Arizona, 2014-2016.

import pathlib
import datetime
import zoneinfo
from dataclasses import dataclass

import pandas as pd
import pytz
import h5py

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

from typing import TypeAlias

class Location:
"""
Location objects are convenient containers for latitude, longitude,
time zone, and altitude data associated with a particular geographic
location. You can also assign a name to a location object.
numeric: TypeAlias = int | float

Location objects have a time-zone attribute ``tz`` (IANA timezone string).

.. deprecated:: 0.15.2

The ``pytz`` attribute is deprecated. Use ``tz`` instead.
@dataclass

Check failure on line 22 in pvlib/location.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E303 too many blank lines (3)
class Location:
"""
Location objects are convenient containers for latitude, longitude,
altitude, and name data associated with a geographic location.

Location objects support the print method.

Expand All @@ -41,17 +37,6 @@
Positive is east of the prime meridian.
Use decimal degrees notation.

tz : time zone as str, int, float, or datetime.tzinfo, default 'UTC'.
See http://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a
list of valid name strings. An ``int`` or ``float`` must be a
whole-number hour offsets from UTC that can be converted to the
IANA-supported 'Etc/GMT-N' format. (Note the limited range of the
offset N and its sign-change convention.) Time zones from the
``zoneinfo`` packages may also be passed.

The `tz` attribute is represented as a valid IANA time zone name
string.

altitude : float, optional
Altitude from sea level in meters.
If not specified, the altitude will be fetched from
Expand All @@ -61,90 +46,43 @@
name : string, optional
Sets the name attribute of the Location object.

Raises
------
ValueError
when the time zone ``tz`` cannot be converted.

zoneinfo.ZoneInfoNotFoundError
when the time zone ``tz`` is not recognizable as an IANA time zone by
the ``zoneinfo.ZoneInfo`` initializer used for internal time-zone
representation.

See also
--------
pvlib.pvsystem.PVSystem
"""

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

if altitude is None:
altitude = lookup_altitude(latitude, longitude)
latitude: numeric
longitude: numeric
altitude: numeric | None = None
name: str | None = None

self.altitude = altitude
self.name = name
def __post_init__(self):
self._validate_value(self.latitude, 'latitude', -90, 90)
self._validate_value(self.longitude, 'longitude', -180, 180)
if self.altitude is None:
self.altitude = lookup_altitude(self.latitude, self.longitude)
else:
self._validate_value(
self.altitude, 'altitude', -430, 8848,
)

def __repr__(self):
attrs = ['name', 'latitude', 'longitude', 'altitude', 'tz']
attrs = ['name', 'latitude', 'longitude', 'altitude']
# Use None as getattr default in case __repr__ is called during
# initialization before all attributes have been assigned.
return ('Location: \n ' + '\n '.join(
f'{attr}: {getattr(self, attr, None)}' for attr in attrs))

@property
def tz(self):
"""The location's IANA time-zone string."""
return str(self._zoneinfo)

@tz.setter
def tz(self, tz_):
# self._zoneinfo holds single source of time-zone truth as IANA name.
if isinstance(tz_, str):
self._zoneinfo = zoneinfo.ZoneInfo(tz_)
elif isinstance(tz_, int):
tz_str = f"Etc/GMT{-tz_:+d}" # noqa: E231
self._zoneinfo = zoneinfo.ZoneInfo(tz_str)
elif isinstance(tz_, float):
if tz_ % 1 != 0:
raise TypeError(
"Floating-point tz has non-zero fractional part: "
f"{tz_}. Only whole-number offsets are supported."
)

tz_str = f"Etc/GMT{-int(tz_):+d}" # noqa: E231
self._zoneinfo = zoneinfo.ZoneInfo(tz_str)
elif isinstance(tz_, datetime.tzinfo):
# Includes time zones generated by zoneinfo packages.
self._zoneinfo = zoneinfo.ZoneInfo(str(tz_))
else:
raise TypeError(
f"invalid tz specification: {tz_}, must be an IANA time zone "
"string, a whole-number int/float UTC offset, or a "
"datetime.tzinfo object (including subclasses)"
@staticmethod
def _validate_value(value: numeric, name: str, lower: numeric, upper: numeric):

Check failure on line 77 in pvlib/location.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E501 line too long (83 > 79 characters)
if not isinstance(value, numeric):
raise TypeError(f'{name} must be a number, got {value!r}')
if not lower <= value <= upper:
raise ValueError(
f'{name} must be between {lower} and {upper}, '
f'got {value!r}'
)

@property
def pytz(self): # pragma: no cover
"""The location's pytz time zone (read only).

.. deprecated:: 0.15.2
The ``pytz`` attribute is deprecated. Use the ``tz`` property
instead.
"""
warn_deprecated(
since='0.15.2',
removal='0.17.0',
name='pytz',
obj_type='attribute',
alternative='tz',
)
return pytz.timezone(str(self._zoneinfo))

@classmethod
def from_tmy(cls, tmy_metadata, tmy_data=None, **kwargs):
"""
Expand Down Expand Up @@ -177,10 +115,9 @@
else:
name = tmy_metadata['Name']

tz = tmy_metadata['TZ']
altitude = tmy_metadata['altitude']

new_object = cls(latitude, longitude, tz=tz, altitude=altitude,
new_object = cls(latitude, longitude, altitude=altitude,
name=name, **kwargs)

# not sure if this should be assigned regardless of input.
Expand Down Expand Up @@ -213,10 +150,9 @@

name = metadata['city']

tz = metadata['TZ']
altitude = metadata['altitude']

new_object = cls(latitude, longitude, tz=tz, altitude=altitude,
new_object = cls(latitude, longitude, altitude=altitude,
name=name, **kwargs)

if data is not None:
Expand Down Expand Up @@ -422,7 +358,7 @@
return result


def lookup_altitude(latitude, longitude):
def lookup_altitude(latitude: numeric, longitude: numeric) -> float:
"""
Look up location altitude from low-resolution altitude map
supplied with pvlib. The data for this map comes from multiple open data
Expand Down
28 changes: 0 additions & 28 deletions pvlib/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,35 +119,7 @@
res = np.degrees(np.arctan(number))
return res


def localize_to_utc(time, location):
"""
Converts ``time`` to UTC, localizing if necessary using location.

Parameters
----------
time : datetime.datetime, pandas.DatetimeIndex,
or pandas.Series/DataFrame with a DatetimeIndex.
location : pvlib.Location object (unused if ``time`` is localized)

Returns
-------
datetime.datetime or pandas object localized to UTC.
"""
if isinstance(time, dt.datetime):
if time.tzinfo is None:
time = time.replace(tzinfo=zoneinfo.ZoneInfo(location.tz))
time_utc = time.astimezone(timezone.utc)
else:
try:
time_utc = time.tz_convert('UTC')
except TypeError:
time_utc = time.tz_localize(location.tz).tz_convert('UTC')

return time_utc


def datetime_to_djd(time):

Check failure on line 122 in pvlib/tools.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E302 expected 2 blank lines, found 1
"""
Converts a datetime to the Dublin Julian Day

Expand Down
12 changes: 8 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import zoneinfo

Check failure on line 1 in tests/conftest.py

View workflow job for this annotation

GitHub Actions / flake8-linter

F401 'zoneinfo' imported but unused
from pathlib import Path
import platform
import warnings
Expand Down Expand Up @@ -236,13 +237,16 @@


@pytest.fixture()
def golden():
return Location(39.742476, -105.1786, 'America/Denver', 1830.14)
def golden_location():
return Location(39.742476, -105.1786, 1830.14)

@pytest.fixture()

Check failure on line 243 in tests/conftest.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E302 expected 2 blank lines, found 1
def golden_tz():
return 'America/Denver'

@pytest.fixture()

Check failure on line 247 in tests/conftest.py

View workflow job for this annotation

GitHub Actions / flake8-linter

E302 expected 2 blank lines, found 1
def golden_mst():
return Location(39.742476, -105.1786, 'MST', 1830.14)
def golden_mst_tz():
return 'MST'


@pytest.fixture()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from pvlib._deprecation import pvlibDeprecationWarning

# fixtures create realistic test input data
# test input data generated at Location(32.2, -111, 'US/Arizona', 700)
# test input data generated at Location(32.2, -111, 700)
# test input data is hard coded to avoid dependencies on other parts of pvlib


Expand Down
Loading
Loading