Giter Site home page Giter Site logo

suncal's Introduction

What suncal can do for you ๐ŸŒžโค๏ธ๐ŸŒœ

Would you like to know when the sun rises next week on Tue and have an entry in your calendar for this event without any manual effort and 100% for free? Then suncal is the right tool for you!

You can use this command line tool to either generate a standard ics file which you can import into any calendar application you like or you can have suncal create events in your Google calendar directly (via calls to the Google calendar API).

To create these events you need to specify the geographic location (longitude & latitude) and a range of dates. Suncal will determine the timezone of the location automatically.

Supported Events

1 Sunrise and Sunset

2 Golden Hour and Blue Hour

The Golden Hour is a period of time around sunrise/sunset, i.e. where the center of the sun is between -4 and 6 degrees above the horizon. The Blue Hour is a period before sunrise/after sunset where the natural light is infused by a lot of blue tones. We define it as the period in which the center of the sun is between 8 and 4 degrees below the horizon. The Golden/Blue Hour actually varies a lot in length depending on location. You can create the corresponding calendar events by using the event names 'golden_hour_morning', 'golden_hour_evening', 'blue_hour_morning' or 'blue_hour_evening'.

3 Moonrise and Moonset

4 The Moon Phases

phase symbol in calendar
New moon ๐ŸŒš
First Quarter ๐ŸŒ“
Full Moon ๐ŸŒ
Last Quarter ๐ŸŒ—

We are using standard symbols for those phases, although we are aware that the partially lit moon appears differently across the latitudes. Suncal creates an all-day event for each phase but the event description contains the precise time at which this phase can be observed.

Install and run Suncal

The application was built with poetry and depends on python 3.11.2, so make sure you have poetry and any minor version of python 3.11 installed on your system. The package will probably work with any python version 3.9 and above but we are only testing in 3.11. The poetry version we tested with is 1.3.2, and we know that older versions (1.1.3 and 1.1.4) do not correctly install the dev tool dependencies despite resolving them correctly.

Clone this repository, cd to the repository and run

poetry install

This will set up a virtual environment and install the command line application (including all dev and non-dev dependencies).

Use Google Calendar api to create calendar events

To allow insertion of events into your Google Calendar, the app has to be registered in Google Cloud and access to the api enabled. The Google Cloud console is also the place where you can create credentials (a file named "credentials.json"). When you run the application for the first time, the authentication flow will lead to the creation of access tokens (the credentials are required in this process!). All details of the process are outlined here.

Navigate into the project folder (top level) (credentials and access tokens must be stored in this directory as well), then run

poetry run suncal api --help

to see a description of all command line options. Example for a complete set of options:

poetry run suncal api --cal Sonne --from 2023-6-10 --to 2023-6-10 --event sunrise \
 --long 14.32 --lat 52

The command above will create only one event in a Google Calendar named "Sonne" - for the sunrise on 6.10.2023 in Berlin/Germany.

Note: you have to specify the name of your target calendar (--cal Sonne in the example above) but if a calendar with that name does not exist, it will be created for you automatically.

Another example for Redwood City in California:

poetry run suncal api --cal Sonnenaufgang --from 2023-1-01 --to 2023-12-31 --event sunrise \ 
--long -122.2281 --lat 37.4848

The command above creates sunrise events for the whole year 2023.

Export events to an ics file

If you want to export the sun calendar to an ics file, registration of this app in Google Cloud is unnecessary. You can provide a name for the ics file, but if you don't, the name will be created automatically. You can see a description of all command line options with:

poetry run suncal ics --help

Example:

poetry run suncal ics --from 2021-6-10 --to 2021-6-10 --event sunrise --long 14.32 --lat 52 \ 
--filename myIcsFile.ics

The file name is an optional argument: if you don't provide a name, it will be generated from the name of the event and the current timestamp.

Rules for collaborators

This repo uses type annotation. To add code, create a new branch and make sure to run all checks before setting up your PR: cd to the repo, then run:

./qa/pychecks.sh

If any of the checks fail, the PR will not be accepted. If you add a function/class, you also add a corresponding test.

Main Dependencies

skyfield

Our calculations of sun and moon events are based on skyfield.

google-api-python-client, google-auth-oauthlib, google

Tools for communication with the Google API and authentication flow.

timezonefinder

We use timezonefinder to determine the timezone from the GPS coordinates provided by the user.

suncal's People

Contributors

rotkehlxen avatar gbordyugov avatar twitzelbos avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar

Forkers

twitzelbos

suncal's Issues

Poetry install fails on Big Sur

Poetry install fails on installing numpy on Big Sur
` running install_scripts
Traceback (most recent call last):
File "/Users/thomaswitzel/Library/Caches/pypoetry/virtualenvs/suncal-1ZS7BLU5-py3.9/lib/python3.9/site-packages/pip/_vendor/pep517/in_process/_in_process.py", line 280, in
main()
File "/Users/thomaswitzel/Library/Caches/pypoetry/virtualenvs/suncal-1ZS7BLU5-py3.9/lib/python3.9/site-packages/pip/_vendor/pep517/in_process/_in_process.py", line 263, in main
json_out['return_val'] = hook(**hook_input['kwargs'])
File "/Users/thomaswitzel/Library/Caches/pypoetry/virtualenvs/suncal-1ZS7BLU5-py3.9/lib/python3.9/site-packages/pip/_vendor/pep517/in_process/_in_process.py", line 204, in build_wheel
return _build_backend().build_wheel(wheel_directory, config_settings,
File "/private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-build-env-v_0of4b8/overlay/lib/python3.9/site-packages/setuptools/build_meta.py", line 211, in build_wheel
return self._build_with_temp_dir(['bdist_wheel'], '.whl',
File "/private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-build-env-v_0of4b8/overlay/lib/python3.9/site-packages/setuptools/build_meta.py", line 197, in _build_with_temp_dir
self.run_setup()
File "/private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-build-env-v_0of4b8/overlay/lib/python3.9/site-packages/setuptools/build_meta.py", line 248, in run_setup
super(_BuildMetaLegacyBackend,
File "/private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-build-env-v_0of4b8/overlay/lib/python3.9/site-packages/setuptools/build_meta.py", line 142, in run_setup
exec(compile(code, file, 'exec'), locals())
File "setup.py", line 513, in
setup_package()
File "setup.py", line 505, in setup_package
setup(**metadata)
File "/private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-req-build-u319hen3/numpy/distutils/core.py", line 169, in setup
return old_setup(**new_attr)
File "/private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-build-env-v_0of4b8/overlay/lib/python3.9/site-packages/setuptools/init.py", line 165, in setup
return distutils.core.setup(**attrs)
File "/usr/local/Cellar/[email protected]/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/distutils/core.py", line 148, in setup
dist.run_commands()
File "/usr/local/Cellar/[email protected]/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/distutils/dist.py", line 966, in run_commands
self.run_command(cmd)
File "/usr/local/Cellar/[email protected]/3.9.4/Frameworks/Python.framework/Versions/3.9/lib/python3.9/distutils/dist.py", line 985, in run_command
cmd_obj.run()
File "/private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-build-env-v_0of4b8/overlay/lib/python3.9/site-packages/wheel/bdist_wheel.py", line 328, in run
impl_tag, abi_tag, plat_tag = self.get_tag()
File "/private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-build-env-v_0of4b8/overlay/lib/python3.9/site-packages/wheel/bdist_wheel.py", line 252, in get_tag
plat_name = get_platform(self.bdist_dir)
File "/private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-build-env-v_0of4b8/overlay/lib/python3.9/site-packages/wheel/bdist_wheel.py", line 48, in get_platform
result = calculate_macosx_platform_tag(archive_root, result)
File "/private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-build-env-v_0of4b8/overlay/lib/python3.9/site-packages/wheel/macosx_libfile.py", line 356, in calculate_macosx_platform_tag
assert len(base_version) == 2
AssertionError

########### EXT COMPILER OPTIMIZATION ###########
Platform      :
  Architecture: x64
  Compiler    : clang

CPU baseline  :
  Requested   : 'min'
  Enabled     : SSE SSE2 SSE3
  Flags       : -msse -msse2 -msse3
  Extra checks: none

CPU dispatch  :
  Requested   : 'max -xop -fma4'
  Enabled     : SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_KNL AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL
  Generated   :
              :
  SSE41       : SSE SSE2 SSE3 SSSE3
  Flags       : -msse -msse2 -msse3 -mssse3 -msse4.1
  Extra checks: none
  Detect      : SSE SSE2 SSE3 SSSE3 SSE41
              : numpy/core/src/umath/_umath_tests.dispatch.c
              :
  SSE42       : SSE SSE2 SSE3 SSSE3 SSE41 POPCNT
  Flags       : -msse -msse2 -msse3 -mssse3 -msse4.1 -mpopcnt -msse4.2
  Extra checks: none
  Detect      : SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42
              : build/src.macosx-11-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
              :
  AVX2        : SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C
  Flags       : -msse -msse2 -msse3 -mssse3 -msse4.1 -mpopcnt -msse4.2 -mavx -mf16c -mavx2
  Extra checks: none
  Detect      : AVX F16C AVX2
              : numpy/core/src/umath/_umath_tests.dispatch.c
              :
  (FMA3 AVX2) : SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C
  Flags       : -msse -msse2 -msse3 -mssse3 -msse4.1 -mpopcnt -msse4.2 -mavx -mf16c -mfma -mavx2
  Extra checks: none
  Detect      : AVX F16C FMA3 AVX2
              : build/src.macosx-11-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
              :
  AVX512F     : SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2
  Flags       : -msse -msse2 -msse3 -mssse3 -msse4.1 -mpopcnt -msse4.2 -mavx -mf16c -mfma -mavx2 -mavx512f
  Extra checks: AVX512F_REDUCE
  Detect      : AVX512F
              : build/src.macosx-11-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
              :
  AVX512_SKX  : SSE SSE2 SSE3 SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD
  Flags       : -msse -msse2 -msse3 -mssse3 -msse4.1 -mpopcnt -msse4.2 -mavx -mf16c -mfma -mavx2 -mavx512f -mavx512cd -mavx512vl -mavx512bw -mavx512dq
  Extra checks: AVX512BW_MASK AVX512DQ_MASK
  Detect      : AVX512_SKX
              : build/src.macosx-11-x86_64-3.9/numpy/core/src/_simd/_simd.dispatch.c
CCompilerOpt._cache_write[796] : write cache to path -> /private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-req-build-u319hen3/build/temp.macosx-11-x86_64-3.9/ccompiler_opt_cache_ext.py

########### CLIB COMPILER OPTIMIZATION ###########
Platform      :
  Architecture: x64
  Compiler    : clang

CPU baseline  :
  Requested   : 'min'
  Enabled     : SSE SSE2 SSE3
  Flags       : -msse -msse2 -msse3
  Extra checks: none

CPU dispatch  :
  Requested   : 'max -xop -fma4'
  Enabled     : SSSE3 SSE41 POPCNT SSE42 AVX F16C FMA3 AVX2 AVX512F AVX512CD AVX512_KNL AVX512_SKX AVX512_CLX AVX512_CNL AVX512_ICL
  Generated   : none
CCompilerOpt._cache_write[796] : write cache to path -> /private/var/folders/d5/hszbv9mj2rg7dxrqbb0dgp540000gn/T/pip-req-build-u319hen3/build/temp.macosx-11-x86_64-3.9/ccompiler_opt_cache_clib.py
----------------------------------------
ERROR: Failed building wheel for numpy

Failed to build numpy
ERROR: Could not build wheels for numpy which use PEP 517 and cannot be installed directly

at /usr/local/lib/python3.9/site-packages/poetry/utils/env.py:1074 in run
1070โ”‚ output = subprocess.check_output(
1071โ”‚ cmd, stderr=subprocess.STDOUT, **kwargs
1072โ”‚ )
1073โ”‚ except CalledProcessError as e:
โ†’ 1074โ”‚ raise EnvCommandError(e, input=input
)
1075โ”‚
1076โ”‚ return decode(output)
1077โ”‚
1078โ”‚ def execute(self, bin, *args, **kwargs):

`

Timezone information for ics file creation required or not?

Currently we do this: we create all timestamps for ics file export in UTC and let the tool that imports the ics file set the local timezone. This led me to conclude that we don't have to know the users timezone when creating the sunrise events etc. So we removed the timezone argument from the suncal ics routine completely (and just use UTC for all internal calculations)

I might be confused, but I think that procedure is not 100% right. If someone wants to know the sunrise for a particular day, say the 13th of March, they are thinking about a sunrise between 13.3.2023 0:00 and 13.3.2023 23:59 in their local time zone. But we are then searching for the sunrise between 13.3.2023 0:00 and 13.3.2023 23:59 in UTC. The difference between the two time intervals can be drastic and lead to wrong results, right?

@twitzelbos @gbordyugov Do you agree that we have to bring back the timezone argument to suncal ics?

suncal only adds events

Right now suncal only adds events. Meaning, if you go on a trip and you like to change the location for three days, it adds additional sunrises to the calendar (for example). I think it would be good to have a feature to have only one sunrise on the calendar per day, with the optional behavior to add to the existing events.

Add moon phases

Moon phases are nice to have.
๐ŸŒ‘ ๐ŸŒ’ ๐ŸŒ“ ๐ŸŒ” ๐ŸŒ• ๐ŸŒ– ๐ŸŒ— ๐ŸŒ˜ ๐ŸŒ‘
I think the full set clutters the calendar a bit too much, so for a start we could offer full moon, first quarter, last quarter and new moon. Also, the phases look slightly different on different places on earth, so we have to consider that as well.

Build a frontend to suncal

This tool is not of much use to the general public.
Its currently targeted at someone who can install python and poetry (successfully ๐Ÿ˜… ) on their system and make use of command line tools .

It would be really nice to have a web application that allows anyone to create an ics file with all events that they are interested in.
I'm thinking of using streamlit for a first iteration.

Wrapper for main function

Separate command line function from main function, i.e. wrap the command line function around a function that contains the main functionality. This way we can either use the command line interface OR import the main function in another project.

Support for missing credentials

Avoid ugly exceptions if someone tries to run suncal using the google api but has no credentials (because the app was not registered in google cloud and credentials were not created yet). Instead, print what the user should do to fix this.

Rename application

Currently our application is called "capi-quickstart.py", as this is the app that I orginally created to test requests at the google calendar api. However, our future command line function will be suncal as it is defined in module main.py. Meaning we will have to rename main.py to suncal.py and register this as our new app in google cloud (most likely it is not necessary to register a new project, but it will be sufficient to create new credentials)

From GPS coordinates to timezone

So the user does not have to enter the timezone at all.

Thus the timezone is an optional argument and automatically derived from the provided GPS coordinates.
The user can override this by providing their own timezone (i.e. its possible that someone wants to know the time of sunrise of a remote place in their own timezone).

Run suncal app from any path

Currently

poetry run suncal --help 

only works when being inside the project folder (top level only!) I thought that installation of the package in combination with the console entry point and click should fix this! But some ingredient seems to be missing ...

It would be great if we could call this tool from any path and without reference to poetry, like this:

suncal --help

Improve design of calendar event

signal-2023-03-10-101248_002

The calendar events could look better. The timestamp of rise/set time in the calendar event title is probably unnecessary/redundant, because the timestamp is already shown by default (or can that look different on different devices?)

HttpError 403 "Rate Limit Exceeded"

I tried to send 365 sequential requests (creating events for one year) and got the following error: HttpError 403
"Rate Limit Exceeded". I guess we have to pack our requests in batches.

Research how this can be done and implement it.

Invalid geographical location throws unhandled exception

Traceback (most recent call last):
File "/Users/thomaswitzel/Library/Caches/pypoetry/virtualenvs/suncal-1ZS7BLU5-py3.9/lib/python3.9/site-packages/astral/sun.py", line 771, in sunrise
return time_of_transit(
File "/Users/thomaswitzel/Library/Caches/pypoetry/virtualenvs/suncal-1ZS7BLU5-py3.9/lib/python3.9/site-packages/astral/sun.py", line 343, in time_of_transit
hourangle = hour_angle(
File "/Users/thomaswitzel/Library/Caches/pypoetry/virtualenvs/suncal-1ZS7BLU5-py3.9/lib/python3.9/site-packages/astral/sun.py", line 243, in hour_angle
HA = acos(h)
ValueError: math domain error

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "/Users/thomaswitzel/projects/suncal/suncal/capi-quickstart.py", line 67, in
suncal(
File "/Users/thomaswitzel/projects/suncal/suncal/main.py", line 90, in suncal
events: List[GoogleCalEvent] = create_calendar_events(
File "/Users/thomaswitzel/projects/suncal/suncal/main.py", line 45, in create_calendar_events
sun_parameters = Celestial(
File "/Users/thomaswitzel/projects/suncal/suncal/models/astro.py", line 32, in event
sunrise = self.location.sunrise(date=self.date)
File "/Users/thomaswitzel/Library/Caches/pypoetry/virtualenvs/suncal-1ZS7BLU5-py3.9/lib/python3.9/site-packages/astral/location.py", line 318, in sunrise
return astral.sun.sunrise(observer, date, self.tzinfo)
File "/Users/thomaswitzel/Library/Caches/pypoetry/virtualenvs/suncal-1ZS7BLU5-py3.9/lib/python3.9/site-packages/astral/sun.py", line 781, in sunrise
raise ValueError(msg) from exc
ValueError: Sun is always below the horizon on this day, at this location.

Feature request: Create all day event "polar night"/"midnight sun" when the sun never rises/sets

In astral 3.0 the calculation of sunset(sunrise) for days with "polar night"/"midnight sun" returns a ValueError with message "Sun is always below the horizon on this day, at this location"/"Sun is always above the horizon on this day, at this location." see for example here:
https://github.com/sffjunkie/astral/blob/bc6885b0685e6dc629f6d1131df26d8beea57e8d/src/astral/sun.py#L820
We could catch this particular exception and instead of creating no event (current behaviour) create an all day event with title "polar night"/"midnight sun".
Unfortunately, the golden hour calculation has no exception handling at all. If golden hour calculation fails, we can use the info from sunrise/sunset to conclude whether the polar night/midnight sun is the culprit.

API request exception handling

Handle exceptions of requests to the google calendar api (for example when the API is currently down).
We could do some retries with exponential backoff, but when these retries also fail, this error should be handled.

Create command line interface

Specs

Use click or typer.
Create command suncal and subcommand api and ics. For example:

suncal api --cal Sun --from 2021-05-01 --to 2021-06-01 --event sunrise --timezone 'America/Los_Angeles'  --long -122.23 --lat 37.48 

or for exporting an ics file instead:

suncal ics --filename 'my-sun-calendar.ics' --from 2021-05-01 --to 2021-06-01 --event sunrise  --long -122.23 --lat 37.48
option specs
--cal Name of target calendar. Create this calendar if it does not exist. Required for suncal api.
--filename Name of ics file. Optional (a filename is autogenerated if it is not provided). Used by suncal ics.
--from "yyyy-mm-dd" from this day ... (optional, default today)
--to "yyyy-mm-dd" to this day ... (optional, default today + 1 month)
--event e.g. "sunrise", for the first draft restrict to choices:
{"sunrise", "sunset", "golden-hour-morning", "golden-hour-evening"}, optional, default sunrise
--timezone e.g. "Europe/Berlin" IANA timezone string (default Berlin)
check that provided timezone is valid IANA string. Required only for suncal api
--long longitude e.g. -122.236355 for Redwood City CA (default Berlin), check value in [-180, 180]. Required.
--lat latitude e.g 37.485215 for Redwood City CA (default Berlin), check value in [-90, 90]. Required.

PPS: we might need to implement yet another different return type as soon as the suncal functionality will be part of a web app backend, e.g. a json that can be easily converted to an ics file on the client side (JS). But returning an ics file could be fine as well.

To see how to expose a function as a command line script in pyproject.toml, see the tutorial here.

Update

Basic implementation done. IANA timezone check implemented as well. We do not have command groups (suncal api and suncal ics) yet - instead we are currently using the additional option --return_val. Also, there are no default values set.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.