Python program to calculate the sun angle

Given a string representing the hour and minute, where: 00:00 <= time <= 23:59, the program should output the number of angles the sun is above the horizon.

• When time is "06:00" the sun angles are 0º
• At "12:00" the sun is at 90º
• At "18:00" the sun goes to 180º
• After "18:00" and before "06:00" the program should output "I don't see the sun!"

Examples:

• sun_angle("07:00") == 15
• sun_angle("01:23") == "I don't see the sun!"
• sun_angle("12:15") == 93.75

The code:

import datetime as dt

def check_you_dont_see_sun(time_dt, end_dt, start_dt):
return ((time_dt > start_dt and time_dt < end_dt)
or (time_dt > start_dt or time_dt < end_dt))

def calculate_sun_angle(time_dt, end_dt):
difference = time_dt - end_dt
hours_of_difference = (difference.seconds / 60) / 60
angle_increase_each_hour = 15
return hours_of_difference * angle_increase_each_hour

def sun_angle(time):

start_dt = dt.datetime.strptime('18:00', '%H:%M')
end_dt = dt.datetime.strptime('06:00', '%H:%M')
time_dt = dt.datetime.strptime(time, '%H:%M')

if check_you_dont_see_sun(time_dt,end_dt,start_dt):
return "I don't see the sun!"
else:
return calculate_sun_angle(time_dt, end_dt)


What could we improve?

• The task involves calculating some kind of angle, but it certainly isn't calculating the angle of the sun above the horizon! – 200_success May 25 '19 at 19:05

One immediate thing I would improve is the naming of your variables. While in general very descriptive, it took me a while to realize that start_dt is actually the time of sunset and end_dt the time of sunrise, instead of the other way around. I would also make these variables global constants and use those as default values. This way you can do it all in one still short and readable function.

I would add some docstring describing what your function(s) do and use a compound comparison instead of two comparisons to determine if the time is day or night.

The datetime module is often imported as from datetime import datetime, although your import is also OK.

The calling of the code should happen under a if __name__ == "__main__": guard, where I would also put at least some very rudimentary testing code.

from datetime import datetime

SUNRISE = datetime.strptime('06:00', '%H:%M')
SUNSET = datetime.strptime('18:00', '%H:%M')

def sun_angle(time, sunrise=SUNRISE, sunset=SUNSET):
"""Return the angle of the sun at time.
Assumes that the sun moves 180 degrees between sunrise and sunset.
During night return 'I don\'t see the sun!'.
"""
time = datetime.strptime(time, '%H:%M')
if not sunrise <= time <= sunset:
return "I don't see the sun!"

angle_per_hour = 180 / (sunset - sunrise).seconds * 3600
time_since_sunrise = (time - sunrise).seconds / 3600
return time_since_sunrise * angle_per_hour

if __name__ == "__main__":
test_cases = [("07:00", 15),
("01:23", "I don't see the sun!"),
("12:15", 93.75)]
for time, expected in test_cases:
output = sun_angle(time)
print(f"solar_angle({time!r}) == {output!r}")
assert output == expected, f"Failed: expected {expected!r}!"


The calculation of the sun angle could probably still be a bit better in order to be able to be extended for different latitudes in the future.

If this is not a class exercise or programming challenge, I would also recommend to change the interface of the function a bit. Make time always a datetime object and instead of returning a string in case of invalid input raise a custom exception:

class NightTimeException(ValueError):
pass

...

if not sunrise <= time <= sunset:
raise NightTimeException("I don't see the sun!")