fa45d8aa5f
- health_checklist.json: 192.168.1.122→node122
- ocr_client.py: docstring IP→node122
- docs/market-data-requirements.md: IP→node122
- 所有API调用通过ProxyHandler({})绕过系统代理
Privoxy对node122:18003返回500,直连正常
1338 lines
39 KiB
Python
1338 lines
39 KiB
Python
"""The hebrewcal module contains Hebrew calendar related classes and functions.
|
|
|
|
It contains classes for representing a Hebrew year and month, functions
|
|
for getting the holiday or fast day for a given date, and classes adapting
|
|
:py:mod:`calendar` classes to render Hebrew calendars.
|
|
|
|
Contents
|
|
--------
|
|
* :class:`Year`
|
|
* :class:`Month`
|
|
* :func:`to_hebrew_numeral`
|
|
* :class:`HebrewCalendar`
|
|
* :class:`HebrewHTMLCalendar`
|
|
* :class:`HebrewTextCalendar`
|
|
* :func:`fast_day`
|
|
* :func:`festival`
|
|
* :func:`holiday`
|
|
"""
|
|
from numbers import Number
|
|
from itertools import repeat
|
|
import calendar
|
|
|
|
from pyluach.dates import HebrewDate
|
|
from pyluach import utils
|
|
from pyluach.gematria import _num_to_str
|
|
|
|
|
|
class IllegalMonthError(ValueError):
|
|
"""An exception for an illegal month.
|
|
|
|
Subclasses ``ValueError`` to show a message for an invalid month number
|
|
for the Hebrew calendar. Mimics :py:class:`calendar.IllegalMonthError`.
|
|
|
|
Parameters
|
|
----------
|
|
month : int
|
|
The invalid month number
|
|
"""
|
|
def __init__(self, month):
|
|
self.month = month
|
|
|
|
def __str__(self):
|
|
return (
|
|
f'bad month number {self.month}; must be 1-12 or 13 in a leap year'
|
|
)
|
|
|
|
|
|
class IllegalWeekdayError(ValueError):
|
|
"""An exception for an illegal weekday.
|
|
|
|
Subclasses ``ValueError`` to show a message for an invalid weekday
|
|
number. Mimics :py:class:`calendar.IllegalWeekdayError`.
|
|
|
|
Parameters
|
|
----------
|
|
month : int
|
|
The invalid month number
|
|
"""
|
|
def __init__(self, weekday):
|
|
self.weekday = weekday
|
|
|
|
def __str__(self):
|
|
return (
|
|
f'bad weekday number {self.weekday}; '
|
|
f'must be 1 (Sunday) to 7 (Saturday)'
|
|
)
|
|
|
|
|
|
class Year:
|
|
"""A Year object represents a Hebrew calendar year.
|
|
|
|
It provided the following operators:
|
|
|
|
===================== ================================================
|
|
Operation Result
|
|
===================== ================================================
|
|
year2 = year1 + int New ``Year`` ``int`` days after year1.
|
|
year2 = year1 - int New ``Year`` ``int`` days before year1.
|
|
int = year1 - year2 ``int`` equal to the absolute value of
|
|
the difference between year2 and year1.
|
|
bool = year1 == year2 True if year1 represents the same year as year2.
|
|
bool = year1 > year2 True if year1 is later than year2.
|
|
bool = year1 >= year2 True if year1 is later or equal to year2.
|
|
bool = year1 < year2 True if year 1 earlier than year2.
|
|
bool = year1 <= year2 True if year 1 earlier or equal to year 2.
|
|
===================== ================================================
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
A Hebrew year.
|
|
|
|
Attributes
|
|
----------
|
|
year : int
|
|
The hebrew year.
|
|
leap : bool
|
|
True if the year is a leap year else false.
|
|
"""
|
|
|
|
def __init__(self, year):
|
|
if year < 1:
|
|
raise ValueError(f'Year {year} is before creation.')
|
|
self.year = year
|
|
self.leap = utils._is_leap(year)
|
|
|
|
def __repr__(self):
|
|
return f'Year({self.year})'
|
|
|
|
def __len__(self):
|
|
return utils._days_in_year(self.year)
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, Year):
|
|
return self.year == other.year
|
|
return NotImplemented
|
|
|
|
def __add__(self, other):
|
|
"""Add int to year."""
|
|
try:
|
|
return Year(self.year + other)
|
|
except TypeError:
|
|
return NotImplemented
|
|
|
|
def __sub__(self, other):
|
|
"""Subtract int or Year from Year.
|
|
|
|
If other is an int return a new Year other before original year. If
|
|
other is a Year object, return delta of the two years as an int.
|
|
"""
|
|
if isinstance(other, Year):
|
|
return abs(self.year - other.year)
|
|
try:
|
|
return Year(self.year - other)
|
|
except TypeError:
|
|
return NotImplemented
|
|
|
|
def __gt__(self, other):
|
|
if isinstance(other, Year):
|
|
return self.year > other.year
|
|
return NotImplemented
|
|
|
|
def __ge__(self, other):
|
|
if isinstance(other, Year):
|
|
return self > other or self == other
|
|
return NotImplemented
|
|
|
|
def __lt__(self, other):
|
|
if isinstance(other, Year):
|
|
return self.year < other.year
|
|
return NotImplemented
|
|
|
|
def __le__(self, other):
|
|
if isinstance(other, Year):
|
|
return self < other or self == other
|
|
return NotImplemented
|
|
|
|
def __iter__(self):
|
|
"""Yield integer for each month in year."""
|
|
yield from utils._monthslist(self.year)
|
|
|
|
def monthscount(self):
|
|
"""Return number of months in the year.
|
|
|
|
Returns
|
|
-------
|
|
int
|
|
"""
|
|
if self.leap:
|
|
return 13
|
|
return 12
|
|
|
|
def itermonths(self):
|
|
"""Yield Month instance for each month of the year.
|
|
|
|
Yields
|
|
------
|
|
:obj:`Month`
|
|
The next month in the Hebrew calendar year as a
|
|
``Month`` instance beginning with Tishrei through Elul.
|
|
"""
|
|
for month in self:
|
|
yield Month(self.year, month)
|
|
|
|
def iterdays(self):
|
|
"""Yield integer for each day of the year.
|
|
|
|
Yields
|
|
------
|
|
:obj:`int`
|
|
An integer beginning with 1 for the the next day of
|
|
the year.
|
|
"""
|
|
for day in range(1, len(self) + 1):
|
|
yield day
|
|
|
|
def iterdates(self):
|
|
"""Iterate through each Hebrew date of the year.
|
|
|
|
Yields
|
|
------
|
|
:obj:`pyluach.dates.HebrewDate`
|
|
The next date of the Hebrew calendar year starting with
|
|
the first of Tishrei.
|
|
"""
|
|
for month in self.itermonths():
|
|
for day in month:
|
|
yield HebrewDate(self.year, month.month, day)
|
|
|
|
@classmethod
|
|
def from_date(cls, date):
|
|
"""Return Year object that given date occurs in.
|
|
|
|
Parameters
|
|
----------
|
|
date : ~pyluach.dates.BaseDate
|
|
Any subclass of ``BaseDate``.
|
|
|
|
Returns
|
|
-------
|
|
Year
|
|
"""
|
|
return cls(date.to_heb().year)
|
|
|
|
@classmethod
|
|
def from_pydate(cls, pydate):
|
|
"""Return Year object from python date object.
|
|
|
|
Parameters
|
|
----------
|
|
pydate : datetime.date
|
|
A python standard library date object
|
|
|
|
Returns
|
|
-------
|
|
Year
|
|
The Hebrew year the given date occurs in
|
|
"""
|
|
return cls.from_date(HebrewDate.from_pydate(pydate))
|
|
|
|
def year_string(self, thousands=False):
|
|
"""Return year as a Hebrew string.
|
|
|
|
Parameters
|
|
----------
|
|
thousands: bool, optional
|
|
``True`` to prefix the year with the thousands place.
|
|
Default is ``False``.
|
|
|
|
Examples
|
|
--------
|
|
>>> year = Year(5781)
|
|
>>> year.year_string()
|
|
תשפ״א
|
|
>>> year.year_string(True)
|
|
ה׳תשפ״א
|
|
"""
|
|
return _num_to_str(self.year, thousands)
|
|
|
|
|
|
class Month:
|
|
"""A Month object represents a month of the Hebrew calendar.
|
|
|
|
It provides the same operators as a `Year` object.
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
month : int
|
|
The month as an integer starting with 7 for Tishrei through 13
|
|
if necessary for Adar Sheni and then 1-6 for Nissan - Elul.
|
|
|
|
Attributes
|
|
----------
|
|
year : int
|
|
The Hebrew year.
|
|
month : int
|
|
The month as an integer starting with 7 for Tishrei through 13
|
|
if necessary for Adar Sheni and then 1-6 for Nissan - Elul.
|
|
"""
|
|
|
|
def __init__(self, year, month):
|
|
if year < 1:
|
|
raise ValueError('Year must be >= 1.')
|
|
self.year = year
|
|
if month < 1 or month > 12 + utils._is_leap(self.year):
|
|
raise IllegalMonthError(month)
|
|
self.month = month
|
|
|
|
def __repr__(self):
|
|
return f'Month({self.year}, {self.month})'
|
|
|
|
def __len__(self):
|
|
return utils._month_length(self.year, self.month)
|
|
|
|
def __iter__(self):
|
|
for day in range(1, len(self) + 1):
|
|
yield day
|
|
|
|
def __eq__(self, other):
|
|
if isinstance(other, Month):
|
|
return (self.year == other.year and self.month == other.month)
|
|
return NotImplemented
|
|
|
|
def __add__(self, other):
|
|
try:
|
|
year, month = utils._add_months(self.year, self.month, other)
|
|
return Month(year, month)
|
|
except (AttributeError, TypeError):
|
|
return NotImplemented
|
|
|
|
def __sub__(self, other):
|
|
if isinstance(other, Number):
|
|
year, month = utils._subtract_months(self.year, self.month, other)
|
|
return Month(year, month)
|
|
try:
|
|
return abs(self._elapsed_months() - other._elapsed_months())
|
|
except AttributeError:
|
|
return NotImplemented
|
|
|
|
def __gt__(self, other):
|
|
if isinstance(other, Month):
|
|
return (
|
|
self.year > other.year
|
|
or (
|
|
self.year == other.year
|
|
and self._month_number() > other._month_number()
|
|
)
|
|
)
|
|
return NotImplemented
|
|
|
|
def __ge__(self, other):
|
|
if isinstance(other, Month):
|
|
return self > other or self == other
|
|
return NotImplemented
|
|
|
|
def __lt__(self, other):
|
|
if isinstance(other, Month):
|
|
return (
|
|
self.year < other.year
|
|
or (
|
|
self.year == other.year
|
|
and self._month_number() < other._month_number()
|
|
)
|
|
)
|
|
return NotImplemented
|
|
|
|
def __le__(self, other):
|
|
if isinstance(other, Month):
|
|
return self < other or self == other
|
|
return NotImplemented
|
|
|
|
@classmethod
|
|
def from_date(cls, date):
|
|
"""Return Month object that given date occurs in.
|
|
|
|
Parameters
|
|
----------
|
|
date : ~pyluach.dates.BaseDate
|
|
Any subclass of ``BaseDate``.
|
|
Returns
|
|
-------
|
|
Month
|
|
The Hebrew month the given date occurs in
|
|
"""
|
|
heb = date.to_heb()
|
|
return Month(heb.year, heb.month)
|
|
|
|
@classmethod
|
|
def from_pydate(cls, pydate):
|
|
"""Return Month object from python date object.
|
|
|
|
Parameters
|
|
----------
|
|
pydate : datetime.date
|
|
A python standard library date object
|
|
|
|
Returns
|
|
-------
|
|
Month
|
|
The Hebrew month the given date occurs in
|
|
"""
|
|
return cls.from_date(HebrewDate.from_pydate(pydate))
|
|
|
|
def _month_number(self):
|
|
"""Return month number 1-12 or 13, Tishrei - Elul."""
|
|
return list(Year(self.year)).index(self.month) + 1
|
|
|
|
def month_name(self, hebrew=False):
|
|
"""Return the name of the month.
|
|
|
|
Replaces `name` attribute.
|
|
|
|
Parameters
|
|
----------
|
|
hebrew : bool, optional
|
|
`True` if the month name should be written with Hebrew letters
|
|
and False to be transliterated into English using the Ashkenazic
|
|
pronunciation. Default is `False`.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
"""
|
|
return utils._month_name(self.year, self.month, hebrew)
|
|
|
|
def month_string(self, thousands=False):
|
|
"""Return month and year in Hebrew.
|
|
|
|
Parameters
|
|
----------
|
|
thousands : bool, optional
|
|
``True`` to prefix year with thousands place.
|
|
Default is ``False``.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The month and year in Hebrew in the form ``f'{month} {year}'``.
|
|
"""
|
|
return f'{self.month_name(True)} {_num_to_str(self.year, thousands)}'
|
|
|
|
def starting_weekday(self):
|
|
"""Return first weekday of the month.
|
|
|
|
Returns
|
|
-------
|
|
int
|
|
The weekday of the first day of the month starting with Sunday as 1
|
|
through Saturday as 7.
|
|
"""
|
|
return HebrewDate(self.year, self.month, 1).weekday()
|
|
|
|
def _elapsed_months(self):
|
|
"""Return number of months elapsed from beginning of calendar"""
|
|
yearmonths = tuple(Year(self.year))
|
|
months_elapsed = (
|
|
utils._elapsed_months(self.year)
|
|
+ yearmonths.index(self.month)
|
|
)
|
|
return months_elapsed
|
|
|
|
def iterdates(self):
|
|
"""Iterate through the Hebrew dates of the month.
|
|
|
|
Yields
|
|
------
|
|
:obj:`pyluach.dates.HebrewDate`
|
|
The next Hebrew date of the month.
|
|
"""
|
|
for day in self:
|
|
yield HebrewDate(self.year, self.month, day)
|
|
|
|
def molad(self):
|
|
"""Return the month's molad.
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
A dictionary in the form
|
|
``{weekday: int, hours: int, parts: int}``
|
|
|
|
Note
|
|
-----
|
|
This method does not return the molad in the form that is
|
|
traditionally announced in the shul. This is the molad in the
|
|
form used to calculate the length of the year.
|
|
|
|
See Also
|
|
--------
|
|
molad_announcement: The molad as it is traditionally announced.
|
|
"""
|
|
months = self._elapsed_months()
|
|
parts = 204 + months*793
|
|
hours = 5 + months*12 + parts//1080
|
|
days = 2 + months*29 + hours//24
|
|
weekday = days % 7 or 7
|
|
return {'weekday': weekday, 'hours': hours % 24, 'parts': parts % 1080}
|
|
|
|
def molad_announcement(self):
|
|
"""Return the month's molad in the announcement form.
|
|
|
|
Returns a dictionary in the form that the molad is traditionally
|
|
announced. The weekday is adjusted to change at midnight and
|
|
the hour of the day and minutes are given as traditionally announced.
|
|
Note that the hour is given as in a twenty four hour clock ie. 0 for
|
|
12:00 AM through 23 for 11:00 PM.
|
|
|
|
Returns
|
|
-------
|
|
dict
|
|
A dictionary in the form::
|
|
|
|
{
|
|
weekday: int,
|
|
hour: int,
|
|
minutes: int,
|
|
parts: int
|
|
}
|
|
"""
|
|
molad = self.molad()
|
|
weekday = molad['weekday']
|
|
hour = 18 + molad['hours']
|
|
if hour < 24:
|
|
if weekday != 1:
|
|
weekday -= 1
|
|
else:
|
|
weekday = 7
|
|
else:
|
|
hour -= 24
|
|
minutes = molad['parts'] // 18
|
|
parts = molad['parts'] % 18
|
|
return {
|
|
'weekday': weekday, 'hour': hour,
|
|
'minutes': minutes, 'parts': parts
|
|
}
|
|
|
|
|
|
def _to_pyweekday(weekday):
|
|
if weekday == 1:
|
|
return 6
|
|
return weekday - 2
|
|
|
|
|
|
def _year_and_month(month):
|
|
return month.year, month.month
|
|
|
|
|
|
def to_hebrew_numeral(num, thousands=False, withgershayim=True):
|
|
"""Convert int to Hebrew numeral.
|
|
|
|
Function useful in formatting Hebrew calendars.
|
|
|
|
Parameters
|
|
----------
|
|
num : int
|
|
The number to convert
|
|
thousands : bool, optional
|
|
True if the hebrew returned should include a letter for the
|
|
thousands place ie. 'ה׳' for five thousand. Default is ``False``.
|
|
withgershayim : bool, optional
|
|
``True`` to include a geresh after a single letter and double
|
|
geresh before the last letter if there is more than one letter.
|
|
Default is ``True``.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
The Hebrew numeral representation of the number.
|
|
"""
|
|
return _num_to_str(num, thousands, withgershayim)
|
|
|
|
|
|
class HebrewCalendar(calendar.Calendar):
|
|
"""Calendar base class.
|
|
|
|
This class extends the python library
|
|
:py:class:`Calendar <calendar.Calendar>` class for the Hebrew calendar. The
|
|
weekdays are 1 for Sunday through 7 for Shabbos.
|
|
|
|
Parameters
|
|
----------
|
|
firstweekday : int, optional
|
|
The weekday to start each week with. Default is ``1`` for Sunday.
|
|
hebrewnumerals : bool, optional
|
|
Default is ``True``, which shows the days of the month with Hebrew
|
|
numerals. ``False`` shows the days of the month as a decimal number.
|
|
hebrewweekdays : bool, optional
|
|
``True`` to show the weekday in Hebrew. Default is ``False``,
|
|
which shows the weekday in English.
|
|
hebrewmonths : bool, optional
|
|
``True`` to show the month name in Hebrew. Default is ``False``,
|
|
which shows the month name transliterated into English.
|
|
hebrewyear : bool, optional
|
|
``True`` to show the year in Hebrew numerals. Default is ``False``,
|
|
which shows the year as a decimal number.
|
|
|
|
Attributes
|
|
----------
|
|
hebrewnumerals : bool
|
|
hebrewweekdays : bool
|
|
hebrewmonths : bool
|
|
hebrewyear : bool
|
|
|
|
Note
|
|
----
|
|
All of the parameters other than `firstweekday` are not used in the
|
|
``HebrewCalendar`` base class. They're there for use in child
|
|
classes.
|
|
"""
|
|
|
|
def __init__(
|
|
self, firstweekday=1, hebrewnumerals=True, hebrewweekdays=False,
|
|
hebrewmonths=False, hebrewyear=False
|
|
):
|
|
if not 1 <= firstweekday <= 7:
|
|
raise IllegalWeekdayError(firstweekday)
|
|
self._firstweekday = firstweekday
|
|
self._firstpyweekday = _to_pyweekday(firstweekday)
|
|
self.hebrewnumerals = hebrewnumerals
|
|
self.hebrewweekdays = hebrewweekdays
|
|
self.hebrewmonths = hebrewmonths
|
|
self.hebrewyear = hebrewyear
|
|
|
|
@property
|
|
def firstweekday(self):
|
|
"""Get and set the weekday the weeks should start with.
|
|
|
|
Returns
|
|
-------
|
|
int
|
|
"""
|
|
return self._firstweekday
|
|
|
|
@firstweekday.setter
|
|
def firstweekday(self, thefirstweekday):
|
|
self._firstweekday = thefirstweekday
|
|
self._firstpyweekday = _to_pyweekday(thefirstweekday)
|
|
|
|
def iterweekdays(self):
|
|
"""Return one week of weekday numbers.
|
|
|
|
The numbers start with the configured first one.
|
|
|
|
Yields
|
|
------
|
|
:obj:`int`
|
|
The next weekday with 1-7 for Sunday - Shabbos.
|
|
The iterator starts with the ``HebrewCalendar`` object's
|
|
configured first weekday ie. if configured to start with
|
|
Monday it will first yield `2` and end with `1`.
|
|
"""
|
|
for i in range(self.firstweekday, self.firstweekday + 7):
|
|
yield i % 7 or 7
|
|
|
|
def itermonthdates(self, year, month):
|
|
"""Yield dates for one month.
|
|
|
|
The iterator will always iterate through complete weeks, so it
|
|
will yield dates outside the specified month.
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
month : int
|
|
The Hebrew month starting with 1 for Nissan through 13 for
|
|
Adar Sheni if necessary.
|
|
|
|
Yields
|
|
------
|
|
:obj:`pyluach.dates.HebrewDate`
|
|
The next Hebrew Date of the month starting with the first
|
|
date of the week the first of the month falls in, and ending
|
|
with the last date of the week that the last day of the month
|
|
falls in.
|
|
"""
|
|
for y, m, d in self.itermonthdays3(year, month):
|
|
yield HebrewDate(y, m, d)
|
|
|
|
def itermonthdays(self, year, month):
|
|
"""Like ``itermonthdates()`` but will yield day numbers.
|
|
For days outside the specified month the day number is 0.
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
month : int
|
|
|
|
Yields
|
|
------
|
|
:obj:`int`
|
|
The day of the month or 0 if the date is before or after the
|
|
month.
|
|
"""
|
|
currmonth = Month(year, month)
|
|
day1 = _to_pyweekday(currmonth.starting_weekday())
|
|
ndays = len(currmonth)
|
|
days_before = (day1 - self._firstpyweekday) % 7
|
|
yield from repeat(0, days_before)
|
|
yield from range(1, ndays + 1)
|
|
days_after = (self._firstpyweekday - day1 - ndays) % 7
|
|
yield from repeat(0, days_after)
|
|
|
|
def itermonthdays2(self, year, month):
|
|
"""Return iterator for the days and weekdays of the month.
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
month : int
|
|
|
|
Yields
|
|
------
|
|
:obj:`tuple` of :obj:`int`
|
|
A tuple of ints in the form ``(day of month, weekday)``.
|
|
"""
|
|
for i, d in enumerate(
|
|
self.itermonthdays(year, month), self.firstweekday
|
|
):
|
|
yield d, i % 7 or 7
|
|
|
|
def itermonthdays3(self, year, month):
|
|
"""Return iterator for the year, month, and day of the month.
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
month : int
|
|
|
|
Yields
|
|
------
|
|
:obj:`tuple` of :obj:`int`
|
|
A tuple of ints in the form ``(year, month, day)``.
|
|
"""
|
|
currmonth = Month(year, month)
|
|
day1 = _to_pyweekday(currmonth.starting_weekday())
|
|
ndays = len(currmonth)
|
|
days_before = (day1 - self._firstpyweekday) % 7
|
|
days_after = (self._firstpyweekday - day1 - ndays) % 7
|
|
try:
|
|
prevmonth = currmonth - 1
|
|
except ValueError:
|
|
prevmonth = currmonth
|
|
y, m = _year_and_month(prevmonth)
|
|
end = len(prevmonth) + 1
|
|
for d in range(end - days_before, end):
|
|
yield y, m, d
|
|
for d in range(1, ndays + 1):
|
|
yield year, month, d
|
|
y, m = _year_and_month(currmonth + 1)
|
|
for d in range(1, days_after + 1):
|
|
yield y, m, d
|
|
|
|
def itermonthdays4(self, year, month):
|
|
"""Return iterator for the year, month, day, and weekday
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
month : int
|
|
|
|
Yields
|
|
------
|
|
:obj:`tuple` of :obj:`int`
|
|
A tuple of ints in the form ``(year, month, day, weekday)``.
|
|
"""
|
|
for i, (y, m, d) in enumerate(self.itermonthdays3(year, month)):
|
|
yield y, m, d, (self.firstweekday + i) % 7 or 7
|
|
|
|
def yeardatescalendar(self, year, width=3):
|
|
"""Return data of specified year ready for formatting.
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
width : int, optional
|
|
The number of months per row. Default is 3.
|
|
|
|
Returns
|
|
------
|
|
:obj:`list` of list of list of list of :obj:`pyluach.dates.HebrewDate`
|
|
Returns a list of month rows. Each month row contains a list
|
|
of up to `width` months. Each month contains either 5 or 6
|
|
weeks, and each week contains 7 days. Days are ``HebrewDate``
|
|
objects.
|
|
"""
|
|
months = [
|
|
self.monthdatescalendar(year, m)
|
|
for m in Year(year)
|
|
]
|
|
return [months[i:i+width] for i in range(0, len(months), width)]
|
|
|
|
def yeardays2calendar(self, year, width=3):
|
|
"""Return the data of the specified year ready for formatting.
|
|
|
|
This method is similar to the ``yeardatescalendar`` except the
|
|
entries in the week lists are ``(day number, weekday number)``
|
|
tuples.
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
width : int, optional
|
|
The number of months per row. Default is 3.
|
|
|
|
Returns
|
|
-------
|
|
:obj:`list` of list of list of list of :obj:`tuple`
|
|
Returns a list of month rows. Each month row contains a list
|
|
of up to `width` months. Each month contains between 4 and 6
|
|
weeks, and each week contains 1-7 days. Days are tuples with
|
|
the form ``(day number, weekday number)``.
|
|
"""
|
|
months = [
|
|
self.monthdays2calendar(year, m)
|
|
for m in Year(year)
|
|
]
|
|
return [months[i:i+width] for i in range(0, len(months), width)]
|
|
|
|
def yeardayscalendar(self, year, width=3):
|
|
"""Return the data of the specified year ready for formatting.
|
|
|
|
This method is similar to the ``yeardatescalendar`` except the
|
|
entries in the week lists are day numbers.
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
width : int, optional
|
|
The number of months per row. Default is 3.
|
|
|
|
Returns
|
|
-------
|
|
:obj:`list` of list of list of list of :obj:`int`
|
|
Returns a list of month rows. Each month row contains a list
|
|
of up to `width` months. Each month contains either 5 or 6
|
|
weeks, and each week contains 1-7 days. Each day is the day of
|
|
the month as an int.
|
|
"""
|
|
months = [
|
|
self.monthdayscalendar(year, m)
|
|
for m in Year(year)
|
|
]
|
|
return [months[i:i+width] for i in range(0, len(months), width)]
|
|
|
|
def monthdatescalendar(self, year, month):
|
|
"""Return matrix (list of lists) of dates for month's calendar.
|
|
|
|
Each row represents a week; week entries are HebrewDate instances.
|
|
|
|
Parameters
|
|
----------
|
|
year : int
|
|
month : int
|
|
|
|
Returns
|
|
-------
|
|
:obj:`list` of list of :obj:`pyluach.dates.HebrewDate`
|
|
List of weeks in the month containing 7 ``HebrewDate``
|
|
instances each.
|
|
"""
|
|
return super().monthdatescalendar(year, month)
|
|
|
|
|
|
class HebrewHTMLCalendar(HebrewCalendar, calendar.HTMLCalendar):
|
|
"""Class to generate html calendars .
|
|
|
|
Adapts :py:class:`calendar.HTMLCalendar` for the Hebrew calendar.
|
|
|
|
Parameters
|
|
----------
|
|
firstweekday : int, optional
|
|
The weekday to start each week with. Default is ``1`` for Sunday.
|
|
hebrewnumerals : bool, optional
|
|
Default is ``True``, which shows the days of the month with Hebrew
|
|
numerals. ``False`` shows the days of the month as a decimal number.
|
|
hebrewweekdays : bool, optional
|
|
``True`` to show the weekday in Hebrew. Default is ``False``,
|
|
which shows the weekday in English.
|
|
hebrewmonths : bool, optional
|
|
``True`` to show the month name in Hebrew. Default is ``False``,
|
|
which shows the month name transliterated into English.
|
|
hebrewyear : bool, optional
|
|
``True`` to show the year in Hebrew numerals. Default is ``False``,
|
|
which shows the year as a decimal number.
|
|
rtl : bool, optional
|
|
``True`` to arrange the months and the days of the month from
|
|
right to left. Default is ``False``.
|
|
|
|
Attributes
|
|
----------
|
|
hebrewnumerals : bool
|
|
hebrewweekdays : bool
|
|
hebrewmonths : bool
|
|
hebrewyear : bool
|
|
rtl : bool
|
|
"""
|
|
|
|
def __init__(
|
|
self, firstweekday=1, hebrewnumerals=True, hebrewweekdays=False,
|
|
hebrewmonths=False, hebrewyear=False, rtl=False
|
|
):
|
|
self.rtl = rtl
|
|
super().__init__(
|
|
firstweekday,
|
|
hebrewnumerals,
|
|
hebrewweekdays,
|
|
hebrewmonths,
|
|
hebrewyear
|
|
)
|
|
|
|
def _rtl_str(self):
|
|
if self.rtl:
|
|
return ' dir="rtl"'
|
|
return ''
|
|
|
|
def formatday(self, day, weekday):
|
|
"""Return a day as an html table cell.
|
|
|
|
Parameters
|
|
----------
|
|
day : int
|
|
The day of the month or zero for a day outside the month.
|
|
weekday : int
|
|
The weekday with 1 as Sunday through 7 as Shabbos.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
"""
|
|
pyweekday = _to_pyweekday(weekday)
|
|
if day == 0:
|
|
return f'<td class="{self.cssclass_noday}"> </td>'
|
|
if self.hebrewnumerals:
|
|
day = to_hebrew_numeral(day, withgershayim=False)
|
|
return f'<td class="{self.cssclasses[pyweekday]}">{day}</td>'
|
|
|
|
def formatweekday(self, day):
|
|
"""Return a weekday name as an html table header.
|
|
|
|
Parameters
|
|
----------
|
|
day : int
|
|
The day of the week 1-7 with Sunday as 1 and Shabbos as 7.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
"""
|
|
pyday = _to_pyweekday(day)
|
|
if self.hebrewweekdays:
|
|
dayname = utils.WEEKDAYS[day][:3]
|
|
else:
|
|
dayname = calendar.day_abbr[pyday]
|
|
return (
|
|
f'<th class="{self.cssclasses_weekday_head[pyday]}">{dayname}</th>'
|
|
)
|
|
|
|
def formatyearnumber(self, theyear):
|
|
"""Return a formatted year.
|
|
|
|
Parameters
|
|
----------
|
|
theyear : int
|
|
|
|
Returns
|
|
-------
|
|
int or str
|
|
If ``self.hebrewyear`` is ``True`` return the year as a Hebrew
|
|
numeral, else return `theyear` as is.
|
|
"""
|
|
if self.hebrewyear:
|
|
return to_hebrew_numeral(theyear)
|
|
return theyear
|
|
|
|
def formatmonthname(self, theyear, themonth, withyear=True):
|
|
"""Return month name as an html table row.
|
|
|
|
Parameters
|
|
----------
|
|
theyear : int
|
|
themonth : int
|
|
The month as an int 1-12 Nissan - Adar and 13 if leap year.
|
|
withyear : bool, optional
|
|
``True`` to append the year to the month name. Default is
|
|
``True``.
|
|
|
|
Return
|
|
------
|
|
str
|
|
"""
|
|
s = Month(theyear, themonth).month_name(self.hebrewmonths)
|
|
if withyear:
|
|
s = f'{s} {self.formatyearnumber(theyear)}'
|
|
return (
|
|
f'<tr><th colspan="7" class="{self.cssclass_month_head}">'
|
|
f'{s}</th></tr>'
|
|
)
|
|
|
|
def formatmonth(self, theyear, themonth, withyear=True):
|
|
"""Return a formatted month as an html table.
|
|
|
|
Parameters
|
|
----------
|
|
theyear : int
|
|
themonth : int
|
|
withyear : bool, optional
|
|
``True`` to have the year appended to the month name. Default
|
|
is ``True``.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
"""
|
|
v = []
|
|
a = v.append
|
|
a(
|
|
'<table border="0" cellpadding="0" cellspacing="0"'
|
|
f'class="{self.cssclass_month}"{self._rtl_str()}>'
|
|
)
|
|
a('\n')
|
|
a(self.formatmonthname(theyear, themonth, withyear=withyear))
|
|
a('\n')
|
|
a(self.formatweekheader())
|
|
a('\n')
|
|
for week in self.monthdays2calendar(theyear, themonth):
|
|
a(self.formatweek(week))
|
|
a('\n')
|
|
a('</table>')
|
|
a('\n')
|
|
return ''.join(v)
|
|
|
|
def formatyear(self, theyear, width=3):
|
|
"""Return a formatted year as an html table.
|
|
|
|
Parameters
|
|
----------
|
|
theyear : int
|
|
width : int, optional
|
|
The number of months to display per row. Default is 3.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
"""
|
|
year = Year(theyear)
|
|
monthscount = year.monthscount()
|
|
yearmonths = list(year)
|
|
v = []
|
|
a = v.append
|
|
width = max(width, 1)
|
|
a(
|
|
'<table border="0" cellpadding="0" cellspacing="0"'
|
|
f'class="{self.cssclass_year}"{self._rtl_str()}>'
|
|
)
|
|
a('\n')
|
|
a(
|
|
f'<tr><th colspan="{width}" class="{self.cssclass_year_head}">'
|
|
f'{self.formatyearnumber(theyear)}</th></tr>'
|
|
)
|
|
for i in range(1, monthscount + 1, width):
|
|
# months in this row
|
|
months = range(i, min(i+width, monthscount + 1))
|
|
a('<tr>')
|
|
for m in months:
|
|
a('<td>')
|
|
a(self.formatmonth(
|
|
theyear, yearmonths[m-1], withyear=False
|
|
))
|
|
a('</td>')
|
|
a('</tr>')
|
|
a('</table>')
|
|
return ''.join(v)
|
|
|
|
|
|
class HebrewTextCalendar(HebrewCalendar, calendar.TextCalendar):
|
|
"""Subclass of HebrewCalendar that outputs a plaintext calendar.
|
|
|
|
``HebrewTextCalendar`` adapts :py:class:`calendar.TextCalendar` for the
|
|
Hebrew calendar.
|
|
|
|
Parameters
|
|
----------
|
|
firstweekday : int, optional
|
|
The weekday to start each week with. Default is ``1`` for Sunday.
|
|
hebrewnumerals : bool, optional
|
|
Default is ``True``, which shows the days of the month with Hebrew
|
|
numerals. ``False`` shows the days of the month as a decimal number.
|
|
hebrewweekdays : bool, optional
|
|
``True`` to show the weekday in Hebrew. Default is ``False``,
|
|
which shows the weekday in English.
|
|
hebrewmonths : bool, optional
|
|
``True`` to show the month name in Hebrew. Default is ``False``,
|
|
which shows the month name transliterated into English.
|
|
hebrewyear : bool, optional
|
|
``True`` to show the year in Hebrew numerals. Default is ``False``,
|
|
which shows the year as a decimal number.
|
|
|
|
Attributes
|
|
----------
|
|
hebrewnumerals : bool
|
|
hebrewweekdays : bool
|
|
hebrewmonths : bool
|
|
hebrewyear : bool
|
|
|
|
Note
|
|
----
|
|
This class generates plain text calendars. Any program that adds
|
|
any formatting may misrender the calendars especially when using any
|
|
Hebrew characters.
|
|
"""
|
|
|
|
def formatday(self, day, weekday, width):
|
|
"""Return a formatted day.
|
|
|
|
Extends calendar.TextCalendar formatday method.
|
|
|
|
Parameters
|
|
----------
|
|
day : int
|
|
The day of the month.
|
|
weekday : int
|
|
The weekday 1-7 Sunday-Shabbos.
|
|
width : int
|
|
The width of the day column.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
"""
|
|
if self.hebrewnumerals:
|
|
if day == 0:
|
|
s = ''
|
|
else:
|
|
s = f'{to_hebrew_numeral(day, withgershayim=False):>2}'
|
|
return s.center(width)
|
|
return super().formatday(day, weekday, width)
|
|
|
|
def formatweekday(self, day, width):
|
|
"""Return formatted weekday.
|
|
|
|
Extends calendar.TextCalendar formatweekday method.
|
|
|
|
Parameters
|
|
----------
|
|
day : int
|
|
The weekday 1-7 Sunday-Shabbos.
|
|
width : int
|
|
The width of the day column.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
"""
|
|
if self.hebrewweekdays:
|
|
if width < 5:
|
|
name = to_hebrew_numeral(day)
|
|
else:
|
|
name = utils.WEEKDAYS[day]
|
|
return name[:width].center(width)
|
|
return super().formatweekday(_to_pyweekday(day), width)
|
|
|
|
def formatmonthname(
|
|
self, theyear, themonth, width=0, withyear=True
|
|
):
|
|
"""Return formatted month name.
|
|
|
|
Parameters
|
|
----------
|
|
theyear : int
|
|
themonth : int
|
|
1-12 or 13 for Nissan-Adar Sheni
|
|
width : int, optional
|
|
The number of columns per day. Default is 0
|
|
withyear : bool, optional
|
|
Default is ``True`` to include the year with the month name.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
"""
|
|
s = Month(theyear, themonth).month_name(self.hebrewmonths)
|
|
if withyear:
|
|
if self.hebrewyear:
|
|
year = to_hebrew_numeral(theyear)
|
|
else:
|
|
year = theyear
|
|
s = f'{s} {year}'
|
|
return s.center(width)
|
|
|
|
def formatyear(self, theyear, w=2, l=1, c=6, m=3): # noqa: E741
|
|
"""Return a year's calendar as a multi-line string.
|
|
|
|
Parameters
|
|
----------
|
|
theyear : int
|
|
w : int, optional
|
|
The date column width. Default is 2
|
|
l : int, optional
|
|
The number of lines per week. Default is 1.
|
|
c : int, optional
|
|
The number of columns in between each month. Default is 6
|
|
m : int, optional
|
|
The number of months per row. Default is 3.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
"""
|
|
w = max(2, w)
|
|
l = max(1, l) # noqa: E741
|
|
c = max(2, c)
|
|
colwidth = (w + 1) * 7 - 1
|
|
v = []
|
|
a = v.append
|
|
a(repr(theyear).center(colwidth*m+c*(m-1)).rstrip())
|
|
a('\n'*l)
|
|
header = self.formatweekheader(w)
|
|
yearmonths = list(Year(theyear))
|
|
for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
|
|
# months in this row
|
|
months = range(m*i+1, min(m*(i+1)+1, len(yearmonths)+1))
|
|
a('\n'*l)
|
|
names = (
|
|
self.formatmonthname(theyear, yearmonths[k-1], colwidth, False)
|
|
for k in months
|
|
)
|
|
a(calendar.formatstring(names, colwidth, c).rstrip())
|
|
a('\n'*l)
|
|
headers = (header for k in months)
|
|
a(calendar.formatstring(headers, colwidth, c).rstrip())
|
|
a('\n'*l)
|
|
# max number of weeks for this row
|
|
height = max(len(cal) for cal in row)
|
|
for j in range(height):
|
|
weeks = []
|
|
for cal in row:
|
|
if j >= len(cal):
|
|
weeks.append('')
|
|
else:
|
|
weeks.append(self.formatweek(cal[j], w))
|
|
a(calendar.formatstring(weeks, colwidth, c).rstrip())
|
|
a('\n' * l)
|
|
return ''.join(v)
|
|
|
|
|
|
def fast_day(date, hebrew=False):
|
|
"""Return name of fast day or None.
|
|
|
|
Parameters
|
|
----------
|
|
date : ~pyluach.dates.BaseDate
|
|
Any date instance from a subclass of ``BaseDate`` can be used.
|
|
hebrew : bool, optional
|
|
``True`` if you want the fast_day name in Hebrew letters. Default
|
|
is ``False``, which returns the name transliterated into English.
|
|
|
|
Returns
|
|
-------
|
|
str or None
|
|
The name of the fast day or ``None`` if the given date is not
|
|
a fast day.
|
|
"""
|
|
return date.fast_day(hebrew)
|
|
|
|
|
|
def festival(
|
|
date,
|
|
israel=False,
|
|
hebrew=False,
|
|
include_working_days=True,
|
|
prefix_day=False
|
|
):
|
|
"""Return Jewish festival of given day.
|
|
|
|
This method will return all major and minor religous
|
|
Jewish holidays not including fast days.
|
|
|
|
Parameters
|
|
----------
|
|
date : ~pyluach.dates.BaseDate
|
|
Any subclass of ``BaseDate`` can be used.
|
|
|
|
israel : bool, optional
|
|
``True`` if you want the festivals according to the Israel
|
|
schedule. Defaults to ``False``.
|
|
hebrew : bool, optional
|
|
``True`` if you want the festival name in Hebrew letters. Default
|
|
is ``False``, which returns the name transliterated into English.
|
|
include_working_days : bool, optional
|
|
``True`` to include festival days on which melacha (work) is
|
|
allowed; ie. Pesach Sheni, Chol Hamoed, etc.
|
|
Default is ``True``.
|
|
prefix_day : bool, optional
|
|
``True`` to prefix multi day festivals with the day of the
|
|
festival. Default is ``False``.
|
|
|
|
Returns
|
|
-------
|
|
str or None
|
|
The name of the festival or ``None`` if the given date is not
|
|
a Jewish festival.
|
|
|
|
Examples
|
|
--------
|
|
>>> from pyluach.dates import HebrewDate
|
|
pesach = HebrewDate(2023, 1, 15)
|
|
>>> festival(pesach, prefix_day=True)
|
|
'1 Pesach'
|
|
>>> festival(pesach, hebrew=True, prefix_day=True)
|
|
'א׳ פסח'
|
|
>>> shavuos = HebrewDate(5783, 3, 6)
|
|
>>> festival(shavuos, israel=True, prefix_day=True)
|
|
'Shavuos'
|
|
"""
|
|
return date.festival(israel, hebrew, include_working_days, prefix_day)
|
|
|
|
|
|
def holiday(date, israel=False, hebrew=False, prefix_day=False):
|
|
"""Return Jewish holiday of given date.
|
|
|
|
The holidays include the major and minor religious Jewish
|
|
holidays including fast days.
|
|
|
|
Parameters
|
|
----------
|
|
date : pyluach.dates.BaseDate
|
|
Any subclass of ``BaseDate`` can be used.
|
|
israel : bool, optional
|
|
``True`` if you want the holidays according to the israel
|
|
schedule. Default is ``False``.
|
|
hebrew : bool, optional
|
|
``True`` if you want the holiday name in Hebrew letters. Default
|
|
is ``False``, which returns the name transliterated into English.
|
|
prefix_day : bool, optional
|
|
``True`` to prefix multi day holidays with the day of the
|
|
holiday. Default is ``False``.
|
|
|
|
Returns
|
|
-------
|
|
str or None
|
|
The name of the holiday or ``None`` if the given date is not
|
|
a Jewish holiday.
|
|
|
|
Examples
|
|
--------
|
|
>>> from pyluach.dates import HebrewDate
|
|
>>> pesach = HebrewDate(2023, 1, 15)
|
|
>>> holiday(pesach, prefix_day=True)
|
|
'1 Pesach'
|
|
>>> holiday(pesach, hebrew=True, prefix_day=True)
|
|
'א׳ פסח'
|
|
>>> taanis_esther = HebrewDate(5783, 12, 13)
|
|
>>> holiday(taanis_esther, prefix_day=True)
|
|
'Taanis Esther'
|
|
"""
|
|
return date.holiday(israel, hebrew, prefix_day)
|