- 
                Notifications
    
You must be signed in to change notification settings  - Fork 1.1k
 
Perform dayofyear-based calculations according to UTC, not local time #2055
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 53 commits
6c4c32a
              9ce02d5
              0ad7a68
              05961d2
              45a36cb
              b00a2d4
              7bb7e02
              6d1ab57
              3fed282
              6f98df7
              fe5b5c1
              97afcb8
              defddfd
              a08ca79
              6baa1ed
              4515366
              83a61ba
              745579c
              b6d238a
              fa62003
              5d80876
              3e95694
              59cb82b
              6e1c251
              0baa338
              cd45c74
              1e6914d
              dd78f3e
              35e0756
              758ea98
              5102347
              1c940fb
              12c6a49
              ab7d307
              82aa0df
              c4862b5
              57d052e
              85eae01
              3fc5579
              f255e8b
              63df590
              639ce7f
              33f778f
              16f0e5a
              2745954
              eb2707f
              ab953b5
              b8246c0
              7da25f4
              70d06cb
              dea79d3
              2143c94
              80a6afb
              87cd719
              c4b65e1
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 
          
            
          
           | 
    @@ -23,7 +23,7 @@ | |||||||||||||||||||||
| import scipy.optimize as so | ||||||||||||||||||||||
| import warnings | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| from pvlib import atmosphere | ||||||||||||||||||||||
| from pvlib import atmosphere, tools | ||||||||||||||||||||||
| from pvlib.tools import datetime_to_djd, djd_to_datetime | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| 
          
            
          
           | 
    @@ -199,11 +199,7 @@ def spa_c(time, latitude, longitude, pressure=101325, altitude=0, | |||||||||||||||||||||
| raise ImportError('Could not import built-in SPA calculator. ' + | ||||||||||||||||||||||
| 'You may need to recompile the SPA code.') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # if localized, convert to UTC. otherwise, assume UTC. | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| time_utc = time.tz_convert('UTC') | ||||||||||||||||||||||
| except TypeError: | ||||||||||||||||||||||
| time_utc = time | ||||||||||||||||||||||
| time_utc = tools._pandas_to_utc(time) | ||||||||||||||||||||||
                
      
                  cwhanse marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| spa_out = [] | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| 
          
            
          
           | 
    @@ -378,7 +374,9 @@ def spa_python(time, latitude, longitude, | |||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| spa = _spa_python_import(how) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| delta_t = delta_t or spa.calculate_deltat(time.year, time.month) | ||||||||||||||||||||||
                
      
                  cwhanse marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||||||||||||||||||||||
| if not delta_t: | ||||||||||||||||||||||
| time_utc = tools._pandas_to_utc(time) | ||||||||||||||||||||||
| delta_t = spa.calculate_deltat(time_utc.year, time_utc.month) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| app_zenith, zenith, app_elevation, elevation, azimuth, eot = \ | ||||||||||||||||||||||
| spa.solar_position(unixtime, lat, lon, elev, pressure, temperature, | ||||||||||||||||||||||
| 
          
            
          
           | 
    @@ -452,12 +450,13 @@ def sun_rise_set_transit_spa(times, latitude, longitude, how='numpy', | |||||||||||||||||||||
| raise ValueError('times must be localized') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # must convert to midnight UTC on day of interest | ||||||||||||||||||||||
| utcday = pd.DatetimeIndex(times.date).tz_localize('UTC') | ||||||||||||||||||||||
| unixtime = _datetime_to_unixtime(utcday) | ||||||||||||||||||||||
| times_utc = times.tz_convert('UTC') | ||||||||||||||||||||||
| unixtime = _datetime_to_unixtime(times_utc.normalize()) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| spa = _spa_python_import(how) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| delta_t = delta_t or spa.calculate_deltat(times.year, times.month) | ||||||||||||||||||||||
| if not delta_t: | ||||||||||||||||||||||
| delta_t = spa.calculate_deltat(times_utc.year, times_utc.month) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| transit, sunrise, sunset = spa.transit_sunrise_sunset( | ||||||||||||||||||||||
| unixtime, lat, lon, delta_t, numthreads) | ||||||||||||||||||||||
| 
          
            
          
           | 
    @@ -581,12 +580,11 @@ def sun_rise_set_transit_ephem(times, latitude, longitude, | |||||||||||||||||||||
| sunrise = [] | ||||||||||||||||||||||
| sunset = [] | ||||||||||||||||||||||
| trans = [] | ||||||||||||||||||||||
| for thetime in times: | ||||||||||||||||||||||
| thetime = thetime.to_pydatetime() | ||||||||||||||||||||||
| for thetime in tools._pandas_to_utc(times): | ||||||||||||||||||||||
| # older versions of pyephem ignore timezone when converting to its | ||||||||||||||||||||||
| # internal datetime format, so convert to UTC here to support | ||||||||||||||||||||||
| # all versions. GH #1449 | ||||||||||||||||||||||
| obs.date = ephem.Date(thetime.astimezone(dt.timezone.utc)) | ||||||||||||||||||||||
| obs.date = ephem.Date(thetime) | ||||||||||||||||||||||
| sunrise.append(_ephem_to_timezone(rising(sun), tzinfo)) | ||||||||||||||||||||||
| sunset.append(_ephem_to_timezone(setting(sun), tzinfo)) | ||||||||||||||||||||||
| trans.append(_ephem_to_timezone(transit(sun), tzinfo)) | ||||||||||||||||||||||
| 
          
            
          
           | 
    @@ -644,11 +642,7 @@ def pyephem(time, latitude, longitude, altitude=0, pressure=101325, | |||||||||||||||||||||
| except ImportError: | ||||||||||||||||||||||
| raise ImportError('PyEphem must be installed') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # if localized, convert to UTC. otherwise, assume UTC. | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| time_utc = time.tz_convert('UTC') | ||||||||||||||||||||||
| except TypeError: | ||||||||||||||||||||||
| time_utc = time | ||||||||||||||||||||||
| time_utc = tools._pandas_to_utc(time) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| sun_coords = pd.DataFrame(index=time) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| 
          
            
          
           | 
    @@ -765,11 +759,7 @@ def ephemeris(time, latitude, longitude, pressure=101325, temperature=12): | |||||||||||||||||||||
| # the SPA algorithm needs time to be expressed in terms of | ||||||||||||||||||||||
| # decimal UTC hours of the day of the year. | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # if localized, convert to UTC. otherwise, assume UTC. | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| time_utc = time.tz_convert('UTC') | ||||||||||||||||||||||
| except TypeError: | ||||||||||||||||||||||
| time_utc = time | ||||||||||||||||||||||
| time_utc = tools._pandas_to_utc(time) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # strip out the day of the year and calculate the decimal hour | ||||||||||||||||||||||
| DayOfYear = time_utc.dayofyear | ||||||||||||||||||||||
| 
          
            
          
           | 
    @@ -956,7 +946,10 @@ def pyephem_earthsun_distance(time): | |||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| sun = ephem.Sun() | ||||||||||||||||||||||
| earthsun = [] | ||||||||||||||||||||||
| for thetime in time: | ||||||||||||||||||||||
| for thetime in tools._pandas_to_utc(time): | ||||||||||||||||||||||
| # older versions of pyephem ignore timezone when converting to its | ||||||||||||||||||||||
| # internal datetime format, so convert to UTC here to support | ||||||||||||||||||||||
| # all versions. GH #1449 | ||||||||||||||||||||||
| sun.compute(ephem.Date(thetime)) | ||||||||||||||||||||||
| earthsun.append(sun.earth_distance) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| 
          
            
          
           | 
    @@ -1013,7 +1006,9 @@ def nrel_earthsun_distance(time, how='numpy', delta_t=67.0, numthreads=4): | |||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| spa = _spa_python_import(how) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| delta_t = delta_t or spa.calculate_deltat(time.year, time.month) | ||||||||||||||||||||||
| if not delta_t: | ||||||||||||||||||||||
| time_utc = tools._pandas_to_utc(time) | ||||||||||||||||||||||
| delta_t = spa.calculate_deltat(time_utc.year, time_utc.month) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| dist = spa.earthsun_distance(unixtime, delta_t, numthreads) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| 
          
            
          
           | 
    @@ -1386,22 +1381,26 @@ def hour_angle(times, longitude, equation_of_time): | |||||||||||||||||||||
| equation_of_time_spencer71 | ||||||||||||||||||||||
| equation_of_time_pvcdrom | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # times must be localized | ||||||||||||||||||||||
| if not times.tz: | ||||||||||||||||||||||
| raise ValueError('times must be localized') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # hours - timezone = (times - normalized_times) - (naive_times - times) | ||||||||||||||||||||||
| if times.tz is None: | ||||||||||||||||||||||
| times = times.tz_localize('utc') | ||||||||||||||||||||||
| 
         
      Comment on lines
    
      +1385
     to 
      -1391
    
   
  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I agree with this change. The docstring says the input timestamps "must be localized to the timezone for the longitude", which is inconsistent with the previous behavior (localize to UTC if not already localized), so I can see some change being needed here. But why is this the correct change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was added in accordance with  pvlib-python/pvlib/solarposition.py Lines 448 to 452 in 524fa55 
 pvlib-python/pvlib/solarposition.py Lines 560 to 564 in 524fa55 
  | 
||||||||||||||||||||||
| tzs = np.array([ts.utcoffset().total_seconds() for ts in times]) / 3600 | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| hrs_minus_tzs = (times - times.normalize()) / pd.Timedelta('1h') - tzs | ||||||||||||||||||||||
| hrs_minus_tzs = _times_to_hours_after_local_midnight(times) - tzs | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # ensure array return instead of a version-dependent pandas <T>Index | ||||||||||||||||||||||
| return np.asarray( | ||||||||||||||||||||||
| 15. * (hrs_minus_tzs - 12.) + longitude + equation_of_time / 4.) | ||||||||||||||||||||||
| return 15. * (hrs_minus_tzs - 12.) + longitude + equation_of_time / 4. | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| def _hour_angle_to_hours(times, hourangle, longitude, equation_of_time): | ||||||||||||||||||||||
| """converts hour angles in degrees to hours as a numpy array""" | ||||||||||||||||||||||
| if times.tz is None: | ||||||||||||||||||||||
| times = times.tz_localize('utc') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # times must be localized | ||||||||||||||||||||||
                
      
                  cwhanse marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||||||||||||||||||||||
| if not times.tz: | ||||||||||||||||||||||
| raise ValueError('times must be localized') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| tzs = np.array([ts.utcoffset().total_seconds() for ts in times]) / 3600 | ||||||||||||||||||||||
| hours = (hourangle - longitude - equation_of_time / 4.) / 15. + 12. + tzs | ||||||||||||||||||||||
| return np.asarray(hours) | ||||||||||||||||||||||
| 
        
          
        
         | 
    @@ -1411,18 +1410,26 @@ def _local_times_from_hours_since_midnight(times, hours): | |||||||||||||||||||||
| """ | ||||||||||||||||||||||
| converts hours since midnight from an array of floats to localized times | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| tz_info = times.tz # pytz timezone info | ||||||||||||||||||||||
| naive_times = times.tz_localize(None) # naive but still localized | ||||||||||||||||||||||
| # normalize local, naive times to previous midnight and add the hours until | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # times must be localized | ||||||||||||||||||||||
| if not times.tz: | ||||||||||||||||||||||
| raise ValueError('times must be localized') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # normalize local times to previous local midnight and add the hours until | ||||||||||||||||||||||
| # sunrise, sunset, and transit | ||||||||||||||||||||||
| return pd.DatetimeIndex( | ||||||||||||||||||||||
| naive_times.normalize() + pd.to_timedelta(hours, unit='h'), tz=tz_info) | ||||||||||||||||||||||
| return times.normalize() + pd.to_timedelta(hours, unit='h') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| def _times_to_hours_after_local_midnight(times): | ||||||||||||||||||||||
| """convert local pandas datetime indices to array of hours as floats""" | ||||||||||||||||||||||
| times = times.tz_localize(None) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # times must be localized | ||||||||||||||||||||||
| if not times.tz: | ||||||||||||||||||||||
| raise ValueError('times must be localized') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| hrs = (times - times.normalize()) / pd.Timedelta('1h') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # ensure array return instead of a version-dependent pandas <T>Index | ||||||||||||||||||||||
| return np.array(hrs) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| 
          
            
          
           | 
    @@ -1468,6 +1475,11 @@ def sun_rise_set_transit_geometric(times, latitude, longitude, declination, | |||||||||||||||||||||
| CRC Press (2012) | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| # times must be localized | ||||||||||||||||||||||
| if not times.tz: | ||||||||||||||||||||||
| raise ValueError('times must be localized') | ||||||||||||||||||||||
| 
     | 
||||||||||||||||||||||
| latitude_rad = np.radians(latitude) # radians | ||||||||||||||||||||||
| sunset_angle_rad = np.arccos(-np.tan(declination) * np.tan(latitude_rad)) | ||||||||||||||||||||||
| sunset_angle = np.degrees(sunset_angle_rad) # degrees | ||||||||||||||||||||||
| 
          
            
          
           | 
    ||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.