Skip to content

Commit

Permalink
Merge tag 'workalendar/16.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed Sep 2, 2022
2 parents ff737be + 2f2d084 commit 2fec2b3
Show file tree
Hide file tree
Showing 19 changed files with 734 additions and 192 deletions.
19 changes: 19 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
v7.4.0
------

Incorporate changes from workalendar v16.0.0 (2021-09-16)

Calendars

- New calendar: Added Philippines calendar by @micodls (#396)

Internal

- Remove `skyfield` dependency, added `[astronomy]` as extra dependency (#660).
- Replace `pyCalverter` with `convertdate` (#536).
- Remove unused `JalaliMixin`
- Replace `pkg_resources` with `importlib_metadata` to fetch the version number in `__init__.py` (#657)
- Added new badges (pypi, conda, license) and installation instructions (pip, conda) to readme file @sugatoray (#673).
- Added the "Workalendar maintainers" in the LICENSE file.
- Changed the maintainer email.

v7.3.0
------

Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
include README.md
include workalendar/equinoxes.json.gz
include workalendar/solar_terms.json.gz
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

[![](https://readthedocs.org/projects/calendra/badge/?version=latest)](https://calendra.readthedocs.io/en/latest/?badge=latest)

[![license](http://img.shields.io/pypi/l/workalendar.svg)](https://github.com/workalendar/workalendar/blob/master/LICENSE)
[![pypi](http://img.shields.io/pypi/v/workalendar.svg)](https://pypi.python.org/pypi/workalendar)
[![conda](https://img.shields.io/conda/v/conda-forge/workalendar?color=blue&logo=anaconda)](https://anaconda.org/conda-forge/workalendar)

## Overview

Calendra is a Python module that offers classes able to handle calendars, list legal / religious holidays and gives working-day-related computation functions.
Expand All @@ -28,6 +32,34 @@ What can Calendra do that Workalendar cannot?
- Consolidates observance logic in the core code rather than requiring
each calendar implementation to implement its own.

## Installation

**With pip**

```sh
pip install workalendar
```

**With conda**

```sh
conda install -c conda-forge workalendar
```

### Extra dependencies

**Note: NEW in v16.0.0**

If the calendar(s) you want to work with requires astronomical computations (such as Asian calendars needing equinoxes or solar terms), Workalendar will provide pre-computed values within the year range from 1991 to 2051.

However, if you want to use astronomical libraries to compute the calendar yourself, you'll need to install the `[astronomy]` extra dependency like this:

```sh
pip install workalendar[astronomy]
```

If you had previously installed the `skyfield` and `skyfield-data` packages, they'll be used to compute the calendars. If you want to benefit from the "astronomical cache", and eventually benefit from performance gains, you'll have to **uninstall** those packages first to fallback to pre-computed files.

## Status

The project is stable and in production use. Calendra follows the principles of [semver](https://semver.org) for released verisons.
Expand Down Expand Up @@ -151,6 +183,7 @@ from the command line.
- Japan
- JapanBank
- Malaysia
- Philippines
- Qatar
- Singapore
- South Korea
Expand Down Expand Up @@ -181,6 +214,7 @@ And more to come (I hope!)

Please take note that some calendars are not 100% accurate. The most common example is the Islamic calendar, where some computed holidays are not exactly on the same official day decided by religious authorities, and this may vary country by country. Whenever it's possible, try to adjust your results with the official data provided by the adequate authorities.

Some countries have some holidays based on ephemerids and equinoxes. Those are computed for the previous and next 30 years to prevent big computations and dependencies.

## Contributing

Expand Down
2 changes: 2 additions & 0 deletions calendra/asia/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .south_korea import SouthKorea
from .taiwan import Taiwan
from .israel import Israel
from .philippines import Philippines


__all__ = (
Expand All @@ -21,4 +22,5 @@
'SouthKorea',
'Taiwan',
'Israel',
'Philippines'
)
43 changes: 43 additions & 0 deletions calendra/asia/philippines.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from ..core import (
WesternMixin, IslamicMixin, ChineseNewYearCalendar,
SAT, SUN
)
from ..registry_tools import iso_register


@iso_register('PH')
class Philippines(WesternMixin, IslamicMixin, ChineseNewYearCalendar):
"Philippines"
# Civil holidays
include_labour_day = True
include_new_years_eve = True

# Christian holiday
include_holy_thursday = True
holy_thursday_label = "Maundy Thursday"
include_good_friday = True
include_easter_saturday = True
include_easter_sunday = True
easter_saturday_label = "Black Saturday"
include_all_saints = True
include_all_souls = True
include_immaculate_conception = True
include_christmas_eve = True

# Islamic holidays
include_eid_al_fitr = True
eid_al_fitr_label = "Eid'l Fitr"
include_eid_al_adha = True
day_of_sacrifice_label = "Eid'l Adha"

WEEKEND_DAYS = (SAT, SUN)

FIXED_HOLIDAYS = ChineseNewYearCalendar.FIXED_HOLIDAYS + (
(2, 25, "EDSA Revolution Anniversary"),
(4, 9, "Araw ng Kagitingan"),
(6, 12, "Independence Day"),
(8, 21, "Ninoy Aquino Day"),
(8, 30, "National Heroes' Day"),
(11, 30, "Bonifacio Day"),
(12, 30, "Rizal Day"),
)
151 changes: 10 additions & 141 deletions calendra/astronomy.py
Original file line number Diff line number Diff line change
@@ -1,142 +1,11 @@
"""
Astronomical functions
"""
from math import pi, radians, tau
try:
# As of Python 3.9, included in the stdlib
from zoneinfo import ZoneInfo
except ImportError: # pre-3.9
from backports.zoneinfo import ZoneInfo
from skyfield.api import Loader
from skyfield import almanac
from skyfield_data import get_skyfield_data_path
from datetime import date, timedelta


# Parameter for the newton method to converge towards the closest solution
# to the function. By default it'll be an approximation of a 10th of a second.
hour = 1 / 24
minute = hour / 60
second = minute / 60
newton_precision = second / 10


def calculate_equinoxes(year, timezone='UTC'):
""" calculate equinox with time zone """
tz = ZoneInfo(timezone)

load = Loader(get_skyfield_data_path())
ts = load.timescale()
planets = load('de421.bsp')

t0 = ts.utc(year, 1, 1)
t1 = ts.utc(year, 12, 31)
datetimes, _ = almanac.find_discrete(t0, t1, almanac.seasons(planets))
vernal_equinox = datetimes[0].astimezone(tz).date()
autumn_equinox = datetimes[2].astimezone(tz).date()
return vernal_equinox, autumn_equinox


def get_current_longitude(current_date, earth, sun):
"""
Return the ecliptic longitude, in radians.
"""
astrometric = earth.at(current_date).observe(sun)
latitude, longitude, _ = astrometric.ecliptic_latlon(epoch='date')
return longitude.radians


def newton(f, x0, x1, precision=newton_precision,
**func_kwargs):
"""Return an x-value at which the given function reaches zero.
Stops and declares victory once the x-value is within ``precision``
of the solution, which defaults to a half-second of clock time.
"""
f0, f1 = f(x0, **func_kwargs), f(x1, **func_kwargs)
while f1 and abs(x1 - x0) > precision and f1 != f0:
new_x1 = x1 + (x1 - x0) / (f0 / f1 - 1)
x0, x1 = x1, new_x1
f0, f1 = f1, f(x1, **func_kwargs)
return x1


def newton_angle_function(t, ts, target_angle, body1, body2):
"""
Compute the longitude of body2 relative to body1
In our case, it's Earth & Sun, but it could be used as any other
combination of solar system planets/bodies.
"""
# We've got a float which is the `tt`
sky_tt = ts.tt_jd(t)
longitude = get_current_longitude(sky_tt, body1, body2)
result = target_angle - longitude
if result > pi:
result = result - pi
if result < -pi:
result = result + pi
return result


def solar_term(year, degrees, timezone='UTC'):
"""
Returns the date of the solar term for the given longitude
and the given year.
Solar terms are used for Chinese and Taiwanese holidays
(e.g. Qingming Festival in Taiwan).
More information:
- https://en.wikipedia.org/wiki/Solar_term
- https://en.wikipedia.org/wiki/Qingming
This function is adapted from the following topic:
https://answers.launchpad.net/pyephem/+question/110832
"""
# Target angle as radians
target_angle = radians(degrees)

load = Loader(get_skyfield_data_path())
planets = load('de421.bsp')
earth = planets['earth']
sun = planets['sun']
ts = load.timescale()
tz = ZoneInfo(timezone)

jan_first = ts.utc(date(year, 1, 1))
current_longitude = get_current_longitude(jan_first, earth, sun)

# Find approximately the right time of year.
difference = (target_angle - current_longitude) % tau
# Here we have an approximation of the number of julian days to go
date_delta = 365.25 * difference / tau
# convert to "tt" and reconvert it back to a Time object
t0 = ts.tt_jd(jan_first.tt + date_delta)

# Using datetimes to compute the next step date
t0_plus_one_minute = t0.utc_datetime() + timedelta(minutes=1)
# Back to Skyfield Time objects
t0_plus_one_minute = ts.utc(t0_plus_one_minute)

# Julian day for the starting date
t0 = t0.tt
# Adding one minute to have a second boundary
t0_plus_one_minute = t0_plus_one_minute.tt
# Newton method to converge towards the target angle
t = newton(
newton_angle_function, t0, t0_plus_one_minute,
precision=newton_precision,
# Passed as kwargs to the angle function
ts=ts,
target_angle=target_angle,
body1=earth,
body2=sun,
)
# Here we have a float to convert to julian days.
t = ts.tt_jd(t)
# To convert to datetime
t = t.utc_datetime()
# Convert in the timezone
result = t.astimezone(tz)
return result.date()
import skyfield # noqa: F401
import skyfield_data # noqa: F401
from .skyfield_astronomy import calculate_equinoxes, solar_term
except ImportError:
from .precomputed_astronomy import calculate_equinoxes, solar_term

__all__ = [
'calculate_equinoxes',
'solar_term',
]

0 comments on commit 2fec2b3

Please sign in to comment.