Skip to content

Commit

Permalink
Handle time normalization for nonexistent and ambiguous times
Browse files Browse the repository at this point in the history
  • Loading branch information
scttnlsn committed Jul 15, 2024
1 parent 048b70f commit 30bb319
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 2 deletions.
3 changes: 2 additions & 1 deletion docs/sphinx/source/whatsnew/v0.11.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Enhancements

Bug fixes
~~~~~~~~~

* Handle DST transitions that happen at midnight in :py:func:`pvlib.solarposition.hour_angle`
(:issue:`2132` :pull:`2133`)

Testing
~~~~~~~
Expand Down
13 changes: 12 additions & 1 deletion pvlib/solarposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -1392,7 +1392,18 @@ def hour_angle(times, longitude, equation_of_time):
times = times.tz_localize('utc')
tzs = np.array([ts.utcoffset().total_seconds() for ts in times]) / 3600

hrs_minus_tzs = (times - times.normalize()) / pd.Timedelta('1h') - tzs
# Some timezones have a DST shift at midnight:
# 11:59pm -> 1:00am - results in a nonexistent midnight
# 12:59am -> 12:00am - results in an ambiguous midnight
# We remove the timezone before normalizing for this reason.
naive_normalized_times = times.tz_localize(None).normalize()

# Use Pandas functionality for shifting nonexistent times forward
# or infering ambiguous times (which arose from normalizing)
normalized_times = naive_normalized_times.tz_localize(
times.tz, nonexistent='shift_forward', ambiguous='infer')

hrs_minus_tzs = (times - normalized_times) / pd.Timedelta('1h') - tzs

# ensure array return instead of a version-dependent pandas <T>Index
return np.asarray(
Expand Down
22 changes: 22 additions & 0 deletions pvlib/tests/test_solarposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,28 @@ def test_hour_angle():
assert np.allclose(hours, expected)


def test_hour_angle_with_tricky_timezones():
# tests timezones that have a DST shift at midnight

eot = np.array([-3.935172, -4.117227])

longitude = 70.6693
times = pd.DatetimeIndex([
'2014-09-07 10:00:00',
'2014-09-07 11:00:00',
]).tz_localize('America/Santiago')
# should not raise `pytz.exceptions.NonExistentTimeError`
solarposition.hour_angle(times, longitude, eot)

longitude = 82.3666
times = pd.DatetimeIndex([
'2014-11-02 10:00:00',
'2014-11-02 11:00:00',
]).tz_localize('America/Havana')
# should not raise `pytz.exceptions.AmbiguousTimeError`
solarposition.hour_angle(times, longitude, eot)


def test_sun_rise_set_transit_geometric(expected_rise_set_spa, golden_mst):
"""Test geometric calculations for sunrise, sunset, and transit times"""
times = expected_rise_set_spa.index
Expand Down

0 comments on commit 30bb319

Please sign in to comment.