Skip to content

Pack calendar definitions into per-year integers - #31

Merged
sugi merged 12 commits into
masterfrom
feat/packed-calendar-def
Jul 13, 2026
Merged

Pack calendar definitions into per-year integers#31
sugi merged 12 commits into
masterfrom
feat/packed-calendar-def

Conversation

@sugi

@sugi sugi commented Jul 13, 2026

Copy link
Copy Markdown
Owner

概要

旧暦カレンダーテーブルの内部表現を「1年 = 整数1個 (40bit bit-pack)」に全面刷新し、メモリ消費とロード時間を大幅に削減します。あわせて required_ruby_version を 2.3 に引き上げます。

ビットレイアウト (Wareki::Calendar::PACKED[西暦年 - YEAR_MIN])

bits 内容
0..21 その年の最初の月の初日のユリウス通日
22..34 月の大小マスク (bit i = i 番目の月が大の月 = 30日)
35 閏月を含む 13 ヶ月の年フラグ
36..39 閏月の月番号 (0 = なし)

明治5年12月 (グレゴリオ切替で2日打ち切り) のみ LAST_MONTH_DAYS 定数で特別扱い。

効果 (Ruby 2.7.8 実測)

指標
データ構造メモリ ~554KB / 8,570 オブジェクト (Struct×1428 + 配列×2856 + Hash) ~11.4KB / 配列1個 (1428×8B バッファ)
calendar_def ロード ~9.6ms ~0.3ms (~30x)
ソースサイズ 295KB 22.5KB

演算スループットは同等〜微増 (ホットパスは全 JD 域 521,542 日の roundtrip spec で担保)。

削除した API (互換なし・意図的)

Wareki::YEAR_DEFS / Wareki::YEAR_BY_NUM / Wareki::Year / Wareki::Utils.find_year。いずれも README 非記載の事実上内部 API のため 2.1.0 のマイナーバンプとし、ChangeLog に削除を明記しています。

検証

  • 旧実装との全域一致: 全 521,542 日の jd→和暦、全 (年, 月, 閏) の to_jd / last_day_of_month が完全一致
  • 生成データ検証: 更新した build-util/gen-jp-cal-def.rb (invariant チェック 8 種付き) の出力が移行用ブートストラップ値と Marshal 完全一致、再実行でバイト一致再現
  • スイート 101 examples green (2.7.8)、Ruby 2.3.8 で全ファイル構文 OK + parse/format スモーク通過
  • spec/calendar_spec.rb 追加: 境界 (JD_MIN/JD_MAX/年境界/閏月/明治5年12月) + 全域 roundtrip
  • マルチエージェントレビュー実施 (タスク毎レビュー + ブランチ全体レビュー)。指摘の dangling 参照 (parse_wikipedia.rb) は修正済み

備考

  • master の PR Generate era definitions from manakai data-locale #30 をマージ済み。Generate era definitions from manakai data-locale #30 由来の build-util/gen-era-def.rb も新 API に追随 (meiji_start の等価性 JD 2403357 を検証、再生成出力がコミット済み era_def.rb とバイト一致)
  • kyuureki-map.txt を .gitignore に追加 (再生成手順のフットガン防止)
  • CI マトリクスは 2.7.8 起点のまま (setup-ruby の古い Ruby 供給が不安定なため)。2.3 対応は手元スモークで担保

🤖 Generated with Claude Code

https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK

sugi and others added 10 commits July 13, 2026 17:18
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
Wareki::Calendar packs each year's lunisolar calendar data into a
single 40-bit integer, replacing the Struct-based YEAR_DEFS lookup
with a bootstrap that repacks the existing table. Later tasks will
swap the bootstrap for generated data.

Also grandfathers spec/calendar_spec.rb into the existing
RSpec/SpecFilePathFormat todo list, following the precedent already
set for the other top-level spec files describing namespaced
constants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
Switch common.rb, utils.rb, and date.rb to use the Wareki::Calendar
API instead of the raw YEAR_DEFS table, and remove the now-unused
Utils.find_year and YEAR_BY_NUM. Behavior is unchanged; spec/utils_spec.rb
is updated to exercise find_date_ary and Calendar::PACKED instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
Rewrite build-util/gen-jp-cal-def.rb to emit the new bit-packed
Wareki::Calendar constants (YEAR_MIN/YEAR_MAX/JD_MIN/JD_MAX/
LAST_MONTH_DAYS/PACKED) directly instead of the old Struct-based
YEAR_DEFS. Replace lib/wareki/calendar_def.rb with the generator's
output (verified byte-identical to the previous bootstrap-derived
values) and drop the temporary bootstrap block from calendar.rb.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
Record the bit-packed kyuureki calendar table (Tasks 1-4) in the
ChangeLog and bump VERSION to 2.1.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
Replace the dangling Wareki::YEAR_DEFS reference in the era_def
generator with Wareki::Calendar.find_date_ary. The old expression
(y.end > jd) assigned an era starting on the last day of a lunisolar
year to the following year; the new one returns the containing year,
which is the intended semantics.

Also align the ChangeLog load-time claim with measurements (~30x)
and gitignore the kyuureki-map.txt data source.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
…r-def

# Conflicts:
#	build-util/parse_wikipedia.rb
PR #30's gen-era-def.rb landed on master referencing Wareki::YEAR_DEFS,
which this branch removes. Replace meiji_start's struct lookup with
Wareki::Calendar.to_jd(1868, 1, 1) (verified equal to the old value,
JD 2403357) and load the repo's lib via $LOAD_PATH instead of
require_relative so the in-tree code wins over any installed wareki gem.
Regenerated output verified byte-identical to the committed era_def.rb.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
@coveralls

coveralls commented Jul 13, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 97.336% (+0.2%) from 97.13% — feat/packed-calendar-def into master

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request replaces the large in-memory kyuureki calendar table with a highly efficient bit-packed integer representation in a new internal module Wareki::Calendar, reducing memory usage and significantly speeding up load times. It also raises the required Ruby version to 2.3.0 to utilize Array#bsearch_index and cleans up deprecated internal constants and methods. The review feedback recommends enhancing the robustness of the new Wareki::Calendar module by replacing non-intuitive and operators with standard if modifiers, adding input validation to month_index to handle invalid months or mismatched leap flags, and adding corresponding nil guards in last_day_of_month and to_jd.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread lib/wareki/calendar.rb
Comment thread lib/wareki/calendar.rb
Comment thread lib/wareki/calendar.rb

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors Wareki’s internal kyuureki (lunisolar) calendar table representation from object-heavy year definitions to a compact per-year 40-bit packed integer array, reducing memory usage and improving load time, while also raising the minimum supported Ruby version and bumping the gem version.

Changes:

  • Introduce Wareki::Calendar with bit-packed calendar lookup APIs and add comprehensive boundary/roundtrip specs.
  • Migrate existing consumers (Wareki::Date, Wareki::Utils, era generator) off the removed internal year-definition APIs.
  • Raise required_ruby_version to >= 2.3.0 and bump version/ChangeLog for release 2.1.0.

Reviewed changes

Copilot reviewed 14 out of 16 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
wareki.gemspec Raises minimum supported Ruby version to 2.3.0.
.rubocop.yml Updates Ruby-version-related RuboCop comments/config.
.rubocop_todo.yml Excludes the new spec/calendar_spec.rb from a path-format cop.
.gitignore Ignores kyuureki-map.txt to prevent accidental commits.
lib/wareki/common.rb Switches calendar loading from calendar_def to calendar and removes YEAR_BY_NUM.
lib/wareki/calendar.rb Adds the packed-calendar lookup API surface (covers_*, to_jd, find_date_ary, etc.).
lib/wareki/calendar_def.rb Replaces old year structs with generated packed integer table + constants.
lib/wareki/utils.rb Routes lunisolar lookups through Wareki::Calendar and removes find_year.
lib/wareki/date.rb Replaces direct year-table access with Wareki::Calendar APIs for indexing/validation/JD conversion.
build-util/gen-jp-cal-def.rb Rewrites generator to emit packed integer calendar definitions with invariants.
build-util/gen-era-def.rb Updates Meiji start computation to use Wareki::Calendar.to_jd and ensures repo lib is loaded.
spec/utils_spec.rb Updates specs to use find_date_ary and checks packed definitions are frozen.
spec/calendar_spec.rb Adds coverage for packed calendar constants, boundaries, leap months, and full-table roundtrip.
lib/wareki/version.rb Bumps gem version from 2.0.0 to 2.1.0.
ChangeLog Adds 2.1.0 release notes for the packed-calendar migration and Ruby requirement bump.
docs/superpowers/plans/2026-07-13-packed-calendar-def.md Adds an implementation plan/reference document for the migration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/wareki/calendar.rb
sugi and others added 2 commits July 13, 2026 18:34
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
The method lost its indirect coverage when _validate_date! and #jd
switched to Wareki::Calendar internally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019zprNf9ALwJ5UgyB4hf3cK
@sugi

sugi commented Jul 13, 2026

Copy link
Copy Markdown
Owner Author

[ClaudeCode] The -0.4% drop was Wareki::Date#month_index losing its indirect coverage: its former internal callers (_validate_date! and #jd) now go through Wareki::Calendar directly. Added a spec exercising it (lunisolar/leap/Gregorian/western paths and the out-of-range error) in 0285349 — only pre-existing uncovered lines remain.

@sugi
sugi merged commit 1d5adc8 into master Jul 13, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants