Skip to content

Commit 30bb319

Browse files
committed
Handle time normalization for nonexistent and ambiguous times
1 parent 048b70f commit 30bb319

File tree

3 files changed

+36
-2
lines changed

3 files changed

+36
-2
lines changed

docs/sphinx/source/whatsnew/v0.11.1.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ Enhancements
1717

1818
Bug fixes
1919
~~~~~~~~~
20-
20+
* Handle DST transitions that happen at midnight in :py:func:`pvlib.solarposition.hour_angle`
21+
(:issue:`2132` :pull:`2133`)
2122

2223
Testing
2324
~~~~~~~

pvlib/solarposition.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1392,7 +1392,18 @@ def hour_angle(times, longitude, equation_of_time):
13921392
times = times.tz_localize('utc')
13931393
tzs = np.array([ts.utcoffset().total_seconds() for ts in times]) / 3600
13941394

1395-
hrs_minus_tzs = (times - times.normalize()) / pd.Timedelta('1h') - tzs
1395+
# Some timezones have a DST shift at midnight:
1396+
# 11:59pm -> 1:00am - results in a nonexistent midnight
1397+
# 12:59am -> 12:00am - results in an ambiguous midnight
1398+
# We remove the timezone before normalizing for this reason.
1399+
naive_normalized_times = times.tz_localize(None).normalize()
1400+
1401+
# Use Pandas functionality for shifting nonexistent times forward
1402+
# or infering ambiguous times (which arose from normalizing)
1403+
normalized_times = naive_normalized_times.tz_localize(
1404+
times.tz, nonexistent='shift_forward', ambiguous='infer')
1405+
1406+
hrs_minus_tzs = (times - normalized_times) / pd.Timedelta('1h') - tzs
13961407

13971408
# ensure array return instead of a version-dependent pandas <T>Index
13981409
return np.asarray(

pvlib/tests/test_solarposition.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,28 @@ def test_hour_angle():
673673
assert np.allclose(hours, expected)
674674

675675

676+
def test_hour_angle_with_tricky_timezones():
677+
# tests timezones that have a DST shift at midnight
678+
679+
eot = np.array([-3.935172, -4.117227])
680+
681+
longitude = 70.6693
682+
times = pd.DatetimeIndex([
683+
'2014-09-07 10:00:00',
684+
'2014-09-07 11:00:00',
685+
]).tz_localize('America/Santiago')
686+
# should not raise `pytz.exceptions.NonExistentTimeError`
687+
solarposition.hour_angle(times, longitude, eot)
688+
689+
longitude = 82.3666
690+
times = pd.DatetimeIndex([
691+
'2014-11-02 10:00:00',
692+
'2014-11-02 11:00:00',
693+
]).tz_localize('America/Havana')
694+
# should not raise `pytz.exceptions.AmbiguousTimeError`
695+
solarposition.hour_angle(times, longitude, eot)
696+
697+
676698
def test_sun_rise_set_transit_geometric(expected_rise_set_spa, golden_mst):
677699
"""Test geometric calculations for sunrise, sunset, and transit times"""
678700
times = expected_rise_set_spa.index

0 commit comments

Comments
 (0)