Skip to content

Commit dc7197a

Browse files
Merge pull request #12 from gregory-halverson-jpl/main
fixing `calculate_daylight`
2 parents b9787e1 + de6a1f1 commit dc7197a

File tree

5 files changed

+74
-21
lines changed

5 files changed

+74
-21
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@ import sun_angles
6060
- Duffie, J. A., & Beckman, W. A. (2013). Solar Engineering of Thermal Processes (4th ed.). Wiley.
6161

6262

63-
### 5. `calculate_daylight(DOY=None, lat=None, SHA_deg=None, datetime_UTC=None, geometry=None)`
64-
- **Description:** Calculates daylight hours for a given day and location. Flexible input: provide SHA directly, or let the function compute it from DOY and latitude.
63+
### 5. `calculate_daylight(day_of_year=None, lat=None, SHA_deg=None, time_UTC=None, geometry=None)`
64+
- **Description:** Computes daylight hours for a specific day and location. Accepts multiple input types: you can provide the sunrise hour angle (SHA) directly, or supply day of year and latitude (or a datetime or geometry), and the function will calculate SHA as needed.
6565
- **Parameters:**
66-
- `DOY` (optional): Day of year.
67-
- `lat` (optional): Latitude in degrees.
66+
- `day_of_year` (optional): Day of year (int, array, or Raster).
67+
- `lat` (optional): Latitude in degrees (float, array, or Raster).
6868
- `SHA_deg` (optional): Sunrise hour angle in degrees.
69-
- `datetime_UTC` (optional): Datetime in UTC.
69+
- `time_UTC` (optional): Datetime in UTC (datetime, string, or array).
7070
- `geometry` (optional): Geometry object with latitude.
71-
- **Returns:** Daylight hours.
71+
- **Returns:** Daylight hours (float, array, or Raster).
7272

7373

7474
### 6. `sunrise_from_SHA(SHA_deg)`

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
requires = ["setuptools>=60", "setuptools-scm>=8.0", "wheel"]
33

44
[project]
5-
name = "sun_angles"
6-
version = "1.3.0"
5+
name = "sun-angles"
6+
version = "1.4.0"
77
description = "calculates solar zenith and azimuth and daylight hours"
88
readme = "README.md"
99
authors = [

sun_angles/daylight.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ def daylight_from_SHA(SHA_deg: Union[Raster, np.ndarray]) -> Union[Raster, np.nd
4848

4949

5050
def calculate_daylight(
51-
DOY: Union[Raster, np.ndarray, int] = None,
51+
day_of_year: Union[Raster, np.ndarray, int] = None,
5252
lat: Union[Raster, np.ndarray, float] = None,
5353
SHA_deg: Union[Raster, np.ndarray, float] = None,
54-
datetime_UTC: datetime = None,
54+
time_UTC: Union[datetime, str, list, np.ndarray] = None,
5555
geometry: SpatialGeometry = None
5656
) -> Union[Raster, np.ndarray]:
5757
"""
@@ -81,23 +81,31 @@ def calculate_daylight(
8181
Union[Raster, np.ndarray]
8282
Daylight hours for the given inputs.
8383
"""
84+
8485
# If SHA_deg is not provided, calculate it from DOY and latitude
8586
if SHA_deg is None:
8687
# If latitude is not provided, try to extract from geometry
8788
if lat is None and geometry is not None:
8889
lat = geometry.lat
8990

90-
# If DOY is not provided, try to extract from datetime_UTC
91-
if DOY is None and datetime_UTC is not None:
92-
# If datetime_UTC is a string, parse it to a datetime object
93-
if isinstance(datetime_UTC, str):
94-
datetime_UTC = parser.parse(datetime_UTC)
95-
96-
# Convert datetime to day of year
97-
DOY = int(pd.Timestamp(datetime_UTC).dayofyear)
98-
91+
# If DOY is not provided, try to extract from time_UTC
92+
if day_of_year is None and time_UTC is not None:
93+
def to_doy(val):
94+
if isinstance(val, str):
95+
val = parser.parse(val)
96+
return int(pd.Timestamp(val).dayofyear)
97+
98+
# Handle array-like or single value
99+
if isinstance(time_UTC, (list, np.ndarray)):
100+
day_of_year = np.array([to_doy(t) for t in time_UTC])
101+
else:
102+
day_of_year = to_doy(time_UTC)
103+
104+
# Ensure day_of_year is a numpy array if it's a list (for downstream math)
105+
if isinstance(day_of_year, list):
106+
day_of_year = np.array(day_of_year)
99107
# Compute SHA_deg using DOY and latitude
100-
SHA_deg = SHA_deg_from_DOY_lat(DOY, lat)
108+
SHA_deg = SHA_deg_from_DOY_lat(day_of_year, lat)
101109

102110
# Compute daylight hours from SHA_deg
103111
daylight_hours = daylight_from_SHA(SHA_deg)

sun_angles/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from os.path import join, abspath, dirname
2-
32
from importlib.metadata import version
3+
44
__version__ = version("sun-angles")

tests/test_daylight_time_utc.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import unittest
2+
import numpy as np
3+
from datetime import datetime
4+
from sun_angles.daylight import calculate_daylight
5+
6+
class TestCalculateDaylight(unittest.TestCase):
7+
def test_time_utc_single_datetime(self):
8+
# Test with a single datetime object
9+
doy = 100
10+
lat = 34.0
11+
dt = datetime(2025, 4, 10) # DOY 100
12+
# Should match result with explicit DOY
13+
daylight1 = calculate_daylight(day_of_year=doy, lat=lat)
14+
daylight2 = calculate_daylight(time_UTC=dt, lat=lat)
15+
np.testing.assert_allclose(daylight1, daylight2, rtol=1e-6)
16+
17+
def test_time_utc_single_string(self):
18+
# Test with a single string
19+
doy = 200
20+
lat = 45.0
21+
dt_str = '2025-07-19' # DOY 200
22+
daylight1 = calculate_daylight(day_of_year=doy, lat=lat)
23+
daylight2 = calculate_daylight(time_UTC=dt_str, lat=lat)
24+
np.testing.assert_allclose(daylight1, daylight2, rtol=1e-6)
25+
26+
def test_time_utc_array_datetime(self):
27+
# Test with an array of datetime objects
28+
lats = np.array([0.0, 45.0, -45.0])
29+
dts = [datetime(2025, 1, 1), datetime(2025, 6, 21), datetime(2025, 12, 21)]
30+
doys = [1, 172, 355]
31+
daylight1 = calculate_daylight(day_of_year=doys, lat=lats)
32+
daylight2 = calculate_daylight(time_UTC=dts, lat=lats)
33+
np.testing.assert_allclose(daylight1, daylight2, rtol=1e-6)
34+
35+
def test_time_utc_array_string(self):
36+
# Test with an array of strings
37+
lats = np.array([10.0, 20.0, 30.0])
38+
dt_strs = ['2025-03-01', '2025-06-01', '2025-09-01']
39+
doys = [60, 152, 244]
40+
daylight1 = calculate_daylight(day_of_year=doys, lat=lats)
41+
daylight2 = calculate_daylight(time_UTC=dt_strs, lat=lats)
42+
np.testing.assert_allclose(daylight1, daylight2, rtol=1e-6)
43+
44+
if __name__ == '__main__':
45+
unittest.main()

0 commit comments

Comments
 (0)