Skip to content

Chronology sources

Scholars disagree about 上古 (pre-Qin) dating. The disagreement is real scholarship, not data error, so this library does not settle it for you — it lets you pick.

import chhiskit

chhiskit.get_age_from_cultural_period("夏", level="epoch")                 # → (-2070.0, ...)
chhiskit.get_age_from_cultural_period("夏", level="epoch", source="shl")   # → (-1989.0, ...)
chhiskit.get_age_from_cultural_period("夏", level="epoch", source="wgd")   # → (-2100.0, ...)

chhiskit.set_default_source("shl")   # or set a preference once

Available sources

source Reference Year Covers
"shl" 上海图书馆开放数据平台 2024 Everything — 朝代 / 年号 / 帝王. The base table.
"xsz" 夏商周断代工程 2000 夏 / 商 / 周 / 东周 only
"wgd" 万国鼎《中国历史纪年表》(中华书局) 1956 国号 / 朝代-level spans only (74 entries)
"default" alias Whatever set_default_source was last given; initially "xsz"

Call chhiskit.list_sources() to get this table at runtime.

How sources compose

Sources are overlays, not replacements. 上图 is the only source with full coverage, so it is always the base; a selected source overrides the spans it knows about and everything else falls back to 上图.

This matters because coverage differs wildly. 断代工程 pronounces on four polities; 万国鼎's parseable index covers 74; neither says anything about 年号:

chhiskit.get_age_from_cultural_period("康熙", source="wgd")
# → (1662.0, 1722.0)   ← 上图 data; 万国鼎's index carries no 年号 spans
# SourceFallbackWarning: source='wgd' has no entry for '康熙'; falling back to 上图 base data

That warning fires whenever a source you asked for by name had nothing to say. The answer is still correct — it just isn't from the source you named, and saying so silently would misrepresent where the numbers came from. It is not raised for the default source (whose fallback is by design) or for "shl" (which is the base).

Overlays only apply to dynasty-level rows. A source saying 「清 1636~1911」 is making a claim about the polity, not about each of its 年号 — so it never rewrites 康熙. One consequence: level="dynasty" unions a polity's rows, so where 上图 has an era row starting earlier than the overlay (清's 天命 1616 vs 万国鼎's 1636 国号改称), the union still reports the earlier year.

Where they disagree

上图 (shl) 万国鼎 (wgd) 断代工程 (xsz)
-1989 ~ -1559 約-2100 ~ -1600 -2070 ~ -1600
-1559 ~ -1123 約-1600 ~ -1028 -1600 ~ -1046
-1123 ~ -256 -1027 ~ -256 -1046 ~ -256
西周 -1046 ~ -771 -1027 ~ -771 -1046 ~ -771
东周 -770 ~ -221 -770 ~ -256 -770 ~ -256
春秋 -770 ~ -477 -770 ~ -481 (not covered)
战国 -476 ~ -221 -480 ~ -222 (not covered)

A full three-way comparison, covering the differences outside 上古 as well, is kept in the repository at data/dynasties/chronology_crosscheck.md.

Why xsz is the default

Two reasons, both about 上图 rather than about who is right:

  1. It closes a silent hole. 新石器 ends at -2070 — already the 断代工程 figure — but 上图 starts 夏 at -1989, so -2069..-1990 matched nothing.
  2. 上图 is internally split. Its 周 begins -1123 on the older dating while its own 西周 begins -1046 on 断代工程's. One table, two chronologies.

Anchoring the ancient rows makes one table tell one story. Note what this claim is not: 断代工程's 夏 -2070 is not universally accepted (its precision is widely disputed outside China). It is canonical for this dataset, not correct in any final sense — which is exactly why the source stays selectable.

source="shl" returns the 上图 record unmodified, and is pinned by tests to keep returning exactly what this library published before sources existed.

东周: an honest exception

xsz.csv carries 东周 -770 ~ -256, but its note says plainly that this is not a 断代工程 finding. 断代工程's new results concern the pre-841 BCE era; 东周 rests on traditional dating that was never in doubt.

It is included because 万国鼎 and traditional chronology agree on -256 (周赧王 卒 / 秦灭东周) against 上图's -221, which appears to conflate 东周 with 战国. Attributing it to 断代工程 would have been fabricated provenance — worse than a wrong year.

春秋 and 战国 are deliberately not overlaid: 断代工程 doesn't cover them, and 春秋's end year is itself a three-way dispute (前481 获麟 / 前476 / 前453). Their differences are reported, not resolved.

Knowing where an answer came from

SourceFallbackWarning is the answer to "did this number actually come from the source I asked for?" — it fires exactly when it did not. To treat a fallback as an error rather than a warning:

import warnings
from chhiskit import SourceFallbackWarning

with warnings.catch_warnings():
    warnings.simplefilter("error", SourceFallbackWarning)
    chhiskit.get_age_from_cultural_period("夏", level="epoch", source="wgd")   # fine
    chhiskit.get_age_from_cultural_period("康熙", source="wgd")                # raises

This is useful when a result must be attributable to one specific reference — an analysis that cites 断代工程, say, should not silently mix in 上图 spans.