Horoscope object
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.
h, _ := chart.Horoscope("2025-6-1", 0)Horoscope holds the natal chart that produced it, so none of the query methods need the astrolabe
passed in again.
The examples on this page start from an "en-US" natal chart, so the display values in the output are
English.
Fields
| Field | Type | Span | Description |
|---|---|---|---|
SolarDate / LunarDate | string | — | The solar string and the Chinese lunar spelling of the target date. The birth date lives on the natal chart; read it from h.Astrolabe().SolarDate |
Decadal | HoroscopeScope | Ten years | The decadal scope; the childhood scope for the years before it begins |
Age | HoroscopeScope | One year | The age scope, moving one palace per nominal year |
Yearly | HoroscopeScope | One year | The yearly scope, its palace fixed by the year's pillar |
Monthly | HoroscopeScope | One month | The monthly scope |
Daily | HoroscopeScope | One day | The daily scope |
Hourly | HoroscopeScope | 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.
HoroscopeScope
| Field | Type | Description |
|---|---|---|
Index | int | Which palace this scope lands on (a palace index) |
Name | string | Display name of the scope, translated into the output language |
NameKey | string | Scope key: decadal / childhood (before the decadals begin) / turn (age fortune) / yearly / monthly / daily / hourly. Predicate on this, never on the translation |
HeavenlyStem / HeavenlyStemKey | string | Stem of the scope, which determines the mutagens it flies |
EarthlyBranch / EarthlyBranchKey | string | Branch of the scope |
PalaceNames / PalaceNameKeys | []string | The twelve palace names re-derived with this scope's palace as the Soul palace, indexed by palace index |
Mutagen / MutagenStarKeys | []string | The stars this scope's stem transforms, in the order lu, quan, ke, ji; MutagenStarKeys holds the mutated stars' star keys, synonymous with the palace field of the same name |
Stars | [][]Star | The scope stars of this layer; nil for layers that have none |
NominalAge | int | Age scope only: the nominal age. 0 on every other layer |
YearlyDecStar | *YearlyDecStar | Yearly scope only: the Sui-qian and Jiang-qian gods. nil on every other layer |
type YearlyDecStar struct {
Suiqian12 []string `json:"suiqian12"` // Sui-qian gods, translated, by palace index
Suiqian12Keys []string `json:"suiqian12Keys"` // their keys
Jiangqian12 []string `json:"jiangqian12"` // Jiang-qian gods, translated
Jiangqian12Keys []string `json:"jiangqian12Keys"` // their keys
}All six scopes share one type
Rather than giving the age and yearly scopes types of their own, the Go side puts NominalAge and
YearlyDecStar into the shared HoroscopeScope, where the other layers leave them at their zero
values. Call sites can therefore write generic logic parameterized by scope.
YearlyDecStar is a pointer and is nil outside the yearly scope
h.Decadal.YearlyDecStar is nil, and reading a field on it panics.
Only h.Yearly.YearlyDecStar is non-nil — check before you walk the layers.
h, _ := chart.Horoscope("2025-6-1", 0)
fmt.Println(h.Decadal.YearlyDecStar == nil, h.Yearly.YearlyDecStar != nil)
fmt.Println(h.Yearly.YearlyDecStar.Suiqian12[:3])
fmt.Println(h.Yearly.YearlyDecStar.Jiangqian12Keys[:3])
fmt.Println(h.Decadal.NominalAge, h.Age.NominalAge)Output
true true
[blessed sorrowing illness]
[jiesha zhaisha tiansha]
0 26PalaceIndexByName
func (item *HoroscopeScope) PalaceIndexByName(nameKeyOrName string) intLooks a palace index up by palace-name key, or by palace name in the current language, among the twelve palaces as re-derived for that scope; returns -1 when nothing matches.
h, _ := chart.Horoscope("2025-6-1", 0)
fmt.Println(h.Decadal.PalaceIndexByName(iztro.PalaceSoul))
fmt.Println(h.Decadal.PalaceIndexByName(iztro.PalaceWealth))
fmt.Println(h.Decadal.PalaceIndexByName("nosuch"))Output
2
10
-1It returns -1 rather than 0 — 0 is a legitimate palace index (the Yin palace).
Always check for a negative before indexing chart.Palaces with it.
Example
h, _ := chart.Horoscope("2025-6-1", 0)
for _, item := range []iztro.HoroscopeScope{h.Decadal, h.Monthly, h.Daily, h.Hourly} {
fmt.Printf("%s lands on palace %d with pillar %s%s\n",
item.Name, item.Index, item.HeavenlyStem, item.EarthlyBranch)
}
fmt.Println("age scope nominal age", h.Age.NominalAge)
fmt.Println("decadal mutagens", h.Decadal.Mutagen)
fmt.Println("yearly Sui-qian gods", h.Yearly.YearlyDecStar.Suiqian12[:3])Output
decadal lands on palace 2 with pillar gengchen
monthly lands on palace 3 with pillar renwoo
daily lands on palace 8 with pillar xinchou
hourly lands on palace 8 with pillar wuzi
age scope nominal age 26
decadal mutagens [sun warrior moon fortunate]
yearly Sui-qian gods [blessed sorrowing illness]The three lists
DecadalList / YearlyList / MonthlyList 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. All three item types embed HoroscopeScope, so the shared fields are
read directly; each adds its own time coordinates:
| Type | Extra fields | Description |
|---|---|---|
DecadalListItem | PalaceName / PalaceNameKey: string | The natal palace this decadal sits in |
AgeRange: [2]int | First and last nominal age, both inclusive | |
YearRange: [2]int | First and last lunar year, both inclusive | |
YearlyListItem | Age: int | The nominal age this year corresponds to |
Year: int | Lunar year | |
MonthlyListItem | Age: int | The nominal age this month falls in |
Year: int | Lunar year | |
Month: int | Lunar month, 1 for the first; a leap month shares its number | |
IsLeapMonth: bool | Whether this item is the leap month | |
Part: string | Segment marker, one of the MonthPart* constants | |
DayRange: [2]int | The lunar days this segment covers, both inclusive |
The three MonthPart* constants: MonthPartNormal for a whole month, MonthPartFirst for a leap
month's first half and MonthPartSecond for its second half.
Each of the three methods also has a *Context variant; ctx cancels the wait for a wasm instance.
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.
DecadalList
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
func (a *Astrolabe) DecadalList() ([]DecadalListItem, error)
func (a *Astrolabe) DecadalListContext(ctx context.Context) ([]DecadalListItem, error)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
dl, _ := chart.DecadalList()
for _, d := range dl[:3] {
fmt.Println(d.PalaceName, d.HeavenlyStem, d.EarthlyBranch, d.AgeRange, d.YearRange)
}
fmt.Println(len(dl))Output
soul ren woo [3 12] [2002 2011]
siblings xin si [13 22] [2012 2021]
spouse geng chen [23 32] [2022 2031]
12This chart starts its decadals at nominal age 3, with the first one in the Soul palace.
Edge cases and pitfalls
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 (NameKey is childhood). DecadalList lists the twelve decadals
themselves, so every item's NameKey is decadal.
YearlyList / YearlyListByPalace
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
func (a *Astrolabe) YearlyList(ordinal int) ([]YearlyListItem, error)
func (a *Astrolabe) YearlyListByPalace(nameKey string) ([]YearlyListItem, error)Both have a *Context variant.
Parameters
| Method | Parameter | Type | Description |
|---|---|---|---|
YearlyList | ordinal | int | The decadal's ordinal, 0 for the first; out of range returns an error |
YearlyListByPalace | nameKey | string | The key of the natal palace it sits in (PalaceSoul and friends); an empty or unknown key returns an error |
When both forms address the same decadal they return identical results; pick whichever you already hold.
Return value 10 items ordered by nominal age.
Example
years, _ := chart.YearlyList(2)
for _, y := range years[:3] {
fmt.Println(y.Age, y.Year, y.HeavenlyStem, y.EarthlyBranch, "->", y.Index)
}
byPalace, _ := chart.YearlyListByPalace(iztro.PalaceSpouse)
fmt.Println(len(years), len(byPalace))Output
23 2022 ren yin -> 0
24 2023 gui mao -> 1
25 2024 jia chen -> 2
10 10Decadal 2 sits in the spouse palace, so both calls name the same one.
Edge cases and pitfalls
There is no default decadal
YearlyListByPalace("") returns an error rather than falling back to some default decadal. Silently
picking a default would turn a forgotten argument into a plausible-looking wrong answer.
MonthlyList
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
func (a *Astrolabe) MonthlyList(lunarYear int, fixLeap *bool) ([]MonthlyListItem, error)
func (a *Astrolabe) MonthlyListContext(ctx context.Context, lunarYear int, fixLeap *bool) ([]MonthlyListItem, error)Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
lunarYear | int | Yes | — | The lunar year |
fixLeap | *bool | Yes | nil takes the core default (true) | Whether to split a leap month into two half-month items; pass iztro.Bool(false) to turn it off |
Return value The length depends on whether the year has a leap month and on fixLeap:
| That lunar year | fixLeap | Items | How the leap month is laid out |
|---|---|---|---|
| No leap month | any | 12 | — |
| Has a leap month | nil or Bool(true) | 14 | Split into MonthPartFirst (days 1-15) and MonthPartSecond (day 16 to month end) |
| Has a leap month | Bool(false) | 13 | One whole-month item with Part set to MonthPartNormal |
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
months, _ := chart.MonthlyList(2020, nil)
fmt.Println(len(months))
for _, m := range months[3:7] {
fmt.Println(m.Month, m.IsLeapMonth, m.Part, m.DayRange, m.HeavenlyStem, m.EarthlyBranch)
}
whole, _ := chart.MonthlyList(2020, iztro.Bool(false))
plain, _ := chart.MonthlyList(2021, nil)
fmt.Println(len(whole), len(plain))Output
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 12Lunar 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
This fixLeap is unrelated to the charting fixLeap
The fixLeap 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.
A leap month shares its number
The leap fourth month also has Month == 4; IsLeapMonth is what tells them apart. Deduplicating by
Month drops the leap month.
AgePalace
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
func (h *Horoscope) AgePalace() *PalaceReturn value *Palace — a palace on the natal chart.
Example
h, _ := chart.Horoscope("2025-6-1", 0)
fmt.Println(h.AgePalace().Name)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
func (h *Horoscope) Palace(nameKeyOrName string, scope string) *PalaceParameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
nameKeyOrName | string | Yes | — | The palace-name key or translated name to fetch |
scope | string | Yes | — | Which scope's twelve palaces to search |
Return value *Palace — a palace on the natal chart (the same cell carries different names under
different scopes). With ScopeOrigin, these are the natal twelve palaces. nil when nothing matches.
Example
h, _ := chart.Horoscope("2025-6-1", 0)
fmt.Println("the decadal Soul palace is the natal", h.Palace(iztro.PalaceSoul, iztro.ScopeDecadal).Name)
fmt.Println("the natal Soul palace is", h.Palace(iztro.PalaceSoul, iztro.ScopeOrigin).Name)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 returned palace object, 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.PalaceNames[index].
SurroundPalaces
Purpose Get the surrounded palaces of a palace under a given horoscope scope.
Signature
func (h *Horoscope) SurroundPalaces(nameKeyOrName string, scope string) *SurroundedPalacesParameters Same as Palace.
Return value *SurroundedPalaces; its predicates are on
Surrounded palaces.
Example
h, _ := chart.Horoscope("2025-6-1", 0)
sp := h.SurroundPalaces(iztro.PalaceWealth, iztro.ScopeYearly)
fmt.Println("the surrounded set of the yearly Wealth palace is anchored on the natal", sp.Target.Name)Output
the surrounded set of the yearly Wealth palace is anchored on the natal healthHasHoroscopeStars / HasOneOfHoroscopeStars / NotHaveHoroscopeStars
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
func (h *Horoscope) HasHoroscopeStars(nameKeyOrName string, scope string, stars []string) bool
func (h *Horoscope) HasOneOfHoroscopeStars(nameKeyOrName string, scope string, stars []string) bool
func (h *Horoscope) NotHaveHoroscopeStars(nameKeyOrName string, scope string, stars []string) boolParameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
nameKeyOrName | string | Yes | — | The palace name under that scope |
scope | string | Yes | — | The horoscope scope |
stars | []string | Yes | — | A slice of scope star keys, which must use that layer's names |
Return value
| Method | Meaning |
|---|---|
HasHoroscopeStars | All of them are present |
HasOneOfHoroscopeStars | At least one is present |
NotHaveHoroscopeStars | None is present |
Example
h, _ := chart.Horoscope("2025-6-1", 0)
fmt.Println(h.HasHoroscopeStars(iztro.PalaceSoul, iztro.ScopeDecadal, []string{"yunlu"}))
fmt.Println(h.HasOneOfHoroscopeStars(iztro.PalaceSoul, iztro.ScopeDecadal, []string{"yunlu", "yunyang"}))
fmt.Println(h.NotHaveHoroscopeStars(iztro.PalaceSoul, iztro.ScopeDecadal, []string{"yuntuo"}))Output
false
false
trueEdge cases and pitfalls
HasHoroscopeMutagen
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
func (h *Horoscope) HasHoroscopeMutagen(nameKeyOrName string, scope string, mutagenKey string) boolParameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
nameKeyOrName | string | Yes | — | The palace name under that scope |
scope | string | Yes | — | The horoscope scope |
mutagenKey | string | Yes | — | A mutagen key |
Return value bool. It checks whether the star transformed by that layer's stem is among the
major or minor stars of the target palace (adjective stars are not scanned).
Example
h, _ := chart.Horoscope("2025-6-1", 0)
fmt.Println(h.HasHoroscopeMutagen(iztro.PalaceSoul, iztro.ScopeDecadal, iztro.MutagenLu))
fmt.Println(h.Decadal.Mutagen)Output
false
[sun warrior moon fortunate]The decadal stem is geng, and geng sends Lu to Taiyang, Quan to Wuqu, Ke to Taiyin and Ji to Tiantong.
Edge cases and pitfalls
Always false when scope is ScopeOrigin
The natal layer has no "layer stem" — the natal mutagens are already marked on the stars' own
MutagenKey. HasHoroscopeMutagen(name, iztro.ScopeOrigin, m) therefore returns false outright,
which does not mean the natal chart lacks that mutagen. For natal mutagens use the palace's
HasMutagen.
ScopeItem / Astrolabe
Purpose Get the HoroscopeScope for a scope key, or get back to the natal chart.
Signature
func (h *Horoscope) ScopeItem(scope string) *HoroscopeScope
func (h *Horoscope) Astrolabe() *AstrolabeReturn value ScopeItem returns nil for ScopeOrigin or an unknown scope — the natal chart is
not a horoscope layer.
Example
h, _ := chart.Horoscope("2025-6-1", 0)
fmt.Println(h.ScopeItem(iztro.ScopeDecadal).Name)
fmt.Println(h.ScopeItem(iztro.ScopeOrigin))
fmt.Println(h.Astrolabe().SolarDate)Output
decadal
<nil>
2000-8-16Edge cases and pitfalls
ScopeItem is for writing generic logic parameterized by scope, which is tidier than a chain of
switch scope. Remember to check for nil.
ToText
Purpose The horoscope's semantic text: a complete description for language models and people.
Signature
func (h *Horoscope) ToText() (string, error)
func (h *Horoscope) ToTextContext(ctx context.Context) (string, error)Return value string — a Markdown subset in the chart's charting language: under the
# Horoscope <date> (<lunar date>) heading, one ## section each for the decadal (childhood
before the decadals start), age fortune, yearly, monthly, daily and hourly scopes; each section
says which natal palace that scope's Soul palace landed on and carries the scope's mutagens (with
landing palaces), flowing stars and pattern line from its own perspective; the decadal and yearly
sections also expand a twelve-palace table. The full format is on
Semantic text.
Example
h, _ := chart.Horoscope("2025-1-1", 0)
text, _ := h.ToText()
fmt.Println(strings.Join(strings.Split(text, "\n")[:5], "\n"))Output
# 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)ToTextWith
Purpose The text of ToText, with readings from a knowledge pack woven in according to
opts: after each scope's table or fact lines come the readings for that scope's flowing stars
and for the patterns hit from that scope's perspective; a flowing star or a pattern is read once
across scopes. Natal star readings are not here — they are in the chart's ToTextWith.
Signature
func (h *Horoscope) ToTextWith(opts TextOptions) (string, error)
func (h *Horoscope) ToTextWithContext(ctx context.Context, opts TextOptions) (string, error)Parameters
| Parameter | Type | Description |
|---|---|---|
opts | TextOptions | Output options. The Knowledge field names the source of readings: BuiltinKnowledge() takes the bundled default pack for the chart language, KnowledgeFrom(pack) uses the given pack; the PatternConfig field sets the criteria for each scope's pattern line and pattern readings; the zero value TextOptions{} behaves like ToText |
Where the readings are inserted and how they are deduplicated is on Text with readings.
Example (on a zh-CN chart, since the bundled pack is Chinese)
h, _ := chart.Horoscope("2025-1-1", 0)
plain, _ := h.ToText()
text, _ := h.ToTextWith(iztro.TextOptions{Knowledge: iztro.BuiltinKnowledge()})
fmt.Println(utf8.RuneCountInString(plain), utf8.RuneCountInString(text))
for _, l := range strings.Split(text, "\n") {
if strings.HasPrefix(l, "## ") {
fmt.Println(l)
}
}Output
2457 8458
## 大限 · 命宫: 本命夫妻 (庚辰)
## 小限 · 命宫: 本命官禄 · 虚岁 25
## 流年 · 命宫: 本命夫妻 (甲辰)
## 流月 · 命宫: 本命仆役 (丁丑)
## 流日 · 命宫: 本命迁移 (庚午)
## 流时 · 命宫: 本命迁移 (丙子)The two texts have identical ## sections: readings do not get sections of their own but sit
right after each scope.
BuiltinKnowledge() on a chart whose language has no bundled pack (only zh-CN has one) returns
ErrInvalidArgument.
For the pattern hits as text, see PatternsToText on Patterns.