2222import pandas as pd
2323import scipy .optimize as so
2424import warnings
25- import datetime
2625
2726from pvlib import atmosphere
2827from pvlib .tools import datetime_to_djd , djd_to_datetime
2928
3029
31- NS_PER_HR = 1.e9 * 3600. # nanoseconds per hour
32-
33-
3430def get_solarposition (time , latitude , longitude ,
3531 altitude = None , pressure = None ,
3632 method = 'nrel_numpy' ,
@@ -51,13 +47,13 @@ def get_solarposition(time, latitude, longitude,
5147 Longitude in decimal degrees. Positive east of prime meridian,
5248 negative to west.
5349
54- altitude : None or float, default None
55- If None , computed from pressure. Assumed to be 0 m
56- if pressure is also None .
50+ altitude : float, optional
51+ If not specified , computed from `` pressure`` . Assumed to be 0 m
52+ if `` pressure`` is not supplied .
5753
58- pressure : None or float, default None
59- If None , computed from altitude. Assumed to be 101325 Pa
60- if altitude is also None .
54+ pressure : float, optional
55+ If not specified , computed from `` altitude`` . Assumed to be 101325 Pa
56+ if `` altitude`` is not supplied .
6157
6258 method : string, default 'nrel_numpy'
6359 'nrel_numpy' uses an implementation of the NREL SPA algorithm
@@ -89,7 +85,7 @@ def get_solarposition(time, latitude, longitude,
8985 solar radiation applications. Solar Energy, vol. 81, no. 6, p. 838,
9086 2007.
9187
92- .. [3] NREL SPA code: http ://rredc .nrel.gov/solar/codesandalgorithms /spa/
88+ .. [3] NREL SPA code: https ://midcdmz .nrel.gov/spa/
9389 """
9490
9591 if altitude is None and pressure is None :
@@ -132,7 +128,7 @@ def get_solarposition(time, latitude, longitude,
132128def spa_c (time , latitude , longitude , pressure = 101325 , altitude = 0 ,
133129 temperature = 12 , delta_t = 67.0 ,
134130 raw_spa_output = False ):
135- """
131+ r """
136132 Calculate the solar position using the C implementation of the NREL
137133 SPA code.
138134
@@ -161,7 +157,7 @@ def spa_c(time, latitude, longitude, pressure=101325, altitude=0,
161157 Temperature in C
162158 delta_t : float, default 67.0
163159 Difference between terrestrial time and UT1.
164- USNO has previous values and predictions.
160+ USNO has previous values and predictions [3]_ .
165161 raw_spa_output : bool, default False
166162 If true, returns the raw SPA output.
167163
@@ -177,17 +173,16 @@ def spa_c(time, latitude, longitude, pressure=101325, altitude=0,
177173
178174 References
179175 ----------
180- .. [1] NREL SPA reference:
181- http://rredc.nrel.gov/solar/codesandalgorithms/spa/
182- NREL SPA C files: https://midcdmz.nrel.gov/spa/
176+ .. [1] NREL SPA reference: https://midcdmz.nrel.gov/spa/
183177
184178 Note: The ``timezone`` field in the SPA C files is replaced with
185179 ``time_zone`` to avoid a nameclash with the function ``__timezone`` that is
186180 redefined by Python>=3.5. This issue is
187181 `Python bug 24643 <https://bugs.python.org/issue24643>`_.
188182
189- .. [2] USNO delta T:
190- http://www.usno.navy.mil/USNO/earth-orientation/eo-products/long-term
183+ .. [2] Delta T: https://en.wikipedia.org/wiki/%CE%94T_(timekeeping)
184+
185+ .. [3] USNO delta T: https://maia.usno.navy.mil/products/deltaT
191186
192187 See also
193188 --------
@@ -274,6 +269,19 @@ def _spa_python_import(how):
274269 return spa
275270
276271
272+ def _datetime_to_unixtime (dtindex ):
273+ # convert a pandas datetime index to unixtime, making sure to handle
274+ # different pandas units (ns, us, etc) and time zones correctly
275+ if dtindex .tz is not None :
276+ # epoch is 1970-01-01 00:00 UTC, but we need to match the input tz
277+ # for compatibility with older pandas versions (e.g. v1.3.5)
278+ epoch = pd .Timestamp ("1970-01-01" , tz = "UTC" ).tz_convert (dtindex .tz )
279+ else :
280+ epoch = pd .Timestamp ("1970-01-01" )
281+
282+ return np .array ((dtindex - epoch ) / pd .Timedelta ("1s" ))
283+
284+
277285def spa_python (time , latitude , longitude ,
278286 altitude = 0 , pressure = 101325 , temperature = 12 , delta_t = 67.0 ,
279287 atmos_refract = None , how = 'numpy' , numthreads = 4 ):
@@ -312,7 +320,7 @@ def spa_python(time, latitude, longitude,
312320 *Note: delta_t = None will break code using nrel_numba,
313321 this will be fixed in a future version.*
314322 The USNO has historical and forecasted delta_t [3]_.
315- atmos_refrac : None or float, optional, default None
323+ atmos_refrac : float, optional
316324 The approximate atmospheric refraction (in degrees)
317325 at sunrise and sunset.
318326 how : str, optional, default 'numpy'
@@ -344,7 +352,7 @@ def spa_python(time, latitude, longitude,
344352 2007.
345353
346354 .. [3] USNO delta T:
347- http ://www .usno.navy.mil/USNO/earth-orientation/eo- products/long-term
355+ https ://maia .usno.navy.mil/products/deltaT
348356
349357 See also
350358 --------
@@ -366,7 +374,7 @@ def spa_python(time, latitude, longitude,
366374 except (TypeError , ValueError ):
367375 time = pd .DatetimeIndex ([time , ])
368376
369- unixtime = np . array (time . view ( np . int64 ) / 10 ** 9 )
377+ unixtime = _datetime_to_unixtime (time )
370378
371379 spa = _spa_python_import (how )
372380
@@ -445,7 +453,7 @@ def sun_rise_set_transit_spa(times, latitude, longitude, how='numpy',
445453
446454 # must convert to midnight UTC on day of interest
447455 utcday = pd .DatetimeIndex (times .date ).tz_localize ('UTC' )
448- unixtime = np . array (utcday . view ( np . int64 ) / 10 ** 9 )
456+ unixtime = _datetime_to_unixtime (utcday )
449457
450458 spa = _spa_python_import (how )
451459
@@ -578,7 +586,7 @@ def sun_rise_set_transit_ephem(times, latitude, longitude,
578586 # older versions of pyephem ignore timezone when converting to its
579587 # internal datetime format, so convert to UTC here to support
580588 # all versions. GH #1449
581- obs .date = ephem .Date (thetime .astimezone (datetime .timezone .utc ))
589+ obs .date = ephem .Date (thetime .astimezone (dt .timezone .utc ))
582590 sunrise .append (_ephem_to_timezone (rising (sun ), tzinfo ))
583591 sunset .append (_ephem_to_timezone (setting (sun ), tzinfo ))
584592 trans .append (_ephem_to_timezone (transit (sun ), tzinfo ))
@@ -832,7 +840,7 @@ def ephemeris(time, latitude, longitude, pressure=101325, temperature=12):
832840 # Calculate refraction correction
833841 Elevation = SunEl
834842 TanEl = pd .Series (np .tan (np .radians (Elevation )), index = time_utc )
835- Refract = pd .Series (0 , index = time_utc )
843+ Refract = pd .Series (0. , index = time_utc )
836844
837845 Refract [(Elevation > 5 ) & (Elevation <= 85 )] = (
838846 58.1 / TanEl - 0.07 / (TanEl ** 3 ) + 8.6e-05 / (TanEl ** 5 ))
@@ -1001,7 +1009,7 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=67.0, numthreads=4):
10011009 except (TypeError , ValueError ):
10021010 time = pd .DatetimeIndex ([time , ])
10031011
1004- unixtime = np . array (time . view ( np . int64 ) / 10 ** 9 )
1012+ unixtime = _datetime_to_unixtime (time )
10051013
10061014 spa = _spa_python_import (how )
10071015
@@ -1327,9 +1335,9 @@ def solar_zenith_analytical(latitude, hourangle, declination):
13271335 .. [4] `Wikipedia: Solar Zenith Angle
13281336 <https://en.wikipedia.org/wiki/Solar_zenith_angle>`_
13291337
1330- .. [5] `PVCDROM: Sun's Position
1331- <http ://www.pveducation.org/pvcdrom/2- properties-sunlight/
1332- suns-position >`_
1338+ .. [5] `PVCDROM: Elevation Angle
1339+ <https ://www.pveducation.org/pvcdrom/properties-of -sunlight/
1340+ elevation-angle >`_
13331341
13341342 See Also
13351343 --------
@@ -1378,21 +1386,23 @@ def hour_angle(times, longitude, equation_of_time):
13781386 equation_of_time_spencer71
13791387 equation_of_time_pvcdrom
13801388 """
1381- naive_times = times .tz_localize (None ) # naive but still localized
13821389 # hours - timezone = (times - normalized_times) - (naive_times - times)
1383- hrs_minus_tzs = 1 / NS_PER_HR * (
1384- 2 * times .view (np .int64 ) - times .normalize ().view (np .int64 ) -
1385- naive_times .view (np .int64 ))
1390+ if times .tz is None :
1391+ times = times .tz_localize ('utc' )
1392+ tzs = np .array ([ts .utcoffset ().total_seconds () for ts in times ]) / 3600
1393+
1394+ hrs_minus_tzs = (times - times .normalize ()) / pd .Timedelta ('1h' ) - tzs
1395+
13861396 # ensure array return instead of a version-dependent pandas <T>Index
13871397 return np .asarray (
13881398 15. * (hrs_minus_tzs - 12. ) + longitude + equation_of_time / 4. )
13891399
13901400
13911401def _hour_angle_to_hours (times , hourangle , longitude , equation_of_time ):
13921402 """converts hour angles in degrees to hours as a numpy array"""
1393- naive_times = times .tz_localize ( None ) # naive but still localized
1394- tzs = 1 / NS_PER_HR * (
1395- naive_times . view ( np . int64 ) - times . view ( np . int64 ))
1403+ if times .tz is None :
1404+ times = times . tz_localize ( 'utc' )
1405+ tzs = np . array ([ ts . utcoffset (). total_seconds () for ts in times ]) / 3600
13961406 hours = (hourangle - longitude - equation_of_time / 4. ) / 15. + 12. + tzs
13971407 return np .asarray (hours )
13981408
@@ -1406,16 +1416,13 @@ def _local_times_from_hours_since_midnight(times, hours):
14061416 # normalize local, naive times to previous midnight and add the hours until
14071417 # sunrise, sunset, and transit
14081418 return pd .DatetimeIndex (
1409- (naive_times .normalize ().view (np .int64 ) +
1410- (hours * NS_PER_HR ).astype (np .int64 )).astype ('datetime64[ns]' ),
1411- tz = tz_info )
1419+ naive_times .normalize () + pd .to_timedelta (hours , unit = 'h' ), tz = tz_info )
14121420
14131421
14141422def _times_to_hours_after_local_midnight (times ):
14151423 """convert local pandas datetime indices to array of hours as floats"""
14161424 times = times .tz_localize (None )
1417- hrs = 1 / NS_PER_HR * (
1418- times .view (np .int64 ) - times .normalize ().view (np .int64 ))
1425+ hrs = (times - times .normalize ()) / pd .Timedelta ('1h' )
14191426 return np .array (hrs )
14201427
14211428
0 commit comments