# Calculate hours between sun.elevation values possible?

A way to reduce this further would be to also store the day before yesterday’s times, then adjust today’s estimate by the same amount as the previous difference.

So `d0 = d1 + (d1 - d2)`.

I think the OP is looking for the period of maximum sunlight intensity from 24 to 24 degrees elevation. Closest length-of-time attribute the sun2 component has is `daylight` and that’s kind of far off because it’s from ~1 degree to ~1 degree.

astronomical_daylight is 18º to 18º, that might be is negative though

Astronomical dawn/dusk is 18 degrees below the horizon (maximum dark-sky). OP wants 24 to 24 above the horizon.

Yes, did you see my edits about 2 minutes ago?

I did after composing and submitting the post when the browser updated …

You’re right about that. I surely underestimated the whole thing, when asking this question!

@Troon This is a marvelous idea! I think this is getting me as close as it could with a reasonable (at least for me) amount of code.

If you want to try a quick test, to see if this gives you the information you’re interested in, you could install my sun2 integration, and in this line:

change the 0.833 to 24. Then configure the `daylight` sensor. It should give you the amount of time the sun is above 24 degrees for the day (and for yesterday and tomorrow. You could even configure the `night` sensor to give you the amount of time the sun will be below 24 degrees.)

I’m not sure, though, that it’s worth it to make this integration more flexible to accept an arbitrary number (kind of like can be done for binary sensors.) This is the only time I’ve heard anyone ask for something like this. But if a case can be made for adding this feature, I might spend some time on it.

2 Likes

Taras, I’ve studied this subject in detail and never seen of such, the thing I’ve ‘heard’ of it is your series of posts in this thread. For me to accept this without proof is akin to saying “Yeah drink this bleach and swallow this UV light bulb and lead, then we’ll turn to light on to cure your Covid-19” - It’s just not happening.

Just think about this for a moment.
At ANY time there are many points on the Earth where the sun is at a given elevation (-90° to +90°). Though for the two extremes there’s only one of each and they are antipodian.
But lets assume we are only interested in a ‘given point’ and only with a given elevation.
The ‘ring’ that the elevation generates forms a cone around the ‘given point’ and we must find where the path of the sun intersects with that cone.
So we need to know where the sun is and that it is on the cone and we can note the time.
For that to happen we need to know exactly where the sun is and that is dependent upon time.
The calculations for the sun position (including the ones you linked to) run something like this : -
From the time and date (UTC) we generate the Julian time and the Julian century.
From that we can calculate the Geometric Mean Long Sun Degree
AND we can calculate the Geometric Mean Anom Sun Degree (they are different)
Using the Julian century we can calculate the ecentricity of the sun’s orbit
From Julian century and Geometric Mean Anom Sun Degree we can calculate the Sun’s Equation of Centre.
From Geometric Mean Long Sun Degree and Sun’s Equation of Centre we can calculate the Sun True Long Degree

The list continues, here’s just the titles of the factors that can be derived from time that combine to interact to the positional result : -

Date
Time (past local midnight)
Julian Day
Julian Century
Geom Mean Long Sun (deg)
Geom Mean Anom Sun (deg)
Eccent Earth Orbit
Sun Eq of Ctr
Sun True Long (deg)
Sun True Anom (deg)
Sun Rad Vector (AUs)
Sun App Long (deg)
Mean Obliq Ecliptic (deg)
Obliq Corr (deg)
Sun Rt Ascen (deg)
Sun Declin (deg)
var y
Eq of Time (minutes)
HA Sunrise (deg)
Solar Noon (LST)
Sunrise Time (LST)
Sunset Time (LST)
Sunlight Duration (minutes)
True Solar Time (min)
Hour Angle (deg)
Solar Zenith Angle (deg)
Solar Elevation Angle (deg)
Approx Atmospheric Refraction (deg)
Solar Elevation corrected for atm refraction (deg)
Solar Azimuth Angle (deg cw from N)

Given that the target elevation is 24° the you ‘can’ completely ignore Atmospheric Refraction but Assuming you start at the end and work backwards, you have to use factors that you derive from time and there is no way to get round that.

You can not get an ‘equation’ that gives you time from a ‘position’ and an elevation, the best you can do is an algorythm using a series of ‘guessed’ times
The module that Phil is using below above does just this (as previously explained).

Do not make statements that go against accepted scientific knowledge without proof and you have provided - NO PROOF.

Your Gish Gallop has failed to suck me into debating the semantics of the definition of the word “equations”. If you thought I meant there’s a straightforward algebraic equation available, I didn’t.

There are existing “mathematical tools” for computing what the OP requested (which is the sum-total of my assertion). Most are in formats that are unavailable for templating or python_script due to aforementioned limitations.

Fun Fact: they scheduled the first moon landing at a time when the landing zone would experience long shadows (to avoid flat lighting and provide the most striking terrain-relief); it speaks volumes about what is understood and calculable (even off-earth).

I found a few spare minutes to explore the astral package and found this:

`astral.sun.` `time_at_elevation` (observer: astral.Observer, elevation: float, date: Optional[datetime.date] = None, direction: astral.SunDirection = <SunDirection.RISING: 1>, tzinfo: Union[str, datetime.tzinfo] = ) → datetime.datetime

Calculates the time when the sun is at the specified elevation on the specified date.

Note

This method uses positive elevations for those above the horizon.

Elevations greater than 90 degrees are converted to a setting sun i.e. an elevation of 110 will calculate a setting sun at 70 degrees.

Parameters: * elevation – Elevation of the sun in degrees above the horizon to calculate for.

• observer – Observer to calculate for
• date – Date to calculate for. Default is today’s date in the timezone tzinfo.
• direction – Determines whether the calculated time is for the sun rising or setting. Use `SunDirection.RISING` or `SunDirection.SETTING`. Default is rising.
• tzinfo – Timezone to return times in. Default is UTC.

Returns: Date and time at which the sun is at the specified elevation.

There are probably other astronomical modules/libraries/packages that can do the same thing (and in many other programming languages) but I looked at `astral` because it’s what Home Assistant uses (in integrations like `sun` and, unless I’m mistaken, pnbruckner’s `sun2`).

I just woke up that morning and had that question in my head, as i saw an degree of the sun above the horizon and the “right” power to optimal power my solar panels sufficiently.
As i said before, it was just out of curiosity, so maybe i will be the only one asking this for a loooong time

Nevertheless, thank you for your contibution to this and if it develops into an “really urgent” case, i will definitely try this out!

For the time being, the mentioned template quite at the start of this thread is working quite well and good for me now.

Yes, my sun2 integration uses the same astral package that HA uses. FWIW, I was aware of that `time_at_elevation` method, and I tried using it at first, but it’s not very precise. The result can vary quite a bit from what the `elevation` method returns (around the same time.) I.e., if you map the values `elevation` returns over a given period of time, then use `time_at_elevation` to find the time for one of those values, they generally won’t agree. So I used a kind of brute force search method instead.

But, if you’re not looking for a very accurate result, `time_at_elevation` is definitely a lot easier/faster to use.

2 Likes

Thanks for the insight into the function’s accuracy (and thank you for going the extra distance to improve upon it, using an iterative technique, in your sun2 component).

FWIW, the only reason I mentioned it was because Mutt needed proof of its existence. Beyond that, I don’t have an application for it. However, I can see how it could be useful for applications such solar power generation and controlling blinds and other covers.

This is indeed proof, but of the use of an iterative method (my point NOT yours !)

You said : -

AND this whole thread, goes through the fact that there aren’t. The ‘models’ we have of the solar system are based on actual observations and they are all predetermined by the time as the base factor.
So calculations to solve for time, HAVE to be algorithms not equations.
And just to be clear : -

``````An Equation expresses the relation between several variables. An equation is a proposition that asserts the equality of two quantities, whereas an algorithm is a set of steps to get from some initial quantities to some desired output. Therefore they cannot be used interchangeably.
``````

This is quite a common statement for distinction.
Moon - Fun Fact 1 - Katherine Johnson, was employed as a ‘computer’ given the repetitive nature of these calculations used in the algorithms and the precision required.
Moon - Fun Fact 2 - There were no seats in the Eagle Lander, the first men on the moon landed standing up.

Are you 6 ?

I am surprised to see that you replied to the thread (i.e. notification) rather than to me given my reduction in attendance, (for much the same reasons as Marc’s), so either sloppy - or perhaps deliberate ???

Edit: Actually I could (for a given location and sun elevation) derive a couple of 5th order polynominals to give the times in UTC, which could be coverted to a span, but this would only be accurate to about the nearest minute. These could be put into a template equation, but would over a number of years drift - require updating.

Technically incorrect usage but : - “Over and Out”

Pnbruckner employed an iterative method because the existing function in the astral library was found to be imprecise. However, that shouldn’t be considered proof that the same function in all other libraries is equally imprecise. The point is you were adamant that such a function cannot exist at all, yet there it is; plug in numbers, get a result. How the function works internally, I leave for you to investigate.

Could be what astral’s function does as well but, like I said, I leave that for you to explore.

Ha Ha Ha !

You are joking right ?

You think that if I use iterative methods, based on time, to determine the times of the day where the sun elevation is 24° for a single location on the planet.
Then by looking at those results derive a set of (it actually fits better with a 6th order) polynomial equations to give time. - Then that is a valid example of an equation solution ? Assuming just ‘an equation’ for each degree of latitude and longitude on the planet you’d need 129,600 equations - then Mutiply that by whatever ‘elevation’ is required. Oh and I was wrong, the calculations would need to be updated for every year as the calculation centres around the summer equinox for the selected year only.

I just set a tracker running on the number of calculations required and it’s over 221,816 (and those ignore the many comparision steps) - but I’m sure you had an equation for that.

I also love the fact that you know next to nothing about the calculations in use but with supreme confidence say : - "There’s an equation for that - but the proof is beneath me"

It’s true, I know next to nothing about the equations in use. Nevertheless, that’s true for most everyone who uses a library function (such as astral’s `time_at_elevation`). However, it’s a safe bet the functions employ mathematical equations and not the Oracle of Delphi.

I made no claim that the “proof is beneath me”. You said:

I described a library function that implements it so now you are free to explore the function’s code. The so-called “proof” lies within the code. There may be other libraries (implemented in other programming languages) that can provide you with additional, possibly better, examples.

Okay, we’ve seen that ‘astral’ determination (via package) is a bit of a finger in the air.
The best way to do this is via an iterative algorhithm that we just can’t process via Jinja2
So I did the full calcs for the location as specified (well Berlin as in 52.52° North and 13.4° East)
This gave me the exact times of the sun elevation at 24° through the year.
I then got another algorhithm to look at that real data and produce a 6th order polynominal (well 2 actually) by looking at that data to try to reproduce the same results.

The output is based on day of the year so can be repeated year after year (though variations will occur due to both the 4 and 100 year UTC matching) The formulas give correlation factors (R2) of 0.9995 and 0.9996 and comparisons against properly calculated figures give maximum errors of 1 minute 08 seconds at ‘one’ part (but as the sensors given below only have a resolution of 1 minute …

So I can’t say I’d use these myself (it just offends the OCD in me) but they are what they are.
I have given the sensor for the time ‘bracket’ (i.e. time between 24° in the morning and 24° in the afternoon) but you can copy and adjust (with the commented out sections) for the bracket ‘start time’ or the ‘stop time’.
This is incase you want to start an action when the slot starts or when it stops. Use a template trigger : -

``````"{{ states('sensor.time') == states('sensor.s_berlin_elev24') }}"
``````

But you ‘may’ have to prevent triggers at midnight with a condition when the sun in winter does not reach 24°

The sensor(s) : -

``````sensor:
- platform: template
sensors:
s_berlin_elev24:
friendly_name: Sun Elevation Bracket
value_template: >
{% set tdy = as_local(states.sensor.date.last_updated).strftime('%j') | int %}
{% set tstdy = as_timestamp(states.sensor.date.last_changed.replace(hour=0,minute=0,second=0,microsecond=0)) %}
{% set strt = (0.00000000252469998477268*(tdy**6) - 0.00000251059967892875*(tdy**5) + 0.000993142676065872*(tdy**4) - 0.200203193424386*(tdy**3) + 22.711821494624*(tdy**2) - 1541.2614147067*tdy + 79570.0417881012) | int %}
{% set stop = (0 - 0.00000000258919433582163*(tdy**6) + 0.00000276855794001385*(tdy**5) - 0.00118433434658982*(tdy**4) + 0.256722704303684*(tdy**3) - 30.2558065308257*(tdy**2) + 1962.38214886188*tdy + 1143.75953197479) | int %}
{% set bracket = stop - strt %}
{% set dytst = 0 if (tdy < 44 or tdy > 301 or strt > 43200 or stop < 43200) else 1 %}
{#{ (tstdy + dytst*strt) | timestamp_custom("%H:%M", false) }#}
{{ (tstdy + dytst*bracket) | timestamp_custom("%H:%M", false) }}
{#{ (tstdy + dytst*stop) | timestamp_custom("%H:%M", false) }#}
icon_template: "{% if is_state('sensor.s_berlin_elev24', '00:00') %}mdi:weather-sunny-off{% else %}mdi:weather-sunny{% endif %}"
``````

This is written for inclusion in a package file so may need adjusting according to your storage preferences.
It is supplied as is (and has been fully tested), it will not be adjusted/corrected for others use. And to show that even an OCD’s best efforts can not substitute for real calculations.
Having said that. given for the ‘day’ to ‘day’ variation (due to Solar Analema) this should be more accurate than just serving up yesterday’s values.
It also does not require any additional ‘helpers’ to store data.

Any ammendments will be made at your own risk (i.e. I won’t help you fix it)

Additional Information : I found that Jinja2 does not seem to like negative mantissa’s so had to subtract a positive one from zero. There ‘may’ be a better way of doing it.

You will also need sensor.date (and sensor.time, if you want time triggers) set up

Edit : Thinking about this further, The Duration of the ‘bracket’ will always be correct but the start and stop times may be related to UTC (ie not DST) . So the ‘tigger time’ formulas may need ‘adjustment’

``````{{ (tstdy + dytst*strt) | timestamp_custom("%H:%M", false) }}
becomes something like : -
{{ (tstdy + dytst*strt + local_dst_adjustment) | timestamp_custom("%H:%M", false) }}
[ i.e. - determine if DST is applied to set the adjustment, else 0 ]
Or it may be as simple as : -
{{ (tstdy + dytst*strt) | timestamp_custom("%H:%M", true) }}

Suck it and see ...
``````

oh my, this is getting ugly