1515DAYS_PER_YEAR = 365.25
1616
1717
18- def julian_day ( date ) :
19- """Day of year as a fractional value .
18+ def _julian_day_scalar ( d : datetime . datetime ) -> float :
19+ """Compute fractional day-of- year for a single datetime .
2020
2121 Parameters
2222 ----------
23- date : array-like
24- Input date .
23+ d : datetime.datetime
24+ Input datetime .
2525
2626 Returns
2727 -------
28- array-like
29- Number of days since January 1st of the same year, including
30- fractional day component based on the time of day.
28+ float
29+ Day-of-year as a fractional value, where January 1st 00:00 is 0.0.
30+ """
31+ if d .tzinfo is not None and d .tzinfo .utcoffset (d ) is not None :
32+ year_start = datetime .datetime (d .year , 1 , 1 , tzinfo = d .tzinfo )
33+ else :
34+ year_start = datetime .datetime (d .year , 1 , 1 )
35+ delta = d - year_start
36+ return delta .days + delta .seconds / 86400.0
37+
38+
39+ def julian_day (date ) -> float :
40+ """Compute fractional day-of-year.
41+
42+ Parameters
43+ ----------
44+ date : datetime.datetime, numpy.datetime64, or numpy.ndarray
45+ Input date(s). May be a scalar or array of numpy.datetime64 or
46+ datetime.datetime objects.
47+
48+ Returns
49+ -------
50+ float or numpy.ndarray
51+ Fractional day-of-year value(s), where January 1st 00:00 is 0.0.
3152
3253 Notes
3354 -----
34- This is not the astronomical Julian Day Number. It is the day-of-year
35- (DOY) expressed as a floating-point value.
55+ This function supports:
56+
57+ - Python ``datetime.datetime`` scalars
58+ - ``numpy.datetime64`` scalars
59+ - numpy arrays of dtype ``datetime64``
60+
61+ This enables compatibility with xarray, which internally represents
62+ time coordinates using numpy.datetime64.
3663 """
37- if date .tzinfo is not None and date .tzinfo .utcoffset (date ) is not None :
38- year_start = datetime .datetime (date .year , 1 , 1 , tzinfo = date .tzinfo )
39- else :
40- year_start = datetime .datetime (date .year , 1 , 1 )
41- delta = date - year_start
42- return delta .days + delta .seconds / 86400.0
64+ # numpy.datetime64 scalar
65+ if isinstance (date , np .datetime64 ):
66+ d_obj = date .astype ("datetime64[us]" ).astype (object )
67+ return _julian_day_scalar (d_obj )
68+
69+ # numpy datetime64 array
70+ if isinstance (date , np .ndarray ) and np .issubdtype (date .dtype , np .datetime64 ):
71+ obj = date .astype ("datetime64[us]" ).astype (object )
72+ return np .vectorize (_julian_day_scalar , otypes = [float ])(obj )
4373
74+ # python datetime.datetime
75+ return _julian_day_scalar (date )
4476
45- def solar_declination_angle (date ):
46- """Compute solar declination and time correction.
77+
78+ def solar_declination_angle (date ) -> tuple [float , float ]:
79+ """Compute solar declination angle and time correction.
4780
4881 Parameters
4982 ----------
50- date : array-like
51- Input date.
83+ date : datetime.datetime, numpy.datetime64, or numpy.ndarray
84+ Input date(s). May be scalar or array .
5285
5386 Returns
5487 -------
55- tuple of float
56- declination : float
57- Solar declination angle [degrees].
58- time_correction : float
59- Time correction factor [hour-degrees].
88+ tuple of float or numpy.ndarray
89+ Tuple containing:
90+
91+ declination : float or numpy.ndarray
92+ Solar declination angle in degrees.
93+
94+ time_correction : float or numpy.ndarray
95+ Time correction factor in hour-degrees.
6096
6197 Notes
6298 -----
6399 Uses a trigonometric approximation based on the fractional year angle.
100+
101+ This function supports numpy.datetime64 inputs, allowing use with xarray
102+ and other numpy-based datetime containers.
103+
104+ Scalar inputs return floats, array inputs return numpy arrays.
64105 """
65106 angle = julian_day (date ) / DAYS_PER_YEAR * np .pi * 2
66107
67- # declination in [degrees]
68- declination = float (
108+ declination = (
69109 0.396372
70110 - 22.91327 * np .cos (angle )
71111 + 4.025430 * np .sin (angle )
@@ -74,62 +114,68 @@ def solar_declination_angle(date):
74114 - 0.154527 * np .cos (3 * angle )
75115 + 0.084798 * np .sin (3 * angle )
76116 )
77- # time correction in [ h.degrees ]
78- time_correction = float (
117+
118+ time_correction = (
79119 0.004297
80120 + 0.107029 * np .cos (angle )
81121 - 1.837877 * np .sin (angle )
82122 - 0.837378 * np .cos (2 * angle )
83123 - 2.340475 * np .sin (2 * angle )
84124 )
125+
126+ if np .ndim (declination ) == 0 :
127+ return float (declination ), float (time_correction )
128+
85129 return declination , time_correction
86130
87131
88- def cos_solar_zenith_angle (date , latitudes , longitudes ):
89- """Cosine of solar zenith angle.
132+ def cos_solar_zenith_angle (
133+ date : datetime .datetime ,
134+ latitudes ,
135+ longitudes ,
136+ ):
137+ """Compute cosine of the solar zenith angle.
90138
91139 Parameters
92140 ----------
93- date: datetime.datetime
94- Date
95- latitudes: array-like
96- Latitude [ degrees]
97- longitudes: array-like
98- Longitude [ degrees]
141+ date : datetime.datetime
142+ Date and time of the observation.
143+ latitudes : array-like
144+ Latitude values in degrees.
145+ longitudes : array-like
146+ Longitude values in degrees.
99147
100148 Returns
101149 -------
102- float array
103- Cosine of the solar zenith angle (all values, including negatives)
104- [Hogan_and_Hirahara2015]_. See also:
105- http://answers.google.com/answers/threadview/id/782886.html
150+ array-like
151+ Cosine of the solar zenith angle. Negative values are clipped to 0.
152+
153+ Notes
154+ -----
155+ Supports any array type compatible with the Python Array API standard,
156+ including numpy, cupy, and torch tensors.
106157
158+ The result is clipped to ensure physically meaningful values.
107159 """
108160 xp = array_namespace (latitudes , longitudes )
109161 latitudes = xp .asarray (latitudes )
110162 longitudes = xp .asarray (longitudes )
111163 device = xp .device (latitudes )
112164
113- # declination angle + time correction for solar angle
114165 declination , time_correction = solar_declination_angle (date )
115166
116167 declination = xp .asarray (declination , device = device )
117168 time_correction = xp .asarray (time_correction , device = device )
118169
119- # solar_declination_angle returns degrees
120- # TODO: deg2rad() is not part of the array API standard
121170 declination = xp .deg2rad (declination )
122171 latitudes = xp .deg2rad (latitudes )
123172
124173 sindec_sinlat = xp .sin (declination ) * xp .sin (latitudes )
125174 cosdec_coslat = xp .cos (declination ) * xp .cos (latitudes )
126175
127- # solar hour angle [h.deg]
128- # TODO: deg2rad() is not part of the array API standard
129176 solar_angle = xp .deg2rad ((date .hour - 12 ) * 15 + longitudes + time_correction )
130177 zenith_angle = sindec_sinlat + cosdec_coslat * xp .cos (solar_angle )
131178
132- # Clip negative values
133179 return xp .clip (zenith_angle , 0.0 , None )
134180
135181
0 commit comments