mirror of
https://github.com/CCOSTAN/Home-AssistantConfig.git
synced 2025-02-22 09:01:22 +00:00
2376 lines
88 KiB
Python
2376 lines
88 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2009-2016, Simon Kennedy, sffjunkie+code@gmail.com
|
|
|
|
"""The :mod:`astral` module provides the means to calculate dawn, sunrise,
|
|
solar noon, sunset, dusk and rahukaalam times, plus solar azimuth and
|
|
elevation, for specific locations or at a specific latitude/longitude. It can
|
|
also calculate the moon phase for a specific date.
|
|
|
|
The module provides 2 main classes :class:`Astral` and :class:`Location`.
|
|
|
|
:class:`Astral`
|
|
Has 2 main responsibilities
|
|
|
|
* Calculates the events in the UTC timezone.
|
|
* Provides access to location data
|
|
|
|
:class:`Location`
|
|
Holds information about a location and provides functions to calculate
|
|
the event times for the location in the correct time zone.
|
|
|
|
For example ::
|
|
|
|
>>> from astral import *
|
|
>>> a = Astral()
|
|
>>> location = a['London']
|
|
>>> print('Information for %s' % location.name)
|
|
Information for London
|
|
>>> timezone = location.timezone
|
|
>>> print('Timezone: %s' % timezone)
|
|
Timezone: Europe/London
|
|
>>> print('Latitude: %.02f; Longitude: %.02f' % (location.latitude,
|
|
... location.longitude))
|
|
Latitude: 51.60; Longitude: 0.05
|
|
>>> from datetime import date
|
|
>>> d = date(2009,4,22)
|
|
>>> sun = location.sun(local=True, date=d)
|
|
>>> print('Dawn: %s' % str(sun['dawn']))
|
|
Dawn: 2009-04-22 05:12:56+01:00
|
|
|
|
The module currently provides 2 methods of obtaining location information;
|
|
:class:`AstralGeocoder` (the default, which uses information from within the
|
|
module) and :class:`GoogleGeocoder` (which obtains information from Google's
|
|
Map Service.)
|
|
|
|
To use the :class:`GoogleGeocoder` pass the class as the `geocoder` parameter
|
|
to :meth:`Astral.__init__` or by setting the `geocoder` property to an
|
|
instance of :class:`GoogleGeocoder`::
|
|
|
|
>>> from astral import GoogleGeocoder
|
|
>>> a = Astral(GoogleGeocoder)
|
|
|
|
or ::
|
|
|
|
>>> from astral import GoogleGeocoder
|
|
>>> a = Astral()
|
|
>>> a.geocoder = GoogleGeocoder()
|
|
"""
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
try:
|
|
import pytz
|
|
except ImportError:
|
|
raise ImportError(('The astral module requires the '
|
|
'pytz module to be available.'))
|
|
|
|
import datetime
|
|
from time import time
|
|
from math import cos, sin, tan, acos, asin, atan2, floor, ceil
|
|
from math import radians, degrees, pow
|
|
import sys
|
|
|
|
try:
|
|
from urllib import quote_plus
|
|
except ImportError:
|
|
from urllib.parse import quote_plus
|
|
|
|
try:
|
|
from urllib2 import urlopen, URLError
|
|
except ImportError:
|
|
from urllib.request import urlopen, URLError
|
|
|
|
try:
|
|
import simplejson as json
|
|
except ImportError:
|
|
import json
|
|
|
|
if sys.version_info[0] >= 3:
|
|
ustr = str
|
|
else:
|
|
ustr = unicode
|
|
|
|
__all__ = ['Astral', 'Location',
|
|
'AstralGeocoder', 'GoogleGeocoder',
|
|
'AstralError']
|
|
|
|
__version__ = "1.2"
|
|
__author__ = "Simon Kennedy <sffjunkie+code@gmail.com>"
|
|
|
|
SUN_RISING = 1
|
|
SUN_SETTING = -1
|
|
|
|
# name,region,latitude,longitude,timezone,elevation
|
|
_LOCATION_INFO = """Abu Dhabi,UAE,24°28'N,54°22'E,Asia/Dubai,5
|
|
Abu Dhabi,United Arab Emirates,24°28'N,54°22'E,Asia/Dubai,5
|
|
Abuja,Nigeria,09°05'N,07°32'E,Africa/Lagos,342
|
|
Accra,Ghana,05°35'N,00°06'W,Africa/Accra,61
|
|
Addis Ababa,Ethiopia,09°02'N,38°42'E,Africa/Addis_Ababa,2355
|
|
Adelaide,Australia,34°56'S,138°36'E,Australia/Adelaide,50
|
|
Al Jubail,Saudi Arabia,25°24'N,49°39'W,Asia/Riyadh,8
|
|
Algiers,Algeria,36°42'N,03°08'E,Africa/Algiers,224
|
|
Amman,Jordan,31°57'N,35°52'E,Asia/Amman,1100
|
|
Amsterdam,Netherlands,52°23'N,04°54'E,Europe/Amsterdam,2
|
|
Andorra la Vella,Andorra,42°31'N,01°32'E,Europe/Andorra,1023
|
|
Ankara,Turkey,39°57'N,32°54'E,Europe/Istanbul,938
|
|
Antananarivo,Madagascar,18°55'S,47°31'E,Indian/Antananarivo,1276
|
|
Apia,Samoa,13°50'S,171°50'W,Pacific/Apia,2
|
|
Ashgabat,Turkmenistan,38°00'N,57°50'E,Asia/Ashgabat,219
|
|
Asmara,Eritrea,15°19'N,38°55'E,Africa/Asmara,2325
|
|
Astana,Kazakhstan,51°10'N,71°30'E,Asia/Qyzylorda,347
|
|
Asuncion,Paraguay,25°10'S,57°30'W,America/Asuncion,124
|
|
Athens,Greece,37°58'N,23°46'E,Europe/Athens,338
|
|
Avarua,Cook Islands,21°12'N,159°46'W,Etc/GMT-10,208
|
|
Baghdad,Iraq,33°20'N,44°30'E,Asia/Baghdad,41
|
|
Baku,Azerbaijan,40°29'N,49°56'E,Asia/Baku,30
|
|
Bamako,Mali,12°34'N,07°55'W,Africa/Bamako,350
|
|
Bandar Seri Begawan,Brunei Darussalam,04°52'N,115°00'E,Asia/Brunei,1
|
|
Bangkok,Thailand,13°45'N,100°35'E,Asia/Bangkok,2
|
|
Bangui,Central African Republic,04°23'N,18°35'E,Africa/Bangui,373
|
|
Banjul,Gambia,13°28'N,16°40'W,Africa/Banjul,5
|
|
Basse-Terre,Guadeloupe,16°00'N,61°44'W,America/Guadeloupe,1
|
|
Basseterre,Saint Kitts and Nevis,17°17'N,62°43'W,America/St_Kitts,50
|
|
Beijing,China,39°55'N,116°20'E,Asia/Harbin,59
|
|
Beirut,Lebanon,33°53'N,35°31'E,Asia/Beirut,56
|
|
Belfast,Northern Ireland,54°36'N,5°56'W,Europe/Belfast,9
|
|
Belgrade,Yugoslavia,44°50'N,20°37'E,Europe/Belgrade,90
|
|
Belmopan,Belize,17°18'N,88°30'W,America/Belize,63
|
|
Berlin,Germany,52°30'N,13°25'E,Europe/Berlin,35
|
|
Bern,Switzerland,46°57'N,07°28'E,Europe/Zurich,510
|
|
Bishkek,Kyrgyzstan,42°54'N,74°46'E,Asia/Bishkek,772
|
|
Bissau,Guinea-Bissau,11°45'N,15°45'W,Africa/Bissau,0
|
|
Bloemfontein,South Africa,29°12'S,26°07'E,Africa/Johannesburg,1398
|
|
Bogota,Colombia,04°34'N,74°00'W,America/Bogota,2620
|
|
Brasilia,Brazil,15°47'S,47°55'W,Brazil/East,1087
|
|
Bratislava,Slovakia,48°10'N,17°07'E,Europe/Bratislava,132
|
|
Brazzaville,Congo,04°09'S,15°12'E,Africa/Brazzaville,156
|
|
Bridgetown,Barbados,13°05'N,59°30'W,America/Barbados,1
|
|
Brisbane,Australia,27°30'S,153°01'E,Australia/Brisbane,25
|
|
Brussels,Belgium,50°51'N,04°21'E,Europe/Brussels,62
|
|
Bucharest,Romania,44°27'N,26°10'E,Europe/Bucharest,71
|
|
Bucuresti,Romania,44°27'N,26°10'E,Europe/Bucharest,71
|
|
Budapest,Hungary,47°29'N,19°05'E,Europe/Budapest,120
|
|
Buenos Aires,Argentina,34°62'S,58°44'W,America/Buenos_Aires,6
|
|
Bujumbura,Burundi,03°16'S,29°18'E,Africa/Bujumbura,782
|
|
Cairo,Egypt,30°01'N,31°14'E,Africa/Cairo,74
|
|
Canberra,Australia,35°15'S,149°08'E,Australia/Canberra,575
|
|
Cape Town,South Africa,33°55'S,18°22'E,Africa/Johannesburg,1700
|
|
Caracas,Venezuela,10°30'N,66°55'W,America/Caracas,885
|
|
Castries,Saint Lucia,14°02'N,60°58'W,America/St_Lucia,125
|
|
Cayenne,French Guiana,05°05'N,52°18'W,America/Cayenne,9
|
|
Charlotte Amalie,United States of Virgin Islands,18°21'N,64°56'W,America/Virgin,0
|
|
Chisinau,Moldova,47°02'N,28°50'E,Europe/Chisinau,122
|
|
Conakry,Guinea,09°29'N,13°49'W,Africa/Conakry,26
|
|
Copenhagen,Denmark,55°41'N,12°34'E,Europe/Copenhagen,5
|
|
Cotonou,Benin,06°23'N,02°42'E,Africa/Porto-Novo,5
|
|
Dakar,Senegal,14°34'N,17°29'W,Africa/Dakar,24
|
|
Damascus,Syrian Arab Republic,33°30'N,36°18'E,Asia/Damascus,609
|
|
Dammam,Saudi Arabia,26°30'N,50°12'E,Asia/Riyadh,1
|
|
Dhaka,Bangladesh,23°43'N,90°26'E,Asia/Dhaka,8
|
|
Dili,East Timor,08°29'S,125°34'E,Asia/Dili,11
|
|
Djibouti,Djibouti,11°08'N,42°20'E,Africa/Djibouti,19
|
|
Dodoma,United Republic of Tanzania,06°08'S,35°45'E,Africa/Dar_es_Salaam,1119
|
|
Doha,Qatar,25°15'N,51°35'E,Asia/Qatar,10
|
|
Douglas,Isle Of Man,54°9'N,4°29'W,Europe/London,35
|
|
Dublin,Ireland,53°21'N,06°15'W,Europe/Dublin,85
|
|
Dushanbe,Tajikistan,38°33'N,68°48'E,Asia/Dushanbe,803
|
|
El Aaiun,Morocco,27°9'N,13°12'W,UTC,64
|
|
Fort-de-France,Martinique,14°36'N,61°02'W,America/Martinique,9
|
|
Freetown,Sierra Leone,08°30'N,13°17'W,Africa/Freetown,26
|
|
Funafuti,Tuvalu,08°31'S,179°13'E,Pacific/Funafuti,2
|
|
Gaborone,Botswana,24°45'S,25°57'E,Africa/Gaborone,1005
|
|
George Town,Cayman Islands,19°20'N,81°24'W,America/Cayman,3
|
|
Georgetown,Guyana,06°50'N,58°12'W,America/Guyana,30
|
|
Gibraltar,Gibraltar,36°9'N,5°21'W,Europe/Gibraltar,3
|
|
Guatemala,Guatemala,14°40'N,90°22'W,America/Guatemala,1500
|
|
Hanoi,Viet Nam,21°05'N,105°55'E,Asia/Saigon,6
|
|
Harare,Zimbabwe,17°43'S,31°02'E,Africa/Harare,1503
|
|
Havana,Cuba,23°08'N,82°22'W,America/Havana,59
|
|
Helsinki,Finland,60°15'N,25°03'E,Europe/Helsinki,56
|
|
Hobart,Tasmania,42°53'S,147°19'E,Australia/Hobart,4
|
|
Hong Kong,China,22°16'N,114°09'E,Asia/Hong_Kong,8
|
|
Honiara,Solomon Islands,09°27'S,159°57'E,Pacific/Guadalcanal,8
|
|
Islamabad,Pakistan,33°40'N,73°10'E,Asia/Karachi,508
|
|
Jakarta,Indonesia,06°09'S,106°49'E,Asia/Jakarta,6
|
|
Jerusalem,Israel,31°47'N,35°12'E,Asia/Jerusalem,775
|
|
Juba,South Sudan,4°51'N,31°36'E,Africa/Juba,550
|
|
Jubail,Saudi Arabia,27°02'N,49°39'E,Asia/Riyadh,2
|
|
Kabul,Afghanistan,34°28'N,69°11'E,Asia/Kabul,1791
|
|
Kampala,Uganda,00°20'N,32°30'E,Africa/Kampala,1155
|
|
Kathmandu,Nepal,27°45'N,85°20'E,Asia/Kathmandu,1337
|
|
Khartoum,Sudan,15°31'N,32°35'E,Africa/Khartoum,380
|
|
Kiev,Ukraine,50°30'N,30°28'E,Europe/Kiev,153
|
|
Kigali,Rwanda,01°59'S,30°04'E,Africa/Kigali,1497
|
|
Kingston,Jamaica,18°00'N,76°50'W,America/Jamaica,9
|
|
Kingston,Norfolk Island,45°20'S,168°43'E,Pacific/Norfolk,113
|
|
Kingstown,Saint Vincent and the Grenadines,13°10'N,61°10'W,America/St_Vincent,1
|
|
Kinshasa,Democratic Republic of the Congo,04°20'S,15°15'E,Africa/Kinshasa,312
|
|
Koror,Palau,07°20'N,134°28'E,Pacific/Palau,33
|
|
Kuala Lumpur,Malaysia,03°09'N,101°41'E,Asia/Kuala_Lumpur,22
|
|
Kuwait,Kuwait,29°30'N,48°00'E,Asia/Kuwait,55
|
|
La Paz,Bolivia,16°20'S,68°10'W,America/La_Paz,4014
|
|
Libreville,Gabon,00°25'N,09°26'E,Africa/Libreville,15
|
|
Lilongwe,Malawi,14°00'S,33°48'E,Africa/Blantyre,1229
|
|
Lima,Peru,12°00'S,77°00'W,America/Lima,13
|
|
Lisbon,Portugal,38°42'N,09°10'W,Europe/Lisbon,123
|
|
Ljubljana,Slovenia,46°04'N,14°33'E,Europe/Ljubljana,385
|
|
Lome,Togo,06°09'N,01°20'E,Africa/Lome,25
|
|
London,England,51°30'N,00°07'W,Europe/London,24
|
|
Luanda,Angola,08°50'S,13°15'E,Africa/Luanda,6
|
|
Lusaka,Zambia,15°28'S,28°16'E,Africa/Lusaka,1154
|
|
Luxembourg,Luxembourg,49°37'N,06°09'E,Europe/Luxembourg,232
|
|
Macau,Macao,22°12'N,113°33'E,Asia/Macau,6
|
|
Madinah,Saudi Arabia,24°28'N,39°36'E,Asia/Riyadh,631
|
|
Madrid,Spain,40°25'N,03°45'W,Europe/Madrid,582
|
|
Majuro,Marshall Islands,7°4'N,171°16'E,Pacific/Majuro,65
|
|
Makkah,Saudi Arabia,21°26'N,39°49'E,Asia/Riyadh,240
|
|
Malabo,Equatorial Guinea,03°45'N,08°50'E,Africa/Malabo,56
|
|
Male,Maldives,04°00'N,73°28'E,Indian/Maldives,2
|
|
Mamoudzou,Mayotte,12°48'S,45°14'E,Indian/Mayotte,420
|
|
Managua,Nicaragua,12°06'N,86°20'W,America/Managua,50
|
|
Manama,Bahrain,26°10'N,50°30'E,Asia/Bahrain,2
|
|
Manila,Philippines,14°40'N,121°03'E,Asia/Manila,21
|
|
Maputo,Mozambique,25°58'S,32°32'E,Africa/Maputo,44
|
|
Maseru,Lesotho,29°18'S,27°30'E,Africa/Maseru,1628
|
|
Masqat,Oman,23°37'N,58°36'E,Asia/Muscat,8
|
|
Mbabane,Swaziland,26°18'S,31°06'E,Africa/Mbabane,1243
|
|
Mecca,Saudi Arabia,21°26'N,39°49'E,Asia/Riyadh,240
|
|
Medina,Saudi Arabia,24°28'N,39°36'E,Asia/Riyadh,631
|
|
Mexico,Mexico,19°20'N,99°10'W,America/Mexico_City,2254
|
|
Minsk,Belarus,53°52'N,27°30'E,Europe/Minsk,231
|
|
Mogadishu,Somalia,02°02'N,45°25'E,Africa/Mogadishu,9
|
|
Monaco,Priciplality Of Monaco,43°43'N,7°25'E,Europe/Monaco,206
|
|
Monrovia,Liberia,06°18'N,10°47'W,Africa/Monrovia,9
|
|
Montevideo,Uruguay,34°50'S,56°11'W,America/Montevideo,32
|
|
Moroni,Comoros,11°40'S,43°16'E,Indian/Comoro,29
|
|
Moscow,Russian Federation,55°45'N,37°35'E,Europe/Moscow,247
|
|
Moskva,Russian Federation,55°45'N,37°35'E,Europe/Moscow,247
|
|
Mumbai,India,18°58'N,72°49'E,Asia/Kolkata,14
|
|
Muscat,Oman,23°37'N,58°32'E,Asia/Muscat,8
|
|
N'Djamena,Chad,12°10'N,14°59'E,Africa/Ndjamena,295
|
|
Nairobi,Kenya,01°17'S,36°48'E,Africa/Nairobi,1624
|
|
Nassau,Bahamas,25°05'N,77°20'W,America/Nassau,7
|
|
Naypyidaw,Myanmar,19°45'N,96°6'E,Asia/Rangoon,104
|
|
New Delhi,India,28°37'N,77°13'E,Asia/Kolkata,233
|
|
Ngerulmud,Palau,7°30'N,134°37'E,Pacific/Palau,3
|
|
Niamey,Niger,13°27'N,02°06'E,Africa/Niamey,223
|
|
Nicosia,Cyprus,35°10'N,33°25'E,Asia/Nicosia,162
|
|
Nouakchott,Mauritania,20°10'S,57°30'E,Africa/Nouakchott,3
|
|
Noumea,New Caledonia,22°17'S,166°30'E,Pacific/Noumea,69
|
|
Nuku'alofa,Tonga,21°10'S,174°00'W,Pacific/Tongatapu,6
|
|
Nuuk,Greenland,64°10'N,51°35'W,America/Godthab,70
|
|
Oranjestad,Aruba,12°32'N,70°02'W,America/Aruba,33
|
|
Oslo,Norway,59°55'N,10°45'E,Europe/Oslo,170
|
|
Ottawa,Canada,45°27'N,75°42'W,US/Eastern,79
|
|
Ouagadougou,Burkina Faso,12°15'N,01°30'W,Africa/Ouagadougou,316
|
|
P'yongyang,Democratic People's Republic of Korea,39°09'N,125°30'E,Asia/Pyongyang,21
|
|
Pago Pago,American Samoa,14°16'S,170°43'W,Pacific/Pago_Pago,0
|
|
Palikir,Micronesia,06°55'N,158°09'E,Pacific/Ponape,71
|
|
Panama,Panama,09°00'N,79°25'W,America/Panama,2
|
|
Papeete,French Polynesia,17°32'S,149°34'W,Pacific/Tahiti,7
|
|
Paramaribo,Suriname,05°50'N,55°10'W,America/Paramaribo,7
|
|
Paris,France,48°50'N,02°20'E,Europe/Paris,109
|
|
Perth,Australia,31°56'S,115°50'E,Australia/Perth,20
|
|
Phnom Penh,Cambodia,11°33'N,104°55'E,Asia/Phnom_Penh,10
|
|
Podgorica,Montenegro,42°28'N,19°16'E,Europe/Podgorica,53
|
|
Port Louis,Mauritius,20°9'S,57°30'E,Indian/Mauritius,5
|
|
Port Moresby,Papua New Guinea,09°24'S,147°08'E,Pacific/Port_Moresby,44
|
|
Port-Vila,Vanuatu,17°45'S,168°18'E,Pacific/Efate,1
|
|
Port-au-Prince,Haiti,18°40'N,72°20'W,America/Port-au-Prince,34
|
|
Port of Spain,Trinidad and Tobago,10°40'N,61°31'W,America/Port_of_Spain,66
|
|
Porto-Novo,Benin,06°23'N,02°42'E,Africa/Porto-Novo,38
|
|
Prague,Czech Republic,50°05'N,14°22'E,Europe/Prague,365
|
|
Praia,Cape Verde,15°02'N,23°34'W,Atlantic/Cape_Verde,35
|
|
Pretoria,South Africa,25°44'S,28°12'E,Africa/Johannesburg,1322
|
|
Pristina,Albania,42°40'N,21°10'E,Europe/Tirane,576
|
|
Quito,Ecuador,00°15'S,78°35'W,America/Guayaquil,2812
|
|
Rabat,Morocco,34°1'N,6°50'W,Africa/Casablanca,75
|
|
Reykjavik,Iceland,64°10'N,21°57'W,Atlantic/Reykjavik,61
|
|
Riga,Latvia,56°53'N,24°08'E,Europe/Riga,7
|
|
Riyadh,Saudi Arabia,24°41'N,46°42'E,Asia/Riyadh,612
|
|
Road Town,British Virgin Islands,18°27'N,64°37'W,America/Virgin,1
|
|
Rome,Italy,41°54'N,12°29'E,Europe/Rome,95
|
|
Roseau,Dominica,15°20'N,61°24'W,America/Dominica,72
|
|
Saint Helier,Jersey,49°11'N,2°6'W,Etc/GMT,54
|
|
Saint Pierre,Saint Pierre and Miquelon,46°46'N,56°12'W,America/Miquelon,5
|
|
Saipan,Northern Mariana Islands,15°12'N,145°45'E,Pacific/Saipan,200
|
|
Sana,Yemen,15°20'N,44°12'W,Asia/Aden,2199
|
|
Sana'a,Yemen,15°20'N,44°12'W,Asia/Aden,2199
|
|
San Jose,Costa Rica,09°55'N,84°02'W,America/Costa_Rica,931
|
|
San Juan,Puerto Rico,18°28'N,66°07'W,America/Puerto_Rico,21
|
|
San Marino,San Marino,43°55'N,12°30'E,Europe/San_Marino,749
|
|
San Salvador,El Salvador,13°40'N,89°10'W,America/El_Salvador,621
|
|
Santiago,Chile,33°24'S,70°40'W,America/Santiago,476
|
|
Santo Domingo,Dominica Republic,18°30'N,69°59'W,America/Santo_Domingo,14
|
|
Sao Tome,Sao Tome and Principe,00°10'N,06°39'E,Africa/Sao_Tome,13
|
|
Sarajevo,Bosnia and Herzegovina,43°52'N,18°26'E,Europe/Sarajevo,511
|
|
Seoul,Republic of Korea,37°31'N,126°58'E,Asia/Seoul,49
|
|
Singapore,Republic of Singapore,1°18'N,103°48'E,Asia/Singapore,16
|
|
Skopje,The Former Yugoslav Republic of Macedonia,42°01'N,21°26'E,Europe/Skopje,238
|
|
Sofia,Bulgaria,42°45'N,23°20'E,Europe/Sofia,531
|
|
Sri Jayawardenapura Kotte,Sri Lanka,6°54'N,79°53'E,Asia/Colombo,7
|
|
St. George's,Grenada,32°22'N,64°40'W,America/Grenada,7
|
|
St. John's,Antigua and Barbuda,17°7'N,61°51'W,America/Antigua,1
|
|
St. Peter Port,Guernsey,49°26'N,02°33'W,Europe/Guernsey,1
|
|
Stanley,Falkland Islands,51°40'S,59°51'W,Atlantic/Stanley,23
|
|
Stockholm,Sweden,59°20'N,18°05'E,Europe/Stockholm,52
|
|
Sucre,Bolivia,16°20'S,68°10'W,America/La_Paz,2903
|
|
Suva,Fiji,18°06'S,178°30'E,Pacific/Fiji,0
|
|
Sydney,Australia,33°53'S,151°13'E,Australia/Sydney,3
|
|
Taipei,Republic of China (Taiwan),25°02'N,121°38'E,Asia/Taipei,9
|
|
T'bilisi,Georgia,41°43'N,44°50'E,Asia/Tbilisi,467
|
|
Tbilisi,Georgia,41°43'N,44°50'E,Asia/Tbilisi,467
|
|
Tallinn,Estonia,59°22'N,24°48'E,Europe/Tallinn,39
|
|
Tarawa,Kiribati,01°30'N,173°00'E,Pacific/Tarawa,2
|
|
Tashkent,Uzbekistan,41°20'N,69°10'E,Asia/Tashkent,489
|
|
Tegucigalpa,Honduras,14°05'N,87°14'W,America/Tegucigalpa,994
|
|
Tehran,Iran,35°44'N,51°30'E,Asia/Tehran,1191
|
|
Thimphu,Bhutan,27°31'N,89°45'E,Asia/Thimphu,2300
|
|
Tirana,Albania,41°18'N,19°49'E,Europe/Tirane,90
|
|
Tirane,Albania,41°18'N,19°49'E,Europe/Tirane,90
|
|
Torshavn,Faroe Islands,62°05'N,06°56'W,Atlantic/Faroe,39
|
|
Tokyo,Japan,35°41'N,139°41'E,Asia/Tokyo,8
|
|
Tripoli,Libyan Arab Jamahiriya,32°49'N,13°07'E,Africa/Tripoli,81
|
|
Tunis,Tunisia,36°50'N,10°11'E,Africa/Tunis,4
|
|
Ulan Bator,Mongolia,47°55'N,106°55'E,Asia/Ulaanbaatar,1330
|
|
Ulaanbaatar,Mongolia,47°55'N,106°55'E,Asia/Ulaanbaatar,1330
|
|
Vaduz,Liechtenstein,47°08'N,09°31'E,Europe/Vaduz,463
|
|
Valletta,Malta,35°54'N,14°31'E,Europe/Malta,48
|
|
Vienna,Austria,48°12'N,16°22'E,Europe/Vienna,171
|
|
Vientiane,Lao People's Democratic Republic,17°58'N,102°36'E,Asia/Vientiane,171
|
|
Vilnius,Lithuania,54°38'N,25°19'E,Europe/Vilnius,156
|
|
W. Indies,Antigua and Barbuda,17°20'N,61°48'W,America/Antigua,0
|
|
Warsaw,Poland,52°13'N,21°00'E,Europe/Warsaw,107
|
|
Washington DC,USA,39°91'N,77°02'W,US/Eastern,23
|
|
Wellington,New Zealand,41°19'S,174°46'E,Pacific/Auckland,7
|
|
Willemstad,Netherlands Antilles,12°05'N,69°00'W,America/Curacao,1
|
|
Windhoek,Namibia,22°35'S,17°04'E,Africa/Windhoek,1725
|
|
Yamoussoukro,Cote d'Ivoire,06°49'N,05°17'W,Africa/Abidjan,213
|
|
Yangon,Myanmar,16°45'N,96°20'E,Asia/Rangoon,33
|
|
Yaounde,Cameroon,03°50'N,11°35'E,Africa/Douala,760
|
|
Yaren,Nauru,0°32'S,166°55'E,Pacific/Nauru,0
|
|
Yerevan,Armenia,40°10'N,44°31'E,Asia/Yerevan,890
|
|
Zagreb,Croatia,45°50'N,15°58'E,Europe/Zagreb,123
|
|
|
|
# UK Cities
|
|
Aberdeen,Scotland,57°08'N,02°06'W,Europe/London,65
|
|
Birmingham,England,52°30'N,01°50'W,Europe/London,99
|
|
Bolton,England,53°35'N,02°15'W,Europe/London,105
|
|
Bradford,England,53°47'N,01°45'W,Europe/London,127
|
|
Bristol,England,51°28'N,02°35'W,Europe/London,11
|
|
Cardiff,Wales,51°29'N,03°13'W,Europe/London,9
|
|
Crawley,England,51°8'N,00°10'W,Europe/London,77
|
|
Edinburgh,Scotland,55°57'N,03°13'W,Europe/London,61
|
|
Glasgow,Scotland,55°50'N,04°15'W,Europe/London,8
|
|
Greenwich,England,51°28'N,00°00'W,Europe/London,24
|
|
Leeds,England,53°48'N,01°35'W,Europe/London,47
|
|
Leicester,England,52°38'N,01°08'W,Europe/London,138
|
|
Liverpool,England,53°25'N,03°00'W,Europe/London,25
|
|
Manchester,England,53°30'N,02°15'W,Europe/London,78
|
|
Newcastle Upon Time,England,54°59'N,01°36'W,Europe/London,47
|
|
Newcastle,England,54°59'N,01°36'W,Europe/London,47
|
|
Norwich,England,52°38'N,01°18'E,Europe/London,18
|
|
Oxford,England,51°45'N,01°15'W,Europe/London,72
|
|
Plymouth,England,50°25'N,04°15'W,Europe/London,50
|
|
Portsmouth,England,50°48'N,01°05'W,Europe/London,9
|
|
Reading,England,51°27'N,0°58'W,Europe/London,84
|
|
Sheffield,England,53°23'N,01°28'W,Europe/London,105
|
|
Southampton,England,50°55'N,01°25'W,Europe/London,9
|
|
Swansea,England,51°37'N,03°57'W,Europe/London,91
|
|
Swindon,England,51°34'N,01°47'W,Europe/London,112
|
|
Wolverhampton,England,52°35'N,2°08'W,Europe/London,89
|
|
Barrow-In-Furness,England,54°06'N,3°13'W,Europe/London,20
|
|
|
|
# US State Capitals
|
|
Montgomery,USA,32°21'N,86°16'W,US/Central,42
|
|
Juneau,USA,58°23'N,134°11'W,US/Alaska,29
|
|
Phoenix,USA,33°26'N,112°04'W,America/Phoenix,331
|
|
Little Rock,USA,34°44'N,92°19'W,US/Central,95
|
|
Sacramento,USA,38°33'N,121°28'W,US/Pacific,15
|
|
Denver,USA,39°44'N,104°59'W,US/Mountain,1600
|
|
Hartford,USA,41°45'N,72°41'W,US/Eastern,9
|
|
Dover,USA,39°09'N,75°31'W,US/Eastern,8
|
|
Tallahassee,USA,30°27'N,84°16'W,US/Eastern,59
|
|
Atlanta,USA,33°45'N,84°23'W,US/Eastern,267
|
|
Honolulu,USA,21°18'N,157°49'W,US/Hawaii,229
|
|
Boise,USA,43°36'N,116°12'W,US/Mountain,808
|
|
Springfield,USA,39°47'N,89°39'W,US/Central,190
|
|
Indianapolis,USA,39°46'N,86°9'W,US/Eastern,238
|
|
Des Moines,USA,41°35'N,93°37'W,US/Central,276
|
|
Topeka,USA,39°03'N,95°41'W,US/Central,289
|
|
Frankfort,USA,38°11'N,84°51'W,US/Eastern,243
|
|
Baton Rouge,USA,30°27'N,91°8'W,US/Central,15
|
|
Augusta,USA,44°18'N,69°46'W,US/Eastern,41
|
|
Annapolis,USA,38°58'N,76°30'W,US/Eastern,0
|
|
Boston,USA,42°21'N,71°03'W,US/Eastern,6
|
|
Lansing,USA,42°44'N,84°32'W,US/Eastern,271
|
|
Saint Paul,USA,44°56'N,93°05'W,US/Central,256
|
|
Jackson,USA,32°17'N,90°11'W,US/Central,90
|
|
Jefferson City,USA,38°34'N,92°10'W,US/Central,167
|
|
Helena,USA,46°35'N,112°1'W,US/Mountain,1150
|
|
Lincoln,USA,40°48'N,96°40'W,US/Central,384
|
|
Carson City,USA,39°9'N,119°45'W,US/Pacific,1432
|
|
Concord,USA,43°12'N,71°32'W,US/Eastern,117
|
|
Trenton,USA,40°13'N,74°45'W,US/Eastern,28
|
|
Santa Fe,USA,35°40'N,105°57'W,US/Mountain,2151
|
|
Albany,USA,42°39'N,73°46'W,US/Eastern,17
|
|
Raleigh,USA,35°49'N,78°38'W,US/Eastern,90
|
|
Bismarck,USA,46°48'N,100°46'W,US/Central,541
|
|
Columbus,USA,39°59'N,82°59'W,US/Eastern,271
|
|
Oklahoma City,USA,35°28'N,97°32'W,US/Central,384
|
|
Salem,USA,44°55'N,123°1'W,US/Pacific,70
|
|
Harrisburg,USA,40°16'N,76°52'W,US/Eastern,112
|
|
Providence,USA,41°49'N,71°25'W,US/Eastern,2
|
|
Columbia,USA,34°00'N,81°02'W,US/Eastern,96
|
|
Pierre,USA,44°22'N,100°20'W,US/Central,543
|
|
Nashville,USA,36°10'N,86°47'W,US/Central,149
|
|
Austin,USA,30°16'N,97°45'W,US/Central,167
|
|
Salt Lake City,USA,40°45'N,111°53'W,US/Mountain,1294
|
|
Montpelier,USA,44°15'N,72°34'W,US/Eastern,325
|
|
Richmond,USA,37°32'N,77°25'W,US/Eastern,68
|
|
Olympia,USA,47°2'N,122°53'W,US/Pacific,35
|
|
Charleston,USA,38°20'N,81°38'W,US/Eastern,11
|
|
Madison,USA,43°4'N,89°24'W,US/Central,281
|
|
Cheyenne,USA,41°8'N,104°48'W,US/Mountain,1860
|
|
|
|
# Major US Cities
|
|
Birmingham,USA,33°39'N,86°48'W,US/Central,197
|
|
Anchorage,USA,61°13'N,149°53'W,US/Alaska,30
|
|
Los Angeles,USA,34°03'N,118°15'W,US/Pacific,50
|
|
San Francisco,USA,37°46'N,122°25'W,US/Pacific,47
|
|
Bridgeport,USA,41°11'N,73°11'W,US/Eastern,13
|
|
Wilmington,USA,39°44'N,75°32'W,US/Eastern,15
|
|
Jacksonville,USA,30°19'N,81°39'W,US/Eastern,13
|
|
Miami,USA,26°8'N,80°12'W,US/Eastern,10
|
|
Chicago,USA,41°50'N,87°41'W,US/Central,189
|
|
Wichita,USA,37°41'N,97°20'W,US/Central,399
|
|
Louisville,USA,38°15'N,85°45'W,US/Eastern,142
|
|
New Orleans,USA,29°57'N,90°4'W,US/Central,10
|
|
Portland,USA,43°39'N,70°16'W,US/Eastern,6
|
|
Baltimore,USA,39°17'N,76°37'W,US/Eastern,31
|
|
Detroit,USA,42°19'N,83°2'W,US/Eastern,189
|
|
Minneapolis,USA,44°58'N,93°15'W,US/Central,260
|
|
Kansas City,USA,39°06'N,94°35'W,US/Central,256
|
|
Billings,USA,45°47'N,108°32'W,US/Mountain,946
|
|
Omaha,USA,41°15'N,96°0'W,US/Central,299
|
|
Las Vegas,USA,36°10'N,115°08'W,US/Pacific,720
|
|
Manchester,USA,42°59'N,71°27'W,US/Eastern,56
|
|
Newark,USA,40°44'N,74°11'W,US/Eastern,4
|
|
Albuquerque,USA,35°06'N,106°36'W,US/Mountain,1523
|
|
New York,USA,40°43'N,74°0'W,US/Eastern,17
|
|
Charlotte,USA,35°13'N,80°50'W,US/Eastern,217
|
|
Fargo,USA,46°52'N,96°47'W,US/Central,271
|
|
Cleveland,USA,41°28'N,81°40'W,US/Eastern,210
|
|
Philadelphia,USA,39°57'N,75°10'W,US/Eastern,62
|
|
Sioux Falls,USA,43°32'N,96°43'W,US/Central,443
|
|
Memphis,USA,35°07'N,89°58'W,US/Central,84
|
|
Houston,USA,29°45'N,95°22'W,US/Central,8
|
|
Dallas,USA,32°47'N,96°48'W,US/Central,137
|
|
Burlington,USA,44°28'N,73°9'W,US/Eastern,35
|
|
Virginia Beach,USA,36°50'N,76°05'W,US/Eastern,9
|
|
Seattle,USA,47°36'N,122°19'W,US/Pacific,63
|
|
Milwaukee,USA,43°03'N,87°57'W,US/Central,188
|
|
San Diego,USA,32°42'N,117°09'W,US/Pacific,16
|
|
Orlando,USA,28°32'N,81°22'W,US/Eastern,35
|
|
Buffalo,USA,42°54'N,78°50'W,US/Eastern,188
|
|
Toledo,USA,41°39'N,83°34'W,US/Eastern,180
|
|
|
|
# Canadian cities
|
|
Vancouver,Canada,49°15'N,123°6'W,America/Vancouver,55
|
|
Calgary,Canada,51°2'N,114°3'W,America/Edmonton,1040
|
|
Edmonton,Canada,53°32'N,113°29'W,America/Edmonton,664
|
|
Saskatoon,Canada,52°8'N,106°40'W,America/Regina,480
|
|
Regina,Canada,50°27'N,104°36'W,America/Regina,577
|
|
Winnipeg,Canada,49°53'N,97°8'W,America/Winnipeg,229
|
|
Toronto,Canada,43°39'N,79°22'W,America/Toronto,77
|
|
Montreal,Canada,45°30'N,73°33'W,America/Montreal,23
|
|
Quebec,Canada,46°48'N,71°14'W,America/Toronto,87
|
|
Fredericton,Canada,45°57'N,66°38'W,America/Halifax,8
|
|
Halifax,Canada,44°38'N,63°34'W,America/Halifax,36
|
|
Charlottetown,Canada,46°14'N,63°7'W,America/Halifax,2
|
|
St. John's,Canada,47°33'N,52°42'W,America/Halifax,116
|
|
Whitehorse,Canada,60°43'N,135°3'W,America/Whitehorse,696
|
|
Yellowknife,Canada,62°27'N,114°22'W,America/Yellowknife,191
|
|
Iqaluit,Canada,63°44'N,68°31'W,America/Iqaluit,3
|
|
"""
|
|
|
|
class AstralError(Exception):
|
|
pass
|
|
|
|
class Location(object):
|
|
"""Provides access to information for single location."""
|
|
|
|
def __init__(self, info=None):
|
|
"""Initializes the object with a tuple of information.
|
|
|
|
:param info: A tuple of information to fill in the location info.
|
|
|
|
The tuple should contain items in the following order
|
|
|
|
================ =============
|
|
Field Default
|
|
================ =============
|
|
name Greenwich
|
|
region England
|
|
latitude 51.168
|
|
longitude 0
|
|
time zone name Europe/London
|
|
elevation 24
|
|
================ =============
|
|
|
|
See :attr:`timezone` property for a method of obtaining time zone
|
|
names
|
|
"""
|
|
|
|
self.astral = None
|
|
if info is None:
|
|
self.name = 'Greenwich'
|
|
self.region = 'England'
|
|
self._latitude = 51.168
|
|
self._longitude = 0.0
|
|
self._timezone_group = 'Europe'
|
|
self._timezone_location = 'London'
|
|
self._elevation = 24
|
|
else:
|
|
self.name = ''
|
|
self.region = ''
|
|
self._latitude = 0.0
|
|
self._longitude = 0.0
|
|
self._timezone_group = ''
|
|
self._timezone_location = ''
|
|
self._elevation = 0
|
|
|
|
try:
|
|
self.name = info[0]
|
|
self.region = info[1]
|
|
self.latitude = info[2]
|
|
self.longitude = info[3]
|
|
self.timezone = info[4]
|
|
self.elevation = info[5]
|
|
except IndexError:
|
|
pass
|
|
|
|
self.url = ''
|
|
|
|
def __repr__(self):
|
|
repr_format = '%s/%s, tz=%s, lat=%0.02f, lon=%0.02f'
|
|
return repr_format % (self.name, self.region,
|
|
self.timezone,
|
|
self.latitude, self.longitude)
|
|
|
|
@property
|
|
def latitude(self):
|
|
"""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._latitude
|
|
|
|
@latitude.setter
|
|
def latitude(self, latitude):
|
|
if isinstance(latitude, str) or isinstance(latitude, ustr):
|
|
(deg, rest) = latitude.split("°", 1)
|
|
(minute, rest) = rest.split("'", 1)
|
|
|
|
self._latitude = float(deg) + (float(minute) / 60)
|
|
|
|
if latitude.endswith("S"):
|
|
self._latitude = -self._latitude
|
|
else:
|
|
self._latitude = float(latitude)
|
|
|
|
@property
|
|
def longitude(self):
|
|
"""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._longitude
|
|
|
|
@longitude.setter
|
|
def longitude(self, longitude):
|
|
if isinstance(longitude, str) or isinstance(longitude, ustr):
|
|
(deg, rest) = longitude.split("°", 1)
|
|
(minute, rest) = rest.split("'", 1)
|
|
|
|
self._longitude = float(deg) + (float(minute) / 60)
|
|
|
|
if longitude.endswith("W"):
|
|
self._longitude = -self._longitude
|
|
else:
|
|
self._longitude = float(longitude)
|
|
|
|
@property
|
|
def elevation(self):
|
|
"""The elevation in metres above sea level."""
|
|
|
|
return self._elevation
|
|
|
|
@elevation.setter
|
|
def elevation(self, elevation):
|
|
self._elevation = int(elevation)
|
|
|
|
@property
|
|
def timezone(self):
|
|
"""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)
|
|
"""
|
|
|
|
if self._timezone_location != '':
|
|
return '%s/%s' % (self._timezone_group,
|
|
self._timezone_location)
|
|
else:
|
|
return self._timezone_group
|
|
|
|
@timezone.setter
|
|
def timezone(self, name):
|
|
if name not in pytz.all_timezones:
|
|
raise ValueError('Timezone \'%s\' not recognized' % name)
|
|
|
|
try:
|
|
self._timezone_group, self._timezone_location = \
|
|
name.split('/', 1)
|
|
except ValueError:
|
|
self._timezone_group = name
|
|
self._timezone_location = ''
|
|
|
|
@property
|
|
def tz(self):
|
|
"""Time zone information."""
|
|
|
|
try:
|
|
tz = pytz.timezone(self.timezone)
|
|
return tz
|
|
except pytz.UnknownTimeZoneError:
|
|
raise AstralError('Unknown timezone \'%s\'' % self.timezone)
|
|
|
|
tzinfo = tz
|
|
|
|
@property
|
|
def solar_depression(self):
|
|
"""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.astral.solar_depression
|
|
|
|
@solar_depression.setter
|
|
def solar_depression(self, depression):
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
self.astral.solar_depression = depression
|
|
|
|
def sun(self, date=None, local=True):
|
|
"""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
|
|
|
|
:returns: Dictionary with keys ``dawn``, ``sunrise``, ``noon``,
|
|
``sunset`` and ``dusk`` whose values are the results of the
|
|
corresponding methods.
|
|
:rtype: dict
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
sun = self.astral.sun_utc(date, self.latitude, self.longitude)
|
|
|
|
if local:
|
|
for key, dt in sun.items():
|
|
sun[key] = dt.astimezone(self.tz)
|
|
|
|
return sun
|
|
|
|
def dawn(self, date=None, local=True):
|
|
"""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
|
|
|
|
:returns: The date and time at which dawn occurs.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
dawn = self.astral.dawn_utc(date, self.latitude, self.longitude)
|
|
|
|
if local:
|
|
return dawn.astimezone(self.tz)
|
|
else:
|
|
return dawn
|
|
|
|
def sunrise(self, date=None, local=True):
|
|
"""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
|
|
|
|
:returns: The date and time at which sunrise occurs.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
sunrise = self.astral.sunrise_utc(date, self.latitude, self.longitude)
|
|
|
|
if local:
|
|
return sunrise.astimezone(self.tz)
|
|
else:
|
|
return sunrise
|
|
|
|
def solar_noon(self, date=None, local=True):
|
|
"""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.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
noon = self.astral.solar_noon_utc(date, self.longitude)
|
|
|
|
if local:
|
|
return noon.astimezone(self.tz)
|
|
else:
|
|
return noon
|
|
|
|
def sunset(self, date=None, local=True):
|
|
"""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
|
|
|
|
:returns: The date and time at which sunset occurs.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
sunset = self.astral.sunset_utc(date, self.latitude, self.longitude)
|
|
|
|
if local:
|
|
return sunset.astimezone(self.tz)
|
|
else:
|
|
return sunset
|
|
|
|
def dusk(self, date=None, local=True):
|
|
"""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
|
|
|
|
:returns: The date and time at which dusk occurs.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
dusk = self.astral.dusk_utc(date, self.latitude, self.longitude)
|
|
|
|
if local:
|
|
return dusk.astimezone(self.tz)
|
|
else:
|
|
return dusk
|
|
|
|
def daylight(self, date=None, local=True):
|
|
"""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
|
|
|
|
:returns: A tuple containing the start and end times
|
|
:rtype: tuple(:class:`~datetime.datetime`, :class:`~datetime.datetime`)
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
start, end = self.astral.daylight_utc(date, self.latitude, self.longitude)
|
|
|
|
if local:
|
|
return start.astimezone(self.tz), end.astimezone(self.tz)
|
|
else:
|
|
return start, end
|
|
|
|
def night(self, date=None, local=True):
|
|
"""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
|
|
|
|
:returns: A tuple containing the start and end times
|
|
:rtype: tuple(:class:`~datetime.datetime`, :class:`~datetime.datetime`)
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
start, end = self.astral.night_utc(date, self.latitude, self.longitude)
|
|
|
|
if local:
|
|
return start.astimezone(self.tz), end.astimezone(self.tz)
|
|
else:
|
|
return start, end
|
|
|
|
def twilight(self, direction=SUN_RISING, date=None, local=True):
|
|
"""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.SUN_SETTING``.
|
|
:type direction: int
|
|
:param date: The date for which to calculate the times.
|
|
:type date: :class:`datetime.date`
|
|
: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
|
|
|
|
:return: A tuple of the UTC date and time at which twilight starts and ends.
|
|
:rtype: (:class:`~datetime.datetime`, :class:`~datetime.datetime`)
|
|
"""
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
start, end = self.astral.twilight_utc(date, direction,
|
|
self.latitude, self.longitude)
|
|
|
|
if local:
|
|
return start.astimezone(self.tz), end.astimezone(self.tz)
|
|
else:
|
|
return start, end
|
|
|
|
def time_at_elevation(self, elevation, direction=SUN_RISING, date=None, local=True):
|
|
"""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.
|
|
:type elevation: float
|
|
:param direction: Determines whether the time is for the sun rising or setting.
|
|
Use ``astral.SUN_RISING`` or ``astral.SUN_SETTING``. Default is rising.
|
|
:type direction: int
|
|
:param date: The date for which to calculate the elevation 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 dusk occurs.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
if elevation > 90.0:
|
|
elevation = 180.0 - elevation
|
|
direction = SUN_SETTING
|
|
|
|
time_ = self.astral.time_at_elevation_utc(elevation, direction, date, self.latitude, self.longitude)
|
|
|
|
if local:
|
|
return time_.astimezone(self.tz)
|
|
else:
|
|
return time_
|
|
|
|
def rahukaalam(self, date=None, local=True):
|
|
"""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.
|
|
|
|
:return: Tuple containing the start and end times for Rahukaalam.
|
|
:rtype: tuple
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
rahukaalam = self.astral.rahukaalam_utc(date,
|
|
self.latitude, self.longitude)
|
|
|
|
if local:
|
|
rahukaalam = (rahukaalam[0].astimezone(self.tz),
|
|
rahukaalam[1].astimezone(self.tz))
|
|
|
|
return rahukaalam
|
|
|
|
def golden_hour(self, direction=SUN_RISING, date=None, local=True):
|
|
"""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 ``astral.SUN_RISING`` or ``astral.SUN_SETTING``. Default is rising.
|
|
:type direction: int
|
|
:param date: The date for which to calculate the times.
|
|
:type date: :class:`datetime.date`
|
|
: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
|
|
|
|
:return: A tuple of the date and time at which the Golden Hour starts and ends.
|
|
:rtype: (:class:`~datetime.datetime`, :class:`~datetime.datetime`)
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
start, end = self.astral.golden_hour_utc(direction, date,
|
|
self.latitude, self.longitude)
|
|
|
|
if local:
|
|
start = start.astimezone(self.tz)
|
|
end = end.astimezone(self.tz)
|
|
|
|
return start, end
|
|
|
|
def blue_hour(self, direction=SUN_RISING, date=None, local=True):
|
|
"""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 ``astral.SUN_RISING`` or ``astral.SUN_SETTING``. Default is rising.
|
|
:type direction: int
|
|
: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
|
|
|
|
:return: A tuple of the date and time at which the Blue Hour starts and ends.
|
|
:rtype: (:class:`~datetime.datetime`, :class:`~datetime.datetime`)
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
start, end = self.astral.blue_hour_utc(direction, date,
|
|
self.latitude, self.longitude)
|
|
|
|
if local:
|
|
start = start.astimezone(self.tz)
|
|
end = end.astimezone(self.tz)
|
|
|
|
return start, end
|
|
|
|
def solar_azimuth(self, dateandtime=None):
|
|
"""Calculates the solar azimuth angle for a specific date/time.
|
|
|
|
:param dateandtime: The date and time for which to calculate the angle.
|
|
:type dateandtime: :class:`~datetime.datetime`
|
|
|
|
:returns: The azimuth angle in degrees clockwise from North.
|
|
:rtype: float
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if dateandtime is None:
|
|
dateandtime = datetime.datetime.now(self.tz)
|
|
elif not dateandtime.tzinfo:
|
|
dateandtime = self.tz.localize(dateandtime)
|
|
|
|
dateandtime = dateandtime.astimezone(pytz.UTC)
|
|
|
|
return self.astral.solar_azimuth(dateandtime,
|
|
self.latitude, self.longitude)
|
|
|
|
def solar_elevation(self, dateandtime=None):
|
|
"""Calculates the solar elevation angle for a specific time.
|
|
|
|
:param dateandtime: The date and time for which to calculate the angle.
|
|
:type dateandtime: :class:`~datetime.datetime`
|
|
|
|
:returns: The elevation angle in degrees above the horizon.
|
|
:rtype: float
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if dateandtime is None:
|
|
dateandtime = datetime.datetime.now(self.tz)
|
|
elif not dateandtime.tzinfo:
|
|
dateandtime = self.tz.localize(dateandtime)
|
|
|
|
dateandtime = dateandtime.astimezone(pytz.UTC)
|
|
|
|
return self.astral.solar_elevation(dateandtime,
|
|
self.latitude, self.longitude)
|
|
|
|
def solar_zenith(self, dateandtime=None):
|
|
"""Calculates the solar zenith angle for a specific time.
|
|
|
|
:param dateandtime: The date and time for which to calculate the angle.
|
|
:type dateandtime: :class:`~datetime.datetime`
|
|
|
|
:returns: The zenith angle in degrees above the horizon.
|
|
:rtype: float
|
|
"""
|
|
|
|
return self.solar_elevation(dateandtime)
|
|
|
|
def moon_phase(self, date=None):
|
|
"""Calculates the moon phase for a specific date.
|
|
|
|
:param date: The date to calculate the phase for.
|
|
If ommitted the current date is used.
|
|
:type date: :class:`datetime.date`
|
|
|
|
:returns:
|
|
A number designating the phase
|
|
|
|
| 0 = New moon
|
|
| 7 = First quarter
|
|
| 14 = Full moon
|
|
| 21 = Last quarter
|
|
:rtype: int
|
|
"""
|
|
|
|
if self.astral is None:
|
|
self.astral = Astral()
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
return self.astral.moon_phase(date)
|
|
|
|
|
|
class LocationGroup(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self._locations = {}
|
|
|
|
def __getitem__(self, key):
|
|
"""Returns a Location object for the specified `key`.
|
|
|
|
group = astral.europe
|
|
location = group['London']
|
|
|
|
You can supply an optional region name by adding a comma
|
|
followed by the region name. Where multiple locations have the
|
|
same name you may need to supply the region name otherwise
|
|
the first result will be returned which may not be the one
|
|
you're looking for.
|
|
|
|
location = group['Abu Dhabi,United Arab Emirates']
|
|
|
|
Handles location names with spaces and mixed case.
|
|
"""
|
|
|
|
key = self._sanitize_key(key)
|
|
|
|
try:
|
|
lookup_name, lookup_region = key.split(',', 1)
|
|
except ValueError:
|
|
lookup_name = key
|
|
lookup_region = ''
|
|
|
|
lookup_name = lookup_name.strip('"\'')
|
|
lookup_region = lookup_region.strip('"\'')
|
|
|
|
for (location_name, location_list) in self._locations.items():
|
|
if location_name == lookup_name:
|
|
if lookup_region == '':
|
|
return location_list[0]
|
|
|
|
for location in location_list:
|
|
if self._sanitize_key(location.region) == lookup_region:
|
|
return location
|
|
|
|
raise KeyError('Unrecognised location name - %s' % key)
|
|
|
|
def __setitem__(self, key, value):
|
|
key = self._sanitize_key(key)
|
|
if key not in self._locations:
|
|
self._locations[key] = [value]
|
|
else:
|
|
self._locations[key].append(value)
|
|
|
|
def __contains__(self, key):
|
|
key = self._sanitize_key(key)
|
|
for name in self._locations.keys():
|
|
if name == key:
|
|
return True
|
|
|
|
return False
|
|
|
|
def __iter__(self):
|
|
for location_list in self._locations.values():
|
|
for location in location_list:
|
|
yield location
|
|
|
|
def keys(self):
|
|
return self._locations.keys()
|
|
|
|
def values(self):
|
|
return self._locations.values()
|
|
|
|
def items(self):
|
|
return self._locations.items()
|
|
|
|
@property
|
|
def locations(self):
|
|
k = []
|
|
for location_list in self._locations.values():
|
|
for location in location_list:
|
|
k.append(location.name)
|
|
|
|
return k
|
|
|
|
def _sanitize_key(self, key):
|
|
return str(key).lower().replace(' ', '_')
|
|
|
|
|
|
class AstralGeocoder(object):
|
|
"""Looks up geographic information from the locations stored within the
|
|
module
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._groups = {}
|
|
|
|
locations = _LOCATION_INFO.split('\n')
|
|
for line in locations:
|
|
line = line.strip()
|
|
if line != '' and line[0] != '#':
|
|
if line[-1] == '\n':
|
|
line = line[:-1]
|
|
|
|
info = line.split(',')
|
|
|
|
l = Location(info)
|
|
|
|
key = l._timezone_group.lower()
|
|
try:
|
|
group = self.__getattr__(key)
|
|
except AttributeError:
|
|
group = LocationGroup(l._timezone_group)
|
|
self._groups[key] = group
|
|
|
|
group[info[0].lower()] = l
|
|
|
|
def __getattr__(self, key):
|
|
"""Access to each timezone group. For example London is in timezone
|
|
group Europe.
|
|
|
|
Attribute lookup is case insensitive"""
|
|
|
|
key = str(key).lower()
|
|
for name, value in self._groups.items():
|
|
if name == key:
|
|
return value
|
|
|
|
raise AttributeError('Group \'%s\' not found' % key)
|
|
|
|
def __getitem__(self, key):
|
|
"""Lookup a location within all timezone groups.
|
|
|
|
Item lookup is case insensitive."""
|
|
|
|
key = str(key).lower()
|
|
for group in self._groups.values():
|
|
try:
|
|
return group[key]
|
|
except KeyError:
|
|
pass
|
|
|
|
raise KeyError('Unrecognised location name - %s' % key)
|
|
|
|
def __iter__(self):
|
|
return self._groups.__iter__()
|
|
|
|
def __contains__(self, key):
|
|
key = str(key).lower()
|
|
for name, group in self._groups.items():
|
|
if name == key:
|
|
return True
|
|
|
|
if key in group:
|
|
return True
|
|
|
|
return False
|
|
|
|
@property
|
|
def locations(self):
|
|
k = []
|
|
for group in self._groups.values():
|
|
k.extend(group.locations)
|
|
|
|
return k
|
|
|
|
@property
|
|
def groups(self):
|
|
return self._groups
|
|
|
|
|
|
class GoogleGeocoder(object):
|
|
"""Use Google Maps API Web Service to lookup GPS co-ordinates, timezone and
|
|
elevation.
|
|
|
|
See the following for more info.
|
|
https://developers.google.com/maps/documentation/
|
|
"""
|
|
|
|
def __init__(self, cache=False):
|
|
self.cache = cache
|
|
self.geocache = {}
|
|
self._location_query_base = 'http://maps.googleapis.com/maps/api/geocode/json?address=%s&sensor=false'
|
|
self._timezone_query_base = 'https://maps.googleapis.com/maps/api/timezone/json?location=%f,%f×tamp=%d&sensor=false'
|
|
self._elevation_query_base = 'http://maps.googleapis.com/maps/api/elevation/json?locations=%f,%f&sensor=false'
|
|
|
|
def __getitem__(self, key):
|
|
if self.cache and key in self.geocache:
|
|
return self.geocache[key]
|
|
|
|
location = Location()
|
|
try:
|
|
self._get_geocoding(key, location)
|
|
self._get_timezone(location)
|
|
self._get_elevation(location)
|
|
except URLError:
|
|
raise AstralError(('GoogleGeocoder: Unable to contact '
|
|
'Google maps API'))
|
|
|
|
url = 'http://maps.google.com/maps?q=loc:%f,%f'
|
|
location.url = url % (location.latitude, location.longitude)
|
|
|
|
if self.cache:
|
|
self.geocache[key] = location
|
|
|
|
return location
|
|
|
|
def _get_geocoding(self, key, location):
|
|
"""Lookup the Google geocoding API information for `key`"""
|
|
|
|
url = self._location_query_base % quote_plus(key)
|
|
data = self._read_from_url(url)
|
|
response = json.loads(data)
|
|
if response['status'] == 'OK':
|
|
formatted_address = response['results'][0]['formatted_address']
|
|
pos = formatted_address.find(',')
|
|
if pos == -1:
|
|
location.name = formatted_address
|
|
location.region = ''
|
|
else:
|
|
location.name = formatted_address[:pos].strip()
|
|
location.region = formatted_address[pos + 1:].strip()
|
|
|
|
l = response['results'][0]['geometry']['location']
|
|
location.latitude = float(l['lat'])
|
|
location.longitude = float(l['lng'])
|
|
else:
|
|
raise AstralError('GoogleGeocoder: Unable to locate %s' % key)
|
|
|
|
def _get_timezone(self, location):
|
|
"""Query the timezone information with the latitude and longitude of
|
|
the specified `location`.
|
|
|
|
This function assumes the timezone of the location has always been
|
|
the same as it is now by using time() in the query string.
|
|
"""
|
|
|
|
url = self._timezone_query_base % (location.latitude,
|
|
location.longitude,
|
|
int(time()))
|
|
data = self._read_from_url(url)
|
|
response = json.loads(data)
|
|
if response['status'] == 'OK':
|
|
location.timezone = response['timeZoneId']
|
|
else:
|
|
location.timezone = 'UTC'
|
|
|
|
def _get_elevation(self, location):
|
|
"""Query the elevation information with the latitude and longitude of
|
|
the specified `location`.
|
|
"""
|
|
|
|
url = self._elevation_query_base % (location.latitude,
|
|
location.longitude)
|
|
data = self._read_from_url(url)
|
|
response = json.loads(data)
|
|
if response['status'] == 'OK':
|
|
location.elevation = int(float(response['results'][0]['elevation']))
|
|
else:
|
|
location.elevation = 0
|
|
|
|
def _read_from_url(self, url):
|
|
ds = urlopen(url)
|
|
content_types = ds.headers.get('Content-Type').split(';')
|
|
|
|
charset = 'UTF-8'
|
|
for ct in content_types:
|
|
if ct.strip().startswith('charset'):
|
|
charset = ct.split('=')[1]
|
|
|
|
data = ds.read().decode(charset)
|
|
ds.close()
|
|
|
|
return data
|
|
|
|
|
|
class Astral(object):
|
|
def __init__(self, geocoder=AstralGeocoder):
|
|
"""Initialise the geocoder and set the default depression."""
|
|
|
|
self.geocoder = geocoder()
|
|
self._depression = 6 # Set default depression in degrees
|
|
|
|
def __getitem__(self, key):
|
|
"""Returns the Location instance specified by ``key``."""
|
|
|
|
location = self.geocoder[key]
|
|
location.astral = self
|
|
return location
|
|
|
|
@property
|
|
def solar_depression(self):
|
|
"""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._depression
|
|
|
|
@solar_depression.setter
|
|
def solar_depression(self, depression):
|
|
if isinstance(depression, str) or isinstance(depression, ustr):
|
|
try:
|
|
self._depression = {
|
|
'civil': 6,
|
|
'nautical': 12,
|
|
'astronomical': 18}[depression]
|
|
except:
|
|
raise KeyError(("solar_depression must be either a number "
|
|
"or one of 'civil', 'nautical' or "
|
|
"'astronomical'"))
|
|
else:
|
|
self._depression = float(depression)
|
|
|
|
def sun_utc(self, date, latitude, longitude):
|
|
"""Calculate all the info for the sun at once.
|
|
All times are returned in the UTC timezone.
|
|
|
|
:param date: Date to calculate for.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:returns: Dictionary with keys ``dawn``, ``sunrise``, ``noon``,
|
|
``sunset`` and ``dusk`` whose values are the results of the
|
|
corresponding `_utc` methods.
|
|
:rtype: dict
|
|
"""
|
|
|
|
dawn = self.dawn_utc(date, latitude, longitude)
|
|
sunrise = self.sunrise_utc(date, latitude, longitude)
|
|
noon = self.solar_noon_utc(date, longitude)
|
|
sunset = self.sunset_utc(date, latitude, longitude)
|
|
dusk = self.dusk_utc(date, latitude, longitude)
|
|
|
|
return {
|
|
'dawn': dawn,
|
|
'sunrise': sunrise,
|
|
'noon': noon,
|
|
'sunset': sunset,
|
|
'dusk': dusk
|
|
}
|
|
|
|
def dawn_utc(self, date, latitude, longitude, depression=0):
|
|
"""Calculate dawn time in the UTC timezone.
|
|
|
|
:param date: Date to calculate for.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
:param depression: Override the depression used
|
|
:type depression: float
|
|
|
|
:return: The UTC date and time at which dawn occurs.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
if depression == 0:
|
|
depression = self._depression
|
|
depression += 90
|
|
|
|
try:
|
|
return self._calc_time(depression, SUN_RISING, date, latitude, longitude)
|
|
except:
|
|
raise AstralError(('Sun never reaches %d degrees below the horizon, '
|
|
'at this location.') % (depression - 90))
|
|
|
|
def sunrise_utc(self, date, latitude, longitude):
|
|
"""Calculate sunrise time in the UTC timezone.
|
|
|
|
:param date: Date to calculate for.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: The UTC date and time at which sunrise occurs.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
try:
|
|
return self._calc_time(90 + 0.833, SUN_RISING, date, latitude, longitude)
|
|
except:
|
|
raise AstralError(('Sun remains below the horizon on this day, '
|
|
'at this location.'))
|
|
|
|
def solar_noon_utc(self, date, longitude):
|
|
"""Calculate solar noon time in the UTC timezone.
|
|
|
|
:param date: Date to calculate for.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: The UTC date and time at which noon occurs.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
julianday = self._julianday(date)
|
|
|
|
newt = self._jday_to_jcentury(julianday + 0.5 + -longitude / 360.0)
|
|
|
|
eqtime = self._eq_of_time(newt)
|
|
timeUTC = 720.0 + (-longitude * 4.0) - eqtime
|
|
|
|
timeUTC = timeUTC / 60.0
|
|
hour = int(timeUTC)
|
|
minute = int((timeUTC - hour) * 60)
|
|
second = int((((timeUTC - hour) * 60) - minute) * 60)
|
|
|
|
if second > 59:
|
|
second -= 60
|
|
minute += 1
|
|
elif second < 0:
|
|
second += 60
|
|
minute -= 1
|
|
|
|
if minute > 59:
|
|
minute -= 60
|
|
hour += 1
|
|
elif minute < 0:
|
|
minute += 60
|
|
hour -= 1
|
|
|
|
if hour > 23:
|
|
hour -= 24
|
|
date += datetime.timedelta(days=1)
|
|
elif hour < 0:
|
|
hour += 24
|
|
date -= datetime.timedelta(days=1)
|
|
|
|
noon = datetime.datetime(date.year, date.month, date.day,
|
|
hour, minute, second)
|
|
noon = pytz.UTC.localize(noon)
|
|
|
|
return noon
|
|
|
|
def sunset_utc(self, date, latitude, longitude):
|
|
"""Calculate sunset time in the UTC timezone.
|
|
|
|
:param date: Date to calculate for.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: The UTC date and time at which sunset occurs.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
try:
|
|
return self._calc_time(90 + 0.833, SUN_SETTING, date, latitude, longitude)
|
|
except:
|
|
raise AstralError(('Sun remains above the horizon on this day, '
|
|
'at this location.'))
|
|
|
|
def dusk_utc(self, date, latitude, longitude, depression=0):
|
|
"""Calculate dusk time in the UTC timezone.
|
|
|
|
:param date: Date to calculate for.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
:param depression: Override the depression used
|
|
:type depression: float
|
|
|
|
:return: The UTC date and time at which dusk occurs.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
if depression == 0:
|
|
depression = self._depression
|
|
depression += 90
|
|
|
|
try:
|
|
return self._calc_time(depression, SUN_SETTING, date, latitude, longitude)
|
|
except:
|
|
raise AstralError(('Sun never reaches %d degrees below the horizon, '
|
|
'at this location.') % (depression - 90))
|
|
|
|
def daylight_utc(self, date, latitude, longitude):
|
|
"""Calculate daylight start and end times in the UTC timezone.
|
|
|
|
:param date: Date to calculate for.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: A tuple of the UTC date and time at which daylight starts and ends.
|
|
:rtype: (:class:`~datetime.datetime`, :class:`~datetime.datetime`)
|
|
"""
|
|
|
|
start = self.sunrise_utc(date, latitude, longitude)
|
|
end = self.sunset_utc(date, latitude, longitude)
|
|
|
|
return start, end
|
|
|
|
def night_utc(self, date, latitude, longitude):
|
|
"""Calculate night start and end times in the UTC timezone.
|
|
|
|
Night is calculated to be between astronomical dusk on the
|
|
date specified and astronomical dawn of the next day.
|
|
|
|
:param date: Date to calculate for.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: A tuple of the UTC date and time at which night starts and ends.
|
|
:rtype: (:class:`~datetime.datetime`, :class:`~datetime.datetime`)
|
|
"""
|
|
|
|
start = self.dusk_utc(date, latitude, longitude, 18)
|
|
tomorrow = date + datetime.timedelta(days=1)
|
|
end = self.dawn_utc(tomorrow, latitude, longitude, 18)
|
|
|
|
return start, end
|
|
|
|
def twilight_utc(self, direction, date, latitude, longitude):
|
|
"""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.SUN_SETTING``.
|
|
:type direction: int
|
|
:param date: The date for which to calculate the times.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: A tuple of the UTC date and time at which twilight starts and ends.
|
|
:rtype: (:class:`~datetime.datetime`, :class:`~datetime.datetime`)
|
|
"""
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
start = self.time_at_elevation_utc(-6, direction, date, latitude, longitude)
|
|
if direction == SUN_RISING:
|
|
end = self.sunrise_utc(date, latitude, longitude)
|
|
else:
|
|
end = self.sunset_utc(date, latitude, longitude)
|
|
|
|
if direction == SUN_RISING:
|
|
return start, end
|
|
else:
|
|
return end, start
|
|
|
|
def golden_hour_utc(self, direction, date, latitude, longitude):
|
|
"""Returns the start and end times of the Golden Hour in the UTC timezone
|
|
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 ``astral.SUN_RISING`` or ``astral.SUN_SETTING``.
|
|
:type direction: int
|
|
:param date: The date for which to calculate the times.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: A tuple of the UTC date and time at which the Golden Hour starts and ends.
|
|
:rtype: (:class:`~datetime.datetime`, :class:`~datetime.datetime`)
|
|
"""
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
start = self.time_at_elevation_utc(-4, direction, date,
|
|
latitude, longitude)
|
|
end = self.time_at_elevation_utc(6, direction, date,
|
|
latitude, longitude)
|
|
|
|
if direction == SUN_RISING:
|
|
return start, end
|
|
else:
|
|
return end, start
|
|
|
|
def blue_hour_utc(self, direction, date, latitude, longitude):
|
|
"""Returns the start and end times of the Blue Hour in the UTC timezone
|
|
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 ``astral.SUN_RISING`` or ``astral.SUN_SETTING``.
|
|
:type direction: int
|
|
:param date: The date for which to calculate the times.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: A tuple of the UTC date and time at which the Blue Hour starts and ends.
|
|
:rtype: (:class:`~datetime.datetime`, :class:`~datetime.datetime`)
|
|
"""
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
start = self.time_at_elevation_utc(-6, direction, date,
|
|
latitude, longitude)
|
|
end = self.time_at_elevation_utc(-4, direction, date,
|
|
latitude, longitude)
|
|
|
|
if direction == SUN_RISING:
|
|
return start, end
|
|
else:
|
|
return end, start
|
|
|
|
def time_at_elevation_utc(self, elevation, direction, date, latitude, longitude):
|
|
"""Calculate the time in the UTC timezone when the sun is at
|
|
the specified elevation on the specified date.
|
|
|
|
Note: This method uses positive elevations for those above the horizon.
|
|
|
|
:param elevation: Elevation in degrees above the horizon to calculate for.
|
|
:type elevation: float
|
|
:param direction: Determines whether the calculated time is for the sun rising or setting.
|
|
Use ``astral.SUN_RISING`` or ``astral.SUN_SETTING``. Default is rising.
|
|
:type direction: int
|
|
:param date: Date to calculate for.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: The UTC date and time at which the sun is at the required
|
|
elevation.
|
|
:rtype: :class:`~datetime.datetime`
|
|
"""
|
|
|
|
if elevation > 90.0:
|
|
elevation = 180.0 - elevation
|
|
direction = SUN_SETTING
|
|
|
|
depression = 90 - elevation
|
|
try:
|
|
return self._calc_time(depression, direction, date, latitude, longitude)
|
|
except Exception:
|
|
raise AstralError(('Sun never reaches an elevation of %d degrees'
|
|
'at this location.') % elevation)
|
|
|
|
def solar_azimuth(self, dateandtime, latitude, longitude):
|
|
"""Calculate the azimuth angle of the sun.
|
|
|
|
:param dateandtime: The date and time for which to calculate
|
|
the angle.
|
|
:type dateandtime: :class:`~datetime.datetime`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: The azimuth angle in degrees clockwise from North.
|
|
:rtype: float
|
|
|
|
If `dateandtime` is a naive Python datetime then it is assumed to be
|
|
in the UTC timezone.
|
|
"""
|
|
|
|
if latitude > 89.8:
|
|
latitude = 89.8
|
|
|
|
if latitude < -89.8:
|
|
latitude = -89.8
|
|
|
|
if dateandtime.tzinfo is None:
|
|
zone = 0
|
|
utc_datetime = dateandtime
|
|
else:
|
|
zone = -dateandtime.utcoffset().total_seconds() / 3600.0
|
|
utc_datetime = dateandtime.astimezone(pytz.utc)
|
|
|
|
timenow = utc_datetime.hour + (utc_datetime.minute / 60.0) + \
|
|
(utc_datetime.second / 3600.0)
|
|
|
|
JD = self._julianday(dateandtime)
|
|
t = self._jday_to_jcentury(JD + timenow / 24.0)
|
|
theta = self._sun_declination(t)
|
|
eqtime = self._eq_of_time(t)
|
|
solarDec = theta # in degrees
|
|
|
|
solarTimeFix = eqtime - (4.0 * -longitude) + (60 * zone)
|
|
trueSolarTime = dateandtime.hour * 60.0 + dateandtime.minute + \
|
|
dateandtime.second / 60.0 + solarTimeFix
|
|
# in minutes
|
|
|
|
while trueSolarTime > 1440:
|
|
trueSolarTime = trueSolarTime - 1440
|
|
|
|
hourangle = trueSolarTime / 4.0 - 180.0
|
|
# Thanks to Louis Schwarzmayr for the next line:
|
|
if hourangle < -180:
|
|
hourangle = hourangle + 360.0
|
|
|
|
harad = radians(hourangle)
|
|
|
|
csz = sin(radians(latitude)) * sin(radians(solarDec)) + \
|
|
cos(radians(latitude)) * cos(radians(solarDec)) * cos(harad)
|
|
|
|
if csz > 1.0:
|
|
csz = 1.0
|
|
elif csz < -1.0:
|
|
csz = -1.0
|
|
|
|
zenith = degrees(acos(csz))
|
|
|
|
azDenom = (cos(radians(latitude)) * sin(radians(zenith)))
|
|
|
|
if (abs(azDenom) > 0.001):
|
|
azRad = ((sin(radians(latitude)) * cos(radians(zenith))) -
|
|
sin(radians(solarDec))) / azDenom
|
|
|
|
if abs(azRad) > 1.0:
|
|
if azRad < 0:
|
|
azRad = -1.0
|
|
else:
|
|
azRad = 1.0
|
|
|
|
azimuth = 180.0 - degrees(acos(azRad))
|
|
|
|
if hourangle > 0.0:
|
|
azimuth = -azimuth
|
|
else:
|
|
if latitude > 0.0:
|
|
azimuth = 180.0
|
|
else:
|
|
azimuth = 0.0
|
|
|
|
if azimuth < 0.0:
|
|
azimuth = azimuth + 360.0
|
|
|
|
return azimuth
|
|
|
|
def solar_elevation(self, dateandtime, latitude, longitude):
|
|
"""Calculate the elevation angle of the sun.
|
|
|
|
:param dateandtime: The date and time for which to calculate
|
|
the angle.
|
|
:type dateandtime: :class:`~datetime.datetime`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: The elevation angle in degrees above the horizon.
|
|
:rtype: float
|
|
|
|
If `dateandtime` is a naive Python datetime then it is assumed to be
|
|
in the UTC timezone.
|
|
"""
|
|
|
|
if latitude > 89.8:
|
|
latitude = 89.8
|
|
|
|
if latitude < -89.8:
|
|
latitude = -89.8
|
|
|
|
if dateandtime.tzinfo is None:
|
|
zone = 0
|
|
utc_datetime = dateandtime
|
|
else:
|
|
zone = -dateandtime.utcoffset().total_seconds() / 3600.0
|
|
utc_datetime = dateandtime.astimezone(pytz.utc)
|
|
|
|
timenow = utc_datetime.hour + (utc_datetime.minute / 60.0) + \
|
|
(utc_datetime.second / 3600)
|
|
|
|
JD = self._julianday(dateandtime)
|
|
t = self._jday_to_jcentury(JD + timenow / 24.0)
|
|
theta = self._sun_declination(t)
|
|
eqtime = self._eq_of_time(t)
|
|
solarDec = theta # in degrees
|
|
|
|
solarTimeFix = eqtime - (4.0 * -longitude) + (60 * zone)
|
|
trueSolarTime = dateandtime.hour * 60.0 + dateandtime.minute + \
|
|
dateandtime.second / 60.0 + solarTimeFix
|
|
# in minutes
|
|
|
|
while trueSolarTime > 1440:
|
|
trueSolarTime = trueSolarTime - 1440
|
|
|
|
hourangle = trueSolarTime / 4.0 - 180.0
|
|
# Thanks to Louis Schwarzmayr for the next line:
|
|
if hourangle < -180:
|
|
hourangle = hourangle + 360.0
|
|
|
|
harad = radians(hourangle)
|
|
|
|
csz = sin(radians(latitude)) * sin(radians(solarDec)) + \
|
|
cos(radians(latitude)) * cos(radians(solarDec)) * cos(harad)
|
|
|
|
if csz > 1.0:
|
|
csz = 1.0
|
|
elif csz < -1.0:
|
|
csz = -1.0
|
|
|
|
zenith = degrees(acos(csz))
|
|
|
|
azDenom = (cos(radians(latitude)) * sin(radians(zenith)))
|
|
|
|
if (abs(azDenom) > 0.001):
|
|
azRad = ((sin(radians(latitude)) * cos(radians(zenith))) -
|
|
sin(radians(solarDec))) / azDenom
|
|
|
|
if abs(azRad) > 1.0:
|
|
if azRad < 0:
|
|
azRad = -1.0
|
|
else:
|
|
azRad = 1.0
|
|
|
|
azimuth = 180.0 - degrees(acos(azRad))
|
|
|
|
if hourangle > 0.0:
|
|
azimuth = -azimuth
|
|
else:
|
|
if latitude > 0.0:
|
|
azimuth = 180.0
|
|
else:
|
|
azimuth = 0.0
|
|
|
|
if azimuth < 0.0:
|
|
azimuth = azimuth + 360.0
|
|
|
|
exoatmElevation = 90.0 - zenith
|
|
|
|
if exoatmElevation > 85.0:
|
|
refractionCorrection = 0.0
|
|
else:
|
|
te = tan(radians(exoatmElevation))
|
|
if exoatmElevation > 5.0:
|
|
refractionCorrection = 58.1 / te - 0.07 / (te * te * te) + \
|
|
0.000086 / (te * te * te * te * te)
|
|
elif exoatmElevation > -0.575:
|
|
step1 = (-12.79 + exoatmElevation * 0.711)
|
|
step2 = (103.4 + exoatmElevation * (step1))
|
|
step3 = (-518.2 + exoatmElevation * (step2))
|
|
refractionCorrection = 1735.0 + exoatmElevation * (step3)
|
|
else:
|
|
refractionCorrection = -20.774 / te
|
|
|
|
refractionCorrection = refractionCorrection / 3600.0
|
|
|
|
solarzen = zenith - refractionCorrection
|
|
|
|
solarelevation = 90.0 - solarzen
|
|
|
|
return solarelevation
|
|
|
|
def solar_zenith(self, dateandtime, latitude, longitude):
|
|
"""Calculates the solar zenith angle.
|
|
|
|
:param dateandtime: The date and time for which to calculate
|
|
the angle.
|
|
:type dateandtime: :class:`~datetime.datetime`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: The zenith angle in degrees above the horizon.
|
|
:rtype: float
|
|
|
|
If `dateandtime` is a naive Python datetime then it is assumed to be
|
|
in the UTC timezone.
|
|
"""
|
|
|
|
return self.solar_elevation(dateandtime, latitude, longitude)
|
|
|
|
def moon_phase(self, date):
|
|
"""Calculates the phase of the moon on the specified date.
|
|
|
|
:param date: The date to calculate the phase for.
|
|
:type date: :class:`datetime.date`
|
|
|
|
:return:
|
|
A number designating the phase
|
|
|
|
| 0 = New moon
|
|
| 7 = First quarter
|
|
| 14 = Full moon
|
|
| 21 = Last quarter
|
|
:rtype: int
|
|
"""
|
|
|
|
jd = self._julianday(date)
|
|
DT = pow((jd - 2382148), 2) / (41048480 * 86400)
|
|
T = (jd + DT - 2451545.0) / 36525
|
|
T2 = pow(T, 2)
|
|
T3 = pow(T, 3)
|
|
D = 297.85 + (445267.1115 * T) - (0.0016300 * T2) + (T3 / 545868)
|
|
D = radians(self._proper_angle(D))
|
|
M = 357.53 + (35999.0503 * T)
|
|
M = radians(self._proper_angle(M))
|
|
M1 = 134.96 + (477198.8676 * T) + (0.0089970 * T2) + (T3 / 69699)
|
|
M1 = radians(self._proper_angle(M1))
|
|
elong = degrees(D) + 6.29 * sin(M1)
|
|
elong -= 2.10 * sin(M)
|
|
elong += 1.27 * sin(2 * D - M1)
|
|
elong += 0.66 * sin(2 * D)
|
|
elong = self._proper_angle(elong)
|
|
elong = round(elong)
|
|
moon = ((elong + 6.43) / 360) * 28
|
|
moon = floor(moon)
|
|
if moon == 28:
|
|
moon = 0
|
|
|
|
return moon
|
|
|
|
def rahukaalam_utc(self, date, latitude, longitude):
|
|
"""Calculate ruhakaalam times in the UTC timezone.
|
|
|
|
:param date: Date to calculate for.
|
|
:type date: :class:`datetime.date`
|
|
:param latitude: Latitude - Northern latitudes should be positive
|
|
:type latitude: float
|
|
:param longitude: Longitude - Eastern longitudes should be positive
|
|
:type longitude: float
|
|
|
|
:return: Tuple containing the start and end times for Rahukaalam.
|
|
:rtype: tuple
|
|
"""
|
|
|
|
if date is None:
|
|
date = datetime.date.today()
|
|
|
|
try:
|
|
sunrise = self.sunrise_utc(date, latitude, longitude)
|
|
sunset = self.sunset_utc(date, latitude, longitude)
|
|
except:
|
|
raise AstralError(('Sun remains below the horizon on this day, '
|
|
'at this location.'))
|
|
|
|
octant_duration = (sunset - sunrise) / 8
|
|
|
|
# Mo,Sa,Fr,We,Th,Tu,Su
|
|
octant_index = [1, 6, 4, 5, 3, 2, 7]
|
|
|
|
weekday = date.weekday()
|
|
octant = octant_index[weekday]
|
|
|
|
start = sunrise + (octant_duration * octant)
|
|
end = start + octant_duration
|
|
|
|
return start, end
|
|
|
|
def _proper_angle(self, value):
|
|
if value > 0.0:
|
|
value /= 360.0
|
|
return (value - floor(value)) * 360.0
|
|
else:
|
|
tmp = ceil(abs(value / 360.0))
|
|
return value + tmp * 360.0
|
|
|
|
def _julianday(self, date, timezone=None):
|
|
day = date.day
|
|
month = date.month
|
|
year = date.year
|
|
|
|
if timezone:
|
|
offset = timezone.localize(datetime.datetime(year, month, day)).utcoffset()
|
|
offset = offset.total_seconds() / 1440.0
|
|
day += offset + 0.5
|
|
|
|
if month <= 2:
|
|
year = year - 1
|
|
month = month + 12
|
|
|
|
A = floor(year / 100.0)
|
|
B = 2 - A + floor(A / 4.0)
|
|
|
|
jd = floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + \
|
|
day - 1524.5
|
|
if jd > 2299160.4999999:
|
|
jd += B
|
|
|
|
return jd
|
|
|
|
def _jday_to_jcentury(self, julianday):
|
|
return (julianday - 2451545.0) / 36525.0
|
|
|
|
def _jcentury_to_jday(self, juliancentury):
|
|
return (juliancentury * 36525.0) + 2451545.0
|
|
|
|
def _mean_obliquity_of_ecliptic(self, juliancentury):
|
|
seconds = 21.448 - juliancentury * \
|
|
(46.815 + juliancentury * (0.00059 - juliancentury * (0.001813)))
|
|
return 23.0 + (26.0 + (seconds / 60.0)) / 60.0
|
|
|
|
def _obliquity_correction(self, juliancentury):
|
|
e0 = self._mean_obliquity_of_ecliptic(juliancentury)
|
|
|
|
omega = 125.04 - 1934.136 * juliancentury
|
|
return e0 + 0.00256 * cos(radians(omega))
|
|
|
|
def _geom_mean_long_sun(self, juliancentury):
|
|
l0 = 280.46646 + \
|
|
juliancentury * (36000.76983 + 0.0003032 * juliancentury)
|
|
return l0 % 360.0
|
|
|
|
def _eccentrilocation_earth_orbit(self, juliancentury):
|
|
return 0.016708634 - \
|
|
juliancentury * (0.000042037 + 0.0000001267 * juliancentury)
|
|
|
|
def _geom_mean_anomaly_sun(self, juliancentury):
|
|
return 357.52911 + \
|
|
juliancentury * (35999.05029 - 0.0001537 * juliancentury)
|
|
|
|
def _eq_of_time(self, juliancentury):
|
|
epsilon = self._obliquity_correction(juliancentury)
|
|
l0 = self._geom_mean_long_sun(juliancentury)
|
|
e = self._eccentrilocation_earth_orbit(juliancentury)
|
|
m = self._geom_mean_anomaly_sun(juliancentury)
|
|
|
|
y = tan(radians(epsilon) / 2.0)
|
|
y = y * y
|
|
|
|
sin2l0 = sin(2.0 * radians(l0))
|
|
sinm = sin(radians(m))
|
|
cos2l0 = cos(2.0 * radians(l0))
|
|
sin4l0 = sin(4.0 * radians(l0))
|
|
sin2m = sin(2.0 * radians(m))
|
|
|
|
Etime = y * sin2l0 - 2.0 * e * sinm + 4.0 * e * y * sinm * cos2l0 - \
|
|
0.5 * y * y * sin4l0 - 1.25 * e * e * sin2m
|
|
|
|
return degrees(Etime) * 4.0
|
|
|
|
def _sun_eq_of_center(self, juliancentury):
|
|
m = self._geom_mean_anomaly_sun(juliancentury)
|
|
|
|
mrad = radians(m)
|
|
sinm = sin(mrad)
|
|
sin2m = sin(mrad + mrad)
|
|
sin3m = sin(mrad + mrad + mrad)
|
|
|
|
c = sinm * (1.914602 - juliancentury * \
|
|
(0.004817 + 0.000014 * juliancentury)) + \
|
|
sin2m * (0.019993 - 0.000101 * juliancentury) + sin3m * 0.000289
|
|
|
|
return c
|
|
|
|
def _sun_true_long(self, juliancentury):
|
|
l0 = self._geom_mean_long_sun(juliancentury)
|
|
c = self._sun_eq_of_center(juliancentury)
|
|
|
|
return l0 + c
|
|
|
|
def _sun_apparent_long(self, juliancentury):
|
|
O = self._sun_true_long(juliancentury)
|
|
|
|
omega = 125.04 - 1934.136 * juliancentury
|
|
return O - 0.00569 - 0.00478 * sin(radians(omega))
|
|
|
|
def _sun_declination(self, juliancentury):
|
|
e = self._obliquity_correction(juliancentury)
|
|
lambd = self._sun_apparent_long(juliancentury)
|
|
|
|
sint = sin(radians(e)) * sin(radians(lambd))
|
|
return degrees(asin(sint))
|
|
|
|
def _sun_rad_vector(self, juliancentury):
|
|
v = self._sun_true_anomoly(juliancentury)
|
|
e = self._eccentrilocation_earth_orbit(juliancentury)
|
|
|
|
return (1.000001018 * (1 - e * e)) / (1 + e * cos(radians(v)))
|
|
|
|
def _sun_rt_ascension(self, juliancentury):
|
|
e = self._obliquity_correction(juliancentury)
|
|
lambd = self._sun_apparent_long(juliancentury)
|
|
|
|
tananum = (cos(radians(e)) * sin(radians(lambd)))
|
|
tanadenom = (cos(radians(lambd)))
|
|
|
|
return degrees(atan2(tananum, tanadenom))
|
|
|
|
def _sun_true_anomoly(self, juliancentury):
|
|
m = self._geom_mean_anomaly_sun(juliancentury)
|
|
c = self._sun_eq_of_center(juliancentury)
|
|
|
|
return m + c
|
|
|
|
def _hour_angle(self, latitude, declination, depression):
|
|
latitude_rad = radians(latitude)
|
|
declination_rad = radians(declination)
|
|
depression_rad = radians(depression)
|
|
|
|
n = cos(depression_rad)
|
|
d = cos(latitude_rad) * cos(declination_rad)
|
|
|
|
t = tan(latitude_rad) * tan(declination_rad)
|
|
h = (n / d) - t
|
|
|
|
HA = acos(h)
|
|
return HA
|
|
|
|
def _calc_time(self, depression, direction, date, latitude, longitude):
|
|
julianday = self._julianday(date)
|
|
|
|
if latitude > 89.8:
|
|
latitude = 89.8
|
|
|
|
if latitude < -89.8:
|
|
latitude = -89.8
|
|
|
|
t = self._jday_to_jcentury(julianday)
|
|
eqtime = self._eq_of_time(t)
|
|
solarDec = self._sun_declination(t)
|
|
|
|
hourangle = -self._hour_angle(latitude, solarDec, 90 + 0.833)
|
|
|
|
delta = -longitude - degrees(hourangle)
|
|
timeDiff = 4.0 * delta
|
|
timeUTC = 720.0 + timeDiff - eqtime
|
|
|
|
newt = self._jday_to_jcentury(self._jcentury_to_jday(t) +
|
|
timeUTC / 1440.0)
|
|
eqtime = self._eq_of_time(newt)
|
|
solarDec = self._sun_declination(newt)
|
|
|
|
hourangle = self._hour_angle(latitude, solarDec, depression)
|
|
if direction == SUN_SETTING:
|
|
hourangle = -hourangle
|
|
|
|
delta = -longitude - degrees(hourangle)
|
|
timeDiff = 4 * delta
|
|
timeUTC = 720 + timeDiff - eqtime
|
|
|
|
timeUTC = timeUTC / 60.0
|
|
hour = int(timeUTC)
|
|
minute = int((timeUTC - hour) * 60)
|
|
second = int((((timeUTC - hour) * 60) - minute) * 60)
|
|
|
|
if second > 59:
|
|
second -= 60
|
|
minute += 1
|
|
elif second < 0:
|
|
second += 60
|
|
minute -= 1
|
|
|
|
if minute > 59:
|
|
minute -= 60
|
|
hour += 1
|
|
elif minute < 0:
|
|
minute += 60
|
|
hour -= 1
|
|
|
|
if hour > 23:
|
|
hour -= 24
|
|
date += datetime.timedelta(days=1)
|
|
elif hour < 0:
|
|
hour += 24
|
|
date -= datetime.timedelta(days=1)
|
|
|
|
dt = datetime.datetime(date.year, date.month, date.day,
|
|
hour, minute, second)
|
|
dt = pytz.UTC.localize(dt)
|
|
|
|
return dt
|