Click here to Skip to main content
15,886,770 members
Articles / Desktop Programming / Win32
Article

C# Class for Calculating Sunrise and Sunset Times

Rate me:
Please Sign up or sign in to vote.
4.28/5 (18 votes)
13 Sep 2008Public Domain1 min read 154.8K   4.8K   43   36
A class for calculating sunrise and sunset times, implemented as a thread-safe Singleton

Introduction

This simple C# Singleton class calculates the sunrise and sunset times for a given date.

Background

After searching for a simple and decent implementation for calculating sunrise and sunset times for given dates, and trying several implementations that were either too complicated to migrate to C# or simply not working, I found a simple yet working JavaScript implementation here.

I migrated the code to C#, tweaking it a little so that it provides accurate calculations. Also, I wrapped it as a Singleton class (assuming multiple instances would not be required for such a class) and added a lock to the main calculation method, in order to make it thread safe (via blocking).

Using the Code

The singleton class SunTimes can be called from anywhere in your code by calling SunTimes.Instance.

The class contains a single method, with one overload, named CalculateSunRiseSetTimes(). You simply call this method, provide it with three input parameters: latitude and longitude of the desired location, and date for which to calculate. Moreover, you need to pass it four (4) output (ref) parameters: riseTime (sunrise time), setTime (sunset time), isSunrise (does the sun rise that day at all?) and isSunset (does the sun set that day at all?).

The method returns a boolean value if the calculation succeeds (it will fail, if the time zone and longitude are incompatible).

Here is a sample usage of the class:

C#
...

DateTime date = DateTime.Today;
bool isSunrise = false;
bool isSunset = false;
DateTime sunrise = DateTime.Now;
DateTime sunset = DateTime.Now;

// Print out the Sunrise and Sunset times for the next 20 days
for (int i=0; i<20; i++)
{
                                                // Coordinates of Tel-Aviv
     SunTimes.Instance.CalculateSunRiseSetTimes(new SunTimes.LatitudeCoords
                                   (32, 4, 0, SunTimes.LatitudeCoords.Direction.North),
                                                new SunTimes.LongitudeCoords
                                   (34, 46, 0, SunTimes.LongitudeCoords.Direction.East),
                                                date, ref sunrise, ref sunset, 
			                     ref isSunrise, ref isSunset);

     Debug.Print(date + '': Sunrise @'' + sunrise.ToString('HH:mm') + ''  
				Sunset @'' + sunset.ToString(''HH:mm''));

     date = date.AddDays(1); // Move to the next day
}

...

Points of Interest

This implementation is not in particular fancy, not is it the slickest design, but hey - it does the work (at least as far as I've tested it). I will be happy to get any comments (not on its design, please, only if you detect any actual bugs).

History

  • 14-Sep-2008: Uploaded the class implementation

License

This article, along with any associated source code and files, is licensed under A Public Domain dedication


Written By
CEO Homee
Israel Israel
With 15 years of experience in the IT/High-Tech industry, as developer, team-leader and product manager.

Fields of expertise include: Networks, Security, Gaming, Embedded/Real Time and Web applications.

I have written many applications, both in structural languages (C, Pascal, Fortran, VB) and OO languages (C++, C#, J2SE/J2EE) as well as in scripting/interpreted/web languages (HTML, JavaScript, LUA Script, PHP, VBScript).

Today, Co-Founder and CEO of Robo Smart Solutions (www.robo.co.il).

Comments and Discussions

 
Questioni ported NAA code to a working project. Pin
uaichiuu20-Jun-14 11:05
professionaluaichiuu20-Jun-14 11:05 
QuestionI think there is a mistake here.. Pin
Nirosh2-Dec-13 18:47
professionalNirosh2-Dec-13 18:47 
AnswerRe: I think there is a mistake here.. Pin
Ryanshane3-May-17 2:18
Ryanshane3-May-17 2:18 
QuestionLong, Lat and time offset Pin
shaydr11-Feb-13 22:51
shaydr11-Feb-13 22:51 
QuestionTimezones Pin
amiller8810-Jan-13 4:50
amiller8810-Jan-13 4:50 
QuestionisSunset and isSunrise Pin
DNZ_at28-Dec-11 22:50
DNZ_at28-Dec-11 22:50 
GeneralEast/West Bug Pin
sorgal3-Mar-11 0:49
sorgal3-Mar-11 0:49 
GeneralRe: East/West Bug Pin
Eur0pa25-Feb-14 2:19
Eur0pa25-Feb-14 2:19 
QuestionWhat about elevation and twilight? Pin
Mark Kestenbaum24-Feb-10 2:19
Mark Kestenbaum24-Feb-10 2:19 
GeneralKILLER BUG [modified] Pin
Jools55726-Apr-09 13:09
Jools55726-Apr-09 13:09 
GeneralRe: KILLER BUG Pin
Zacky Pickholz5-May-09 22:54
Zacky Pickholz5-May-09 22:54 
GeneralNice but one question [modified] Pin
Rafone16-Mar-09 19:50
Rafone16-Mar-09 19:50 
GeneralRe: Nice but one question Pin
Zacky Pickholz21-Apr-09 22:51
Zacky Pickholz21-Apr-09 22:51 
GeneralRe: Nice but one question Pin
PIEBALDconsult22-Apr-09 4:13
mvePIEBALDconsult22-Apr-09 4:13 
GeneralRe: Nice but one question Pin
Rafone22-Apr-09 7:05
Rafone22-Apr-09 7:05 
GeneralRe: Nice but one question Pin
Zacky Pickholz5-May-09 22:49
Zacky Pickholz5-May-09 22:49 
GeneralRe: Nice but one question Pin
Rafone6-May-09 2:46
Rafone6-May-09 2:46 
Hi Guys;

I think the site is...

http://www.srrb.noaa.gov/highlights/sunrise/sunrise.html[^]

The adopted class

using System;
using System.Collections.Generic;
using System.Text;

// NAA - NOAA's Astronomical Algorithms
// (JavaScript web page http://www.srrb.noaa.gov/highlights/sunrise/sunrise.html by
// Chris Cornwall, Aaron Horiuchi and Chris Lehman)
// Ported to C# by Pete Gray (petegray@ieee.org), June 2006
// Released as Open Source and can be used in any way, as long as the above description
// remains in place.

namespace your program
{

static public partial class NAA
{
//*********************************************************************/

// Convert radian angle to degrees

static public double radToDeg(double angleRad)
{
return (180.0 * angleRad / Math.PI);
}

//*********************************************************************/

// Convert degree angle to radians

static public double degToRad(double angleDeg)
{
return (Math.PI * angleDeg / 180.0);
}


//***********************************************************************/
//* Name: calcJD
//* Type: Function
//* Purpose: Julian day from calendar day
//* Arguments:
//* year : 4 digit year
//* month: January = 1
//* day : 1 - 31
//* Return value:
//* The Julian day corresponding to the date
//* Note:
//* Number is returned for start of day. Fractional days should be
//* added later.
//***********************************************************************/

static public double calcJD(int year, int month, int day)
{
if (month <= 2) {
year -= 1;
month += 12;
}
double A = Math.Floor(year/100.0);
double B = 2 - A + Math.Floor(A/4);

double JD = Math.Floor(365.25*(year + 4716)) + Math.Floor(30.6001*(month+1)) + day + B - 1524.5;
return JD;
}

//***********************************************************************/
//* Name: calcTimeJulianCent
//* Type: Function
//* Purpose: convert Julian Day to centuries since J2000.0.
//* Arguments:
//* jd : the Julian Day to convert
//* Return value:
//* the T value corresponding to the Julian Day
//***********************************************************************/

static public double calcTimeJulianCent(double jd)
{
double T = (jd - 2451545.0)/36525.0;
return T;
}


//***********************************************************************/
//* Name: calcJDFromJulianCent
//* Type: Function
//* Purpose: convert centuries since J2000.0 to Julian Day.
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* the Julian Day corresponding to the t value
//***********************************************************************/

static public double calcJDFromJulianCent(double t)
{
double JD = t * 36525.0 + 2451545.0;
return JD;
}


//***********************************************************************/
//* Name: calGeomMeanLongSun
//* Type: Function
//* Purpose: calculate the Geometric Mean Longitude of the Sun
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* the Geometric Mean Longitude of the Sun in degrees
//***********************************************************************/

static public double calcGeomMeanLongSun(double t)
{
double L0 = 280.46646 + t * (36000.76983 + 0.0003032 * t);
while(L0 > 360.0)
{
L0 -= 360.0;
}
while(L0 < 0.0)
{
L0 += 360.0;
}
return L0; // in degrees
}


//***********************************************************************/
//* Name: calGeomAnomalySun
//* Type: Function
//* Purpose: calculate the Geometric Mean Anomaly of the Sun
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* the Geometric Mean Anomaly of the Sun in degrees
//***********************************************************************/

static public double calcGeomMeanAnomalySun(double t)
{
double M = 357.52911 + t * (35999.05029 - 0.0001537 * t);
return M; // in degrees
}

//***********************************************************************/
//* Name: calcEccentricityEarthOrbit
//* Type: Function
//* Purpose: calculate the eccentricity of earth's orbit
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* the unitless eccentricity
//***********************************************************************/


static public double calcEccentricityEarthOrbit(double t)
{
double e = 0.016708634 - t * (0.000042037 + 0.0000001267 * t);
return e; // unitless
}

//***********************************************************************/
//* Name: calcSunEqOfCenter
//* Type: Function
//* Purpose: calculate the equation of center for the sun
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* in degrees
//***********************************************************************/


static public double calcSunEqOfCenter(double t)
{
double m = calcGeomMeanAnomalySun(t);

double mrad = degToRad(m);
double sinm = Math.Sin(mrad);
double sin2m = Math.Sin(mrad+mrad);
double sin3m = Math.Sin(mrad+mrad+mrad);

double C = sinm * (1.914602 - t * (0.004817 + 0.000014 * t)) + sin2m * (0.019993 - 0.000101 * t) + sin3m * 0.000289;
return C; // in degrees
}

//***********************************************************************/
//* Name: calcSunTrueLong
//* Type: Function
//* Purpose: calculate the true longitude of the sun
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* sun's true longitude in degrees
//***********************************************************************/


static public double calcSunTrueLong(double t)
{
double l0 = calcGeomMeanLongSun(t);
double c = calcSunEqOfCenter(t);

double O = l0 + c;
return O; // in degrees
}

//***********************************************************************/
//* Name: calcSunTrueAnomaly
//* Type: Function
//* Purpose: calculate the true anamoly of the sun
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* sun's true anamoly in degrees
//***********************************************************************/

static public double calcSunTrueAnomaly(double t)
{
double m = calcGeomMeanAnomalySun(t);
double c = calcSunEqOfCenter(t);

double v = m + c;
return v; // in degrees
}

//***********************************************************************/
//* Name: calcSunRadVector
//* Type: Function
//* Purpose: calculate the distance to the sun in AU
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* sun radius vector in AUs
//***********************************************************************/

static public double calcSunRadVector(double t)
{
double v = calcSunTrueAnomaly(t);
double e = calcEccentricityEarthOrbit(t);

double R = (1.000001018 * (1 - e * e)) / (1 + e * Math.Cos(degToRad(v)));
return R; // in AUs
}

//***********************************************************************/
//* Name: calcSunApparentLong
//* Type: Function
//* Purpose: calculate the apparent longitude of the sun
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* sun's apparent longitude in degrees
//***********************************************************************/

static public double calcSunApparentLong(double t)
{
double o = calcSunTrueLong(t);

double omega = 125.04 - 1934.136 * t;
double lambda = o - 0.00569 - 0.00478 * Math.Sin(degToRad(omega));
return lambda; // in degrees
}

//***********************************************************************/
//* Name: calcMeanObliquityOfEcliptic
//* Type: Function
//* Purpose: calculate the mean obliquity of the ecliptic
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* mean obliquity in degrees
//***********************************************************************/

static public double calcMeanObliquityOfEcliptic(double t)
{
double seconds = 21.448 - t*(46.8150 + t*(0.00059 - t*(0.001813)));
double e0 = 23.0 + (26.0 + (seconds/60.0))/60.0;
return e0; // in degrees
}

//***********************************************************************/
//* Name: calcObliquityCorrection
//* Type: Function
//* Purpose: calculate the corrected obliquity of the ecliptic
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* corrected obliquity in degrees
//***********************************************************************/

static public double calcObliquityCorrection(double t)
{
double e0 = calcMeanObliquityOfEcliptic(t);

double omega = 125.04 - 1934.136 * t;
double e = e0 + 0.00256 * Math.Cos(degToRad(omega));
return e; // in degrees
}

//***********************************************************************/
//* Name: calcSunRtAscension
//* Type: Function
//* Purpose: calculate the right ascension of the sun
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* sun's right ascension in degrees
//***********************************************************************/

static public double calcSunRtAscension(double t)
{
double e = calcObliquityCorrection(t);
double lambda = calcSunApparentLong(t);

double tananum = (Math.Cos(degToRad(e)) * Math.Sin(degToRad(lambda)));
double tanadenom = (Math.Cos(degToRad(lambda)));
double alpha = radToDeg(Math.Atan2(tananum, tanadenom));
return alpha; // in degrees
}

//***********************************************************************/
//* Name: calcSunDeclination
//* Type: Function
//* Purpose: calculate the declination of the sun
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* sun's declination in degrees
//***********************************************************************/

static public double calcSunDeclination(double t)
{
double e = calcObliquityCorrection(t);
double lambda = calcSunApparentLong(t);

double sint = Math.Sin(degToRad(e)) * Math.Sin(degToRad(lambda));
double theta = radToDeg(Math.Asin(sint));
return theta; // in degrees
}

//***********************************************************************/
//* Name: calcEquationOfTime
//* Type: Function
//* Purpose: calculate the difference between true solar time and mean
//* solar time
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* Return value:
//* equation of time in minutes of time
//***********************************************************************/

static public double calcEquationOfTime(double t)
{
double epsilon = calcObliquityCorrection(t);
double l0 = calcGeomMeanLongSun(t);
double e = calcEccentricityEarthOrbit(t);
double m = calcGeomMeanAnomalySun(t);

double y = Math.Tan(degToRad(epsilon)/2.0);
y *= y;

double sin2l0 = Math.Sin(2.0 * degToRad(l0));
double sinm = Math.Sin(degToRad(m));
double cos2l0 = Math.Cos(2.0 * degToRad(l0));
double sin4l0 = Math.Sin(4.0 * degToRad(l0));
double sin2m = Math.Sin(2.0 * degToRad(m));

double Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0
- 0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m;

return radToDeg(Etime)*4.0; // in minutes of time
}

//***********************************************************************/
//* Name: calcHourAngleSunrise
//* Type: Function
//* Purpose: calculate the hour angle of the sun at sunrise for the
//* latitude
//* Arguments:
//* lat : latitude of observer in degrees
//* solarDec : declination angle of sun in degrees
//* Return value:
//* hour angle of sunrise in radians
//***********************************************************************/

static public double calcHourAngleSunrise(double lat, double solarDec)
{
double latRad = degToRad(lat);
double sdRad = degToRad(solarDec);

double HAarg = (Math.Cos(degToRad(90.833))/(Math.Cos(latRad)*Math.Cos(sdRad))-Math.Tan(latRad) * Math.Tan(sdRad));

double HA = (Math.Acos(Math.Cos(degToRad(90.833))/(Math.Cos(latRad)*Math.Cos(sdRad))-Math.Tan(latRad) * Math.Tan(sdRad)));

return HA; // in radians
}

//***********************************************************************/
//* Name: calcHourAngleSunset
//* Type: Function
//* Purpose: calculate the hour angle of the sun at sunset for the
//* latitude
//* Arguments:
//* lat : latitude of observer in degrees
//* solarDec : declination angle of sun in degrees
//* Return value:
//* hour angle of sunset in radians
//***********************************************************************/

static public double calcHourAngleSunset(double lat, double solarDec)
{
double latRad = degToRad(lat);
double sdRad = degToRad(solarDec);

double HAarg = (Math.Cos(degToRad(90.833))/(Math.Cos(latRad)*Math.Cos(sdRad))-Math.Tan(latRad) * Math.Tan(sdRad));

double HA = (Math.Acos(Math.Cos(degToRad(90.833))/(Math.Cos(latRad)*Math.Cos(sdRad))-Math.Tan(latRad) * Math.Tan(sdRad)));

return -HA; // in radians
}


//***********************************************************************/
//* Name: calcSunriseUTC
//* Type: Function
//* Purpose: calculate the Universal Coordinated Time (UTC) of sunrise
//* for the given day at the given location on earth
//* Arguments:
//* JD : julian day
//* latitude : latitude of observer in degrees
//* longitude : longitude of observer in degrees
//* Return value:
//* time in minutes from zero Z
//***********************************************************************/

static public double calcSunriseUTC(double JD, double latitude, double longitude)
{
double t = calcTimeJulianCent(JD);

// *** Find the time of solar noon at the location, and use
// that declination. This is better than start of the
// Julian day

double noonmin = calcSolNoonUTC(t, longitude);
double tnoon = calcTimeJulianCent (JD+noonmin/1440.0);

// *** First pass to approximate sunrise (using solar noon)

double eqTime = calcEquationOfTime(tnoon);
double solarDec = calcSunDeclination(tnoon);
double hourAngle = calcHourAngleSunrise(latitude, solarDec);

double delta = longitude - radToDeg(hourAngle);
double timeDiff = 4 * delta; // in minutes of time
double timeUTC = 720 + timeDiff - eqTime; // in minutes

// alert("eqTime = " + eqTime + "\nsolarDec = " + solarDec + "\ntimeUTC = " + timeUTC);

// *** Second pass includes fractional jday in gamma calc

double newt = calcTimeJulianCent(calcJDFromJulianCent(t) + timeUTC/1440.0);
eqTime = calcEquationOfTime(newt);
solarDec = calcSunDeclination(newt);
hourAngle = calcHourAngleSunrise(latitude, solarDec);
delta = longitude - radToDeg(hourAngle);
timeDiff = 4 * delta;
timeUTC = 720 + timeDiff - eqTime; // in minutes

// alert("eqTime = " + eqTime + "\nsolarDec = " + solarDec + "\ntimeUTC = " + timeUTC);

return timeUTC;
}

//***********************************************************************/
//* Name: calcSolNoonUTC
//* Type: Function
//* Purpose: calculate the Universal Coordinated Time (UTC) of solar
//* noon for the given day at the given location on earth
//* Arguments:
//* t : number of Julian centuries since J2000.0
//* longitude : longitude of observer in degrees
//* Return value:
//* time in minutes from zero Z
//***********************************************************************/

static public double calcSolNoonUTC(double t, double longitude)
{
// First pass uses approximate solar noon to calculate eqtime
double tnoon = calcTimeJulianCent(calcJDFromJulianCent(t) + longitude/360.0);
double eqTime = calcEquationOfTime(tnoon);
double solNoonUTC = 720 + (longitude * 4) - eqTime; // min

double newt = calcTimeJulianCent(calcJDFromJulianCent(t) -0.5 + solNoonUTC/1440.0);

eqTime = calcEquationOfTime(newt);
// double solarNoonDec = calcSunDeclination(newt);
solNoonUTC = 720 + (longitude * 4) - eqTime; // min

return solNoonUTC;
}

//***********************************************************************/
//* Name: calcSunsetUTC
//* Type: Function
//* Purpose: calculate the Universal Coordinated Time (UTC) of sunset
//* for the given day at the given location on earth
//* Arguments:
//* JD : julian day
//* latitude : latitude of observer in degrees
//* longitude : longitude of observer in degrees
//* Return value:
//* time in minutes from zero Z
//***********************************************************************/

static public double calcSunsetUTC(double JD, double latitude, double longitude)
{
double t = calcTimeJulianCent(JD);

// *** Find the time of solar noon at the location, and use
// that declination. This is better than start of the
// Julian day

double noonmin = calcSolNoonUTC(t, longitude);
double tnoon = calcTimeJulianCent (JD+noonmin/1440.0);

// First calculates sunrise and approx length of day

double eqTime = calcEquationOfTime(tnoon);
double solarDec = calcSunDeclination(tnoon);
double hourAngle = calcHourAngleSunset(latitude, solarDec);

double delta = longitude - radToDeg(hourAngle);
double timeDiff = 4 * delta;
double timeUTC = 720 + timeDiff - eqTime;

// first pass used to include fractional day in gamma calc

double newt = calcTimeJulianCent(calcJDFromJulianCent(t) + timeUTC/1440.0);
eqTime = calcEquationOfTime(newt);
solarDec = calcSunDeclination(newt);
hourAngle = calcHourAngleSunset(latitude, solarDec);

delta = longitude - radToDeg(hourAngle);
timeDiff = 4 * delta;
timeUTC = 720 + timeDiff - eqTime; // in minutes

return timeUTC;
}

}
}


happy coding
rafone

Statistics are like bikini's...
What they reveal is astonishing ...
But what they hide is vital ...

GeneralRe: Nice but one question Pin
Zacky Pickholz6-May-09 10:29
Zacky Pickholz6-May-09 10:29 
GeneralRe: Nice but one question Pin
Rafone6-May-09 11:26
Rafone6-May-09 11:26 
GeneralRe: Nice but one question Pin
Deulis18-Dec-12 7:14
Deulis18-Dec-12 7:14 
QuestionWhy not static? Pin
PIEBALDconsult9-Jan-09 17:08
mvePIEBALDconsult9-Jan-09 17:08 
AnswerRe: Why not static? Pin
Zacky Pickholz21-Apr-09 22:41
Zacky Pickholz21-Apr-09 22:41 
GeneralRe: Why not static? Pin
PIEBALDconsult22-Apr-09 4:02
mvePIEBALDconsult22-Apr-09 4:02 
QuestionHow do I reference this in VB.Net? Pin
pmannino8-Jan-09 23:52
pmannino8-Jan-09 23:52 
AnswerRe: How do I reference this in VB.Net? Pin
Zacky Pickholz21-Apr-09 22:43
Zacky Pickholz21-Apr-09 22:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.