日本語 · English(未訳)

表現力検証 3: 天文データ依存の暦(旧暦・二十四節気・月相)と計算暦(イースター)

表現力検証の第 3 部。周期規則から生成できない(天文データが要る)暦と、複雑だが純粋に 計算できる暦(イースター)を対にして、「規則とデータの境界」を言語がどう受けるかを試す。 設計上の受け皿は確認済み: ADR-07 は「月相のストリームで区切れば太陰暦」と明言し、segmentBy が 任意マーカーで窓を切る。欠けているのはマーカーデータの持ち込み口である(01 §1.3 の F5 と同根)。


3.1 二十四節気

仕様: 太陽黄経が 15° の倍数を通過する瞬間(立春 315°・春分 0°・夏至 90°・秋分 180°・冬至 270° …)。 不等間隔(ケプラー運動)で、周期規則からは生成できない。暦要項(国立天文台)が権威データ。 期待値(NAOJ 令和8年暦要項・95-reference-data.md): 立春 2026-02-04 05:02、春分 03-20 23:46、 夏至 06-21 17:25、秋分 09-23 09:05、冬至 12-22 05:50。時刻を持つ点であることに注意(F21 の動機)。

premise Sekki24 {
  source: "nao.ac.jp/koyomi"        # 暦要項
  asof:   2025-02-03                # 令和8年暦要項の告示日

  # 節気の瞬間列(テーブルリテラル・実データ。時刻は暦要項の値。24 個の全列は 95-reference-data.md)
  sekkiInstants = [2026-01-05T17:23, 2026-01-20T10:45, 2026-02-04T05:02, 2026-02-19T00:52,
                   2026-03-05T22:59, 2026-03-20T23:46, 2026-04-05T03:40, 2026-04-20T10:39,
                   2026-05-05T20:49, 2026-05-21T09:37, 2026-06-06T00:48, 2026-06-21T17:25,
                   2026-07-07T10:57, 2026-07-23T04:13, 2026-08-07T20:43, 2026-08-23T11:19,
                   2026-09-07T23:41, 2026-09-23T09:05, 2026-10-08T15:29, 2026-10-23T18:38,
                   2026-11-07T18:52, 2026-11-22T16:23, 2026-12-07T11:53, 2026-12-22T05:50]

  # 日への落とし込み: 瞬間を含む市民日(暫定 snapTo。F21)
  sekkiDays = sekkiInstants |> snapTo(day)
}

# 利用例: 節気で区切った窓と、その第 1 日。labels: が節気名を窓に結ぶ(ADR-39——リスト長=窓数を検査)
@Sekki24
sekkiW = everyDay |> segmentBy(sekkiDays, edges: drop, empties: error,
                               labels: ["小寒", "大寒", "立春", "雨水", "啓蟄", "春分", "清明", "穀雨",
                                        "立夏", "小満", "芒種", "夏至", "小暑", "大暑", "立秋", "処暑",
                                        "白露", "秋分", "寒露", "霜降", "立冬", "小雪", "大雪", "冬至"])
sekkiW |> first

判定: 要外部データ(テーブルリテラル F5)。窓化の受け皿 segmentBy は仕様通り機能する。 実データ(時刻つき 24 点)を流し込んで期待値が再現できることを確認済み(95-reference-data.md)。

綻び:

3.2 旧暦(天保暦様式の太陰太陽暦)

仕様: 朔(新月)の瞬間を含む日が月の第 1 日(朔日)。朔から次の朔の前日までが一月(29 日=小の月/ 30 日=大の月)。月名は「中気」(二十四節気の偶数番目。雨水・春分・穀雨…)を含むかで決まり、中気を 含まない月は閏月(前月の名を継いで「閏 N 月」)。 期待値: 2026-02-17 が旧暦 1 月 1 日(旧正月)。実データで裏づけ済み——朔 2026-02-17 21:01 が その根拠(95-reference-data.md。朔の瞬間を含む日=2/17 が朔日)。

premise Lunisolar {
  source: "nao.ac.jp/koyomi"
  asof:   2025-02-03

  # 朔の瞬間列(実データ・12 回。NAOJ 令和8年暦要項)
  newMoons  = [2026-01-19T04:52, 2026-02-17T21:01, 2026-03-19T10:23, 2026-04-17T20:52,
               2026-05-17T05:01, 2026-06-15T11:54, 2026-07-14T18:44, 2026-08-13T02:37,
               2026-09-11T12:27, 2026-10-11T00:50, 2026-11-09T16:02, 2026-12-09T09:52]
  # 中気=黄経が 30 の倍数(雨水330・春分0・穀雨30…)。暦年順の先頭は小寒(節気)なので
  # stride(2) は起点をずらさないと節気を拾う(実データ投入で判明。F32)。黄経ラベルで filter するのが厳密。
  chukiDays = (Sekki24.sekkiInstants |> stride(2, from: <大寒>) |> snapTo(day))

  # 月番号: 「含む中気」で決まる(雨水を含む月=1 月)。中気を含まなければ閏月(前月名を継ぐ)——
  # 導出はデータ側(95-reference-data.md)に置き、窓には labels: で番号列を結ぶ
  # (F23 → ADR-34〈label: ラムダ〉+ADR-39〈labels: リスト〉で解消。リスト長=窓数の同長検査つき)
  monthNos   = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

  # 月窓: 朔日で day 列を区切る。読みは束縛名射影 lunarMonth(d) = 月番号
  lunarMonth = day |> segmentBy(newMoons |> snapTo(day), edges: drop, empties: error, labels: monthNos)
}

判定: 要外部データ+要補完。骨格(朔で切る)は ADR-07 の想定通り segmentBy 一発で書けた。 大小の月は窓幅の帰結として自動で出る(数える必要すらない)。閏月の判定(中気を含まない)も 窓とストリームの交差なので既存概念で表せる。書けないのは月名の付与である。

綻び:

3.3 雑節(八十八夜・二百十日・彼岸)

仕様: 立春から数えて 88 日目の夜=八十八夜(立春+87 日)、210 日目=二百十日(立春+209 日)。 彼岸は春分・秋分を中日とする前後 3 日ずつの 7 日間。 期待値: 2026 年の八十八夜 05-02、二百十日 09-01。春彼岸 2026-03-17〜03-23。

@Sekki24
# 立春=黄経 315°。だが暦年の最初の節気は小寒(1/5)なので nth(1, of: year) は小寒を返す(実データで判明・F33)。
# 節気を名前で一意に選ぶには窓ラベル(§3.1 の sekkiW=labels: 付き segmentBy)の束縛名射影を使う:
risshun     = sekkiDays |> filter(s => sekkiW(s) == "立春")  # 束縛名射影で一意(2026 は 2/4。F48 で字面統一)
hachijuhachiya = risshun |> shift(+87, unit: day)     # 立春 2/4 → 5/2(実データの八十八夜 5/2 と一致)
nihyakutoka    = risshun |> shift(+209, unit: day)    # 立春 2/4 → 9/1(実データの二百十日 9/1 と一致)

# 彼岸(中日±3 日の 7 日間): 固定幅の和で列挙
higan(mid) = mid | (mid |> shift(-1, unit: day)) | (mid |> shift(-2, unit: day)) | (mid |> shift(-3, unit: day))
                 | (mid |> shift(+1, unit: day)) | (mid |> shift(+2, unit: day)) | (mid |> shift(+3, unit: day))

判定: 節気データがあれば書ける。shift の素直な適用で、雑節は「節気からの派生値」だと確かめられた。

綻び:

3.4 月齢・月相

仕様: 満月・上弦・下弦・新月(月相イベント)と、月齢 n の日(朔からの経過日数)。 期待値: 2026 年最初の満月は 01-03(望 2026-01-03 19:03。実データ照合済み・95-reference-data.md)。

premise Moon {
  source: "nao.ac.jp/koyomi"
  asof:   2025-02-03
  # 望(満月)の瞬間列(実データ・13 回。NAOJ 令和8年暦要項)
  fullMoons = [2026-01-03T19:03, 2026-02-02T07:09, 2026-03-03T20:38, 2026-04-02T11:12,
               2026-05-02T02:23, 2026-05-31T17:45, 2026-06-30T08:57, 2026-07-29T23:36,
               2026-08-28T13:19, 2026-09-27T01:49, 2026-10-26T13:12, 2026-11-24T23:54,
               2026-12-24T10:28]
}

@Moon
fullMoonDays = fullMoons |> snapTo(day)

# 月齢 n の日: 朔日から n 日後(Lunisolar の朔データを再利用)
moonAge(n) = (Lunisolar.newMoons |> snapTo(day)) |> shift(+n, unit: day)

判定: 要外部データ。「月齢」という連続量そのものは点列言語の外(射影値)で、スケジュールとして 意味を持つのはイベント化した列(満月の日・月齢 15 の日)——これは既存語彙で書ける。連続量→ イベント列への還元は利用者ではなくデータ供給側(暦要項が満月の瞬間を出す)の仕事、と整理できた。

綻び: 新規なし(F5・F21 に依存するのみ。「連続量はイベント化してから持ち込む」を テーブルリテラルの設計原則として記録)。

「計算できないから外部データ」ではない——できるが、あえて取らない(2026-07-11 追記)

外部データ供給は能力の欠如ではなく真正性の統治(ADR-15)の帰結である。「月相の計算」は三段に 割れ、切り分けるとこの判断の論拠が立つ:

(1)平均朔望月の算術近似は、今日の確定語彙でそのまま書ける(規則 premise・外部データゼロ・ 暦法純粋 I8)。朔望月の平均 29.530589 日=経過時間幅 708h44m3s:

# eval: 2026-01-01..2027-01-01
premise MeanMoon {
  calendar-system: Gregorian
  tz: "Asia/Tokyo"
  # 平均朔望月による朔の近似。錨は既知の朔(2000-01-06T18:14 UTC = JST 翌 03:14)
  meanNewMoons = everyInstant |> strideBy(708h44m3s, from: 2000-01-07T03:14)
}
premise J { calendar-system: Gregorian; tz: "Asia/Tokyo"; wkst: Mon }
@J
MeanMoon.meanNewMoons |> snapTo(day)
#=> 2026-01-18 2026-02-17 2026-03-19 2026-04-17 2026-05-17 2026-06-15
#=> 2026-07-15 2026-08-13 2026-09-12 2026-10-11 2026-11-10 2026-12-09

NAOJ の朔日(95-reference-data)との照合は 8/12 一致・4 か月が ±1 日ずれ(真の朔は楕円軌道の 摂動で平均から最大 ±14 時間揺れる)——「書ける」と「官暦に合う」は別物であることを実行が示す。

(2)真の朔弦望(Meeus 級の天文アルゴリズム)は原理的には決定的計算だが、現在の値式では書けず、 この欠けは意図的。周期項の総和には三角関数が要り、値式は sin/cos を持たない(必然基準)。 足せば書けるが、60 項超の係数列を premise に並べる姿は実質「データの持ち込み」と同型であり、 さらに ΔT(地球自転の不規則減速)は観測値で予測不能——天文アルゴリズム自体が観測フィットの 近似模型で、遠未来の「純粋計算」は原理的に崩れる。

(3)官暦との一致は、どれだけ精密に計算しても買えない。旧暦の月切り・六曜・春節は「NAOJ が計算し 官報で告示した値」が正で、独自計算が 1 分ずれて日境界を跨げば別の暦になる(実例は記録済み—— 2025-08-23 の朔と処暑の同日競合〈95-reference-data〉・2027 春節の JST/CST 割れ)。

まとめ: 算術近似は書ける(上の実行例)・精密計算は sin/cos を足せば書けるが導入の必然が無い・ 公式暦への一致は計算では買えない——だから権威データを差し込む。スタンスは「できないから外部」 ではなく「できるが、真正性のために取らない」。

3.5 イースター(復活祭)— 計算可能な移動祝日

仕様: 春分後の最初の満月の次の日曜(教会暦の算法=Computus。天文データでなく純粋な算術で 確定する)。Anonymous Gregorian algorithm を値式で書けるかのストレステスト。 期待値: 2025-04-20、2026-04-05。

# 値レイヤー(§1.10)だけで書く Computus。y は暦年
a = y => y mod 19
b = y => y div 100
c = y => y mod 100
h = y => (19*a(y) + b(y) - b(y) div 4 - (b(y) - (b(y)+8) div 25 + 1) div 3 + 15) mod 30
l = y => (32 + 2*(b(y) mod 4) + 2*((y mod 100) div 4) - h(y) - (y mod 100) mod 4) mod 7
m = y => (a(y) + 11*h(y) + 22*l(y)) div 451
easterMonth = y => (h(y) + l(y) - 7*m(y) + 114) div 31          # 3 または 4
easterDay   = y => ((h(y) + l(y) - 7*m(y) + 114) mod 31) + 1

# 時点列への持ち上げ: 年ごとの (month, day) 値を日付に変える手段が無い(F28)
# 暫定案 1(値→時点の持ち上げ dateOf があれば):
#   easter = everyYear |> mapToDate(y => dateOf(y, easterMonth(y), easterDay(y)))
# 暫定案 2(射影値との突き合わせ。F18 のラベル射影があれば):
@JPX
easter = everyDay |> filter(d => monthNo(d) == easterMonth(yearNo(d)) and dayNo(d) == easterDay(yearNo(d)))

判定: 値計算は書ける・時点化で要補完moddiv・四則・入れ子の関数適用だけで Computus は 完全に書けた(値レイヤーの表現力は十分)。書けないのは最後の一歩=値 (y, m, d) から基底上の点への 持ち上げである。

綻び:

3.6 ISO 週番号(参考・サンプルのみ)

仕様: ISO 8601 の週番号(月曜始まり・その年の第 1 木曜を含む週が W01)。「2026-W01-1」のような 週ラベルの体系。 期待値: 2026-W01 は 2025-12-29(月)〜2026-01-04(日)。

# week 窓(wkst: Mon)自体は §1.14 暫定で立つ。問題は「どの週が W01 か」=週への年・番号ラベル付与
# = F23(窓ラベル付与)+年度ラベルの射影(90-open-questions の既存宿題)と同一問題。機構設計は宿題送り。

判定: 要補完(F23 と年度ラベル射影に依存)。週窓そのものは書ける。番号付けは旧暦の月名付け (3.2)・年度ラベルと同じ「窓→値」問題の一族だと確認した——三例が同じ機構に還元されるのは 補完機構(§1.17)の設計妥当性の傍証。


小括

判定 依存する暫定/欠落
二十四節気 要外部データ テーブルリテラル(F5)・snapTo(F21)・segmentBy 所属規約(F22)
旧暦 要外部データ+要補完 窓ラベル付与(F23)・隣接窓参照(F24)・層またぎ規則(F25)
雑節 節気があれば書ける 範囲 shift(F26)・裸ストリームへの of:(F27)
月齢・月相 要外部データ 「連続量はイベント化して持ち込む」原則で整理済み
イースター 値は書ける・時点化で要補完 値→時点の持ち上げ or 点→値の射影(F28)・値束縛の置き場(F29)
ISO 週番号 要補完 F23+年度ラベル射影(既存宿題と同一)

天文暦の壁は予想通りデータの持ち込み口(テーブルリテラル)に集約され、窓化の受け皿 (segmentBy)は仕様の想定通り機能した。予想外の収穫は三つ——(1) snap/所属規約という「粒度の 継ぎ目」の未定義(F21/F22)、(2) 旧暦の月名・ISO 週番号・年度ラベルが同一の「窓→値の射影」問題に 還元されること(F23)、(3) 値レイヤーは Computus 級の計算に耐えるが基底との出入口(射影・ 持ち上げ)が無いこと(F28)。