%PDF- %PDF-
Direktori : /data/old/usr/local/lib/python3.6/site-packages/astral/ |
Current File : //data/old/usr/local/lib/python3.6/site-packages/astral/location.py |
import dataclasses import datetime from typing import Optional, Tuple, Union import pytz import astral.moon import astral.sun from astral import ( Depression, Elevation, LocationInfo, Observer, SunDirection, dms_to_float, today, ) class Location: """Provides access to information for single location.""" def __init__(self, info: Optional[LocationInfo] = None): """Initializes the Location with a LocationInfo object. The tuple should contain items in the following order ================ ============= Field Default ================ ============= name Greenwich region England time zone name Europe/London latitude 51.4733 longitude -0.0008333 ================ ============= See the :attr:`timezone` property for a method of obtaining time zone names """ self._location_info: LocationInfo self._solar_depression: float = Depression.CIVIL.value if not info: self._location_info = LocationInfo( "Greenwich", "England", "Europe/London", 51.4733, -0.0008333 ) else: self._location_info = info def __eq__(self, other: object) -> bool: if type(other) is Location: return self._location_info == other._location_info # type: ignore return NotImplemented def __repr__(self) -> str: if self.region: _repr = "%s/%s" % (self.name, self.region) else: _repr = self.name return f"{_repr}, tz={self.timezone}, lat={self.latitude:0.02f}, lon={self.longitude:0.02f}" @property def info(self) -> LocationInfo: return LocationInfo( self.name, self.region, self.timezone, self.latitude, self.longitude, ) @property def observer(self) -> Observer: return Observer(self.latitude, self.longitude, 0.0) @property def name(self) -> str: return self._location_info.name @name.setter def name(self, name: str) -> None: self._location_info = dataclasses.replace(self._location_info, name=name) @property def region(self) -> str: return self._location_info.region @region.setter def region(self, region: str) -> None: self._location_info = dataclasses.replace(self._location_info, region=region) @property def latitude(self) -> float: """The location's latitude ``latitude`` can be set either as a string or as a number For strings they must be of the form degrees°minutes'[N|S] e.g. 51°31'N For numbers, positive numbers signify latitudes to the North. """ return self._location_info.latitude @latitude.setter def latitude(self, latitude: Union[float, str]) -> None: self._location_info = dataclasses.replace( self._location_info, latitude=dms_to_float(latitude, 90.0) ) @property def longitude(self) -> float: """The location's longitude. ``longitude`` can be set either as a string or as a number For strings they must be of the form degrees°minutes'[E|W] e.g. 51°31'W For numbers, positive numbers signify longitudes to the East. """ return self._location_info.longitude @longitude.setter def longitude(self, longitude: Union[float, str]) -> None: self._location_info = dataclasses.replace( self._location_info, longitude=dms_to_float(longitude, 180.0) ) @property def timezone(self) -> str: """The name of the time zone for the location. A list of time zone names can be obtained from pytz. For example. >>> from pytz import all_timezones >>> for timezone in all_timezones: ... print(timezone) """ return self._location_info.timezone @timezone.setter def timezone(self, name: str) -> None: if name not in pytz.all_timezones: raise ValueError("Timezone '%s' not recognized" % name) self._location_info = dataclasses.replace(self._location_info, timezone=name) @property def tzinfo(self) -> pytz.tzinfo: # type: ignore """Time zone information.""" try: tz = pytz.timezone(self._location_info.timezone) return tz except pytz.UnknownTimeZoneError as exc: raise ValueError( "Unknown timezone '%s'" % self._location_info.timezone ) from exc tz = tzinfo @property def solar_depression(self) -> float: """The number of degrees the sun must be below the horizon for the dawn/dusk calculation. Can either be set as a number of degrees below the horizon or as one of the following strings ============= ======= String Degrees ============= ======= civil 6.0 nautical 12.0 astronomical 18.0 ============= ======= """ return self._solar_depression @solar_depression.setter def solar_depression(self, depression: Union[float, str, Depression]) -> None: if isinstance(depression, str): try: self._solar_depression = { "civil": 6.0, "nautical": 12.0, "astronomical": 18.0, }[depression] except KeyError: raise KeyError( ( "solar_depression must be either a number " "or one of 'civil', 'nautical' or " "'astronomical'" ) ) elif isinstance(depression, Depression): self._solar_depression = depression.value else: self._solar_depression = float(depression) def today(self, local: bool = True) -> datetime.date: if local: return today(self.tzinfo) else: return today() def sun( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> dict: """Returns dawn, sunrise, noon, sunset and dusk as a dictionary. :param date: The date for which to calculate the times. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: Dictionary with keys ``dawn``, ``sunrise``, ``noon``, ``sunset`` and ``dusk`` whose values are the results of the corresponding methods. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.sun(observer, date, self.solar_depression, self.tzinfo) else: return astral.sun.sun(observer, date, self.solar_depression) def dawn( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> datetime.datetime: """Calculates the time in the morning when the sun is a certain number of degrees below the horizon. By default this is 6 degrees but can be changed by setting the :attr:`Astral.solar_depression` property. :param date: The date for which to calculate the dawn time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: The date and time at which dawn occurs. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.dawn(observer, date, self.solar_depression, self.tzinfo) else: return astral.sun.dawn(observer, date, self.solar_depression) def sunrise( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> datetime.datetime: """Return sunrise time. Calculates the time in the morning when the sun is a 0.833 degrees below the horizon. This is to account for refraction. :param date: The date for which to calculate the sunrise time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: The date and time at which sunrise occurs. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.sunrise(observer, date, self.tzinfo) else: return astral.sun.sunrise(observer, date) def noon(self, date: datetime.date = None, local: bool = True) -> datetime.datetime: """Calculates the solar noon (the time when the sun is at its highest point.) :param date: The date for which to calculate the noon time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :returns: The date and time at which the solar noon occurs. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude) if local: return astral.sun.noon(observer, date, self.tzinfo) else: return astral.sun.noon(observer, date) def sunset( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> datetime.datetime: """Calculates sunset time (the time in the evening when the sun is a 0.833 degrees below the horizon. This is to account for refraction.) :param date: The date for which to calculate the sunset time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: The date and time at which sunset occurs. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.sunset(observer, date, self.tzinfo) else: return astral.sun.sunset(observer, date) def dusk( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> datetime.datetime: """Calculates the dusk time (the time in the evening when the sun is a certain number of degrees below the horizon. By default this is 6 degrees but can be changed by setting the :attr:`solar_depression` property.) :param date: The date for which to calculate the dusk time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: The date and time at which dusk occurs. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.dusk(observer, date, self.solar_depression, self.tzinfo) else: return astral.sun.dusk(observer, date, self.solar_depression) def midnight( self, date: datetime.date = None, local: bool = True ) -> datetime.datetime: """Calculates the solar midnight (the time when the sun is at its lowest point.) :param date: The date for which to calculate the midnight time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :returns: The date and time at which the solar midnight occurs. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude) if local: return astral.sun.midnight(observer, date, self.tzinfo) else: return astral.sun.midnight(observer, date) def daylight( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> Tuple[datetime.datetime, datetime.datetime]: """Calculates the daylight time (the time between sunrise and sunset) :param date: The date for which to calculate daylight. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: A tuple containing the start and end times """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.daylight(observer, date, self.tzinfo) else: return astral.sun.daylight(observer, date) def night( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> Tuple[datetime.datetime, datetime.datetime]: """Calculates the night time (the time between astronomical dusk and astronomical dawn of the next day) :param date: The date for which to calculate the start of the night time. If no date is specified then the current date will be used. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :returns: A tuple containing the start and end times """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.night(observer, date, self.tzinfo) else: return astral.sun.night(observer, date) def twilight( self, date: datetime.date = None, direction: SunDirection = SunDirection.RISING, local: bool = True, observer_elevation: Elevation = 0.0, ): """Returns the start and end times of Twilight in the UTC timezone when the sun is traversing in the specified direction. This method defines twilight as being between the time when the sun is at -6 degrees and sunrise/sunset. :param direction: Determines whether the time is for the sun rising or setting. Use ``astral.SUN_RISING`` or ``astral.SunDirection.SETTING``. :param date: The date for which to calculate the times. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :return: A tuple of the UTC date and time at which twilight starts and ends. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.twilight(observer, date, direction, self.tzinfo) else: return astral.sun.twilight(observer, date, direction) def time_at_elevation( self, elevation: float, date: datetime.date = None, direction: SunDirection = SunDirection.RISING, local: bool = True, ) -> datetime.datetime: """Calculate the time when the sun is at the specified elevation. Note: This method uses positive elevations for those above the horizon. Elevations greater than 90 degrees are converted to a setting sun i.e. an elevation of 110 will calculate a setting sun at 70 degrees. :param elevation: Elevation in degrees above the horizon to calculate for. :param date: The date for which to calculate the elevation time. If no date is specified then the current date will be used. :param direction: Determines whether the time is for the sun rising or setting. Use ``SunDirection.RISING`` or ``SunDirection.SETTING``. Default is rising. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. If not specified then the time will be returned in local time :returns: The date and time at which dusk occurs. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) if elevation > 90.0: elevation = 180.0 - elevation direction = SunDirection.SETTING observer = Observer(self.latitude, self.longitude, 0.0) if local: return astral.sun.time_at_elevation( observer, elevation, date, direction, self.tzinfo ) else: return astral.sun.time_at_elevation(observer, elevation, date, direction) def rahukaalam( self, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> Tuple[datetime.datetime, datetime.datetime]: """Calculates the period of rahukaalam. :param date: The date for which to calculate the rahukaalam period. A value of ``None`` uses the current date. :param local: True = Time to be returned in location's time zone; False = Time to be returned in UTC. :param observer_elevation: Elevation of the observer in metres above the location. :return: Tuple containing the start and end times for Rahukaalam. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.rahukaalam(observer, date, self.tzinfo) else: return astral.sun.rahukaalam(observer, date) def golden_hour( self, direction: SunDirection = SunDirection.RISING, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> Tuple[datetime.datetime, datetime.datetime]: """Returns the start and end times of the Golden Hour when the sun is traversing in the specified direction. This method uses the definition from PhotoPills i.e. the golden hour is when the sun is between 4 degrees below the horizon and 6 degrees above. :param direction: Determines whether the time is for the sun rising or setting. Use ``SunDirection.RISING`` or ``SunDirection.SETTING``. Default is rising. :param date: The date for which to calculate the times. :param local: True = Times to be returned in location's time zone; False = Times to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :return: A tuple of the date and time at which the Golden Hour starts and ends. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.golden_hour(observer, date, direction, self.tzinfo) else: return astral.sun.golden_hour(observer, date, direction) def blue_hour( self, direction: SunDirection = SunDirection.RISING, date: datetime.date = None, local: bool = True, observer_elevation: Elevation = 0.0, ) -> Tuple[datetime.datetime, datetime.datetime]: """Returns the start and end times of the Blue Hour when the sun is traversing in the specified direction. This method uses the definition from PhotoPills i.e. the blue hour is when the sun is between 6 and 4 degrees below the horizon. :param direction: Determines whether the time is for the sun rising or setting. Use ``SunDirection.RISING`` or ``SunDirection.SETTING``. Default is rising. :param date: The date for which to calculate the times. If no date is specified then the current date will be used. :param local: True = Times to be returned in location's time zone; False = Times to be returned in UTC. If not specified then the time will be returned in local time :param observer_elevation: Elevation of the observer in metres above the location. :return: A tuple of the date and time at which the Blue Hour starts and ends. """ if local and self.timezone is None: raise ValueError("Local time requested but Location has no timezone set.") if date is None: date = self.today(local) observer = Observer(self.latitude, self.longitude, observer_elevation) if local: return astral.sun.blue_hour(observer, date, direction, self.tzinfo) else: return astral.sun.blue_hour(observer, date, direction) def solar_azimuth( self, dateandtime: datetime.datetime = None, observer_elevation: Elevation = 0.0, ) -> float: """Calculates the solar azimuth angle for a specific date/time. :param dateandtime: The date and time for which to calculate the angle. :returns: The azimuth angle in degrees clockwise from North. """ if dateandtime is None: dateandtime = astral.sun.now(self.tzinfo) elif not dateandtime.tzinfo: dateandtime = self.tzinfo.localize(dateandtime) observer = Observer(self.latitude, self.longitude, observer_elevation) dateandtime = dateandtime.astimezone(pytz.utc) # type: ignore return astral.sun.azimuth(observer, dateandtime) def solar_elevation( self, dateandtime: datetime.datetime = None, observer_elevation: Elevation = 0.0, ) -> float: """Calculates the solar elevation angle for a specific time. :param dateandtime: The date and time for which to calculate the angle. :returns: The elevation angle in degrees above the horizon. """ if dateandtime is None: dateandtime = astral.sun.now(self.tzinfo) elif not dateandtime.tzinfo: dateandtime = self.tzinfo.localize(dateandtime) observer = Observer(self.latitude, self.longitude, observer_elevation) dateandtime = dateandtime.astimezone(pytz.utc) # type: ignore return astral.sun.elevation(observer, dateandtime) def solar_zenith( self, dateandtime: datetime.datetime, observer_elevation: Elevation = 0.0, ) -> float: """Calculates the solar zenith angle for a specific time. :param dateandtime: The date and time for which to calculate the angle. :returns: The zenith angle in degrees from vertical. """ return 90.0 - self.solar_elevation(dateandtime, observer_elevation) def moon_phase( self, date: datetime.date = None, local: bool = True ): """Calculates the moon phase for a specific date. :param date: The date to calculate the phase for. If ommitted the current date is used. :returns: A number designating the phase ============ ============== 0 .. 6.99 New moon 7 .. 13.99 First quarter 14 .. 20.99 Full moon 21 .. 27.99 Last quarter ============ ============== """ if date is None: date = self.today(local) return astral.moon.phase(date)