9

I need to create a function (in C++) to calculate the sunrise and sunset times, but I am not a mathematician and I cannot find a correct (and easy) way to do that.

I need to get the same results as can be found in:

https://www.esrl.noaa.gov/gmd/grad/solcalc/ and http://sunrise-sunset.org/api

I tried to implement a function based on these articles https://en.wikipedia.org/wiki/Sunrise_equation and http://www.wikihow.com/Estimate-the-Time-of-Sunrise-or-Sunset but the results are wrong. (maybe I am doing something wrong)

Does anyone know a correct (and easy) formula to calculate it? Maybe the formula used by the websites that I mentioned.

Note: values that I have as input: latitude, longitude, date and UTC offset. (I don't have the altitude)

Thanks


Update:

I developed a new function on Matlab that seems to be more accurate but I still not get the exact sunrise and sunset times:

% Parameters definition
lat = -23.545570; % Latitude
lng = -46.704082; % Longitude
UTCoff = -3; % UTC offset
nDays = daysact('01-jan-2017',  '15-mar-2017'); % Number of days since 01/01

% Longitudinal correction
longCorr = 4*(lng - 15*UTCoff);

B = 360*(nDays - 81)/365; % I have no idea

% Equation of Time Correction
EoTCorr = 9.87*sind(2*B) - 7.53*cosd(B) - 1.5*sind(B);

% Solar correction
solarCorr = longCorr - EoTCorr;

% Solar declination
delta = asind(sind(23.45)*sind(360*(nDays - 81)/365));

sunrise = 12 - acosd(-tand(lat)*tand(delta))/15 - solarCorr/60;
sunset  = 12 + acosd(-tand(lat)*tand(delta))/15 - solarCorr/60;

sprintf('%2.0f:%2.0f:%2.0f\n', degrees2dms(sunrise))
sprintf('%2.0f:%2.0f:%2.0f\n', degrees2dms(sunset))

This function gives me the sunrise at 05:51:25 when it should be 06:09 and the sunset as 18:02:21 when it should be 18:22, according to ESRL (NOAA).

The function was developed based on this: https://www.mathworks.com/matlabcentral/fileexchange/55509-sunrise-sunset/content/SunriseSunset.mlx

Any idea what can I do to improve the accuracy?

KelvinS
  • 255
  • 1
    How inaccurate are your results, and how accurate would you like them to be? For example, if you can tolerate errors of about 10 minutes at some times of the year, you can get away with a vastly simpler calculation than if you want it to the second and corrected for atmospheric effects. – hmakholm left over Monica Mar 14 '17 at 19:19
  • Sorry, the results are not inaccurate, the results are wrong, I updated the question. I'm getting the sunrise result as 08:09 and sunset as 20:26 (local time) when the sunrise should be 06:08 and sunset 18:23 (local time), based on the sunrise-sunset API. Actually, I can tolerate errors of about 10 minutes sometimes (if the more accurate way is too complex). – KelvinS Mar 14 '17 at 19:43
  • I've been using this approximation for some time, and now I have noticed that var B is related to the 81th day of the year being the 1st day of spring equinox! Thank you. – sissi_luaty Apr 01 '21 at 16:57

2 Answers2

5

Here is a complete routine to that calculates the sunrise (optionally the sunset, see the code) in C++. It only requires latitude and longitude as input, and is accurate to within seconds for the civil sunrise time. This code is running in a UWP C++ app created with Visual Studio 2017. It includes a subroutine to fill the output minutes and seconds with zeros in the case of single digit results.

String^ toplatformstring(bool fill, long ll) {
    // convert ll to platform string
    Platform::String^ p_string;
    std::string doit;

    if (fill == false) {
        doit = std::to_string(ll); // convert, don't fill with zeros
    }
    else {
        //convert ll to std doit and fill with zeros
        std::stringstream ss;
        ss << std::setw(2) << std::setfill('0') << ll;
        doit = ss.str();
    }

    //convert doit to platform string
    char const *pchar = doit.c_str();
    std::string s_str = std::string(pchar);
    std::wstring wid_str = std::wstring(s_str.begin(), s_str.end());
    const wchar_t* w_char = wid_str.c_str();
    p_string = ref new Platform::String(w_char);

    return p_string;
}

//double la = 39.299236;  // baltimore
//double lo = -76.609383;
//double la = 37.0;  // SF California
//double lo = -122.0;
Platform::String^ MainPage::sunrise(double la, double lo) {


    /*double la = 39.300213;
    double lo = -76.610516;*/
    Platform::String^ sunrisetime;

    //// get year, month, day integers
    time_t rawtime;
    struct tm timeinfo;  // get date and time info
    time(&rawtime);
    localtime_s(&timeinfo, &rawtime);

    double xday = timeinfo.tm_mday;
    double xmonth = timeinfo.tm_mon;
    xmonth = xmonth + 1; // correct for origin 0
    //textblockc->Text = xmonth.ToString();  // for debugging
    double xyear = timeinfo.tm_year;
    //double dayofyear = timeinfo.tm_yday; // day of year also

    // calculate the day of the year
    //  N1 = floor(275 * month / 9)
    double xxN1 = floor(275 * xmonth / 9);
    //  N2 = floor((month + 9) / 12)
    double xxN2 = floor((xmonth + 9) / 12);
    //  N3 = (1 + floor((year - 4 * floor(year / 4) + 2) / 3))
    double xxN3 = (1 + floor((xyear - 4 * floor(xyear / 4) + 2) / 3));
    //  N = N1 - (N2 * N3) + day - 30
    double day = xxN1 - (xxN2 * xxN3) + xday - 30;

    double zenith = 90.83333333333333;
    double D2R = M_PI / 180;
    double R2D = 180 / M_PI;

    // convert the longitude to hour value and calculate an approximate time
    double lnHour = lo / 15;
    double t;
    //if (sunrise) {
    t = day + ((6 - lnHour) / 24);
    //} else {
    //t = day + ((18 - lnHour) / 24);
    //};

    //calculate the Sun's mean anomaly
    double M = (0.9856 * t) - 3.289;

    //calculate the Sun's true longitude
    double L = M + (1.916 * sin(M * D2R)) + (0.020 * sin(2 * M * D2R)) + 282.634;
    if (L > 360) {
        L = L - 360;
    }
    else if (L < 0) {
        L = L + 360;
    };

    //calculate the Sun's right ascension
    double RA = R2D * atan(0.91764 * tan(L * D2R));
    if (RA > 360) {
        RA = RA - 360;
    }
    else if (RA < 0) {
        RA = RA + 360;
    };

    //right ascension value needs to be in the same qua
    double Lquadrant = (floor(L / (90))) * 90;
    double RAquadrant = (floor(RA / 90)) * 90;
    RA = RA + (Lquadrant - RAquadrant);

    //right ascension value needs to be converted into hours
    RA = RA / 15;

    //calculate the Sun's declination
    double sinDec = 0.39782 * sin(L * D2R);
    double cosDec = cos(asin(sinDec));

    //calculate the Sun's local hour angle
    double cosH = (cos(zenith * D2R) - (sinDec * sin(la * D2R))) / (cosDec * cos(la * D2R));
    double H;
    //if (sunrise) {
    H = 360 - R2D * acos(cosH);
    //} else {
    //H = R2D * Math.acos(cosH)
    //};
    H = H / 15;

    //calculate local mean time of rising/setting
    double T = H + RA - (0.06571 * t) - 6.622;

    //adjust back to UTC
    double UT = T - lnHour;
    if (UT > 24) {
        UT = UT - 24;
    }
    else if (UT < 0) {
        UT = UT + 24;
    }

    //convert UT value to local time zone of latitude/longitude
    int offset = (int)(lo / 15); // estimate utc correction
    double localT = UT + offset; // -5 for baltimore

                                 //convert to seconds
    int seconds = (int)(localT * 3600);

    long sec = seconds % 60;
    long minutes = seconds % 3600 / 60;
    long hours = seconds % 86400 / 3600;
    hours = hours % 12;

    Platform::String^ ssec = toplatformstring(true, sec);
    Platform::String^ mminutes = toplatformstring(true, minutes);
    Platform::String^ hhours = toplatformstring(false, hours);


    sunrisetime = hhours + ":" + mminutes + ":" + ssec;
    return sunrisetime;
}
pollaris
  • 151
  • 1
    Great. I have also implemented it in Go: https://github.com/kelvins/sunrisesunset – KelvinS Jan 11 '18 at 21:30
  • Have implemented in Javascript in case anyone is looking :) https://gist.github.com/adam-carter-fms/a44a14c0a8cdacbbc38276f6d553e024 – ACarter Jul 13 '20 at 17:19
  • 1
    I have implement it in shell: https://gist.github.com/bderenzo/24398ed80d5660d4c6ffd5d63e08ce7c – BDR Sep 16 '21 at 18:56
1

As @Richard already answered here, I was mixing things.

  • My Matlab script is calculating the actual sunrise and sunset (geometrically).
  • The NOAA website gives the apparent sunrise and sunset. These values are corrected for atmospheric refraction.

In the glossary to the NOAA website, it is written:

Due to atmospheric refraction, sunrise occurs shortly before the sun crosses above the horizon. Light from the sun is bent, or refracted, as it enters earth's atmosphere. See Apparent Sunrise Figure. This effect causes the apparent sunrise to be earlier than the actual sunrise. Similarly, apparent sunset occurs slightly later than actual sunset.

So this is exactly the effect that is causing the 'calculation error'.

@Richard have reverse engineered the functions from the Excel sheet provided on NOAA's website and created a Matlab function to calculate it:

function [sun_rise_set, varargout] = sunRiseSet( lat, lng, UTCoff, date, PLOT)
%SUNRISESET Compute apparent sunrise and sunset times in seconds.
%     sun_rise_set = sunRiseSet( lat, lng, UTCoff, date, PLOT) Computes the *apparent** (refraction
%     corrected) sunrise  and sunset times in seconds from mignight and returns them as
%     sun_rise_set.  lat and lng are the latitude (+ to N) and longitude (+ to E), UTCoff is the
%     local time offset to UTC in hours and date is the date in format 'dd-mmm-yyyy' ( see below for
%     an example). Set PLOT to true to create some plots.
% 
%     [sun_rise_set, noon] = sunRiseSet( lat, lng, UTCoff, date, PLOT) additionally returns the
%     solar noon in seconds from midnight.
% 
%     [sun_rise_set, noon, opt] = sunRiseSet( lat, lng, UTCoff, date, PLOT) additionally returns the
%     information opt, which contains information on every second of the day:
%       opt.elev_ang_corr   : Apparent (refraction corrected) solar elevation in degrees
%       opt.azmt_ang        : Solar azimuthal angle (deg cw from N)
%       opt.solar_decl      : Solar declination in degrees
% 
% EXAMPLE:
%     lat = -23.545570;     % Latitude
%     lng = -46.704082;     % Longitude
%     UTCoff = -3;          % UTC offset
%     date = '15-mar-2017';
% 
%     [sun_rise_set, noon, opt] = sunRiseSet( lat, lng, UTCoff, date, 1);
%
% 
% Richard Droste
% 
% Reverse engineered from the NOAA Excel:
% (https://www.esrl.noaa.gov/gmd/grad/solcalc/calcdetails.html)
% 
% The formulas are from:
% Meeus, Jean H. Astronomical algorithms. Willmann-Bell, Incorporated, 1991.

% Process input
nDays = daysact('30-dec-1899',  date);  % Number of days since 01/01
nTimes = 24*3600;                       % Number of seconds in the day
tArray = linspace(0,1,nTimes);

% Compute
% Letters correspond to colums in the NOAA Excel
E = tArray;
F = nDays+2415018.5+E-UTCoff/24;
G = (F-2451545)/36525;
I = mod(280.46646+G.*(36000.76983+G*0.0003032),360);
J = 357.52911+G.*(35999.05029-0.0001537*G);
K = 0.016708634-G.*(0.000042037+0.0000001267*G);
L = sin(deg2rad(J)).*(1.914602-G.*(0.004817+0.000014*G))+sin(deg2rad(2*J)).* ...
    (0.019993-0.000101*G)+sin(deg2rad(3*J))*0.000289;
M = I+L;
P = M-0.00569-0.00478*sin(deg2rad(125.04-1934.136*G));
Q = 23+(26+((21.448-G.*(46.815+G.*(0.00059-G*0.001813))))/60)/60;
R = Q+0.00256*cos(deg2rad(125.04-1934.136*G));
T = rad2deg(asin(sin(deg2rad(R)).*sin(deg2rad(P))));
U = tan(deg2rad(R/2)).*tan(deg2rad(R/2));
V = 4*rad2deg(U.*sin(2*deg2rad(I))-2*K.*sin(deg2rad(J))+4*K.*U.*sin(deg2rad(J)).* ...
    cos(2*deg2rad(I))-0.5.*U.*U.*sin(4*deg2rad(I))-1.25.*K.*K.*sin(2.*deg2rad(J)));
AB = mod(E*1440+V+4*lng-60*UTCoff,1440);
if AB/4 < 0, AC = AB/4+180;else, AC = AB/4-180; end
AD = rad2deg(acos(sin(deg2rad(lat))*sin(deg2rad(T))+cos(deg2rad(lat))*cos(deg2rad(T)).*...
    cos(deg2rad(AC))));
W = rad2deg(acos(cos(deg2rad(90.833))./(cos(deg2rad(lat))*cos(deg2rad(T))) ...
    -tan(deg2rad(lat))*tan(deg2rad(T))));
X = (720-4*lng-V+UTCoff*60)*60;

% Results in seconds
[~,noon]    = min(abs(X - nTimes*tArray));
[~,sunrise] = min(abs(X-round(W*4*60) - nTimes*tArray));
[~,sunset] = min(abs(X+round(W*4*60) - nTimes*tArray));

% Results in degrees
if nargout > 2 || PLOT
    solar_decl = T;
    elev_ang_corr = 90-AD;
    AC_ind = AC > 0;
    azmt_ang = mod(rad2deg(acos(((sin(deg2rad(lat))*cos(deg2rad(AD)))-sin(deg2rad(T)))./ ...
        (cos(deg2rad(lat))*sin(deg2rad(AD)))))+180,360);
    azmt_ang_2 = mod(540-rad2deg(acos(((sin(deg2rad(lat))*cos(deg2rad(AD)))-sin(deg2rad(T)))./ ...
        (cos(deg2rad(lat))*sin(deg2rad(AD))))),360);
    azmt_ang(~AC_ind) = azmt_ang_2(~AC_ind);
end

% Print in hours, minutes and seconds
fprintf('Sunrise: %s  \nSunset:  %s\n', ...
    datestr(sunrise/nTimes,'HH:MM:SS'), datestr(sunset/nTimes,'HH:MM:SS'));

sun_rise_set = [sunrise sunset];
if nargout > 1
    varargout{1} = noon;
end
if nargout > 2
    opt.elev_ang_corr = elev_ang_corr;
    opt.azmt_ang = azmt_ang;
    opt.solar_decl = solar_decl;
    varargout{2} = opt;
end

if PLOT
    figure; hold on
    plot(linspace(0,24,nTimes), elev_ang_corr);
    xlabel('Hour'), ylabel('Angle [Deg]')
    xlim([0 24]), grid on
    title('Corrected Elevation Angle')

    figure;
    plot(linspace(0,24,nTimes), azmt_ang);
    xlabel('Hour'), ylabel('Angle [Deg]')
    xlim([0 24]), grid on
    title('Azimuthal Angle')
end

Edit: Richard's uploaded an extended version on Matlab File Exchange

See the complete discussion here.

Now, I can use this Matlab function to convert it to a C++ function.

KelvinS
  • 255
  • This looks more like a long comment rather than an answer. You don't do much more than refer to the answer for the cross-posted question. You also provide superfluous information about your own work that doesn't actually address the question. I'd suggest significantly editing this or deleting. – horchler Mar 23 '17 at 19:12
  • 1
    @horchler I updated the answer, please, take a look. – KelvinS Mar 23 '17 at 19:45
  • I really wonder how and why the atmospheric refraction is corrected, given that it would mainly be effective when the Sun rises over an ocean surface (or a flat earth surface), and given that most landscapes are surrounded by mountain topography, how is that accounted for; which is a much larger source of error for the apparent rise/set times of the Sun on the horizon defined by mountainscapes...? – Fat32 Sep 15 '20 at 15:33