Solar Position

Overview

Introduction The category of solar position methods covers the set of calculations used to determine where the sun appears in the sky, and when key daily events such as sunrise, solar noon, and sunset occur. In mathematical terms, solar position converts time and site coordinates into geometric angles and event timestamps using celestial mechanics and spherical trigonometry. In practical terms, these calculations are foundational for photovoltaic (PV) system design, solar resource assessment, daylighting studies, thermal load modeling, and any workflow where irradiance depends on sun angle. A concise astronomical framing is available in the Solar zenith angle and Equation of time references, both of which map directly to the functions in this category.

For engineering users, solar position is not an academic side topic; it is the first dependency in a long modeling chain. Plane-of-array irradiance, incidence angle modifiers, tracker backtracking, row-to-row shading, and even inverter clipping diagnostics all depend on correct sun geometry. A one-degree bias in zenith or azimuth can propagate into energy-yield error, and a few minutes of sunrise/sunset drift can affect production windows, operating schedules, and dispatch assumptions. That is why this category includes both fast analytical approximations and higher-accuracy astronomical methods: different project phases need different speed-versus-fidelity tradeoffs.

Boardflare’s solar-position calculators are built on pvlib, specifically the pvlib.solarposition module, which is a standard open-source toolkit in PV engineering. The functions in this category are designed as interoperable pieces rather than isolated utilities. For example, DECLINATION_SPENCER and EOT_SPENCER provide fast geometric inputs that can feed SUN_RISE_SET_GEOM, while SOLARPOSITION and SUN_RISE_SET_SPA provide event and angle outputs from the NREL SPA family where higher precision is needed. This dual-path design supports both spreadsheet-scale throughput and engineering-grade validation.

Another important conceptual distinction is coordinate and unit handling. Some functions in this category expect radians for angular inputs (for example SOLAR_ZEN_AN and SOLAR_AZIM_AN), while others accept geographic latitude/longitude in degrees and timestamps with timezone context (for example SOLARPOSITION, SUN_RISE_SET_GEOM, and SUN_RISE_SET_SPA). Successful deployment therefore depends on treating data preparation as part of the model, not just pre-processing: consistent time zones, verified day-of-year mapping, and explicit unit conversion are core quality controls.

When to Use It Solar-position tools are best selected by the job to be done, not by familiarity with one formula. The first common job is PV energy simulation and performance diagnostics. In this workflow, analysts need a timestamped sun vector to transform global horizontal irradiance (GHI) into direct and diffuse components on a tilted plane. SOLARPOSITION is typically the starting point because it returns azimuth, elevation, and apparent zenith in one call and supports multiple algorithm methods. When high-accuracy event boundaries are required for clipping-window checks or curtailment accounting, SUN_RISE_SET_SPA becomes the preferred event-timing function. If analysts need fast annual sweeps or sensitivity studies before final validation, DECLINATION_SPENCER plus EOT_SPENCER can support coarse screening.

The second common job is controls and operations for tracking or sun-following systems. Tracker control logic often requires rapid updates of sun direction under strict compute constraints, especially in edge devices or digital twins running many site scenarios. In this context, SOLAR_ZEN_AN and SOLAR_AZIM_AN are useful when the model already has hour angle and declination available, because they are direct analytical evaluations with low overhead. These are particularly useful in deterministic simulations where the engineering team controls all geometric inputs and wants transparent formula-level behavior. For production supervisory logic where atmospheric refraction and robust timestamp handling matter, SOLARPOSITION provides a safer operational interface.

The third job is planning and permitting workflows that need sunrise/sunset and solar-noon events for specific geographies. Examples include defining maintenance windows, estimating daylight construction periods, or validating expected generation windows in power-purchase analysis. SUN_RISE_SET_SPA is preferred when contractual, compliance, or settlement decisions depend on timing precision. SUN_RISE_SET_GEOM is appropriate when users need a lightweight approximation, understand its assumptions, and mainly care about comparative trends across many dates rather than minute-level absolute accuracy.

The fourth job is resource normalization and extraterrestrial irradiance scaling. Seasonal Earth–Sun distance variation changes top-of-atmosphere irradiance by a few percent, which is material in calibration and clear-sky normalization tasks. EARTHSUN_DISTANCE provides this correction using SPA-based distance estimates in astronomical units, making it useful in clear-sky benchmarking and quality-control pipelines where expected irradiance envelopes are date dependent.

A final job is education, model explainability, and audit trails. Teams that must explain why a modeled outcome changed between datasets often need decomposed geometry terms. Here, DECLINATION_SPENCER, EOT_SPENCER, SOLAR_ZEN_AN, and SOLAR_AZIM_AN are especially valuable because each quantity is explicit and traceable. This decomposition supports peer review, validation notebooks, and clear handoff between data, controls, and performance teams.

How It Works Solar-position modeling starts with Earth–Sun geometry. At a high level, the apparent sun position depends on date, time, observer latitude/longitude, and the Earth’s orbital/rotational configuration. Two core intermediate terms are solar declination \delta and hour angle h. Declination expresses the apparent tilt of the sun relative to the equatorial plane. Hour angle converts local solar time into angular displacement from solar noon. In many engineering pipelines, these terms are sufficient to compute zenith and azimuth analytically.

For fast declination and time-correction estimates, this category includes Spencer-1971 approximations. DECLINATION_SPENCER maps day-of-year n to \delta, and EOT_SPENCER provides equation-of-time offsets used to convert clock time to apparent solar time. The equation of time captures the seasonal mismatch between mean solar time and true apparent solar time due to orbital eccentricity and axial tilt. A common correction structure is:

t_{\text{solar}} = t_{\text{clock}} + \frac{4(\lambda_{\text{std}}-\lambda)}{60} + \frac{\mathrm{EOT}}{60}

where longitude terms are in degrees and EOT is in minutes. These approximations are computationally efficient and often adequate for screening, comparative studies, or as inputs into simplified sunrise/sunset methods.

Given \delta, h, and latitude \phi, solar zenith follows from spherical geometry. SOLAR_ZEN_AN evaluates:

\cos(\theta_z)=\sin(\phi)\sin(\delta)+\cos(\phi)\cos(\delta)\cos(h)

where \theta_z is the solar zenith angle. This relation is central to irradiance transposition because incidence on a tilted surface depends directly on sun-vector orientation. Solar azimuth is then resolved with SOLAR_AZIM_AN, which uses analytical trigonometric relations and quadrant handling to place the horizontal direction correctly. In production models, azimuth conventions (clockwise from north, or alternate definitions) must be verified before integrating with tracker or shading logic.

The category also supports full timestamp-to-angle computation through SOLARPOSITION, which wraps pvlib’s get_solarposition interface. This function accepts time, site coordinates, and atmospheric inputs and returns azimuth, elevation, and apparent zenith. Apparent zenith includes atmospheric refraction correction, which can matter near the horizon and in event boundary calculations. Because SOLARPOSITION exposes method selection (nrel_numpy, nrel_numba, and others), teams can choose a runtime profile that balances speed and deployment constraints while preserving a consistent API.

Event timing functions solve a related but distinct problem: rather than “where is the sun now?”, they ask “when does the sun cross key geometric thresholds today?”. SUN_RISE_SET_GEOM uses a geometric approximation with declination and equation-of-time inputs. It is fast and transparent, and it is useful when upstream inputs are already available from DECLINATION_SPENCER and EOT_SPENCER. The canonical sunrise/sunset condition is based on the event hour angle H_0:

\cos(H_0) = -\tan(\phi)\tan(\delta)

from which sunrise and sunset times are inferred after applying longitude and time corrections.

For higher fidelity, SUN_RISE_SET_SPA computes sunrise, sunset, and transit with the NREL Solar Position Algorithm (SPA), a widely used astronomical method in PV engineering. This is generally preferred when minute-level timing differences matter, especially at high latitudes, near solstice conditions, or in reporting contexts where reproducibility and known algorithmic pedigree are required. EARTHSUN_DISTANCE complements these calculations by quantifying Earth–Sun distance in AU, supporting extraterrestrial irradiance scaling:

E_{0n} = E_{sc}\left(\frac{1}{d^2}\right)

where E_{sc} is the solar constant and d is Earth–Sun distance in AU.

Across all functions, assumptions and data quality boundaries should be explicit. Key assumptions include: accurate timestamp parsing with timezone awareness; correct unit discipline (degrees vs radians); physically realistic latitude/longitude ranges; and valid day-of-year indexing for leap years when applicable. In addition, users should separate approximation error from input error: coarse formulas may be acceptable for trend analysis but not for contractual timing, while even high-accuracy methods fail if time zones or offsets are wrong.

Practical Example Consider a utility-scale PV analyst building a one-day production diagnostics workflow for a site at latitude 39.742^\circ and longitude -105.179^\circ. The analyst needs to answer three questions: (1) what are the sunrise/sunset windows used for operations planning, (2) what is the sun path during plant operating hours, and (3) whether a quick approximation workflow is close enough to the high-accuracy benchmark for daily reporting.

Step 1 is event-window setup. The analyst computes sunrise, sunset, and transit with SUN_RISE_SET_SPA using timezone-aware timestamps and a suitable \Delta T value. This establishes the benchmark day boundary for dispatch and reporting. In parallel, the analyst computes SUN_RISE_SET_GEOM using declination and equation-of-time inputs to get a fast approximation. The difference between these two outputs provides an immediate error envelope for “fast mode” operations.

Step 2 is geometry decomposition for explainability. The analyst evaluates DECLINATION_SPENCER and EOT_SPENCER for the date sequence and then computes SOLAR_ZEN_AN and SOLAR_AZIM_AN over the hour-angle grid. This path gives transparent intermediate values that are easy to audit and communicate. If a control engineer asks why a tracker orientation changed at a specific hour, the team can trace the exact chain from day-of-year to declination to hour angle to azimuth/zenith.

Step 3 is production-model alignment. The analyst runs SOLARPOSITION on the same timestamps to get azimuth, elevation, and apparent zenith in one consistent API call. These outputs feed irradiance transposition and incidence-angle modeling. Because SOLARPOSITION includes atmospheric/refraction-aware quantities, it usually becomes the operational source of truth for energy simulation, while analytical outputs remain useful for QA and sensitivity checks.

Step 4 is extraterrestrial scaling. The analyst computes EARTHSUN_DISTANCE for the day and applies the 1/d^2 correction to normalize expected top-of-atmosphere irradiance. This helps distinguish geometry-driven seasonal effects from sensor bias or cloud impacts when comparing observed and modeled performance.

Step 5 is acceptance criteria. The team defines practical tolerances: for example, approximate event times may be acceptable for early-stage planning if they remain within an internal threshold versus SPA outputs, while monthly performance reporting always uses SUN_RISE_SET_SPA and SOLARPOSITION. This dual workflow preserves speed for iterative analysis and rigor for external-facing results.

Operationally, this approach replaces fragile manual spreadsheet logic with explicit, reusable calculator calls. The key gain is not only numerical consistency but also model governance: each output can be tied to a known algorithm, documented assumptions, and reproducible inputs.

How to Choose Choosing the right calculator in this category is primarily a question of output type (angles vs events), required accuracy, and available inputs. A robust selection process starts with the decision tree below and then uses function-level guidance.

graph TD
    A[Start: What do you need?] --> B{Need sun angles\n(zenith/azimuth/elevation)?}
    B -- Yes --> C{Have timestamps + site coords?}
    C -- Yes --> D[Use SOLARPOSITION]
    C -- No --> E{Have hour angle + declination?}
    E -- Yes --> F[Use SOLAR_ZEN_AN and SOLAR_AZIM_AN]
    E -- No --> G[Compute DECLINATION_SPENCER + EOT_SPENCER\nthen derive hour angle]
    B -- No --> H{Need sunrise/sunset/transit times?}
    H -- Yes --> I{Need high timing accuracy?}
    I -- Yes --> J[Use SUN_RISE_SET_SPA]
    I -- No --> K[Use SUN_RISE_SET_GEOM]
    A --> L{Need Earth-Sun irradiance scaling?}
    L -- Yes --> M[Use EARTHSUN_DISTANCE]

For direct angle outputs from timestamped data, SOLARPOSITION is generally the default. It is the best fit when teams need azimuth, elevation, and apparent zenith together, especially in bankable or operational simulations. It reduces integration risk because it handles timestamp parsing and algorithm selection in one interface. The tradeoff is slightly more computational overhead than purely analytical formulas.

When users already have hour angle and declination (for example from a custom astronomical preprocessing pipeline), SOLAR_ZEN_AN and SOLAR_AZIM_AN are efficient and explicit. Their strength is transparency and speed; their limitation is that input preparation burden shifts to the user. They are excellent for deterministic studies and educational audits, but they require strict unit discipline and correct quadrant handling assumptions.

For fast date-dependent primitives, DECLINATION_SPENCER and EOT_SPENCER are the right pair. They support quick-turn studies, Monte Carlo-like sweeps, and analytical pipelines where approximate geometry is sufficient. Their limitation is approximation fidelity versus SPA-based methods. In practice, they are often used upstream of SUN_RISE_SET_GEOM or custom hour-angle calculations.

For sunrise/sunset/transit events, select between SUN_RISE_SET_GEOM and SUN_RISE_SET_SPA based on consequence of error. SUN_RISE_SET_GEOM is lightweight and useful for comparative planning across large date ranges. SUN_RISE_SET_SPA is preferred for compliance, contracts, performance guarantees, and high-latitude edge cases. A common governance pattern is to prototype with geometric events and finalize with SPA events.

Use EARTHSUN_DISTANCE whenever extraterrestrial irradiance normalization matters. It is not a substitute for sun-angle or event calculations; it is a complementary correction that improves seasonal consistency in clear-sky and benchmarking workflows.

A practical selection matrix is: choose SOLARPOSITION for timestamped angle products; choose SOLAR_ZEN_AN/SOLAR_AZIM_AN for formula-driven geometry with known intermediate terms; choose SUN_RISE_SET_SPA for precise events; choose SUN_RISE_SET_GEOM for rapid approximations; choose DECLINATION_SPENCER and EOT_SPENCER for lightweight date corrections; and choose EARTHSUN_DISTANCE for top-of-atmosphere scaling. This mapping keeps models both efficient and fit for purpose.

DECLINATION_SPENCER

This function computes solar declination, the angle between the Earth-sun line and the equatorial plane, from day-of-year values using the Spencer (1971) Fourier approximation.

Declination is a core geometric input for solar zenith, azimuth, sunrise/sunset timing, and plane-of-array irradiance models.

In solar-geometry notation, declination enters relationships such as:

\cos(\theta_z) = \sin(\phi)\sin(\delta) + \cos(\phi)\cos(\delta)\cos(h)

where \theta_z is solar zenith, \phi is latitude, \delta is declination, and h is hour angle.

Excel Usage

=DECLINATION_SPENCER(dayofyear)
  • dayofyear (list[list], required): Day of the year (1 to 366).

Returns (list[list]): 2D list of declination angles (radians), or an error string.

Example 1: Declination at summer solstice

Inputs:

dayofyear
172

Excel formula:

=DECLINATION_SPENCER({172})

Expected output:

0.409315

Example 2: Declination near March equinox

Inputs:

dayofyear
80

Excel formula:

=DECLINATION_SPENCER({80})

Expected output:

-0.00115059

Example 3: Declination near December solstice

Inputs:

dayofyear
355

Excel formula:

=DECLINATION_SPENCER({355})

Expected output:

-0.408754

Example 4: Scalar day-of-year input

Inputs:

dayofyear
1

Excel formula:

=DECLINATION_SPENCER(1)

Expected output:

-0.402449

Python Code

Show Code
import pandas as pd
import numpy as np
from pvlib.solarposition import declination_spencer71 as result_func

def declination_spencer(dayofyear):
    """
    Compute the solar declination angle using Spencer's (1971) formula.

    See: https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.solarposition.declination_spencer71.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        dayofyear (list[list]): Day of the year (1 to 366).

    Returns:
        list[list]: 2D list of declination angles (radians), or an error string.
    """
    try:
        def flatten_num(data):
            if not isinstance(data, list): return [float(data)]
            flat = []
            for row in data:
                row = row if isinstance(row, list) else [row]
                for val in row:
                    if val == "": flat.append(float('nan'))
                    else: flat.append(float(val))
            return flat

        doy_list = flatten_num(dayofyear)
        if len(doy_list) == 0:
            return "Error: input array cannot be empty"

        res = result_func(np.array(doy_list))

        return [[float(v) if not pd.isna(v) else ""] for v in res]
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Day of the year (1 to 366).

EARTHSUN_DISTANCE

This function computes the Earth-sun distance for one or more timestamps using the NREL SPA model.

The result is returned in astronomical units (AU) and is commonly used to scale extraterrestrial irradiance in clear-sky and transposition calculations.

A common relation is:

E_{0n} = E_{sc}\left(\frac{1}{d^2}\right)

where E_{0n} is extraterrestrial normal irradiance, E_{sc} is the solar constant, and d is Earth-sun distance in AU.

Excel Usage

=EARTHSUN_DISTANCE(times, delta_t)
  • times (list[list], required): Timestamps for the distance calculation (ISO8601 format).
  • delta_t (float, optional, default: 67): Difference between terrestrial time and UT1 (seconds).

Returns (list[list]): 2D list of distances (AU), or an error string.

Example 1: Earth-Sun distance at winter solstice

Inputs:

times delta_t
2024-12-21T12:00:00Z 67

Excel formula:

=EARTHSUN_DISTANCE({"2024-12-21T12:00:00Z"}, 67)

Expected output:

0.983724

Example 2: Earth-Sun distance near perihelion

Inputs:

times delta_t
2024-01-03T12:00:00Z 67

Excel formula:

=EARTHSUN_DISTANCE({"2024-01-03T12:00:00Z"}, 67)

Expected output:

0.983307

Example 3: Earth-Sun distance near aphelion

Inputs:

times delta_t
2024-07-04T12:00:00Z 67

Excel formula:

=EARTHSUN_DISTANCE({"2024-07-04T12:00:00Z"}, 67)

Expected output:

1.01672

Example 4: Scalar timestamp input

Inputs:

times delta_t
2024-03-20T12:00:00Z 67

Excel formula:

=EARTHSUN_DISTANCE("2024-03-20T12:00:00Z", 67)

Expected output:

0.995965

Python Code

Show Code
import pandas as pd
from pvlib.solarposition import nrel_earthsun_distance as result_func

def earthsun_distance(times, delta_t=67):
    """
    Calculate the Earth-Sun distance in AU using the NREL SPA algorithm.

    See: https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.solarposition.nrel_earthsun_distance.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        times (list[list]): Timestamps for the distance calculation (ISO8601 format).
        delta_t (float, optional): Difference between terrestrial time and UT1 (seconds). Default is 67.

    Returns:
        list[list]: 2D list of distances (AU), or an error string.
    """
    try:
        def flatten_str(data):
            if not isinstance(data, list): return [str(data)]
            return [str(val) for row in data for val in (row if isinstance(row, list) else [row]) if val != ""]

        time_list = flatten_str(times)
        if len(time_list) == 0:
            return "Error: times array cannot be empty"

        dt = float(delta_t) if delta_t is not None else 67.0
        idx = pd.DatetimeIndex(time_list)

        res = result_func(
            time=idx,
            delta_t=dt
        )

        return [[float(v)] for v in res]
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Timestamps for the distance calculation (ISO8601 format).
Difference between terrestrial time and UT1 (seconds).

EOT_SPENCER

This function computes the equation of time (EOT), which quantifies the offset between apparent solar time and mean solar time as a function of day of year.

EOT is a key correction used to map civil clock time into solar geometry calculations.

Solar time can be expressed as:

t_{solar} = t_{clock} + \frac{4(\lambda_{std}-\lambda)}{60} + \frac{\mathrm{EOT}}{60}

where longitudes are in degrees and EOT is in minutes.

Excel Usage

=EOT_SPENCER(dayofyear)
  • dayofyear (list[list], required): Day of the year (1 to 366).

Returns (list[list]): 2D list of equation of time offsets (minutes), or an error string.

Example 1: EOT at summer solstice

Inputs:

dayofyear
172

Excel formula:

=EOT_SPENCER({172})

Expected output:

-1.34372

Example 2: EOT near March equinox

Inputs:

dayofyear
80

Excel formula:

=EOT_SPENCER({80})

Expected output:

-7.87367

Example 3: EOT near December solstice

Inputs:

dayofyear
355

Excel formula:

=EOT_SPENCER({355})

Expected output:

2.15509

Example 4: Scalar day-of-year input

Inputs:

dayofyear
1

Excel formula:

=EOT_SPENCER(1)

Expected output:

-2.91968

Python Code

Show Code
import pandas as pd
import numpy as np
from pvlib.solarposition import equation_of_time_spencer71 as result_func

def eot_spencer(dayofyear):
    """
    Compute the equation of time (EOT) using Spencer's (1971) formula.

    See: https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.solarposition.equation_of_time_spencer71.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        dayofyear (list[list]): Day of the year (1 to 366).

    Returns:
        list[list]: 2D list of equation of time offsets (minutes), or an error string.
    """
    try:
        def flatten_num(data):
            if not isinstance(data, list): return [float(data)]
            flat = []
            for row in data:
                row = row if isinstance(row, list) else [row]
                for val in row:
                    if val == "": flat.append(float('nan'))
                    else: flat.append(float(val))
            return flat

        doy_list = flatten_num(dayofyear)
        if len(doy_list) == 0:
            return "Error: input array cannot be empty"

        res = result_func(np.array(doy_list))

        return [[float(v) if not pd.isna(v) else ""] for v in res]
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Day of the year (1 to 366).

SOLAR_AZIM_AN

This function computes solar azimuth from latitude, hour angle, declination, and zenith using an analytical spherical-trigonometry relation.

Azimuth describes the sun’s horizontal direction and is useful for tracker control, façade shading, and incidence-angle calculations.

A common form is:

\sin(\gamma_s)=\frac{\cos(\delta)\sin(h)}{\sin(\theta_z)}

with quadrant handling applied to obtain the final azimuth angle.

Excel Usage

=SOLAR_AZIM_AN(latitude, hourangle, declination, zenith)
  • latitude (float, required): Site latitude (radians).
  • hourangle (list[list], required): Solar hour angle (radians).
  • declination (list[list], required): Solar declination angle (radians).
  • zenith (list[list], required): Solar zenith angle (radians).

Returns (list[list]): 2D list of solar azimuth angles (radians), or an error string.

Example 1: Solar azimuth at solar noon in radians

Inputs:

latitude hourangle declination zenith
0.6936 0 0.4091 0.2845

Excel formula:

=SOLAR_AZIM_AN(0.6936, {0}, {0.4091}, {0.2845})

Expected output:

3.14159

Example 2: Morning hour angle gives eastward azimuth

Inputs:

latitude hourangle declination zenith
0.6936 -0.5 0.2 0.8

Excel formula:

=SOLAR_AZIM_AN(0.6936, {-0.5}, {0.2}, {0.8})

Expected output:

2.03455

Example 3: Afternoon hour angle gives westward azimuth

Inputs:

latitude hourangle declination zenith
0.6936 0.5 0.2 0.8

Excel formula:

=SOLAR_AZIM_AN(0.6936, {0.5}, {0.2}, {0.8})

Expected output:

4.24863

Example 4: Multiple solar geometry rows

Inputs:

latitude hourangle declination zenith
0.6936 -0.3 0.1 0.9
0.3 0.1 0.9

Excel formula:

=SOLAR_AZIM_AN(0.6936, {-0.3;0.3}, {0.1;0.1}, {0.9;0.9})

Expected output:

Result
2.0875
4.19568

Python Code

Show Code
import pandas as pd
import numpy as np
from pvlib.solarposition import solar_azimuth_analytical as result_func

def solar_azim_an(latitude, hourangle, declination, zenith):
    """
    Calculate the solar azimuth angle using an analytical expression.

    See: https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.solarposition.solar_azimuth_analytical.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        latitude (float): Site latitude (radians).
        hourangle (list[list]): Solar hour angle (radians).
        declination (list[list]): Solar declination angle (radians).
        zenith (list[list]): Solar zenith angle (radians).

    Returns:
        list[list]: 2D list of solar azimuth angles (radians), or an error string.
    """
    try:
        def flatten_num(data):
            if not isinstance(data, list): return [float(data)]
            flat = []
            for row in data:
                row = row if isinstance(row, list) else [row]
                for val in row:
                    if val == "": flat.append(float('nan'))
                    else: flat.append(float(val))
            return flat

        ha_list = flatten_num(hourangle)
        dec_list = flatten_num(declination)
        zen_list = flatten_num(zenith)

        n = len(ha_list)
        if n == 0 or len(dec_list) != n or len(zen_list) != n:
            return "Error: All input arrays must have the same non-zero length"

        lat = float(latitude)

        res = result_func(
            latitude=lat,
            hourangle=np.array(ha_list),
            declination=np.array(dec_list),
            zenith=np.array(zen_list)
        )

        return [[float(v) if not pd.isna(v) else ""] for v in res]
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Site latitude (radians).
Solar hour angle (radians).
Solar declination angle (radians).
Solar zenith angle (radians).

SOLAR_ZEN_AN

This function computes solar zenith from latitude, hour angle, and declination using an analytical spherical-geometry expression.

Zenith is the angle between the sun vector and local vertical and is a primary quantity for irradiance decomposition and transposition.

The analytical relation is:

\cos(\theta_z)=\sin(\phi)\sin(\delta)+\cos(\phi)\cos(\delta)\cos(h)

where \phi is latitude, \delta is declination, and h is hour angle, all in radians.

Excel Usage

=SOLAR_ZEN_AN(latitude, hourangle, declination)
  • latitude (float, required): Site latitude (radians).
  • hourangle (list[list], required): Solar hour angle (radians).
  • declination (list[list], required): Solar declination angle (radians).

Returns (list[list]): 2D list of solar zenith angles (radians), or an error string.

Example 1: Solar zenith at solar noon in radians

Inputs:

latitude hourangle declination
0.6936 0 0.4091

Excel formula:

=SOLAR_ZEN_AN(0.6936, {0}, {0.4091})

Expected output:

0.2845

Example 2: Zenith angle for morning hour angle

Inputs:

latitude hourangle declination
0.6936 -0.5 0.2

Excel formula:

=SOLAR_ZEN_AN(0.6936, {-0.5}, {0.2})

Expected output:

0.662631

Example 3: Zenith angle for afternoon hour angle

Inputs:

latitude hourangle declination
0.6936 0.5 0.2

Excel formula:

=SOLAR_ZEN_AN(0.6936, {0.5}, {0.2})

Expected output:

0.662631

Example 4: Multiple hour angles in one call

Inputs:

latitude hourangle declination
0.6936 -0.3 0.1
0.3 0.1

Excel formula:

=SOLAR_ZEN_AN(0.6936, {-0.3;0.3}, {0.1;0.1})

Expected output:

Result
0.652184
0.652184

Python Code

Show Code
import pandas as pd
import numpy as np
from pvlib.solarposition import solar_zenith_analytical as result_func

def solar_zen_an(latitude, hourangle, declination):
    """
    Calculate the solar zenith angle using an analytical expression.

    See: https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.solarposition.solar_zenith_analytical.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        latitude (float): Site latitude (radians).
        hourangle (list[list]): Solar hour angle (radians).
        declination (list[list]): Solar declination angle (radians).

    Returns:
        list[list]: 2D list of solar zenith angles (radians), or an error string.
    """
    try:
        def flatten_num(data):
            if not isinstance(data, list): return [float(data)]
            flat = []
            for row in data:
                row = row if isinstance(row, list) else [row]
                for val in row:
                    if val == "": flat.append(float('nan'))
                    else: flat.append(float(val))
            return flat

        ha_list = flatten_num(hourangle)
        dec_list = flatten_num(declination)

        n = len(ha_list)
        if n == 0 or len(dec_list) != n:
            return "Error: hourangle and declination arrays must have the same non-zero length"

        lat = float(latitude)

        res = result_func(
            latitude=lat,
            hourangle=np.array(ha_list),
            declination=np.array(dec_list)
        )

        return [[float(v) if not pd.isna(v) else ""] for v in res]
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Site latitude (radians).
Solar hour angle (radians).
Solar declination angle (radians).

SOLARPOSITION

This function computes solar position angles for one or more timestamps at a specified site.

For each timestamp, it returns azimuth, elevation, and apparent zenith using one of pvlib’s solar-position algorithms.

Elevation and zenith are related by:

\mathrm{elevation} = 90^\circ - \mathrm{zenith}

Apparent zenith includes atmospheric refraction and is commonly preferred over true zenith for irradiance modeling.

Excel Usage

=SOLARPOSITION(time, latitude, longitude, altitude, pressure, solarpos_meth, temperature)
  • time (list[list], required): Timestamp or 2D range of timestamps in ISO8601 format.
  • latitude (float, required): Latitude north of equator (deg).
  • longitude (float, required): Longitude east of prime meridian (deg).
  • altitude (float, optional, default: 0): Site elevation above sea level (m).
  • pressure (float, optional, default: 101325): Atmospheric pressure (Pa).
  • solarpos_meth (str, optional, default: “nrel_numpy”): Solar position algorithm method.
  • temperature (float, optional, default: 12): Ambient temperature used in refraction calculation (deg C).

Returns (list[list]): 2D list with one row per timestamp containing azimuth, elevation, and apparent zenith in degrees, or an error string.

Example 1: Summer midday position at sea level

Inputs:

time latitude longitude altitude pressure solarpos_meth temperature
2024-06-20T12:00:00Z 35 -120 0 101325 nrel_numpy 12

Excel formula:

=SOLARPOSITION("2024-06-20T12:00:00Z", 35, -120, 0, 101325, "nrel_numpy", 12)

Expected output:

Result
53.1775 -8.77273 98.7727
Example 2: Summer morning position at sea level

Inputs:

time latitude longitude altitude pressure solarpos_meth temperature
2024-06-20T06:00:00Z 35 -120 0 101325 nrel_numpy 12

Excel formula:

=SOLARPOSITION("2024-06-20T06:00:00Z", 35, -120, 0, 101325, "nrel_numpy", 12)

Expected output:

Result
329.216 -24.8381 114.838
Example 3: Winter midday position at higher altitude

Inputs:

time latitude longitude altitude pressure solarpos_meth temperature
2024-12-21T12:00:00Z 35 -120 100 90000 nrel_numpy 12

Excel formula:

=SOLARPOSITION("2024-12-21T12:00:00Z", 35, -120, 100, 90000, "nrel_numpy", 12)

Expected output:

Result
94.7359 -36.8049 126.805
Example 4: Row vector of timestamps

Inputs:

time latitude longitude altitude pressure solarpos_meth temperature
2024-06-20T12:00:00Z 2024-06-20T18:00:00Z 35 -120 0 101325 nrel_numpy 12

Excel formula:

=SOLARPOSITION({"2024-06-20T12:00:00Z","2024-06-20T18:00:00Z"}, 35, -120, 0, 101325, "nrel_numpy", 12)

Expected output:

Result
53.1775 -8.77273 98.7727
105.383 61.176 28.8148
Example 5: Winter morning position at sea level

Inputs:

time latitude longitude altitude pressure solarpos_meth temperature
2024-12-21T09:00:00Z 35 -120 0 101325 nrel_numpy 12

Excel formula:

=SOLARPOSITION("2024-12-21T09:00:00Z", 35, -120, 0, 101325, "nrel_numpy", 12)

Expected output:

Result
53.4021 -72.2847 162.285

Python Code

Show Code
import pandas as pd
from pvlib.solarposition import get_solarposition as pvlib_get_solarposition

def solarposition(time, latitude, longitude, altitude=0, pressure=101325, solarpos_meth='nrel_numpy', temperature=12):
    """
    Calculate solar azimuth, elevation, and apparent zenith for given times and location.

    See: https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.solarposition.get_solarposition.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        time (list[list]): Timestamp or 2D range of timestamps in ISO8601 format.
        latitude (float): Latitude north of equator (deg).
        longitude (float): Longitude east of prime meridian (deg).
        altitude (float, optional): Site elevation above sea level (m). Default is 0.
        pressure (float, optional): Atmospheric pressure (Pa). Default is 101325.
        solarpos_meth (str, optional): Solar position algorithm method. Valid options: NREL NumPy, NREL Numba, PyEphem, Ephemeris, NREL C. Default is 'nrel_numpy'.
        temperature (float, optional): Ambient temperature used in refraction calculation (deg C). Default is 12.

    Returns:
        list[list]: 2D list with one row per timestamp containing azimuth, elevation, and apparent zenith in degrees, or an error string.
    """
    try:
      def flatten_str(data):
        if not isinstance(data, list):
          return [str(data)]

        flat = []
        for row in data:
          row = row if isinstance(row, list) else [row]
          for value in row:
            if value == "":
              continue
            flat.append(str(value))
        return flat

      times = flatten_str(time)
      if len(times) == 0:
        return "Error: time array cannot be empty"

      dt_index = pd.DatetimeIndex(times)

      lat = float(latitude)
      lon = float(longitude)
      alt = float(altitude)
      pres = float(pressure)
      temp = float(temperature)
      meth = str(solarpos_meth)

      valid_methods = ['nrel_numpy', 'nrel_numba', 'pyephem', 'ephemeris', 'nrel_c']
      if meth not in valid_methods:
        return "Error: solarpos_meth must be one of nrel_numpy, nrel_numba, pyephem, ephemeris, nrel_c"

      df = pvlib_get_solarposition(
        time=dt_index,
        latitude=lat,
        longitude=lon,
        altitude=alt,
        pressure=pres,
        method=meth,
        temperature=temp
      )

      result = []
      for i in range(len(dt_index)):
        az = float(df.iloc[i]["azimuth"])
        el = float(df.iloc[i]["elevation"])
        zen = float(df.iloc[i]["apparent_zenith"])
        result.append([az, el, zen])
      return result
    except Exception as e:
      return f"Error: {str(e)}"

Online Calculator

Timestamp or 2D range of timestamps in ISO8601 format.
Latitude north of equator (deg).
Longitude east of prime meridian (deg).
Site elevation above sea level (m).
Atmospheric pressure (Pa).
Solar position algorithm method.
Ambient temperature used in refraction calculation (deg C).

SUN_RISE_SET_GEOM

This function estimates sunrise, sunset, and solar transit times using a geometric approximation based on declination and equation of time.

The method assumes a circular orbit and neglects atmospheric refraction, so it is faster but less accurate than SPA-based event timing.

Event hour-angle geometry is based on relations such as:

\cos(H_0) = -\tan(\phi)\tan(\delta)

where H_0 is the sunrise/sunset hour angle, \phi is latitude, and \delta is declination.

Excel Usage

=SUN_RISE_SET_GEOM(times, latitude, longitude, declination, equation_of_time)
  • times (list[list], required): Timestamps for the events (ISO8601 format).
  • latitude (float, required): Site latitude (degrees).
  • longitude (float, required): Site longitude (degrees).
  • declination (list[list], required): Solar declination angle (radians).
  • equation_of_time (list[list], required): Equation of time offset (minutes).

Returns (list[list]): 2D list [[sunrise, sunset, transit]] as ISO8601 strings, or an error string.

Example 1: Geometric events for basic inputs

Inputs:

times latitude longitude declination equation_of_time
2024-06-21T12:00:00-06:00 39.742 -105.179 0.4091 -1.5

Excel formula:

=SUN_RISE_SET_GEOM({"2024-06-21T12:00:00-06:00"}, 39.742, -105.179, {0.4091}, {-1.5})

Expected output:

Result
2024-06-21T05:37:41.734449192-06:00 2024-06-21T20:26:44.185550809-06:00 2024-06-21T13:02:12.959999998-06:00
Example 2: Geometric events for winter solstice

Inputs:

times latitude longitude declination equation_of_time
2024-12-21T12:00:00-07:00 39.742 -105.179 -0.4091 2

Excel formula:

=SUN_RISE_SET_GEOM({"2024-12-21T12:00:00-07:00"}, 39.742, -105.179, {-0.4091}, {2})

Expected output:

Result
2024-12-21T07:23:14.185550806-07:00 2024-12-21T16:34:11.734449193-07:00 2024-12-21T11:58:42.960000-07:00
Example 3: Geometric events near equinox

Inputs:

times latitude longitude declination equation_of_time
2024-03-20T12:00:00-06:00 39.742 -105.179 0 -7.5

Excel formula:

=SUN_RISE_SET_GEOM({"2024-03-20T12:00:00-06:00"}, 39.742, -105.179, {0}, {-7.5})

Expected output:

Result
2024-03-20T07:08:12.959999998-06:00 2024-03-20T19:08:12.959999998-06:00 2024-03-20T13:08:12.959999998-06:00
Example 4: Scalar timestamp geometric events

Inputs:

times latitude longitude declination equation_of_time
2024-06-21T12:00:00-06:00 39.742 -105.179 0.4091 -1.5

Excel formula:

=SUN_RISE_SET_GEOM("2024-06-21T12:00:00-06:00", 39.742, -105.179, {0.4091}, {-1.5})

Expected output:

Result
2024-06-21T05:37:41.734449192-06:00 2024-06-21T20:26:44.185550809-06:00 2024-06-21T13:02:12.959999998-06:00

Python Code

Show Code
import pandas as pd
import numpy as np
from pvlib.solarposition import sun_rise_set_transit_geometric as result_func

def sun_rise_set_geom(times, latitude, longitude, declination, equation_of_time):
    """
    Geometric calculation of solar sunrise, sunset, and transit.

    See: https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.solarposition.sun_rise_set_transit_geometric.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        times (list[list]): Timestamps for the events (ISO8601 format).
        latitude (float): Site latitude (degrees).
        longitude (float): Site longitude (degrees).
        declination (list[list]): Solar declination angle (radians).
        equation_of_time (list[list]): Equation of time offset (minutes).

    Returns:
        list[list]: 2D list [[sunrise, sunset, transit]] as ISO8601 strings, or an error string.
    """
    try:
        def flatten_str(data):
            if not isinstance(data, list): return [str(data)]
            return [str(val) for row in data for val in (row if isinstance(row, list) else [row]) if val != ""]

        def flatten_num(data):
            if not isinstance(data, list): return [float(data)]
            flat = []
            for row in data:
                row = row if isinstance(row, list) else [row]
                for val in row:
                    if val == "": flat.append(float('nan'))
                    else: flat.append(float(val))
            return flat

        time_list = flatten_str(times)
        dec_list = flatten_num(declination)
        eot_list = flatten_num(equation_of_time)

        n = len(time_list)
        if n == 0 or len(dec_list) != n or len(eot_list) != n:
            return "Error: All input arrays must have the same non-zero length"

        lat = float(latitude)
        lon = float(longitude)
        idx = pd.DatetimeIndex(time_list)

        res = result_func(
            times=idx,
            latitude=lat,
            longitude=lon,
            declination=np.array(dec_list),
            equation_of_time=np.array(eot_list)
        )

        # res is a tuple (sunrise, sunset, transit) which are DatetimeIndex-like Series
        sr = res[0]
        ss = res[1]
        tr = res[2]

        out = []
        for i in range(n):
            out.append([
                sr[i].isoformat() if not pd.isna(sr[i]) else "",
                ss[i].isoformat() if not pd.isna(ss[i]) else "",
                tr[i].isoformat() if not pd.isna(tr[i]) else ""
            ])
        return out
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Timestamps for the events (ISO8601 format).
Site latitude (degrees).
Site longitude (degrees).
Solar declination angle (radians).
Equation of time offset (minutes).

SUN_RISE_SET_SPA

This function computes sunrise, sunset, and solar transit times for localized timestamps using the NREL Solar Position Algorithm (SPA).

SPA is a high-accuracy astronomical method used widely in PV and solar-resource workflows where event-timing precision matters.

Conceptually, sunrise and sunset occur near the condition:

\mathrm{zenith} \approx 90^\circ

with corrections from detailed SPA geometry, refraction, and time-scale terms.

Excel Usage

=SUN_RISE_SET_SPA(times, latitude, longitude, delta_t)
  • times (list[list], required): Timestamps for which to calculate events (ISO8601 format).
  • latitude (float, required): Site latitude (degrees).
  • longitude (float, required): Site longitude (degrees).
  • delta_t (float, optional, default: 67): Difference between terrestrial time and UT1 (seconds).

Returns (list[list]): 2D list [[sunrise, sunset, transit]] as ISO8601 strings, or an error string.

Example 1: Solstice events in Golden, CO

Inputs:

times latitude longitude delta_t
2024-06-21T12:00:00-06:00 39.742 -105.179 67

Excel formula:

=SUN_RISE_SET_SPA({"2024-06-21T12:00:00-06:00"}, 39.742, -105.179, 67)

Expected output:

Result
2024-06-21T05:33:02.276770560-06:00 2024-06-21T20:32:09.189224704-06:00 2024-06-21T13:02:42.069548544-06:00
Example 2: Winter solstice events in Golden, CO

Inputs:

times latitude longitude delta_t
2024-12-21T12:00:00-07:00 39.742 -105.179 67

Excel formula:

=SUN_RISE_SET_SPA({"2024-12-21T12:00:00-07:00"}, 39.742, -105.179, 67)

Expected output:

Result
2024-12-21T07:18:31.492916224-07:00 2024-12-21T16:39:47.010416128-07:00 2024-12-21T11:59:09.093026048-07:00
Example 3: Equinox events in Golden, CO

Inputs:

times latitude longitude delta_t
2024-03-20T12:00:00-06:00 39.742 -105.179 67

Excel formula:

=SUN_RISE_SET_SPA({"2024-03-20T12:00:00-06:00"}, 39.742, -105.179, 67)

Expected output:

Result
2024-03-20T07:03:07.839649792-06:00 2024-03-20T19:12:23.359081216-06:00 2024-03-20T13:07:56.168577536-06:00
Example 4: Scalar timestamp SPA events

Inputs:

times latitude longitude delta_t
2024-06-21T12:00:00-06:00 39.742 -105.179 67

Excel formula:

=SUN_RISE_SET_SPA("2024-06-21T12:00:00-06:00", 39.742, -105.179, 67)

Expected output:

Result
2024-06-21T05:33:02.276770560-06:00 2024-06-21T20:32:09.189224704-06:00 2024-06-21T13:02:42.069548544-06:00

Python Code

Show Code
import pandas as pd
from pvlib.solarposition import sun_rise_set_transit_spa as result_func

def sun_rise_set_spa(times, latitude, longitude, delta_t=67):
    """
    Calculate sunrise, sunset, and solar transit times using the NREL SPA algorithm.

    See: https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.solarposition.sun_rise_set_transit_spa.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        times (list[list]): Timestamps for which to calculate events (ISO8601 format).
        latitude (float): Site latitude (degrees).
        longitude (float): Site longitude (degrees).
        delta_t (float, optional): Difference between terrestrial time and UT1 (seconds). Default is 67.

    Returns:
        list[list]: 2D list [[sunrise, sunset, transit]] as ISO8601 strings, or an error string.
    """
    try:
        def flatten_str(data):
            if not isinstance(data, list): return [str(data)]
            return [str(val) for row in data for val in (row if isinstance(row, list) else [row]) if val != ""]

        time_list = flatten_str(times)
        if len(time_list) == 0:
            return "Error: times array cannot be empty"

        lat = float(latitude)
        lon = float(longitude)
        dt = float(delta_t) if delta_t is not None else 67.0

        idx = pd.DatetimeIndex(time_list)

        res = result_func(
            times=idx,
            latitude=lat,
            longitude=lon,
            delta_t=dt
        )

        # res is a DataFrame with columns: sunrise, sunset, transit
        out = []
        for i in range(len(idx)):
            sr = res['sunrise'].iloc[i]
            ss = res['sunset'].iloc[i]
            tr = res['transit'].iloc[i]
            out.append([
                sr.isoformat() if not pd.isna(sr) else "",
                ss.isoformat() if not pd.isna(ss) else "",
                tr.isoformat() if not pd.isna(tr) else ""
            ])
        return out
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Timestamps for which to calculate events (ISO8601 format).
Site latitude (degrees).
Site longitude (degrees).
Difference between terrestrial time and UT1 (seconds).