Attachment 'sunrisesunset.py'

Download

   1 #!/usr/bin/env python
   2 #
   3 # sunrisesunset.py
   4 #
   5 # This code is valid for dates from 1901 to 2099, and will not calculate
   6 # sunrise/sunset times for latitudes above/below 63/-63 degrees.
   7 #
   8 # No external packages are used when using the class SunriseSunset. If you
   9 # run the tests you will need to install pytz as shown below or use your
  10 # package installer if it's available.
  11 #
  12 # $ sudo easy_install --upgrade pytz
  13 #
  14 # CVS/SVN Info
  15 #----------------------------------
  16 # $Author: cnobile $
  17 # $Date: 2009-08-03 00:47:13 $
  18 # $Revision: 1.9 $
  19 #----------------------------------
  20 #
  21 # Contributions by:
  22 #    Darryl Smith -- Noticed a bug with atan fixed with atan2.
  23 #
  24 ##########################################################################
  25 # Copyright (c) 2009 Carl J. Nobile.
  26 # All rights reserved. This program and the accompanying materials
  27 # are made available under the terms of the Eclipse Public License v1.0
  28 # which accompanies this distribution, and is available at
  29 # http://www.eclipse.org/legal/epl-v10.html
  30 #
  31 # This Copyright covers only the implimentation of the algorithm used in
  32 # this code and does not cover the algorithm itself which is in the
  33 # public domain.
  34 #
  35 # Contributors:
  36 #    Carl J. Nobile - initial API and implementation
  37 ##########################################################################
  38 
  39 import datetime
  40 from math import degrees, radians, atan2, cos, sin, pi, sqrt, fabs
  41 
  42 
  43 class SunriseSunset(object):
  44     """
  45     This class determines the sunrise and sunset for zenith standards for the
  46     given day. It can also tell you if the given time is during the nigh or
  47     day.
  48     """
  49     __ZENITH = {'official': -0.833,
  50                 'civil': -6.0,
  51                 'nautical': -12.0,
  52                 'amateur': -15.0,
  53                 'astronomical': -18.0}
  54 
  55     def __init__(self, date, lat, lon, zenith='official'):
  56         """
  57         Set the values for the sunrise and sunset calculation.
  58 
  59         @param date: A localized datetime object that is timezone aware.
  60         @param lat: The latitude.
  61         @param lon: The longitude.
  62         @keyword zenith: The zenith name.
  63         """
  64         if not isinstance(date, datetime.datetime) or date.tzinfo is None:
  65             msg = "The date must be a datetime object with timezone info."
  66             raise ValueError(msg)
  67 
  68         if zenith not in self.__ZENITH:
  69             msg = "Invalid zenith name [%s] must be one of: %s"
  70             raise ValueError(msg % (zenith, self.__ZENITH.keys()))
  71 
  72         if abs(lat) > 63:
  73             raise ValueError('Invalid latitude: %s' % lat)
  74 
  75         self.__dateLocal = date
  76         self.__lat = lat
  77         self.__lon = lon
  78         self.__zenith = zenith
  79         localTuple = date.timetuple()
  80         utcTuple = date.utctimetuple()
  81         self.__offsetUTC = (localTuple[3] - utcTuple[3]) + \
  82                            (localTuple[4] - utcTuple[4]) / 60.0
  83         self.__sunrise = None
  84         self.__sunset = None
  85         self.__determineRiseSet()
  86 
  87     def isNight(self, collar=0):
  88         """
  89         Check if it is day or night. If the 'collar' keyword argument is
  90         changed it will skew the results to either before or after the real
  91         sunrise and sunset. This is useful if lead and lag timea are needed
  92         around the actual sunrise and sunset.
  93 
  94         Note::
  95             If collar == 30 then this method will say it is daytime 30
  96             minutes before the actual sunrise and likewise 30 minutes after
  97             sunset it would indicate it is night.
  98 
  99         @keyword collar: The minutes before or after sunrise and sunset.
 100         @return: True if it is night else False if day.
 101         """
 102         result = False
 103         delta = datetime.timedelta(minutes=collar)
 104 
 105         if (self.__sunrise - delta) > self.__dateLocal or \
 106                self.__dateLocal > (self.__sunset + delta):
 107             result = True
 108 
 109         return result
 110 
 111     def getSunRiseSet(self):
 112         """
 113         Get the sunrise and sunset.
 114 
 115         @return: A C{datetime} object in a tuple (sunrise, sunset).
 116         """
 117         return self.__sunrise, self.__sunset
 118 
 119     def __determineRiseSet(self):
 120         """
 121         Determine both the sunrise and sunset.
 122         """
 123         year = self.__dateLocal.year
 124         month = self.__dateLocal.month
 125         day = self.__dateLocal.day
 126         # Ephemeris
 127         ephem2000Day = 367 * year - (7 * (year + (month + 9) / 12) / 4) + \
 128                        (275 * month / 9) + day - 730531.5
 129         self.__sunrise = self.__determineRiseOrSet(ephem2000Day, 1)
 130         self.__sunset = self.__determineRiseOrSet(ephem2000Day, -1)
 131 
 132     def __determineRiseOrSet(self, ephem2000Day, rs):
 133         """
 134         Determine either the sunrise or the sunset.
 135 
 136         @param ephem2000Day: The Ephemeris from the beginning of the
 137                              21st century.
 138         @param rs: The factor that determines either sunrise or sunset where
 139                    1 equals sunrise and -1 sunset.
 140         @return: Either the sunrise or sunset as a C{datetime} object.
 141         """
 142         utold = pi
 143         utnew = 0
 144         altitude = self.__ZENITH[self.__zenith]
 145         sinAlt = sin(radians(altitude))       # solar altitude
 146         sinPhi = sin(radians(self.__lat))     # viewer's latitude
 147         cosPhi = cos(radians(self.__lat))     #
 148         lon = radians(self.__lon)             # viewer's longitude
 149         ct = 0
 150         #print rs, ephem2000Day, sinAlt, sinPhi, cosPhi, lon
 151 
 152         while fabs(utold - utnew) > 0.001 and ct < 35:
 153             ct += 1
 154             utold = utnew
 155             days = ephem2000Day + utold / (2 * pi)
 156             t = days / 36525
 157             # The magic numbers are orbital elements of the sun.
 158             l = self.__getRange(4.8949504201433 + 628.331969753199 * t)
 159             g = self.__getRange(6.2400408 + 628.3019501 * t)
 160             ec = 0.033423 * sin(g) + 0.00034907 * sin(2 * g)
 161             lam = l + ec
 162             e = -1 * ec + 0.0430398 * sin(2 * lam) - 0.00092502 * sin(4 * lam)
 163             obl = 0.409093 - 0.0002269 * t
 164             delta = sin(obl) * sin(lam)
 165             delta = atan2(delta, sqrt(1 - delta * delta))
 166             gha = utold - pi + e
 167             cosc = (sinAlt - sinPhi * sin(delta)) / (cosPhi * cos(delta))
 168 
 169             if cosc > 1:
 170                 correction = 0
 171             elif cosc < -1:
 172                 correction = pi
 173             else:
 174                 correction = atan2((sqrt(1 - cosc * cosc)), cosc)
 175 
 176             #print cosc, correction, utold, utnew
 177             utnew = self.__getRange(utold - (gha + lon + rs * correction))
 178 
 179         decimalTime = degrees(utnew) / 15
 180         #print utnew, decimalTime
 181         return self.__get24HourLocalTime(rs, decimalTime)
 182 
 183     def __getRange(self, value):
 184         """
 185         Get the range of the value.
 186 
 187         @param value: The domain.
 188         @return: The resultant range.
 189         """
 190         tmp1 = value / (2.0 * pi)
 191         tmp2 = (2.0 * pi) * (tmp1 - int(tmp1))
 192         if tmp2 < 0.0: tmp2 += (2.0 * pi)
 193         return tmp2
 194 
 195     def __get24HourLocalTime(self, rs, decimalTime):
 196         """
 197         Convert the decimal time into a local time (C{datetime} object)
 198         and correct for a 24 hour clock.
 199 
 200         @param rs: The factor that determines either sunrise or sunset where
 201                    1 equals sunrise and -1 sunset.
 202         @param decimalTime: The decimal time.
 203         @return: The C{datetime} objects set to either sunrise or sunset.
 204         """
 205         decimalTime += self.__offsetUTC
 206         #print decimalTime
 207 
 208         if decimalTime < 0.0:
 209             decimalTime += 24.0
 210         elif decimalTime > 24.0:
 211             decimalTime -= 24.0
 212 
 213         #print decimalTime
 214         hour = int(decimalTime)
 215         tmp = (decimalTime - hour) * 60
 216         minute = int(tmp)
 217         tmp = (tmp - minute) * 60
 218         second = int(tmp)
 219         micro = int(round((tmp - second) * 1000000))
 220         localDT = self.__dateLocal.replace(hour=hour, minute=minute,
 221                                            second=second, microsecond=micro)
 222         return localDT
 223 
 224 
 225 def __getRiseSet(date, lat=35.9513, lon=-83.9142, zenith='official'):
 226     """
 227     The default lat and lon are for Knoxville, TN. The default zenith is
 228     'official'.
 229     """
 230     rs = SunriseSunset(date, lat, lon, zenith=zenith)
 231     riseTime, setTime = rs.getSunRiseSet()
 232     print "Using zenith: %s" % zenith
 233     print "Date/Time now: %s" % date
 234     print "Sunrise: %s" % riseTime
 235     print " Sunset: %s" % setTime
 236     print "Is night: %s\n" % rs.isNight()
 237 
 238 
 239 if __name__ == '__main__':
 240     import sys, pytz
 241     zone = pytz.timezone("US/Eastern")
 242 
 243     # Get sunrise and sunset for now and all the zenith types.
 244     now = datetime.datetime.now(zone)
 245     # Get the zenith types.
 246     zenithKeys = SunriseSunset._SunriseSunset__ZENITH.keys()
 247     print "Test zenith"
 248 
 249     for zenith in zenithKeys:
 250         __getRiseSet(now, zenith=zenith)
 251 
 252     # Get sunrise sunset for every hour of the day using the default zenith.
 253     print "\nTest 24 hours"
 254 
 255     for hour in range(24):
 256         for minute in range(0, 60, 10):
 257             date = now.replace(hour=hour, minute=minute, second=0,
 258                                microsecond=0)
 259             __getRiseSet(date)
 260 
 261     sys.exit(0)

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.

You are not allowed to attach a file to this page.