Horoscope object
The data structures of the six scopes, plus palace lookups that need not be handed the astrolabe again.
A horoscope projects the natal chart onto a point in time. The same chart shows a different palace layout in different years — which is exactly what "the decadal scope has moved to that palace" means.
let h = chart.horoscope("2025-6-1", 0)?;HoroscopeRef holds the natal chart that produced it, so none of the query methods need the
astrolabe passed in again.
The examples on this page all start from a Language::EnUS natal chart, so the display values in the
output are iztro's en-US vocabulary.
HoroscopeData
HoroscopeRef derefs to HoroscopeData, which has eight fields: two date strings and the six scopes.
| Field | Type | Description |
|---|---|---|
solar_date | String | Target solar date, as passed in |
lunar_date | String | The target date's lunar form, written in Chinese numerals |
decadal | HoroscopeItem | The decadal scope |
age | AgeItem | The age scope |
yearly | YearlyItem | The yearly scope |
monthly | HoroscopeItem | The monthly scope |
daily | HoroscopeItem | The daily scope |
hourly | HoroscopeItem | The hourly scope |
solar_date is the target date, not the birth date; the birth date lives on the natal chart, as
h.astrolabe().solar_date.
The six scopes
| Field | Type | Span | Description |
|---|---|---|---|
decadal | HoroscopeItem | Ten years | The decadal scope; the childhood scope for the years before it begins |
age | AgeItem | One year | The age scope, moving one palace per nominal year |
yearly | YearlyItem | One year | The yearly scope, its palace fixed by the year's pillar |
monthly | HoroscopeItem | One month | The monthly scope |
daily | HoroscopeItem | One day | The daily scope |
hourly | HoroscopeItem | One double-hour | The hourly scope |
The age scope versus the yearly scope
Both advance once per year, but they start differently: the age scope starts from the birth-year branch and steps forward with the nominal age, while the yearly scope simply asks which palace that year's pillar falls in. The two lines are independent, and Zi Wei practice usually reads them together.
HoroscopeItem
| Field | Type | Description |
|---|---|---|
index | usize | Which palace this scope lands on (a palace index) |
name | String | Display name of the scope, translated into the output language |
name_key | HoroscopeName | The scope's language-independent key; before the decadals begin the decadal scope carries Childhood, a different reading semantics from Decadal — predicate on this, never on the translation |
heavenly_stem | HeavenlyStem | Stem of the scope, which determines the mutagens it flies |
earthly_branch | EarthlyBranch | Branch of the scope |
palace_names | Vec<Palace> | The twelve palace names re-derived with this scope's palace as the Soul palace, indexed by palace index |
mutagen | Vec<StarKey> | The stars this scope's stem transforms, in the order lu, quan, ke, ji |
stars | Option<Vec<Vec<Star>>> | The scope stars of this layer; None for layers that have none |
age and yearly are not HoroscopeItems themselves but wrappers carrying one extra datum each:
pub struct AgeItem {
pub base: HoroscopeItem,
pub nominal_age: u32, // the nominal age for that date
}
pub struct YearlyItem {
pub base: HoroscopeItem,
pub yearly_dec_star: YearlyDecStar,
}
pub struct YearlyDecStar {
pub jiangqian12: Vec<StarKey>, // the yearly Jiang-qian gods, indexed by palace index
pub suiqian12: Vec<StarKey>, // the yearly Sui-qian gods, indexed by palace index
}AgeItem and YearlyItem both implement Deref<Target = HoroscopeItem>, so the shared fields
read directly: h.yearly.heavenly_stem, h.age.index; take .base when you need the whole
HoroscopeItem. All four types are re-exported at the crate root.
Example
let h = chart.horoscope("2025-6-1", 0)?;
for item in [&h.decadal, &h.monthly, &h.daily, &h.hourly] {
println!("{} lands on palace {} with pillar {} {}", item.name, item.index,
translate_heavenly_stem(item.heavenly_stem, Language::EnUS),
translate_earthly_branch(item.earthly_branch, Language::EnUS));
}
println!("age scope nominal age {}", h.age.nominal_age);Output
decadal lands on palace 2 with pillar geng chen
monthly lands on palace 3 with pillar ren woo
daily lands on palace 8 with pillar xin chou
hourly lands on palace 8 with pillar wu zi
age scope nominal age 26age_palace
Purpose Get the palace the age scope occupies this year.
Zi Wei meaning The age scope is a line advancing year by year; whichever palace it lands on becomes the focus for that year.
Signature
pub fn age_palace(&self) -> PalaceRef<'a>Return value PalaceRef — a palace on the natal chart, always present.
Example
let h = chart.horoscope("2025-6-1", 0)?;
println!("{}", translate_palace(h.age_palace().name, Language::EnUS));Output
propertypalace
Purpose Get one of the twelve palaces as re-derived under a given horoscope scope.
Zi Wei meaning Once the decadal scope reaches a palace, the twelve palaces are re-anchored with that palace as the "decadal Soul palace". "The decadal Spouse palace" refers to that re-anchored naming, and it is usually not the same palace as the natal Spouse palace.
Signature
pub fn palace(&self, name: Palace, scope: Scope) -> Option<PalaceRef<'a>>Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | Palace | Yes | — | The palace name to fetch |
scope | Scope | Yes | — | Which scope's twelve palaces to search |
Return value Option<PalaceRef<'a>> — a palace on the natal chart (the same cell carries
different names under different scopes). With Origin as the scope, these are the natal twelve
palaces.
Example
let en = Language::EnUS;
let h = chart.horoscope("2025-6-1", 0)?;
println!("the decadal Soul palace is the natal {}",
translate_palace(h.palace(Palace::Soul, Scope::Decadal).unwrap().name, en));
println!("the natal Soul palace is {}",
translate_palace(h.palace(Palace::Soul, Scope::Origin).unwrap().name, en));Output
the decadal Soul palace is the natal spouse
the natal Soul palace is soulEdge cases and pitfalls
What comes back is the cell on the natal chart
On the palace object returned by palace(Soul, Decadal), name is still the natal palace name
(Spouse in the example), because it is that cell on the natal chart. To see what the cell is called at
the decadal layer, read h.decadal.palace_names[index].
surround_palaces
Purpose Get the surrounded palaces of a palace under a given horoscope scope.
Signature
pub fn surround_palaces(&self, name: Palace, scope: Scope) -> Option<SurroundedPalaces<'a>>Parameters Same as palace.
Return value Option<SurroundedPalaces<'a>>; its predicates are on
Surrounded palaces.
Example
let h = chart.horoscope("2025-6-1", 0)?;
let sp = h.surround_palaces(Palace::Wealth, Scope::Yearly).unwrap();
println!("the surrounded set of the yearly Wealth palace is anchored on the natal {}",
translate_palace(sp.target.name, Language::EnUS));Output
the surrounded set of the yearly Wealth palace is anchored on the natal healthhas_horoscope_stars / has_one_of_horoscope_stars / not_have_horoscope_stars
Purpose Test whether a palace under a given scope holds the given scope stars.
Zi Wei meaning Scope stars are a group produced by each horoscope layer: Tiankui, Tianyue, Wenchang, Wenqu, Lucun, Qingyang, Tuoluo, Tianma, Hongluan and Tianxi. They carry different names in different layers — Yunkui and Yunyue at the decadal layer, Liukui and Liuyue at the yearly layer — with the same meaning applied to their own time span.
Signature
pub fn has_horoscope_stars(&self, name: Palace, scope: Scope, stars: &[StarKey]) -> bool
pub fn has_one_of_horoscope_stars(&self, name: Palace, scope: Scope, stars: &[StarKey]) -> bool
pub fn not_have_horoscope_stars(&self, name: Palace, scope: Scope, stars: &[StarKey]) -> boolParameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | Palace | Yes | — | The palace name under that scope |
scope | Scope | Yes | — | The horoscope scope |
stars | &[StarKey] | Yes | — | Scope star keys, which must use that layer's names |
Return value
| Method | Meaning |
|---|---|
has_horoscope_stars | All of them are present |
has_one_of_horoscope_stars | At least one is present |
not_have_horoscope_stars | None is present |
Example
use x_iztro::StarKey::*;
let h = chart.horoscope("2025-6-1", 0)?;
println!("{}", h.has_horoscope_stars(Palace::Soul, Scope::Decadal, &[Yunlu]));
println!("{}", h.has_one_of_horoscope_stars(Palace::Soul, Scope::Decadal, &[Yunlu, Yunyang]));
println!("{}", h.not_have_horoscope_stars(Palace::Soul, Scope::Decadal, &[Yuntuo]));Output
false
false
trueEdge cases and pitfalls
has_horoscope_mutagen
Purpose Test whether a palace under a given scope carries a mutagen flown by that scope's stem.
Zi Wei meaning Every horoscope layer has a stem of its own, and it transforms four stars just as the birth-year stem does. A question like "does the decadal lu land in the decadal Wealth palace?" is asking about this.
Signature
pub fn has_horoscope_mutagen(&self, name: Palace, scope: Scope, mutagen: Mutagen) -> boolParameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | Palace | Yes | — | The palace name under that scope |
scope | Scope | Yes | — | The horoscope scope |
mutagen | Mutagen | Yes | — | One of the four mutagens |
Return value bool. It checks whether the star transformed by that layer's stem sits among the
target palace's major or minor stars (adjective stars are not considered).
Example
let h = chart.horoscope("2025-6-1", 0)?;
println!("{}", h.has_horoscope_mutagen(Palace::Soul, Scope::Decadal, Mutagen::Lu));
// the four stars this layer transforms can be read directly
println!("{:?}", h.decadal.mutagen.iter()
.map(|s| translate_star(*s, Language::EnUS)).collect::<Vec<_>>());Output
false
["sun", "general", "moon", "fortunate"]The decadal stem is geng, and geng sends lu to Taiyang (sun), quan to Wuqu (general), ke to Taiyin
(moon) and ji to Tiantong (fortunate).
Edge cases and pitfalls
Always false when the scope is Origin
There is no such thing as a "layer stem" at the natal layer — the natal mutagens are already marked on
the stars' own mutagen fields.
has_horoscope_mutagen(name, Scope::Origin, m) therefore returns false outright, which does not
mean the natal chart lacks that mutagen. For natal mutagens use the palace's
has_mutagen.
astrolabe / data / into_data
Purpose Get back to the natal chart, or take out the horoscope's plain data.
Signature
pub fn astrolabe(&self) -> &'a Astrolabe
pub fn data(&self) -> &HoroscopeData
pub fn into_data(self) -> HoroscopeDataReturn value
| Method | Use |
|---|---|
astrolabe | Back to the natal chart that produced this horoscope |
data | Borrow the underlying data; the view implements Deref, so h.decadal usually suffices |
into_data | Take the data and drop the borrow of the astrolabe, for cases needing a 'static lifetime |
Example
let h = chart.horoscope("2025-6-1", 0)?;
println!("{}", h.astrolabe().solar_date);
let data: HoroscopeData = h.into_data(); // no longer borrows chart
println!("{}", data.solar_date);Output
2000-8-16
2025-6-1to_text
Purpose The horoscope's semantic text: a complete description for language models and people.
Signature
pub fn to_text(&self) -> StringDefined on HoroscopeRef and emitting in the chart's charting language; for an explicit language use
the free function text::horoscope_to_text(astrolabe, horoscope, lang).
Return value String — sectioned plain text; each scope carries a patterns line and
flowing-star lines from its own perspective. The full format is on
Semantic text.
Example
let h = chart.horoscope("2025-1-1", 0)?;
println!("{}", h.to_text().chars().take(39).collect::<String>());Output
=== Horoscope ===
Target Date: 2025-1-1to_dto
Purpose Convert the horoscope data into the serialization structure that matches the JS iztro field contract.
Signature
pub fn to_dto(&self, lang: Language) -> HoroscopeDtoParameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
lang | Language | Yes | — | Which language the translated fields use |
It is defined on HoroscopeData (not on HoroscopeRef). The horoscope data does not itself record a
language, so it has to be given explicitly here — usually chart.language, to stay consistent with
the natal chart.
Return value x_iztro::dto::HoroscopeDto, camelCase keys plus the *Key identifiers.
Example
let h = chart.horoscope("2025-6-1", 0)?;
let json = serde_json::to_string(&h.to_dto(chart.language))?;
let v: serde_json::Value = serde_json::from_str(&json)?;
println!("{} {}", v["solarDate"], v["decadal"]["heavenlyStem"]);
println!("{}", v["age"]["nominalAge"]);Output
"2025-6-1" "geng"
26