Skip to main content

A Python toolkit for generating Zi Wei Dou Shu astrolabes.

Project description

izthon

中文 README

izthon is a pure-Python re-implementation of the TypeScript library iztro (Zi Wei Dou Shu astrolabe generator). It is implemented as a normal Python package (no runtime dependency on the TypeScript code).

Upstream Reference

  • izthon follows the behavior of the original TypeScript project iztro as a specification reference.
  • The bundled lunar_lite module follows the upstream lunar-lite behavior used by iztro.
  • These upstream projects are references only; izthon does not depend on them at runtime.

Design Goals

  • Pure Python implementation; TS sources are only used as a spec (no runtime dependency on TS).
  • Public APIs are Pythonic snake_case (no TS-style alias names).
  • Built-in i18n follows stdlib gettext semantics, plus an iztro-compatible reverse lookup helper kot().
  • Behavior is regression-tested against upstream Jest test cases via pytest.

Requirements

  • Python >= 3.12
  • uv (recommended; this repo uses uv to manage environments and dependencies)

Install / Run

Run From Source (this repo)

uv run python -c "from izthon.astro import by_solar; print(by_solar('2000-8-16', 2, '女').five_elements_class)"

Use As a Dependency (uv)

In another project (example):

# Local editable (recommended for development)
uv add --editable /path/to/izthon

Quick Start

Generate An Astrolabe (Solar Date)

from izthon.astro import by_solar

astrolabe = by_solar("2000-8-16", time_index=2, gender="女")
print(astrolabe.lunar_date)          # 二〇〇〇年七月十七
print(astrolabe.chinese_date)        # 庚辰 甲申 丙午 庚寅
print(astrolabe.five_elements_class) # 木三局

Generate An Astrolabe (Lunar Date)

from izthon.astro import by_lunar

# lunar_date: "YYYY-M-D", use is_leap_month=True for leap-month dates
astrolabe = by_lunar("2023-2-20", time_index=4, gender="女", is_leap_month=True)

High-level API (izthon.astro)

Entry points:

  • by_solar(solar_date, time_index, gender, *, fix_leap=True, language="zh-CN", config=None, plate="sky") -> FunctionalAstrolabe
  • by_lunar(lunar_date, time_index, gender, *, is_leap_month=False, fix_leap=True, language="zh-CN", config=None, plate="sky") -> FunctionalAstrolabe
  • with_options(*, date_value, time_index, gender, date_type="solar", is_leap_month=False, fix_leap=True, language="zh-CN", config=None, plate="sky") -> FunctionalAstrolabe
    • plate: "sky" | "earth" | "human"
    • config: per-call config patch (no cross-call leakage)
  • Plugins:
    • load_plugin(plugin) / load_plugins([...])
    • get_plugins() / reset_plugins()
    • astrolabe.use(plugin)
  • Low-level astro helpers:
    • get_soul_and_body(...)
    • get_five_elements_class(...)
    • get_palace_names(...)
    • get_horoscope(...)
    • rearrange_astrolabe(...)
  • Helpers:
    • get_zodiac_by_solar_date(..., language=..., config=...)
    • get_sign_by_solar_date(..., language=..., config=...)
    • get_sign_by_lunar_date(..., is_leap_month=..., language=..., config=...)
    • get_major_star_by_solar_date(..., fix_leap=..., language=..., config=...)
    • get_major_star_by_lunar_date(..., is_leap_month=..., fix_leap=..., language=..., config=...)

Plate Selection (Sky / Earth / Human)

from izthon.astro import by_solar

astrolabe = by_solar(
    "1979-08-21",
    7,
    "male",
    plate="earth",  # "sky" | "earth" | "human"
    config={"algorithm": "zhongzhou"},
)

Unified Entry (with_options)

from izthon.astro import with_options

astrolabe = with_options(
    date_value="2023-10-18",
    date_type="lunar",
    time_index=4,
    gender="female",
)

Plugins

from izthon.astro import by_solar, get_plugins, load_plugin, reset_plugins


def add_five_elements(self):
    self.five_elements = lambda: self.five_elements_class


load_plugin(add_five_elements)
print(len(get_plugins()))

global_astrolabe = by_solar("2023-10-18", 4, "female")
print(global_astrolabe.five_elements())

reset_plugins()

instance_astrolabe = by_solar("2023-10-18", 4, "female")
instance_astrolabe.use(add_five_elements)
print(instance_astrolabe.five_elements())

Helper APIs (sign/zodiac/major stars)

from izthon.astro import (
    get_major_star_by_lunar_date,
    get_major_star_by_solar_date,
    get_sign_by_lunar_date,
    get_sign_by_solar_date,
    get_zodiac_by_solar_date,
)

print(get_zodiac_by_solar_date("2023-2-20"))                 # 兔
print(get_zodiac_by_solar_date("2023-2-20", language="en-US"))        # rabbit
print(get_sign_by_solar_date("2023-9-5"))                    # 处女座
print(get_sign_by_lunar_date("2023-2-3", is_leap_month=True))  # 白羊座 (leap month)

print(get_major_star_by_solar_date("1987-05-16", 7))         # 天机,天梁
print(get_major_star_by_lunar_date("2023-2-17", 0, is_leap_month=True))    # 贪狼

Core Concepts & Objects

All main APIs live in izthon.astro.

  • FunctionalAstrolabe: the astrolabe object returned by by_solar() / by_lunar().
  • FunctionalPalace: 12 palaces, accessed via astrolabe.palace(...).
  • FunctionalStar: stars in each palace; accessed via astrolabe.star(...) or palace.major_stars etc.
  • FunctionalHoroscope: luck-cycle view for a given target date (astrolabe.horoscope(...)).
  • FunctionalSurroundingPalaces: surrounded palaces (三方四正), accessed via astrolabe.surrounding_palaces(...).

Query Palaces / Stars

astrolabe = by_solar("2023-8-15", 0, "女")

ming = astrolabe.palace("命宫")
print(ming.name, ming.heavenly_stem, ming.earthly_branch)
print([s.name for s in ming.major_stars])

ziwei = astrolabe.star("紫微")
print(ziwei.palace().name)

surroundings = astrolabe.surrounding_palaces("命宫")
print(surroundings.opposite.name, surroundings.wealth.name, surroundings.career.name)
print(surroundings.has_stars(["武曲", "贪狼"]))  # all included?
print(surroundings.lacks_stars(["地空", "地劫"]))
print(surroundings.has_any_star(["太阳", "文曲"]))  # any included?
print(surroundings.has_mutagen("禄"))
print(surroundings.lacks_mutagen("忌"))

Palace Helper Methods

palace = astrolabe.palace("命宫")

print(palace.has_stars(["武曲"]))
print(palace.lacks_stars(["火星"]))
print(palace.has_any_star(["武曲", "贪狼"]))
print(palace.has_mutagen("禄"))
print(palace.lacks_mutagen("忌"))
print(palace.is_empty())

print(palace.flies_to("兄弟", "忌"))
print(palace.flies_one_of_to("夫妻", ["权", "科"]))
print(palace.does_not_fly_to("兄弟", "科"))

print(palace.has_self_mutagen("禄"))
print(palace.has_any_self_mutagen())
print(palace.lacks_self_mutagen("忌"))
print([p.name if p else None for p in palace.mutagen_palaces()])

Star Helper Methods

star = astrolabe.star("紫微")

print(star.palace().name)
print(star.opposite_palace().name)
print(star.surrounding_palaces().target.name)
print(star.with_mutagen(["禄", "权"]))
print(star.with_brightness(["庙", "旺"]))

Horoscope (Decadal/Yearly/Monthly/Daily/Hourly)

astrolabe = by_solar("2000-8-16", 2, "女")
horoscope = astrolabe.horoscope("2023-8-19 3:12")

print(horoscope.decadal.index, horoscope.decadal.heavenly_stem, horoscope.decadal.earthly_branch)
print(horoscope.yearly.index, horoscope.yearly.heavenly_stem, horoscope.yearly.earthly_branch)

# Query horoscope stars (decadal + yearly stars on a given palace in a given scope)
print(horoscope.has_horoscope_stars("疾厄", "decadal", ["流陀", "流曲", "运昌"]))
print(horoscope.has_horoscope_mutagen("兄弟", "decadal", "禄"))

Horoscope Helper Methods

print(horoscope.age_palace().name)
print(horoscope.palace("命宫", "yearly").name)
print(horoscope.surrounding_palaces("命宫", "decadal").opposite.name)
print(horoscope.lacks_horoscope_stars("疾厄", "decadal", ["流喜", "流鸾"]))
print(horoscope.has_any_horoscope_star("疾厄", "decadal", ["流陀", "流曲"]))

Low-level Astro Helpers

from izthon.astro import (
    get_five_elements_class,
    get_horoscope,
    get_palace_names,
    get_soul_and_body,
)

print(get_palace_names(0))
print(get_five_elements_class("丙", "寅"))
print(get_soul_and_body(solar_date="2023-8-15", time_index=0, fix_leap=True))
decadals, ages = get_horoscope(solar_date="2023-8-15", time_index=0, gender="女", fix_leap=True)
print(decadals[0], ages[0])

# `rearrange_astrolabe(...)` is the low-level primitive behind `plate="earth"` and `plate="human"`.

Parameters & Conventions

time_index (0..12)

time_index is the traditional 12-branch hour index used by iztro.

time_index time (zh-CN) range
0 早子时 00:00~01:00
1 丑时 01:00~03:00
2 寅时 03:00~05:00
3 卯时 05:00~07:00
4 辰时 07:00~09:00
5 巳时 09:00~11:00
6 午时 11:00~13:00
7 未时 13:00~15:00
8 申时 15:00~17:00
9 酉时 17:00~19:00
10 戌时 19:00~21:00
11 亥时 21:00~23:00
12 晚子时 23:00~00:00

Helper:

from izthon.util import time_to_index
print(time_to_index(23))  # 12

gender

gender accepts values in any supported language (e.g. "male", "female", "男", "女"), thanks to kot().

Note: astrolabe.gender is localized to the current language (default: zh-CN), so you may see "男"/"女" even if you pass "male"/"female".

from izthon.astro import by_solar

print(by_solar("2000-8-16", 2, "female").gender)                 # 女 (default zh-CN)
print(by_solar("2000-8-16", 2, "female", language="en-US").gender)  # female

Leap Month Fix (fix_leap)

fix_leap=True follows iztro behavior for leap months: for a leap month, days after the 15th may be treated as the next month for some placements (unless time_index==12).

Global Config

Use set_config() to set defaults for the current context, or pass config=... per call to avoid cross-call state:

from izthon.astro import get_config, reset_config, set_config

set_config(
    {
        "year_divide": "exact",         # "normal" | "exact"
        "horoscope_divide": "exact",    # "normal" | "exact"
        "age_divide": "birthday",       # "normal" | "birthday"
        "day_divide": "forward",        # "current" | "forward"
        "algorithm": "default",         # "default" | "zhongzhou"
    }
)
print(get_config())
reset_config()

You can also customize:

  • mutagens: map a heavenly stem to 4 stars (禄/权/科/忌)
  • brightness: map a star to 12 brightness values (one per palace index)

Both keys/values can be provided in any supported language.

Multi-language (i18n)

Supported languages:

  • en-US, ja-JP, ko-KR, zh-CN, zh-TW, vi-VN

Two ways to set language:

  1. Pass language to by_solar / by_lunar / helper APIs:
from izthon.astro import by_solar
print(by_solar("2000-8-16", 2, "女", language="vi-VN").chinese_date)
  1. Set global language:
from izthon.i18n import set_language

set_language("ko-KR")

Language is context-local and can be safely overridden per API call.

Direct i18n Helpers

from izthon.i18n import get_language, kot, set_language, t, translate

set_language("en-US")
print(get_language())
print(t("soulPalace"))
print(translate("bodyPalace"))
print(kot("female"))

Lunar Conversion (lunar_lite)

izthon.lunar_lite is a Python port of lunar-lite (range: 1900..2100).

from izthon.lunar_lite import (
    Options,
    get_heavenly_stem_and_earthly_branch_by_lunar_date,
    get_heavenly_stem_and_earthly_branch_by_solar_date,
    get_sign,
    get_total_days_of_lunar_month,
    get_zodiac,
    lunar_to_solar,
    solar_to_lunar,
)

print(solar_to_lunar("2023-4-1").to_chinese())  # 二〇二三年闰二月十一
print(lunar_to_solar("2023-2-11", is_leap_month=True).isoformat())  # 2023-4-1
print(get_sign("2023-9-5"))
print(get_zodiac("卯"))
print(get_total_days_of_lunar_month("2023-4-1"))

options = Options(year="exact", month="exact")
print(get_heavenly_stem_and_earthly_branch_by_solar_date("2025-2-3", 12, options).yearly)
print(get_heavenly_stem_and_earthly_branch_by_lunar_date("2023-2-11", 2, True, options).daily)

Data classes exported by izthon.lunar_lite:

  • LunarDate
  • SolarDate
  • HeavenlyStemAndEarthlyBranchDate
  • Options

Low-level Modules

izthon.star

  • Container: init_stars()
  • Location helpers: get_start_index(), get_lu_yang_tuo_ma_index(), get_kui_yue_index(), get_zuo_you_index(), get_chang_qu_index(), get_daily_star_index(), get_timely_star_index(), get_kong_jie_index(), get_huo_ling_index(), get_luan_xi_index(), get_huagai_xianchi_index(), get_gu_gua_index(), get_yearly_star_index(), get_tianshi_tianshang_index(), get_nianjie_index(), get_monthly_star_index(), get_chang_qu_index_by_heavenly_stem(), get_jiesha_adj_index(), get_dahao_index()
  • Star builders: get_major_star(), get_minor_star(), get_adjective_star()
  • Decorative / cycle helpers: get_changsheng_12_start_index(), get_jiangqian_12_start_index(), get_changsheng_12(), get_boshi_12(), get_yearly_12(), get_horoscope_star()

izthon.util

  • Index helpers: fix_index(), earthly_branch_index_to_palace_index(), fix_earthly_branch_index(), fix_lunar_month_index(), fix_lunar_day_index(), get_age_index()
  • Brightness / mutagen helpers: get_brightness(), get_mutagen(), get_mutagens_by_heavenly_stem()
  • Misc helpers: merge_stars(), time_to_index(), translate_chinese_date()

Development

Run tests:

uv run --extra dev pytest

Generate i18n catalogs (maintainer only; requires the upstream iztro locale sources):

uv run python tools/generate_iztro_i18n_catalogs.py --src-locales /path/to/iztro/src/i18n/locales

Attribution

This project re-implements (as a spec) the behavior of:

  • iztro@2.5.7
  • lunar-lite@0.2.8

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

izthon-0.2.0.tar.gz (46.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

izthon-0.2.0-py3-none-any.whl (65.3 kB view details)

Uploaded Python 3

File details

Details for the file izthon-0.2.0.tar.gz.

File metadata

  • Download URL: izthon-0.2.0.tar.gz
  • Upload date:
  • Size: 46.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for izthon-0.2.0.tar.gz
Algorithm Hash digest
SHA256 55f32d812ac68d28decbd2718337f890739a4bc2351f4506ea5a3be89cedd819
MD5 b8096745dda978338ca95929f157340e
BLAKE2b-256 198b51cc50ac5a0d5f2d086f48b7a9b3ee51493f535008af29c22685c7be00a5

See more details on using hashes here.

File details

Details for the file izthon-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: izthon-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 65.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for izthon-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 890407173d2e75151055254d493750cc0cd387eed0e4d87ed881d89b042f47a2
MD5 0bdf66c21bd946bb105df6c9ea845b8c
BLAKE2b-256 4311d9141d6bd8ee98bcd1d7a0cb2e80ad86abd0fa926fd7b2ae7c86941020d5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page