astral-1.6.1/0000777000000000000000000000000013272332316011141 5ustar 00000000000000astral-1.6.1/LICENSE0000666000000000000000000002645013110530122012137 0ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. astral-1.6.1/MANIFEST.in0000666000000000000000000000014413252461721012677 0ustar 00000000000000include notes.txt include src/test/*.py global-exclude *.pyc include monkeypatch.py include LICENSE astral-1.6.1/monkeypatch.py0000666000000000000000000000456013244005066014040 0ustar 00000000000000"""Monkey patch distutils to also use setup-dev.cfg and setuptools to add README.md to the default files""" import os import sys import distutils.dist from distutils.util import (check_environ, convert_path) from distutils.debug import DEBUG import setuptools.command.sdist def find_config_files(self): """Find as many configuration files as should be processed for this platform, and return a list of filenames in the order in which they should be parsed. The filenames returned are guaranteed to exist (modulo nasty race conditions). There are three possible config files: distutils.cfg in the Distutils installation directory (ie. where the top-level Distutils __inst__.py file lives), a file in the user's home directory named .pydistutils.cfg on Unix and pydistutils.cfg on Windows/Mac; and setup.cfg in the current directory. The file in the user's home directory can be disabled with the --no-user-cfg option. """ files = [] check_environ() # Where to look for the system-wide Distutils config file sys_dir = os.path.dirname(sys.modules['distutils'].__file__) # Look for the system config file sys_file = os.path.join(sys_dir, "distutils.cfg") if os.path.isfile(sys_file): files.append(sys_file) # What to call the per-user config file if os.name == 'posix': user_filename = ".pydistutils.cfg" else: user_filename = "pydistutils.cfg" # And look for the user config file if self.want_user_cfg: user_file = os.path.join(os.path.expanduser('~'), user_filename) if os.path.isfile(user_file): files.append(user_file) # All platforms support local setup.cfg local_file = "setup.cfg" if os.path.isfile(local_file): files.append(local_file) local_dev_file = "setup-dev.cfg" if os.path.isfile(local_dev_file): files.append(local_dev_file) if DEBUG: self.announce("using config files: %s" % ', '.join(files)) return files def check_readme(self): for f in self.READMES + ('README.md',): if os.path.exists(f): return else: self.warn( "standard file not found: should have one of " + ', '.join(self.READMES) ) distutils.dist.Distribution.find_config_files = find_config_files setuptools.command.sdist.sdist.check_readme = check_readme astral-1.6.1/notes.txt0000666000000000000000000000034113244005066013026 0ustar 00000000000000Written Consent from Google has been obtained by following the steps outlined at the following location http://support.google.com/maps/bin/static.py?hl=en&ts=1342531&page=ts.cs Refer to the README.md file for Attribution. astral-1.6.1/PKG-INFO0000666000000000000000000000372513272332316012245 0ustar 00000000000000Metadata-Version: 1.2 Name: astral Version: 1.6.1 Summary: Calculations for the position of the sun and moon. Home-page: https://github.com/sffjunkie/astral Author: Simon Kennedy Author-email: sffjunkie+code@gmail.com License: Apache-2.0 Project-URL: Documentation, https://astral.readthedocs.io/en/stable/index.html Project-URL: Source, https://github.com/sffjunkie/astral Project-URL: Issues, https://github.com/sffjunkie/astral/issues Description-Content-Type: UNKNOWN Description: Astral ====== |travis_status| |pypi_ver| .. |travis_status| image:: https://travis-ci.org/sffjunkie/astral.svg?branch=master :target: https://travis-ci.org/sffjunkie/astral .. |pypi_ver| image:: https://img.shields.io/pypi/v/astral.svg :target: https://pypi.org/project/astral/ This is 'astral' a Python module which calculates * Times for various positions of the sun: dawn, sunrise, solar noon, sunset, dusk, solar elevation, solar azimuth and rahukaalam. * The phase of the moon. For documentation see the https://astral.readthedocs.io/en/stable/index.html GoogleGeocoder ~~~~~~~~~~~~~~ `GoogleGeocoder` uses the mapping services provided by Google Access to the `GoogleGeocoder` requires you to agree to be bound by Google Maps/Google Earth APIs Terms of Service found at https://developers.google.com/maps/terms which includes but is not limited to having a Google Account. More information on Google's maps service can be found at https://developers.google.com/maps/documentation/ Platform: UNKNOWN Classifier: Intended Audience :: Developers Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 astral-1.6.1/README.rst0000666000000000000000000000177313254205421012634 0ustar 00000000000000Astral ====== |travis_status| |pypi_ver| .. |travis_status| image:: https://travis-ci.org/sffjunkie/astral.svg?branch=master :target: https://travis-ci.org/sffjunkie/astral .. |pypi_ver| image:: https://img.shields.io/pypi/v/astral.svg :target: https://pypi.org/project/astral/ This is 'astral' a Python module which calculates * Times for various positions of the sun: dawn, sunrise, solar noon, sunset, dusk, solar elevation, solar azimuth and rahukaalam. * The phase of the moon. For documentation see the https://astral.readthedocs.io/en/stable/index.html GoogleGeocoder ~~~~~~~~~~~~~~ `GoogleGeocoder` uses the mapping services provided by Google Access to the `GoogleGeocoder` requires you to agree to be bound by Google Maps/Google Earth APIs Terms of Service found at https://developers.google.com/maps/terms which includes but is not limited to having a Google Account. More information on Google's maps service can be found at https://developers.google.com/maps/documentation/ astral-1.6.1/setup.cfg0000666000000000000000000000142113272332316012760 0ustar 00000000000000[metadata] name = astral version = 1.5 description = Calculations for the position of the sun and moon. description-file = README.rst author = Simon Kennedy author_email = sffjunkie+code@gmail.com home-page = https://github.com/sffjunkie/astral license = Apache-2.0 classifiers = Intended Audience :: Developers Programming Language :: Python Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 requires_dist = pytz [files] packages_root = src modules = astral [aliases] test = pytest build_docs = build_sphinx [bdist_wheel] universal = 1 [tool:pytest] norecursedirs = .eggs .git .bzr .hg .tox .cache .settings dist build [pycodestyle] max_line_length = 100 [egg_info] tag_build = tag_date = 0 astral-1.6.1/setup.py0000666000000000000000000000252313272324171012655 0ustar 00000000000000# Copyright 2009-2018, Simon Kennedy, sffjunkie+code@gmail.com import io import os from setuptools import setup import monkeypatch # pylint: disable=W0611 def read_contents(*names, **kwargs): return io.open( os.path.join(*names), encoding=kwargs.get("encoding", "utf8") ).read() description = 'Calculations for the position of the sun and moon.' try: long_description = read_contents(os.path.dirname(__file__), 'README.rst') except: long_description = description setup(name='astral', version='1.6.1', description=description, long_description=long_description, author='Simon Kennedy', author_email='sffjunkie+code@gmail.com', url="https://github.com/sffjunkie/astral", project_urls={ 'Documentation': 'https://astral.readthedocs.io/en/stable/index.html', 'Source': 'https://github.com/sffjunkie/astral', 'Issues': 'https://github.com/sffjunkie/astral/issues', }, license='Apache-2.0', classifiers=[ "Intended Audience :: Developers", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", ], package_dir={'': 'src'}, py_modules=['astral'], install_requires=['pytz', 'requests'], tests_require=['pytest-runner'], ) astral-1.6.1/src/0000777000000000000000000000000013272332316011730 5ustar 00000000000000astral-1.6.1/src/astral.py0000666000000000000000000026462713272324171013611 0ustar 00000000000000# -*- coding: utf-8 -*- # Copyright 2009-2018, Simon Kennedy, sffjunkie+code@gmail.com # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Calculations for the position of the sun and moon. 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, division try: import pytz except ImportError: raise ImportError(('The astral module requires the ' 'pytz module to be available.')) try: import requests except ImportError: raise ImportError(('The astral module requires the ' 'requests 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 from numbers import Number import sys try: from urllib import quote_plus except ImportError: from urllib.parse import quote_plus try: from urllib2 import URLError except ImportError: from urllib.request import URLError try: import simplejson as json except ImportError: import json if sys.version_info[0] >= 3: ustr = str else: ustr = unicode # pylint: disable=E0602 __all__ = ['Astral', 'Location', 'AstralGeocoder', 'GoogleGeocoder', 'AstralError'] __version__ = "1.6.1" __author__ = "Simon Kennedy " 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): """Astral base exception class""" def excel_datediff(start_date, end_date): """Return the same number of days between 2 dates as Excel does""" return end_date.toordinal() - start_date.toordinal() + 2 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): if self.region: _repr = '%s/%s' % (self.name, self.region) else: _repr = self.name repr_format = '%s, tz=%s, lat=%0.02f, lon=%0.02f' return repr_format % (_repr, 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 solar_midnight(self, date=None, local=True): """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. :rtype: :class:`~datetime.datetime` """ if self.astral is None: self.astral = Astral() if date is None: date = datetime.date.today() midnight = self.astral.solar_midnight_utc(date, self.longitude) if local: return midnight.astimezone(self.tz) else: return midnight 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(direction, date, 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 from vertical. :rtype: float """ return 90.0 - self.solar_elevation(dateandtime) def moon_phase(self, date=None, rtype=int): """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 """ if self.astral is None: self.astral = Astral() if date is None: date = datetime.date.today() return self.astral.moon_phase(date, rtype) class LocationGroup(object): """Groups a set of timezones by the timezone group""" 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(',') location = Location(info) key = location._timezone_group.lower() try: group = self.__getattr__(key) except AttributeError: group = LocationGroup(location._timezone_group) self._groups[key] = group group[info[0].lower()] = location 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, api_key=''): self.cache = cache self.api_key = api_key self.geocache = {} self._location_query_base = ('https://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 = ('https://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 = 'https://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) if self.api_key: url += '&key=%s' % self.api_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() geo_location = response['results'][0]['geometry']['location'] location.latitude = float(geo_location['lat']) location.longitude = float(geo_location['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())) if self.api_key != '': url += '&key=%s' % self.api_key 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) if self.api_key != '': url += '&key=%s' % self.api_key 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 = requests.get(url) return ds.text 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 KeyError: 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 ValueError as exc: if exc.args[0] == 'math domain error': raise AstralError(('Sun never reaches %d degrees below the horizon, ' 'at this location.') % (depression - 90)) else: raise 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 ValueError as exc: if exc.args[0] == 'math domain error': raise AstralError(('Sun never reaches the horizon on this day, ' 'at this location.')) else: raise 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 longitude: Longitude - Eastern longitudes should be positive :type longitude: float :return: The UTC date and time at which noon occurs. :rtype: :class:`~datetime.datetime` """ jc = self._jday_to_jcentury(self._julianday(date)) eqtime = self._eq_of_time(jc) timeUTC = (720.0 - (4 * longitude) - eqtime) / 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) # pylint: disable=E1120 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 ValueError as exc: if exc.args[0] == 'math domain error': raise AstralError(('Sun never reaches the horizon on this day, ' 'at this location.')) else: raise 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 ValueError as exc: if exc.args[0] == 'math domain error': raise AstralError(('Sun never reaches %d degrees below the horizon, ' 'at this location.') % (depression - 90)) else: raise def solar_midnight_utc(self, date, longitude): """Calculate solar midnight time in the UTC timezone. Note that this claculates the solar midgnight that is closest to 00:00:00 of the specified date i.e. it may return a time that is on the previous day. :param date: Date to calculate for. :type date: :class:`datetime.date` :param longitude: Longitude - Eastern longitudes should be positive :type longitude: float :return: The UTC date and time at which midnight 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 = (-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 < 0: hour += 24 date -= datetime.timedelta(days=1) midnight = datetime.datetime(date.year, date.month, date.day, hour, minute, second) midnight = pytz.UTC.localize(midnight) # pylint: disable=E1120 return midnight 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 ValueError as exc: if exc.args[0] == 'math domain error': raise AstralError(('Sun never reaches an elevation of %d degrees' 'at this location.') % elevation) else: raise 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 from vertical. :rtype: float If `dateandtime` is a naive Python datetime then it is assumed to be in the UTC timezone. """ return 90.0 - self.solar_elevation(dateandtime, latitude, longitude) def moon_phase(self, date, rtype=int): """Calculates the phase of the moon on the specified date. :param date: The date to calculate the phase for. :type date: :class:`datetime.date` :param rtype: The type to return either int (default) or float. :return: A number designating the phase. | 0 = New moon | 7 = First quarter | 14 = Full moon | 21 = Last quarter """ if rtype != float and rtype != int: rtype = int moon = self._moon_phase_asfloat(date) if moon >= 28.0: moon -= 28.0 moon = rtype(moon) 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() sunrise = self.sunrise_utc(date, latitude, longitude) sunset = self.sunset_utc(date, latitude, longitude) octant_duration = datetime.timedelta(seconds=(sunset - sunrise).seconds / 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, utcdatetime, timezone=None): if isinstance(utcdatetime, datetime.datetime): end_date = utcdatetime.date() hour = utcdatetime.hour minute = utcdatetime.minute second = utcdatetime.second else: end_date = utcdatetime hour = 0 minute = 0 second = 0 if timezone: if isinstance(timezone, int): hour_offset = timezone else: offset = timezone.localize(utcdatetime).utcoffset() hour_offset = offset.total_seconds() / 3600.0 else: hour_offset = 0 start_date = datetime.date(1900, 1, 1) time_fraction = (hour * 3600.0 + minute * 60.0 + second) / (24.0 * 3600.0) date_diff = excel_datediff(start_date, end_date) jd = date_diff + 2415018.5 + time_fraction - (hour_offset / 24) 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 _geom_mean_long_sun(self, juliancentury): l0 = 280.46646 + \ juliancentury * (36000.76983 + 0.0003032 * juliancentury) return l0 % 360.0 def _geom_mean_anomaly_sun(self, juliancentury): return 357.52911 + \ juliancentury * (35999.05029 - 0.0001537 * juliancentury) def _eccentrilocation_earth_orbit(self, juliancentury): return 0.016708634 - \ juliancentury * (0.000042037 + 0.0000001267 * juliancentury) 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_true_anomoly(self, juliancentury): m = self._geom_mean_anomaly_sun(juliancentury) c = self._sun_eq_of_center(juliancentury) return m + c 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_apparent_long(self, juliancentury): true_long = self._sun_true_long(juliancentury) omega = 125.04 - 1934.136 * juliancentury return true_long - 0.00569 - 0.00478 * sin(radians(omega)) 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 _sun_rt_ascension(self, juliancentury): oc = self._obliquity_correction(juliancentury) al = self._sun_apparent_long(juliancentury) tananum = (cos(radians(oc)) * sin(radians(al))) tanadenom = cos(radians(al)) return degrees(atan2(tananum, tanadenom)) 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 _var_y(self, juliancentury): epsilon = self._obliquity_correction(juliancentury) y = tan(radians(epsilon) / 2.0) return y * y def _eq_of_time(self, juliancentury): l0 = self._geom_mean_long_sun(juliancentury) e = self._eccentrilocation_earth_orbit(juliancentury) m = self._geom_mean_anomaly_sun(juliancentury) y = self._var_y(juliancentury) 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 _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): if not isinstance(latitude, Number) or not isinstance(longitude, Number): raise TypeError('Latitude and longitude must be a numbers') 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, depression) if direction == SUN_SETTING: hourangle = -hourangle delta = -longitude - degrees(hourangle) timeDiff = 4.0 * delta timeUTC = 720.0 + 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) # pylint: disable=E1120 return dt def _moon_phase_asfloat(self, date): 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 return moon astral-1.6.1/src/test/0000777000000000000000000000000013272332316012707 5ustar 00000000000000astral-1.6.1/src/test/conftest.py0000666000000000000000000000017013244005066015102 0ustar 00000000000000import os import sys sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) astral-1.6.1/src/test/test_Astral.py0000666000000000000000000002005113244005066015542 0ustar 00000000000000# -*- coding: utf-8 -*- import pytest import pytz import datetime import math from astral import Astral def float_almost_equal(value1, value2, diff=0.5): return abs(value1 - value2) <= diff def test_AstralBadLocationName(): with pytest.raises(KeyError): dd = Astral() _c = dd['wally'] def test_AstralLocationName(): dd = Astral() c = dd['London'] assert c.name == 'London' def test_AstralAssign(): with pytest.raises(TypeError): dd = Astral() dd['London'] = 'wally' def test_Astral(): location_name = 'Jubail' dd = Astral() dd.solar_depression = 'civil' location = dd[location_name] assert location.timezone == 'Asia/Riyadh' sun = location.sun() sunrise = location.sunrise(local=True) assert sunrise == sun['sunrise'] def test_Astral_SolarNoon(): dd = Astral() dt = datetime.datetime(2017, 2, 10) noon = dd.solar_noon_utc(dt, 49.65) assert noon.hour == 8 assert noon.minute == 55 assert noon.second == 37 def test_Astral_SolarElevation(): dd = Astral() dt = datetime.datetime(2015, 2, 3, 9, 0, 0, tzinfo=pytz.UTC) elevation = dd.solar_elevation(dt, 51.5, -0.12) assert float_almost_equal(elevation, 9.97, 0.1) def test_Astral_SolarAzimuth(): dd = Astral() dt = datetime.datetime(2015, 2, 3, 9, 0, 0, tzinfo=pytz.UTC) azimuth = dd.solar_azimuth(dt, 51.5, -0.12) assert float_almost_equal(azimuth, 133.162, 0.1) def test_Astral_SolarZenith(): dd = Astral() dt = datetime.datetime(2015, 2, 3, 9, 0, 0, tzinfo=pytz.UTC) zenith = dd.solar_zenith(dt, 51.5, -0.12) assert float_almost_equal(zenith, 90.0 - 9.97, 0.1) def test_Astral_SolarElevationWithTimezone(): dd = Astral() location = dd['Jubail'] dt = datetime.datetime(2015, 2, 4, 9, 0, 0, tzinfo=location.tz) elevation = dd.solar_elevation(dt, location.latitude, location.longitude) assert float_almost_equal(elevation, 28.118, 0.1) def test_Astral_SolarAzimuthWithTimezone(): dd = Astral() location = dd['Jubail'] dt = datetime.datetime(2015, 2, 4, 9, 0, 0, tzinfo=location.tz) azimuth = dd.solar_azimuth(dt, location.latitude, location.longitude) assert float_almost_equal(azimuth, 129.02, 0.1) def test_Astral_JulianDay_Date(): a = Astral() dt = datetime.date(2015, 1, 1) jd = a._julianday(dt) assert float_almost_equal(jd, 2457023.5, 0.1) dt = datetime.date(2015, 2, 9) jd = a._julianday(dt) assert float_almost_equal(jd, 2457062.5, 0.1) dt = datetime.date(2000, 8, 12) jd = a._julianday(dt) assert float_almost_equal(jd, 2451768.5, 0.1) dt = datetime.date(1632, 8, 12) jd = a._julianday(dt) assert float_almost_equal(jd, 2317359.5, 0.1) def test_Astral_JulianDay_DateTime(): a = Astral() dt = datetime.datetime(2015, 1, 1, 1, 36, 0) jd = a._julianday(dt) assert float_almost_equal(jd, 2457023.57, 0.1) dt = datetime.datetime(2015, 1, 1, 15, 12, 0) jd = a._julianday(dt) assert float_almost_equal(jd, 2457024.13, 0.1) def test_Astral_JulianDay_DateTimeZone(): a = Astral() dt = datetime.datetime(2015, 1, 1, 1, 36, 0) jd = a._julianday(dt, 1) assert float_almost_equal(jd, 2457023.51, 0.1) dt = datetime.datetime(2015, 10, 10, 1, 36, 0) jd = a._julianday(dt, 5) assert float_almost_equal(jd, 2457305.36, 0.1) def test_GeomMeanLongSun(): a = Astral() dt = datetime.datetime(2015, 10, 10, 1, 36, 0) jc = a._jday_to_jcentury(a._julianday(dt, 5)) geom = a._geom_mean_long_sun(jc) assert float_almost_equal(geom, 198.1484524, 0.1) dt = datetime.datetime(2015, 1, 1, 1, 36, 0) jc = a._jday_to_jcentury(a._julianday(dt, -4)) geom = a._geom_mean_long_sun(jc) assert float_almost_equal(geom, 280.5655139, 0.1) def test_GeomMeanAnomalySun(): a = Astral() dt = datetime.date(2015, 10, 10) jc = a._jday_to_jcentury(a._julianday(dt, -4)) geom = a._geom_mean_anomaly_sun(jc) assert float_almost_equal(geom, 6035.243796, 0.1) def test_EccentrilocationEarthOrbit(): a = Astral() dt = datetime.date(2015, 10, 10) jc = a._jday_to_jcentury(a._julianday(dt, -4)) ecc = a._eccentrilocation_earth_orbit(jc) assert float_almost_equal(ecc, 0.016702001, 0.1) def test_SunEqOfCenter(): a = Astral() dt = datetime.date(2015, 10, 10) jc = a._jday_to_jcentury(a._julianday(dt, -4)) eoc = a._sun_eq_of_center(jc) assert float_almost_equal(eoc, -1.909033734, 0.1) def test_SunTrueLong(): a = Astral() dt = datetime.date(2015, 10, 10) jc = a._jday_to_jcentury(a._julianday(dt, -4)) true_long = a._sun_true_long(jc) assert float_almost_equal(true_long, 196.6090364, 0.1) def test_SunTrueAnomoly(): a = Astral() dt = datetime.date(2015, 10, 10) jc = a._jday_to_jcentury(a._julianday(dt, -4)) true_long = a._sun_true_anomoly(jc) assert float_almost_equal(true_long, 6033.400469, 0.1) def test_SunRadVector(): a = Astral() dt = datetime.date(2015, 10, 10) jc = a._jday_to_jcentury(a._julianday(dt, -4)) rad_vector = a._sun_rad_vector(jc) assert float_almost_equal(rad_vector, 0.998732645, 0.1) def test_SunApparentLong(): a = Astral() dt = datetime.date(2015, 10, 10) jc = a._jday_to_jcentury(a._julianday(dt, -4)) apparent_long = a._sun_apparent_long(jc) assert float_almost_equal(apparent_long, 196.6033454, 0.1) def test_MeanObliquityOfEcliptic(): a = Astral() dt = datetime.date(2015, 10, 10) jc = a._jday_to_jcentury(a._julianday(dt, -4)) mean_obliquity = a._mean_obliquity_of_ecliptic(jc) assert float_almost_equal(mean_obliquity, 23.43724009, 0.1) def test_ObliquityCorrection(): a = Astral() dt = datetime.datetime(2015, 10, 10, 1, 36, 0) jc = a._jday_to_jcentury(a._julianday(dt, -4)) obliquity_correction = a._obliquity_correction(jc) assert float_almost_equal(obliquity_correction, 23.43468009, 0.0001) def test_SunRightAscension(): a = Astral() dt = datetime.date(2015, 10, 10) jc = a._jday_to_jcentury(a._julianday(dt, -4)) rt_ascension = a._sun_rt_ascension(jc) assert float_almost_equal(rt_ascension, -164.7605748, 0.001) def test_SunDeclination(): a = Astral() dt = datetime.datetime(2015, 10, 10, 1, 36, 0) jc = a._jday_to_jcentury(a._julianday(dt, -4)) declination = a._sun_declination(jc) assert float_almost_equal(declination, -6.525273018, 0.0001) def test_VarY(): a = Astral() dt = datetime.datetime(2015, 10, 10, 0, 54, 0) jc = a._jday_to_jcentury(a._julianday(dt, -4)) y = a._var_y(jc) assert float_almost_equal(y, 0.043017118, 0.0001) def test_EquationOfTime(): a = Astral() dt = datetime.datetime(2015, 10, 10, 0, 54, 0) jc = a._jday_to_jcentury(a._julianday(dt, -4)) etime = a._eq_of_time(jc) assert float_almost_equal(etime, 12.84030157, 0.0001) dt = datetime.datetime(2017, 1, 1, 2, 0, 0) jc = a._jday_to_jcentury(a._julianday(dt, 2)) etime = a._eq_of_time(jc) assert float_almost_equal(etime, -3.438084536, 0.0001) def test_SunriseHourAngle(): a = Astral() dt = datetime.date(2015, 1, 1) jc = a._jday_to_jcentury(a._julianday(dt, 1)) declination = a._sun_declination(jc) ha = math.degrees(a._hour_angle(52.169, declination, 90.833)) assert float_almost_equal(ha, 58.6102679, 0.1) @pytest.mark.parametrize("test", ["dawn_utc", "sunrise_utc", "sunset_utc", "dusk_utc"]) def test_UTC_BadDate(test): a = Astral() func = getattr(a, test) with pytest.raises(AttributeError): l = func('s', 1, -0.5) @pytest.mark.parametrize("test", ["dawn_utc", "sunrise_utc", "sunset_utc", "dusk_utc"]) def test_UTC_BadLatitude(test): a = Astral() func = getattr(a, test) dt = datetime.date(2015, 1, 1) with pytest.raises(TypeError): l = func(dt, 1, 'a') @pytest.mark.parametrize("test", ["dawn_utc", "sunrise_utc", "sunset_utc", "dusk_utc"]) def test_UTC_BadLongitude(test): a = Astral() func = getattr(a, test) dt = datetime.date(2015, 1, 1) with pytest.raises(TypeError): l = func(dt, 'a', 1) astral-1.6.1/src/test/test_AstralGeocoder.py0000666000000000000000000000265113244005066017220 0ustar 00000000000000# -*- coding: utf-8 -*- from pytest import raises from astral import AstralGeocoder def test_Group(): db = AstralGeocoder() _e = db.europe def test_UnknownGroup(): with raises(AttributeError): db = AstralGeocoder() _e = db.wallyland def test_CityContainment(): db = AstralGeocoder() assert 'london' in db def test_GroupContainment(): db = AstralGeocoder() assert 'africa' in db def test_CityCountry(): city_name = 'Birmingham,England' db = AstralGeocoder() city = db[city_name] assert city.name == 'Birmingham' assert city.region == 'England' def test_MultiCountry(): db = AstralGeocoder() city = db['Abu Dhabi'] assert city.name == 'Abu Dhabi' def test_MultiCountryWithCountry(): """Test for fix made due to bug report from Klaus Alexander Seistrup""" db = AstralGeocoder() city = db['Abu Dhabi,United Arab Emirates'] assert city.name == 'Abu Dhabi' city = db['Abu Dhabi,UAE'] assert city.name == 'Abu Dhabi' def test_Adelaide(): """Test for fix made due to bug report from Klaus Alexander Seistrup""" db = AstralGeocoder() _city = db['Adelaide'] def test_CandianCities(): db = AstralGeocoder() city = db['Fredericton'] assert city.elevation == 8 def test_AllCities(): db = AstralGeocoder() locations = db.locations locations.sort() for city_name in locations: _city = db[city_name] astral-1.6.1/src/test/test_BuenosAries.py0000666000000000000000000000026013244005066016533 0ustar 00000000000000# -*- coding: utf-8 -*- from astral import Astral, Location def test_BuenosAries(): a = Astral() b = a['Buenos Aires'] assert b.timezone == 'America/Buenos_Aires' astral-1.6.1/src/test/test_DepressionNotReached.py0000666000000000000000000000060513244005066020367 0ustar 00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import pytest import astral import datetime def test_Dawn_NeverReachesDepression(): d = datetime.date(2016, 5, 29) with pytest.raises(astral.AstralError): l = astral.Location(("Ghent", "Belgium", "51°3'N", "3°44'W", "Europe/Brussels")) l.solar_depression = 18 l.dawn(date=d, local=True) astral-1.6.1/src/test/test_GoldenBlue.py0000666000000000000000000000540013244005066016335 0ustar 00000000000000# -*- coding: utf-8 -*- # Test data taken from http://www.timeanddate.com/sun/uk/london import pytz import datetime from astral import Astral, SUN_RISING, SUN_SETTING def datetime_almost_equal(datetime1, datetime2, seconds=60): dd = datetime1 - datetime2 sd = (dd.days * 24 * 60 * 60) + dd.seconds return abs(sd) <= seconds def test_Location_GoldenHour_Morning(): a = Astral() l = a['London'] test_data = { datetime.date(2016, 5, 18): (datetime.datetime(2016, 5, 18, 3, 38), datetime.datetime(2016, 5, 18, 4, 53)), datetime.date(2016, 1, 1): (datetime.datetime(2016, 1, 1, 7, 41), datetime.datetime(2016, 1, 1, 9, 7)), } for day, golden_hour in test_data.items(): start1 = pytz.UTC.localize(golden_hour[0]) end1 = pytz.UTC.localize(golden_hour[1]) start2, end2 = l.golden_hour(SUN_RISING, day) assert datetime_almost_equal(end1, end2, seconds=90) assert datetime_almost_equal(start1, start2, seconds=90) def test_Location_GoldenHour_Evening(): a = Astral() l = a['London'] test_data = { datetime.date(2016, 5, 18): (datetime.datetime(2016, 5, 18, 19, 0), datetime.datetime(2016, 5, 18, 20, 16)), } for day, golden_hour in test_data.items(): start1 = pytz.UTC.localize(golden_hour[0]) end1 = pytz.UTC.localize(golden_hour[1]) start2, end2 = l.golden_hour(SUN_SETTING, day) assert datetime_almost_equal(end1, end2, seconds=90) assert datetime_almost_equal(start1, start2, seconds=90) def test_Location_BlueHour_Morning(): a = Astral() l = a['London'] test_data = { datetime.date(2016, 5, 19): (datetime.datetime(2016, 5, 19, 3, 19), datetime.datetime(2016, 5, 19, 3, 36)), } for day, blue_hour in test_data.items(): start1 = pytz.UTC.localize(blue_hour[0]) end1 = pytz.UTC.localize(blue_hour[1]) start2, end2 = l.blue_hour(SUN_RISING, day) assert datetime_almost_equal(end1, end2, seconds=90) assert datetime_almost_equal(start1, start2, seconds=90) def test_Location_BlueHour_Evening(): a = Astral() l = a['London'] test_data = { datetime.date(2016, 5, 19): (datetime.datetime(2016, 5, 19, 20, 17), datetime.datetime(2016, 5, 19, 20, 34)), } for day, blue_hour in test_data.items(): start1 = pytz.UTC.localize(blue_hour[0]) end1 = pytz.UTC.localize(blue_hour[1]) start2, end2 = l.blue_hour(SUN_SETTING, day) assert datetime_almost_equal(end1, end2, seconds=90) assert datetime_almost_equal(start1, start2, seconds=90) astral-1.6.1/src/test/test_GoogleGeocoder.py0000666000000000000000000000136713245231705017213 0ustar 00000000000000import io import os.path import pytest from astral import GoogleGeocoder def read_contents(*names, **kwargs): return io.open( os.path.join(*names), encoding=kwargs.get("encoding", "utf8") ).read() try: api_key = read_contents(os.path.dirname(__file__), '.api_key').strip() except: api_key = '' @pytest.mark.webtest @pytest.mark.skipif(api_key != '', reason="api key file found") def test_GoogleLocator(): locator = GoogleGeocoder() l = locator['Eiffel Tower'] assert l is not None @pytest.mark.webtest @pytest.mark.skipif(api_key == '', reason="api key file not found") def test_GoogleLocator_WithAPIKey(): locator = GoogleGeocoder(api_key=api_key) l = locator['Eiffel Tower'] assert l is not None astral-1.6.1/src/test/test_Location.py0000666000000000000000000000710413244005066016070 0ustar 00000000000000# -*- coding: utf-8 -*- from pytest import raises from astral import Astral, AstralError, Location import datetime import pytz def datetime_almost_equal(datetime1, datetime2, seconds=60): dd = datetime1 - datetime2 sd = (dd.days * 24 * 60 * 60) + dd.seconds return abs(sd) <= seconds def test_Location_Name(): c = Location() assert c.name == 'Greenwich' c.name = 'London' assert c.name == 'London' c.name = 'Köln' assert c.name == 'Köln' def test_Location_Country(): c = Location() assert c.region == 'England' c.region = 'Australia' assert c.region == 'Australia' def test_Location_Elevation(): dd = Astral() c = dd['London'] assert c.elevation == 24 def test_Location_TimezoneName(): c = Location() assert c.timezone == 'Europe/London' c.name = 'Asia/Riyadh' assert c.name == 'Asia/Riyadh' def test_Location_TimezoneNameNoLocation(): c = Location() c._timezone_group = 'Europe' c._timezone_location = '' assert c.timezone == 'Europe' def test_Location_TimezoneNameBad(): c = Location() with raises(ValueError): c.timezone = 'bad/timezone' def test_Location_TimezoneLookup(): c = Location() assert c.tz == pytz.timezone('Europe/London') c.timezone='Europe/Stockholm' assert c.tz == pytz.timezone('Europe/Stockholm') def test_Location_TimezoneLookupBad(): c = Location() c._timezone_group = 'bad' c._timezone_location = 'timezone' with raises(AstralError): c.tz def test_Location_Sun(): c = Location() c.sun() def test_Location_Dawn(): c = Location() c.dawn() def test_Location_DawnUTC(): c = Location() c.dawn(local=False) def test_Location_Sunrise(): c = Location() c.sunrise() def test_Location_SunriseUTC(): c = Location() c.sunrise(local=False) def test_Location_SolarNoon(): c = Location() c.solar_noon() def test_Location_SolarNoonUTC(): c = Location() c.solar_noon(local=False) def test_Location_Dusk(): c = Location() c.dusk() def test_Location_DuskUTC(): c = Location() c.dusk(local=False) def test_Location_Sunset(): c = Location() c.sunset() def test_Location_SunsetUTC(): c = Location() c.sunset(local=False) def test_Location_SolarElevation(): dd = Astral() location = dd['Riyadh'] dt = datetime.datetime(2015, 12, 14, 8, 0, 0) dt = location.tz.localize(dt) elevation = location.solar_elevation(dt) assert abs(elevation - 17) < 0.5 def test_Location_SolarAzimuth(): dd = Astral() location = dd['Riyadh'] dt = datetime.datetime(2015, 12, 14, 8, 0, 0) dt = location.tz.localize(dt) azimuth = location.solar_azimuth(dt) assert abs(azimuth - 126) < 0.5 def test_Location_TimeAtElevation(): dd = Astral() location = dd['New Delhi'] test_data = { datetime.date(2016, 1, 5): datetime.datetime(2016, 1, 5, 10, 0), } for day, cdt in test_data.items(): cdt = location.tz.localize(cdt) dt = location.time_at_elevation(28, date=day) assert datetime_almost_equal(dt, cdt, seconds=600) def test_Location_SolarDepression(): c = Location(("Heidelberg", "Germany", 49.412, -8.71, "Europe/Berlin")) c.solar_depression = 'nautical' assert c.solar_depression == 12 c.solar_depression = 18 assert c.solar_depression == 18 def test_Location_Moon(): d = datetime.date(2017, 12, 1) c=Location() assert c.moon_phase(date=d) == 11 def test_Location_TzError(): with raises(AttributeError): c = Location() c.tz = 1 astral-1.6.1/src/test/test_Moon.py0000666000000000000000000000170013244005066015224 0ustar 00000000000000# -*- coding: utf-8 -*- import pytest from astral import Astral import datetime def test_Astral_Moon_PhaseNumber(): dates = { datetime.date(2015, 12, 1): 19, datetime.date(2015, 12, 2): 20, datetime.date(2015, 12, 3): 21, datetime.date(2014, 12, 1): 9, datetime.date(2014, 12, 2): 10, datetime.date(2014, 1, 1): 0, } a = Astral() for date_, moon in dates.items(): assert a.moon_phase(date_) == moon def test_Location_Moon_PhaseNumber(): a = Astral() d = datetime.date(2011, 1, 1) l = a['London'] assert l.moon_phase(d) == 25 def test_Location_Moon_PhaseNumberAsFloat(): a = Astral() d = datetime.date(2011, 1, 1) l = a['London'] assert l.moon_phase(d, float) == pytest.approx(25.3, abs=0.1) def test_Location_Moon_Phase_BadRType(): a = Astral() d = datetime.date(2011, 1, 1) l = a['London'] assert l.moon_phase(d, str) == 25 astral-1.6.1/src/test/test_Repr.py0000666000000000000000000000106713244005066015232 0ustar 00000000000000# -*- coding: utf-8 -*- from astral import Astral, Location def test_LocationReprDefault(): l = Location() assert l.__repr__() == 'Greenwich/England, tz=Europe/London, lat=51.17, lon=0.00' def test_LocationReprFull(): l = Location(('London', 'England', 51.68, -0.05, 'Europe/London', 3)) assert l.__repr__() == 'London/England, tz=Europe/London, lat=51.68, lon=-0.05' def test_LocationReprNoRegion(): l = Location(('London', None, 51.68, -0.05, 'Europe/London', 3)) assert l.__repr__() == 'London, tz=Europe/London, lat=51.68, lon=-0.05' astral-1.6.1/src/test/test_Sun.py0000666000000000000000000002355013244005066015070 0ustar 00000000000000# -*- coding: utf-8 -*- # Test data taken from http://www.timeanddate.com/sun/uk/london import pytest import pytz import datetime from astral import Astral, AstralError, SUN_RISING, SUN_SETTING def float_almost_equal(value1, value2, diff=0.5): return abs(value1 - value2) <= diff def datetime_almost_equal(datetime1, datetime2, seconds=60): dd = datetime1 - datetime2 sd = (dd.days * 24 * 60 * 60) + dd.seconds return abs(sd) <= seconds def test_Astral_Dawn_Civil(): a = Astral() l = a['London'] test_data = { datetime.date(2015, 12, 1): datetime.datetime(2015, 12, 1, 7, 4), datetime.date(2015, 12, 2): datetime.datetime(2015, 12, 2, 7, 6), datetime.date(2015, 12, 3): datetime.datetime(2015, 12, 3, 7, 7), datetime.date(2015, 12, 12): datetime.datetime(2015, 12, 12, 7, 17), datetime.date(2015, 12, 25): datetime.datetime(2015, 12, 25, 7, 25), } for day, dawn in test_data.items(): dawn = pytz.UTC.localize(dawn) dawn_utc = a.dawn_utc(day, l.latitude, l.longitude) assert datetime_almost_equal(dawn, dawn_utc) def test_Astral_Dawn_Nautical(): a = Astral() a.solar_depression = 'nautical' l = a['London'] test_data = { datetime.date(2015, 12, 1): datetime.datetime(2015, 12, 1, 6, 22), datetime.date(2015, 12, 2): datetime.datetime(2015, 12, 2, 6, 23), datetime.date(2015, 12, 3): datetime.datetime(2015, 12, 3, 6, 24), datetime.date(2015, 12, 12): datetime.datetime(2015, 12, 12, 6, 34), datetime.date(2015, 12, 25): datetime.datetime(2015, 12, 25, 6, 42), } for day, dawn in test_data.items(): dawn = pytz.UTC.localize(dawn) dawn_utc = a.dawn_utc(day, l.latitude, l.longitude) assert datetime_almost_equal(dawn, dawn_utc) def test_Astral_Sunrise(): a = Astral() l = a['London'] test_data = { datetime.date(2015, 1, 1): datetime.datetime(2015, 1, 1, 8, 6), datetime.date(2015, 12, 1): datetime.datetime(2015, 12, 1, 7, 43), datetime.date(2015, 12, 2): datetime.datetime(2015, 12, 2, 7, 45), datetime.date(2015, 12, 3): datetime.datetime(2015, 12, 3, 7, 46), datetime.date(2015, 12, 12): datetime.datetime(2015, 12, 12, 7, 57), datetime.date(2015, 12, 25): datetime.datetime(2015, 12, 25, 8, 5), } for day, sunrise in test_data.items(): sunrise = pytz.UTC.localize(sunrise) sunrise_utc = a.sunrise_utc(day, l.latitude, l.longitude) assert datetime_almost_equal(sunrise, sunrise_utc) def test_Astral_Sunset(): a = Astral() l = a['London'] test_data = { datetime.date(2015, 1, 1): datetime.datetime(2015, 1, 1, 16, 2), datetime.date(2015, 12, 1): datetime.datetime(2015, 12, 1, 15, 55), datetime.date(2015, 12, 2): datetime.datetime(2015, 12, 2, 15, 55), datetime.date(2015, 12, 3): datetime.datetime(2015, 12, 3, 15, 54), datetime.date(2015, 12, 12): datetime.datetime(2015, 12, 12, 15, 51), datetime.date(2015, 12, 25): datetime.datetime(2015, 12, 25, 15, 56), } for day, sunset in test_data.items(): sunset = pytz.UTC.localize(sunset) sunset_utc = a.sunset_utc(day, l.latitude, l.longitude) assert datetime_almost_equal(sunset, sunset_utc) def test_Astral_Dusk_Civil(): a = Astral() l = a['London'] test_data = { datetime.date(2015, 12, 1): datetime.datetime(2015, 12, 1, 16, 34), datetime.date(2015, 12, 2): datetime.datetime(2015, 12, 2, 16, 34), datetime.date(2015, 12, 3): datetime.datetime(2015, 12, 3, 16, 33), datetime.date(2015, 12, 12): datetime.datetime(2015, 12, 12, 16, 31), datetime.date(2015, 12, 25): datetime.datetime(2015, 12, 25, 16, 36), } for day, dusk in test_data.items(): dusk = pytz.UTC.localize(dusk) dusk_utc = a.dusk_utc(day, l.latitude, l.longitude) assert datetime_almost_equal(dusk, dusk_utc) def test_Astral_Dusk_Nautical(): a = Astral() a.solar_depression = 'nautical' l = a['London'] test_data = { datetime.date(2015, 12, 1): datetime.datetime(2015, 12, 1, 17, 16), datetime.date(2015, 12, 2): datetime.datetime(2015, 12, 2, 17, 16), datetime.date(2015, 12, 3): datetime.datetime(2015, 12, 3, 17, 16), datetime.date(2015, 12, 12): datetime.datetime(2015, 12, 12, 17, 14), datetime.date(2015, 12, 25): datetime.datetime(2015, 12, 25, 17, 19), } for day, dusk in test_data.items(): dusk = pytz.UTC.localize(dusk) dusk_utc = a.dusk_utc(day, l.latitude, l.longitude) assert datetime_almost_equal(dusk, dusk_utc) def test_Astral_SolarNoon(): a = Astral() l = a['London'] test_data = { datetime.date(2015, 12, 1): datetime.datetime(2015, 12, 1, 11, 49), datetime.date(2015, 12, 2): datetime.datetime(2015, 12, 2, 11, 50), datetime.date(2015, 12, 3): datetime.datetime(2015, 12, 3, 11, 50), datetime.date(2015, 12, 12): datetime.datetime(2015, 12, 12, 11, 54), datetime.date(2015, 12, 25): datetime.datetime(2015, 12, 25, 12, 00), } for day, solar_noon in test_data.items(): solar_noon = pytz.UTC.localize(solar_noon) solar_noon_utc = a.solar_noon_utc(day, l.longitude) assert datetime_almost_equal(solar_noon, solar_noon_utc) def test_Astral_SolarMidnight(): a = Astral() l = a['London'] test_data = { datetime.date(2016, 2, 18): datetime.datetime(2016, 2, 18, 0, 14), datetime.date(2016, 10, 26): datetime.datetime(2016, 10, 25, 23, 44), } for day, solar_midnight in test_data.items(): solar_midnight = pytz.UTC.localize(solar_midnight) solar_midnight_utc = a.solar_midnight_utc(day, l.longitude) assert datetime_almost_equal(solar_midnight, solar_midnight_utc) # Test data from http://www.astroloka.com/rahukaal.aspx?City=Delhi def test_Astral_Rahukaalam(): a = Astral() l = a['New Delhi'] test_data = { datetime.date(2015, 12, 1): (datetime.datetime(2015, 12, 1, 9, 17), datetime.datetime(2015, 12, 1, 10, 35)), datetime.date(2015, 12, 2): (datetime.datetime(2015, 12, 2, 6, 40), datetime.datetime(2015, 12, 2, 7, 58)), } for day, (start, end) in test_data.items(): start = pytz.UTC.localize(start) end = pytz.UTC.localize(end) info = a.rahukaalam_utc(day, l.latitude, l.longitude) start_utc = info[0] end_utc = info[1] assert datetime_almost_equal(start, start_utc) assert datetime_almost_equal(end, end_utc) def test_Astral_SolarElevation(): a = Astral() l = a['London'] test_data = { datetime.datetime(2015, 12, 14, 11, 0, 0): 14, datetime.datetime(2015, 12, 14, 20, 1, 0): -37, } for dt, angle1 in test_data.items(): angle2 = a.solar_elevation(dt, l.latitude, l.longitude) assert float_almost_equal(angle1, angle2) def test_Astral_SolarAzimuth(): a = Astral() l = a['London'] test_data = { datetime.datetime(2015, 12, 14, 11, 0, 0, tzinfo=pytz.UTC): 167, datetime.datetime(2015, 12, 14, 20, 1, 0): 279, } for dt, angle1 in test_data.items(): angle2 = a.solar_azimuth(dt, l.latitude, l.longitude) assert float_almost_equal(angle1, angle2) def test_Astral_TimeAtElevation_SunRising(): a = Astral() l = a['London'] d = datetime.date(2016, 1, 4) dt = a.time_at_elevation_utc(6, SUN_RISING, d, l.latitude, l.longitude) cdt = datetime.datetime(2016, 1, 4, 9, 5, 0, tzinfo=pytz.UTC) # Use error of 5 minutes as website has a rather coarse accuracy assert datetime_almost_equal(dt, cdt, 300) def test_Astral_TimeAtElevation_SunSetting(): a = Astral() l = a['London'] d = datetime.date(2016, 1, 4) dt = a.time_at_elevation_utc(14, SUN_SETTING, d, l.latitude, l.longitude) cdt = datetime.datetime(2016, 1, 4, 13, 20, 0, tzinfo=pytz.UTC) assert datetime_almost_equal(dt, cdt, 300) def test_Astral_TimeAtElevation_GreaterThan90(): a = Astral() l = a['London'] d = datetime.date(2016, 1, 4) dt = a.time_at_elevation_utc(166, SUN_RISING, d, l.latitude, l.longitude) cdt = datetime.datetime(2016, 1, 4, 13, 20, 0, tzinfo=pytz.UTC) assert datetime_almost_equal(dt, cdt, 300) def test_Astral_TimeAtElevation_GreaterThan180(): a = Astral() l = a['London'] d = datetime.date(2015, 12, 1) dt = a.time_at_elevation_utc(186, SUN_RISING, d, l.latitude, l.longitude) cdt = datetime.datetime(2015, 12, 1, 16, 34, tzinfo=pytz.UTC) assert datetime_almost_equal(dt, cdt, 300) def test_Astral_TimeAtElevation_SunRisingBelowHorizon(): a = Astral() l = a['London'] d = datetime.date(2016, 1, 4) dt = a.time_at_elevation_utc(-18, SUN_RISING, d, l.latitude, l.longitude) cdt = datetime.datetime(2016, 1, 4, 6, 0, 0, tzinfo=pytz.UTC) assert datetime_almost_equal(dt, cdt, 300) def test_Astral_TimeAtElevation_BadElevation(): a = Astral() l = a['London'] d = datetime.date(2016, 1, 4) with pytest.raises(AstralError): a.time_at_elevation_utc(20, SUN_RISING, d, l.latitude, l.longitude) def test_Astral_Daylight(): a = Astral() l = a['London'] d = datetime.date(2016, 1, 6) start, end = a.daylight_utc(d, l.latitude, l.longitude) cstart = datetime.datetime(2016, 1, 6, 8, 5, 0, tzinfo=pytz.UTC) cend = datetime.datetime(2016, 1, 6, 16, 7, 0, tzinfo=pytz.UTC) assert datetime_almost_equal(start, cstart, 300) assert datetime_almost_equal(end, cend, 300) def test_Astral_Nighttime(): a = Astral() l = a['London'] d = datetime.date(2016, 1, 6) start, end = a.night_utc(d, l.latitude, l.longitude) cstart = datetime.datetime(2016, 1, 6, 18, 10, 0, tzinfo=pytz.UTC) cend = datetime.datetime(2016, 1, 7, 6, 2, 0, tzinfo=pytz.UTC) assert datetime_almost_equal(start, cstart, 300) assert datetime_almost_equal(end, cend, 300) astral-1.6.1/src/test/test_UnicodeLiteral.py0000666000000000000000000000140013244005066017214 0ustar 00000000000000# -*- coding: utf-8 -*- # Import unicode_literals as that caused problems with the code # See bug https://bugs.launchpad.net/astral/+bug/1588198 from __future__ import unicode_literals import pytest import astral @pytest.mark.py2only def test_Location_WithUnicodeLiteral(): a = astral.Astral() _l = a['London'] @pytest.mark.py2only def test_Latitude_WithUnicodeLiteral(): l = astral.Location(('a place', 'a region', 1, 1)) l.latitude = "24°28'N" @pytest.mark.py2only def test_Longitude_WithUnicodeLiteral(): l = astral.Location(('a place', 'a region', 1, 1)) l.longitude = "54°22'E" @pytest.mark.py2only def test_Depression_WithUnicodeLiteral(): l = astral.Location(('a place', 'a region', 1, 1)) l.solar_depression = 'civil'