Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ __pycache__/
htmlcov/
releases-test/
releases-prod/
.venv/
101 changes: 69 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,31 @@

Thailand has used several calendar systems during its history and some are still in use today. This library provides `datetime.date`-like classes for working with the Thai lunisolar calendar of the Chulasakarat Era (จุลศักราช) and the lunar Pakkhakhananaa calendar (ปฏิทินปักขคณนา).

# Installation
## Documentation

Full documentation is available at [docs.pythaidate.dev](https://docs.pythaidate.dev).

To build and serve the documentation locally:

```bash
# Install documentation dependencies
python3 -m pip install -r requirements-docs.txt

# Serve documentation (with live reload)
mkdocs serve

# Build static site
mkdocs build
```
$ python3 -m pip install pythaidate

Then visit http://127.0.0.1:8000 to view the documentation.

## Quick Start

### Installation

```bash
python3 -m pip install pythaidate
```

# Examples
Expand All @@ -25,12 +46,14 @@ $ python3 -m pip install pythaidate
```

The `days` property gives the zero-indexed count of days since new years day.

```
>>> cs.days
260
```

The `horakhun` (หรคุฌ) property gives the days since the epoch and `julianday` gives the Julian Day Number, useful for converting between calendar formats.

```
>>> cs.horakhun
497378
Expand All @@ -39,27 +62,31 @@ The `horakhun` (หรคุฌ) property gives the days since the epoch and `ju
```

Other properties show the internal calculation values:
* `kammabucapon`: (กัมมัขผล) the excess of solar days over whole solar days
* `masaken`: (มาสเกฌฑ์) The number of lunar months since the epoch
* `uccapon`: (อุจจพล) The measure of the position of the Moon's apogee (furthest distance from the Earth). It increases by one unit a day to a maximum of 3232.
* `avoman`: (อวมาน) The excess of lunar days over solar days in units of 1/692 of a lunar day modulus 692, increasing by 11 units each solar day. It is used to determine when to add intercalary days in the calendar
* `tithi`: (ดิถี) a lunar day, equal to 1/30th of a synodic month

- `kammabucapon`: (กัมมัขผล) the excess of solar days over whole solar days
- `masaken`: (มาสเกฌฑ์) The number of lunar months since the epoch
- `uccapon`: (อุจจพล) The measure of the position of the Moon's apogee (furthest distance from the Earth). It increases by one unit a day to a maximum of 3232.
- `avoman`: (อวมาน) The excess of lunar days over solar days in units of 1/692 of a lunar day modulus 692, increasing by 11 units each solar day. It is used to determine when to add intercalary days in the calendar
- `tithi`: (ดิถี) a lunar day, equal to 1/30th of a synodic month

The year and day count since new years day can also be used to create a `CsDate` object with the `fromyd` class method:

```
>>> cs = CsDate.fromyd(1361, 260)
>>> cs.julianday
2451545
```

Similarly, a `CsDate` object can be created from the Julian Day Number:

```
>>> cs = CsDate.fromjulianday(2451545)
>>> cs.year, cs.month, cs.day
(1361, 1, 24)
```

A `CsDate` can be displayed as text with `.csformat()` or by converting the object to a string:

```
>>> cs.csformat()
'วันเสาร์ เดือน ๑ แรม ๙ ค่ำ ปีเถาะ จ.ศ.๑๓๖๑'
Expand All @@ -68,15 +95,17 @@ A `CsDate` can be displayed as text with `.csformat()` or by converting the obje
```

`CsDate` objects have 3 properties for intercalations and a day count:
* `solar_leap_year`: for the solar leap year (อธิกสุรทิน)
* `leap_day`: for the lunar intercalary day (อธิกวาร)
* `leap_month`: for the lunar intercalary month (อธิกมาส)
* `days_in_year`: returns the number of days in the year. This will be one of:
* 354: no intercalations (ปกติมาส ปกติวาร)
* 355: no intercalary month, intercalary day (ปกติมาส อธิกวาร)
* 384: intercalary month, no intercalary day (อธิกมาส ปกติวาร)

- `solar_leap_year`: for the solar leap year (อธิกสุรทิน)
- `leap_day`: for the lunar intercalary day (อธิกวาร)
- `leap_month`: for the lunar intercalary month (อธิกมาส)
- `days_in_year`: returns the number of days in the year. This will be one of:
- 354: no intercalations (ปกติมาส ปกติวาร)
- 355: no intercalary month, intercalary day (ปกติมาส อธิกวาร)
- 384: intercalary month, no intercalary day (อธิกมาส ปกติวาร)

In the Thai lunisolar calendar system a year can only have either zero or one intercalations. There can't be both an intercalary day (อธิกวาร) and month (อธิกมาส) in the same year.

```
>>> cs.solar_leap_year
False
Expand All @@ -91,6 +120,7 @@ True
## `PakDate`: Pakkhakhananaa Date

Create a `PakDate` object from a pakcode. The `1-` prefix is the cycle number (1-indexed), followed by the ปักขคณนา, สัมพยุหะ, พยุหะ, สมุหะ, วรรค and day of moon phase. The Pakkhakhananaa cycle repeats every 289,577 days.

```
>>> from pythaidate import PakDate
>>> p = PakDate(pakcode="1-6:11:5:2:2:10")
Expand All @@ -101,9 +131,11 @@ Create a `PakDate` object from a pakcode. The `1-` prefix is the cycle number (1
>>> p.iswanphra
False
```

Note that the `horakhun` value from Pakkhakhananaa lunar and (Chulasakarat era) lunisolar calendars are not compatible as they represent day count since the epoch of each calendar. For comparisons use `julianday` instead. `iswanphra` has an alias `issabbath`.

The Pakkhakhananaa code and abbreviations are available:

```
>>> p.pakcode
'1-6:11:5:2:2:10'
Expand All @@ -113,14 +145,17 @@ The Pakkhakhananaa code and abbreviations are available:
```

Pakkhakhananaa can be created from a `datetime.date` object:

```
>>> from datetime import date
>>> from pythaidate import PakDate
>>> p = PakDate(date=date(2000, 1, 1))
>>> p.julianday
2451545
```

...and from Julian Day Number:

```
>>> from pythaidate import PakDate
>>> p = PakDate(jd=2451545)
Expand All @@ -129,7 +164,9 @@ Pakkhakhananaa can be created from a `datetime.date` object:
>>> p.pakcode
'1-6:11:5:2:2:10'
```

`pakboard()` will display an ASCII Pakkhakhananaa board (กระดานปักขคณนา) and (best viewed with a fixed-width font):

```
>>> p.pakboard()
๑ ๒ ๓ ๔ ๕ ๖ ๗ ๘ ๙ ๑๐ ๑๑ ๑๒ ๑๓ ๑๔ ๑๕ ๑๖ ๑๗ ๑๘
Expand All @@ -149,11 +186,11 @@ Pakkhakhananaa can be created from a `datetime.date` object:

## Julian Day Number (JDN) helpers

* `to_julianday(year, month, day)`: Returns JDN from a year, month, day triple
* `from_julianday(jd)`: Returns a year, month, day triple from a JDN
* `today()`: returns JDN for today
* `date_to_julianday(d)`: converts `datetime.date` object or other object with a `julianday` property to JDN
* `julianday_to_date(jd)`: converts JDN to a `datetime.date` object
- `to_julianday(year, month, day)`: Returns JDN from a year, month, day triple
- `from_julianday(jd)`: Returns a year, month, day triple from a JDN
- `today()`: returns JDN for today
- `date_to_julianday(d)`: converts `datetime.date` object or other object with a `julianday` property to JDN
- `julianday_to_date(jd)`: converts JDN to a `datetime.date` object

## `pythaidate.date`: A `datetime.date` subclass

Expand All @@ -170,38 +207,38 @@ Pakkhakhananaa can be created from a `datetime.date` object:

## General

* Tested and supported on Python 3.8 - 3.12.
- Tested and supported on Python 3.8 - 3.12.

## Chulasakarat Era Lunisolar Calendar

* The determination of which years are intercalary has been a somewhat subjective process and changed over the centuries, along with regional variations too. This library produces 7 intercalary months per 19 year period and 11 intercalary days per 57 years. This maintains the overall "pace" of the calendar but there may be slight short-term deviations from other calendars. But don't worry, those other calendars are just as wrong too - there's no definitive reference calendar.
* Currently only supports Sukothai-style month numbering (eg. first month of the year is month 5)
* `strftime` and `strptime` are not implemented
- The determination of which years are intercalary has been a somewhat subjective process and changed over the centuries, along with regional variations too. This library produces 7 intercalary months per 19 year period and 11 intercalary days per 57 years. This maintains the overall "pace" of the calendar but there may be slight short-term deviations from other calendars. But don't worry, those other calendars are just as wrong too - there's no definitive reference calendar.
- Currently only supports Sukothai-style month numbering (eg. first month of the year is month 5)
- `strftime` and `strptime` are not implemented

# Selected References

## Thai

* หลวงวิศาลดรุณกร (อั้น สาริกบุตร) (1997) คัมภีร์โหราศาสตร์ไทย มาตรฐาน ฉบับสมบูรณ์. Thailand: ศรีปัญญา, สนพ.
- หลวงวิศาลดรุณกร (อั้น สาริกบุตร) (1997) คัมภีร์โหราศาสตร์ไทย มาตรฐาน ฉบับสมบูรณ์. Thailand: ศรีปัญญา, สนพ.

* [ความรู้เรื่องปักขคณนา ตําราการคํานวณปฏิทินทางจันทรคติ](https://archive.org/details/unset0000unse_d6m6). (1999). Thailand: มูลนิธิมหามกุฏราชวิทยาลัยฯ.
- [ความรู้เรื่องปักขคณนา ตําราการคํานวณปฏิทินทางจันทรคติ](https://archive.org/details/unset0000unse_d6m6). (1999). Thailand: มูลนิธิมหามกุฏราชวิทยาลัยฯ.

## English

* Eade, J.C. (2018). The Calendrical Systems of Mainland South-East Asia. Netherlands: Brill.
* Gislén L., Eade, J.C. (2019). The Calendars of Southeast Asia 2: Burma, Thailand, Laos and Cambodia. Journal of Astronomical History and Heritage, 22(3).
- Eade, J.C. (2018). The Calendrical Systems of Mainland South-East Asia. Netherlands: Brill.
- Gislén L., Eade, J.C. (2019). The Calendars of Southeast Asia 2: Burma, Thailand, Laos and Cambodia. Journal of Astronomical History and Heritage, 22(3).

## French

* Faraut, F. G. (1910). [Astronomie cambodgienne](https://archive.org/details/farraut0astonomiecambodgienne). Vietnam: Imprimerie F.-H. Schneider.
- Faraut, F. G. (1910). [Astronomie cambodgienne](https://archive.org/details/farraut0astonomiecambodgienne). Vietnam: Imprimerie F.-H. Schneider.

* Billard, R. L'Astronomie Indienne. Investigation des Textes Sanskrits et des Donnees Numeriques. Paris: Ecole francaise d'extreme-orient. (1971).
- Billard, R. L'Astronomie Indienne. Investigation des Textes Sanskrits et des Donnees Numeriques. Paris: Ecole francaise d'extreme-orient. (1971).

# Other Resources

* [NASA's Julian Day Number Calculator](https://core2.gsfc.nasa.gov/time/julian.html)
* [ปฏิทินปักขคณนา.com](https://xn--12ccg5bxauoekd6vraqb.com/)
- [NASA's Julian Day Number Calculator](https://core2.gsfc.nasa.gov/time/julian.html)
- [ปฏิทินปักขคณนา.com](https://xn--12ccg5bxauoekd6vraqb.com/)

# Contributors

* Mark Hollow <dev {at} hmmbug.com> (Project Owner)
- Mark Hollow <dev {at} hmmbug.com> (Project Owner)
25 changes: 25 additions & 0 deletions docs/about/license.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# License

This project is licensed under the MIT License.

## MIT License

Copyright (c) 2024 Mark Hollow

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
24 changes: 24 additions & 0 deletions docs/about/references.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# References

## Thai Language Sources

* หลวงวิศาลดรุณกร (อั้น สาริกบุตร) (1997) คัมภีร์โหราศาสตร์ไทย มาตรฐาน ฉบับสมบูรณ์. Thailand: ศรีปัญญา, สนพ.

* [ความรู้เรื่องปักขคณนา ตําราการคํานวณปฏิทินทางจันทรคติ](https://archive.org/details/unset0000unse_d6m6). (1999). Thailand: มูลนิธิมหามกุฏราชวิทยาลัยฯ.

## English Language Sources

* Eade, J.C. (2018). The Calendrical Systems of Mainland South-East Asia. Netherlands: Brill.

* Gislén L., Eade, J.C. (2019). The Calendars of Southeast Asia 2: Burma, Thailand, Laos and Cambodia. Journal of Astronomical History and Heritage, 22(3).

## French Language Sources

* Faraut, F. G. (1910). [Astronomie cambodgienne](https://archive.org/details/farraut0astonomiecambodgienne). Vietnam: Imprimerie F.-H. Schneider.

* Billard, R. L'Astronomie Indienne. Investigation des Textes Sanskrits et des Donnees Numeriques. Paris: Ecole francaise d'extreme-orient. (1971).

## Online Resources

* [NASA's Julian Day Number Calculator](https://core2.gsfc.nasa.gov/time/julian.html)
* [ปฏิทินปักขคณนา.com](https://xn--12ccg5bxauoekd6vraqb.com/)
60 changes: 60 additions & 0 deletions docs/api/csdate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# CsDate API Reference

## Class: CsDate

### Constructors

#### `CsDate(year: int, month: int, day: int, month_style: int = MONTH_SUK)`

Create a new CsDate object from year, month and day values.

Parameters:
- `year`: Chulasakarat year
- `month`: Month number (Sukothai format)
- `day`: Day of month
- `month_style`: Month numbering style (default: Sukothai)

#### Class Methods

- `fromyd(year: int, days: int)`: Create from year and days since new year
- `fromjulianday(jd: int)`: Create from Julian Day Number
- `today()`: Create for current date

### Properties

- `year`: The Chulasakarat year
- `month`: The month number
- `day`: The day of month
- `days`: Days since new year
- `horakhun`: Days since epoch
- `julianday`: Julian Day Number
- `kammacapon`: Excess of solar days
- `masaken`: Lunar months since epoch
- `uccapon`: Moon's apogee position
- `avoman`: Excess of lunar days
- `tithi`: Lunar day number
- `solar_leap_year`: Is solar leap year
- `leap_day`: Has intercalary day
- `leap_month`: Has intercalary month
- `days_in_year`: Total days in year

### Methods

- `csformat()`: Format date as Thai text
- `csformatymd()`: Format as YYYY-MM-DD
- `fromcsformat(s: str)`: Parse Thai text format
- `weekday()`: Get weekday (0-6)
- `isoweekday()`: Get ISO weekday (1-7)

### Comparison Operations

Supports standard comparison operators (`<`, `<=`, `==`, `>=`, `>`) with:
- Other CsDate objects
- datetime.date objects
- Objects with a julianday property

### Arithmetic Operations

- Addition with timedelta
- Subtraction of timedelta
- Subtraction of another CsDate/date returns timedelta
29 changes: 29 additions & 0 deletions docs/api/helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Helper Functions

## Julian Day Number Helpers

### `to_julianday(year: int, month: int, day: int) -> int`
Convert year, month, day to Julian Day Number.

### `from_julianday(jd: int) -> tuple`
Convert Julian Day Number to (year, month, day) tuple.

### `today() -> int`
Get Julian Day Number for current date.

### `date_to_julianday(d: date) -> int`
Convert datetime.date or object with julianday property to Julian Day Number.

### `julianday_to_date(jd: int) -> date`
Convert Julian Day Number to datetime.date object.

## Thai Number Conversion

### `digit_thai_to_arabic(s: str) -> str`
Convert Thai numerals to Arabic numerals in string.

### `digit_arabic_to_thai(s: str) -> str`
Convert Arabic numerals to Thai numerals in string.

### `thai_string_width(s: str) -> int`
Calculate display width of Thai string.
Loading