# Horoscope object (/en/docs/rust/horoscope)

The data structures of the six scopes, the three whole-scope lists, 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.

```rust
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.

<Callout type="info">
  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.
</Callout>

## HoroscopeData [#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                                          |

<Callout type="info">
  `solar_date` is the **target** date, not the birth date; the birth date lives on the natal chart, as
  `h.astrolabe().solar_date`.
</Callout>

## The six scopes [#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                                                      |

<Callout type="info" title="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.
</Callout>

### HoroscopeItem [#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 `HoroscopeItem`s themselves but wrappers carrying one extra datum each:

```rust
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**

```rust
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**

```text
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 26
```

***

## The three lists [#the-three-lists]

`decadal_list` / `yearly_list` / `monthly_list` are methods on `Astrolabe`, not on the horoscope object:
they return a whole scope in one call, sparing you from looping over dates with `horoscope` and
assembling the result yourself. Each item carries that scope's time coordinates on top of the shared
`HoroscopeItem`:

```rust
pub struct DecadalHoroscope {
    pub base: HoroscopeItem,
    pub palace_name: Palace,     // the natal palace this decadal sits in
    pub age_range: (u32, u32),   // first and last nominal age, both inclusive
    pub year_range: (i64, i64),  // first and last lunar year, both inclusive
}

pub struct YearlyHoroscope {
    pub base: HoroscopeItem,
    pub age: u32,                // the nominal age this year corresponds to
    pub year: i64,               // lunar year
}

pub struct MonthlyHoroscope {
    pub base: HoroscopeItem,
    pub age: u32,                // the nominal age this month falls in
    pub year: i64,               // lunar year
    pub month: u32,              // lunar month, 1 for the first; a leap month shares its number
    pub is_leap_month: bool,     // whether this item is the leap month
    pub part: MonthPart,         // Normal whole month / First leap first half / Second leap second half
    pub day_range: (u32, u32),   // the lunar days this segment covers, both inclusive
}
```

All three implement `Deref<Target = HoroscopeItem>`, so the shared fields read straight through
(`d.heavenly_stem`, `d.palace_names`); take `.base` when you want the whole `HoroscopeItem`.
The four types and `MonthPart` are all re-exported from the crate root.

<Callout type="info" title="The lists use the hour pillar's branch">
  Every item in the three lists is computed from the hour matching the **hour pillar's earthly branch**,
  not the `time_index` passed at charting time. The two differ only for the late zi hour: on a chart
  built with index 12, the hour pillar's branch is zi, so the lists use hour index 0.
</Callout>

***

## decadal\_list [#decadal_list]

**Purpose** Get all twelve decadals of this chart at once, ordered by when they begin.

**Zi Wei meaning** A decadal is a ten-year step walking the twelve palaces from the starting palace.
Laid out flat, the line tells you which palace a given stretch of life falls in and which ten years it covers.

**Signature**

```rust
pub fn decadal_list(&self) -> Vec<DecadalHoroscope>
```

**Return value** Always 12 items; index 0 is the first decadal. The order follows the starting nominal
age, not the palace index — decadals run forwards or backwards depending on yin/yang and gender.

**Example**

```rust
let en = Language::EnUS;

for d in chart.decadal_list().iter().take(3) {
    println!("{} {} {} {}-{} {}-{}",
        translate_palace(d.palace_name, en),
        translate_heavenly_stem(d.heavenly_stem, en),
        translate_earthly_branch(d.earthly_branch, en),
        d.age_range.0, d.age_range.1, d.year_range.0, d.year_range.1);
}
println!("{}", chart.decadal_list().len());
```

**Output**

```text
soul ren woo 3-12 2002-2011
siblings xin si 13-22 2012-2021
spouse geng chen 23-32 2022-2031
12
```

This chart starts its decadals at nominal age 3, with the first one in the Soul palace.

**Edge cases and pitfalls**

<Callout type="info" title="Childhood is not in the list">
  The years before the first decadal begins are the childhood scope, which only appears when you query
  `horoscope` for a specific date (`name_key` is `Childhood`). `decadal_list` lists the twelve decadals
  themselves, so every item's `name_key` is `Decadal`.
</Callout>

***

## yearly\_list [#yearly_list]

**Purpose** Get every yearly scope inside one decadal.

**Zi Wei meaning** Fix the decadal, then read year by year — the usual order in which a reading advances.
A decadal spans ten years, and these are those ten.

**Signature**

```rust
pub fn yearly_list(&self, target: impl Into<DecadalTarget>) -> Result<Vec<YearlyHoroscope>, IztroError>
```

**Parameters**

| Parameter | Type                       | Required | Default | Description                                                                                |
| --------- | -------------------------- | -------- | ------- | ------------------------------------------------------------------------------------------ |
| `target`  | `impl Into<DecadalTarget>` | Yes      | —       | The decadal's ordinal (`usize`, 0 for the first) or the natal palace it sits in (`Palace`) |

```rust
pub enum DecadalTarget {
    Ordinal(usize),  // ordinal by starting age
    Name(Palace),    // the natal palace this decadal sits in
}
```

When both forms address the same decadal they return identical results; pick whichever you already hold.

**Return value** `Result<Vec<YearlyHoroscope>, IztroError>` with 10 items ordered by nominal age.
An out-of-range ordinal or an unmatched palace name returns `Err`.

**Example**

```rust
let en = Language::EnUS;
let list = chart.yearly_list(2usize)?;

for y in list.iter().take(3) {
    println!("{} {} {} {} -> {}", y.age, y.year,
        translate_heavenly_stem(y.heavenly_stem, en),
        translate_earthly_branch(y.earthly_branch, en), y.index);
}
println!("{} {}", list.len(), chart.yearly_list(Palace::Spouse)?.len());
```

**Output**

```text
23 2022 ren yin -> 0
24 2023 gui mao -> 1
25 2024 jia chen -> 2
10 10
```

Decadal 2 sits in the spouse palace, so `yearly_list(2)` and `yearly_list(Palace::Spouse)` name the same one.

**Edge cases and pitfalls**

<Callout type="warn" title="There is no default decadal">
  One of the two addressing forms is mandatory; there is no "omit it and get the current decadal" behaviour.
  Silently picking a default would turn a forgotten argument into a plausible-looking wrong answer.
</Callout>

***

## monthly\_list [#monthly_list]

**Purpose** Get every monthly scope of one lunar year.

**Zi Wei meaning** The monthly scope sits below the yearly one and advances month by month. How to treat
a leap month is a point where schools disagree, so whether to split it is the caller's decision.

**Signature**

```rust
pub fn monthly_list(&self, year: i64, fix_leap: bool) -> Result<Vec<MonthlyHoroscope>, IztroError>
```

**Parameters**

| Parameter  | Type   | Required | Default | Description                                             |
| ---------- | ------ | -------- | ------- | ------------------------------------------------------- |
| `year`     | `i64`  | Yes      | —       | The lunar year                                          |
| `fix_leap` | `bool` | Yes      | —       | Whether to split a leap month into two half-month items |

**Return value** `Result<Vec<MonthlyHoroscope>, IztroError>`; the length depends on whether the year has a
leap month and on `fix_leap`:

| That lunar year  | `fix_leap` | Items | How the leap month is laid out                                    |
| ---------------- | ---------- | ----- | ----------------------------------------------------------------- |
| No leap month    | either     | 12    | —                                                                 |
| Has a leap month | `true`     | 14    | Split into `First` (days 1-15) and `Second` (day 16 to month end) |
| Has a leap month | `false`    | 13    | One whole-month item with `part` set to `Normal`                  |

The leap month follows the ordinary month of the same number. Each item is computed from the first day of
its segment (day 16 for a second half).

**Example**

```rust
let en = Language::EnUS;
let list = chart.monthly_list(2020, true)?;

println!("{}", list.len());
for m in list.iter().skip(3).take(4) {
    println!("{} {} {} {}-{} {} {}", m.month, m.is_leap_month, m.part.as_key(),
        m.day_range.0, m.day_range.1,
        translate_heavenly_stem(m.heavenly_stem, en),
        translate_earthly_branch(m.earthly_branch, en));
}
println!("{} {}", chart.monthly_list(2020, false)?.len(), chart.monthly_list(2021, true)?.len());
```

**Output**

```text
14
4 false normal 1-30 xin si
4 true first 1-15 xin si
4 true second 16-29 ren woo
5 false normal 1-30 ren woo
13 12
```

Lunar 2020 has a leap fourth month. Split apart, its first half shares the pillar of the fourth month
(xin si) and its second half shares the fifth month's (ren woo) — which is what "the second half of a leap
month counts as the next month" means. Lunar 2021 has no leap month, so it is always 12 items.

**Edge cases and pitfalls**

<Callout type="warn" title="This fix_leap is unrelated to the charting fix_leap">
  The `fix_leap` on the charting entry decides whether someone born in the second half of a leap month is
  placed by the next month's stars — it changes the natal layout. This one only decides whether this list
  splits the leap month. The two may differ and do not affect each other.
</Callout>

<Callout type="info" title="A leap month shares its number">
  The leap fourth month also has `month == 4`; `is_leap_month` is what tells them apart. Deduplicating by
  `month` drops the leap month.
</Callout>

***

## age\_palace [#age_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**

```rust
pub fn age_palace(&self) -> PalaceRef<'a>
```

**Return value** `PalaceRef` — a palace on the natal chart, always present.

**Example**

```rust
let h = chart.horoscope("2025-6-1", 0)?;
println!("{}", translate_palace(h.age_palace().name, Language::EnUS));
```

**Output**

```text
property
```

***

## palace [#palace]

**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**

```rust
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**

```rust
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**

```text
the decadal Soul palace is the natal spouse
the natal Soul palace is soul
```

**Edge cases and pitfalls**

<Callout type="info" title="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]`.
</Callout>

***

## surround\_palaces [#surround_palaces]

**Purpose** Get the surrounded palaces of a palace under a given horoscope scope.

**Signature**

```rust
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](/en/docs/rust/surpalaces).

**Example**

```rust
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**

```text
the surrounded set of the yearly Wealth palace is anchored on the natal health
```

***

## has\_horoscope\_stars / has\_one\_of\_horoscope\_stars / not\_have\_horoscope\_stars [#has_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**

```rust
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]) -> bool
```

**Parameters**

| 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**

```rust
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**

```text
false
false
true
```

**Edge cases and pitfalls**

<Accordions>
  <Accordion title="scope picks the palace, not the set of stars compared against">
    The three methods use `scope` + `name` to locate one cell on the natal chart, but the set of stars
    compared against is always the **union of the decadal and yearly scope stars**, regardless of `scope`.

    So with `scope` set to `Monthly` the question is "does this cell, named as a monthly palace, hold any
    decadal or yearly scope star?" — not the monthly scope's own stars. The scope stars of the monthly,
    daily and hourly layers take no part in the comparison here.
    For a layer's scope-star distribution, use a field like `h.monthly.stars`, or
    [`get_horoscope_stars`](/en/docs/rust/star#get_horoscope_stars).
  </Accordion>

  <Accordion title="Scope star keys are layer-specific">
    The decadal scope stars are `Yunlu`, `Yunyang` and so on, the yearly ones `Liulu`, `Liuyang` and so
    on; the two groups have different key names. Since the compared set is always the union of those two
    groups, both `Yunlu` and `Liulu` can be found under any `scope` — only their palaces differ.
    The per-layer key table is on [Star placement](/en/docs/rust/star#get_horoscope_stars).
  </Accordion>

  <Accordion title="With Origin as the scope the cell is located on the natal palaces">
    `Origin` walks the natal twelve palaces, so a palace is still located; there simply are no scope stars
    on the natal chart, and what is compared remains whichever decadal and yearly scope stars fall on that
    cell.
  </Accordion>
</Accordions>

***

## has\_horoscope\_mutagen [#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**

```rust
pub fn has_horoscope_mutagen(&self, name: Palace, scope: Scope, mutagen: Mutagen) -> bool
```

**Parameters**

| 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**

```rust
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**

```text
false
["sun", "warrior", "moon", "fortunate"]
```

The decadal stem is geng, and geng sends lu to Taiyang (`sun`), quan to Wuqu (`warrior`), ke to Taiyin
(`moon`) and ji to Tiantong (`fortunate`).

**Edge cases and pitfalls**

<Callout type="warn" title="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`](/en/docs/rust/palace#has_mutagen--not_have_mutagen).
</Callout>

***

## astrolabe / data / into\_data [#astrolabe--data--into_data]

**Purpose** Get back to the natal chart, or take out the horoscope's plain data.

**Signature**

```rust
pub fn astrolabe(&self) -> &'a Astrolabe
pub fn data(&self) -> &HoroscopeData
pub fn into_data(self) -> HoroscopeData
```

**Return 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**

```rust
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**

```text
2000-8-16
2025-6-1
```

***

## to\_text [#to_text]

**Purpose** The horoscope's semantic text: a complete description for language models and people,
as a Markdown subset.

**Signature**

```rust
pub fn to_text(&self) -> String
```

Defined 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` — one section each for the decadal (childhood before the decadals
start), age fortune, yearly, monthly, daily and hourly scopes, each with its mutagens and the
patterns and flowing stars from its own perspective; the decadal and yearly sections expand into a
twelve-palace table. The full format is on [Semantic text](/en/docs/guide/guides/to-text).

**Example**

```rust
let h = chart.horoscope("2025-1-1", 0)?;

for line in h.to_text().lines().take(5) {
    println!("{line}");
}
```

**Output**

```text
# Horoscope 2025-1-1 (二〇二四年腊月初二)

## Decadal Fortune · Soul Palace: Natal spouse (geng chen)
- Mutagen: sun [A]→Natal children, warrior [B]→Natal wealth, moon [C]→Natal friends, fortunate [D]→Natal health
- Patterns: Marshal, Rebel and Wolf (soul), Meeting of Wind and Cloud (soul)
```

***

## to\_text\_with [#to_text_with]

**Purpose** The same text as `to_text`, with readings attached per
[`TextOptions`](/en/docs/rust/astro#textoptions): each scope's table or facts is followed by
readings of that scope's flowing stars and of the patterns hit from its perspective, deduplicated
across scopes. Natal star readings are not repeated here — they are in the chart's `to_text_with`.

**Signature**

```rust
pub fn to_text_with(&self, opts: &TextOptions) -> String
```

**Parameters**

| Parameter | Type           | Required | Default | Description                                                                                                   |
| --------- | -------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------- |
| `opts`    | `&TextOptions` | yes      | —       | Output options; `TextOptions::new().knowledge(pack)` adds readings, `TextOptions::default()` equals `to_text` |

Defined on `HoroscopeRef` and emitting in the chart's charting language; for an explicit language
use the free function `text::horoscope_to_text_with(astrolabe, horoscope, opts, lang)`. Placement
is on [Text with readings](/en/docs/guide/guides/to-text#text-with-readings).

**Example** (on a zh-CN chart, since the bundled pack is Chinese)

```rust
let pack = KnowledgePack::builtin(Language::ZhCN).unwrap();
let h = chart.horoscope("2025-1-1", 0)?;
let text = h.to_text_with(&TextOptions::new().knowledge(pack));

println!("{} {}", h.to_text().chars().count(), text.chars().count());
for line in text.lines().filter(|l| l.starts_with("## ")) {
    println!("{line}");
}
```

**Output**

```text
2457 8458
## 大限 · 命宫: 本命夫妻 (庚辰)
## 小限 · 命宫: 本命官禄 · 虚岁 25
## 流年 · 命宫: 本命夫妻 (甲辰)
## 流月 · 命宫: 本命仆役 (丁丑)
## 流日 · 命宫: 本命迁移 (庚午)
## 流时 · 命宫: 本命迁移 (丙子)
```

***

## to\_dto [#to_dto]

**Purpose** Convert the horoscope data into the serialization structure that matches the JS iztro
field contract.

**Signature**

```rust
pub fn to_dto(&self, lang: Language) -> HoroscopeDto
```

**Parameters**

| 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**

```rust
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**

```text
"2025-6-1" "geng"
26
```
