wareki 1.1.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00264c0e982ca9fc5ddddf077d222e473281f09ae91d1fb4cfc5cd0b445c200a
4
- data.tar.gz: 90d886bb8ef63e259e24134ccaeaf3df0722ebc415e15c932f5a6d770a93de55
3
+ metadata.gz: 1559659acd73e0b2e1760b9d5d889f59f7ac592ec6ff357ebe17e9631398cd18
4
+ data.tar.gz: 9c2271acf0e4522b2bc69b00abe579e6ae10299d6d560bbd987bd88581ad578a
5
5
  SHA512:
6
- metadata.gz: 8b06ba92792afd6213be68dc40a836f1b7799e8e836052ce3ff17c036a7f33770a8319dc1e714a07168485c791b8e682622bfb5f21e2905696a35fa355a55892
7
- data.tar.gz: ad8e3fd61fe9a697d13c90eeead0f3fdc6b973591f9cb99624e609224c326b1a3f8c775ef0d55b8ffbfc408285eb841fbee918d76bcb33fd0c7a03a45e86e766
6
+ metadata.gz: b6e16627c6d96af8128ecba167c59c077970373f1f37dfac0f156b88478869a29d8fe04e0bcf91c77dacd482c32cc1493b6aaa2cd065915b5a6c1696a293d450
7
+ data.tar.gz: bc6ba557cff73de604dc895f95b60f0baa4ecd82c8796b1b8e3c23cb9f6395c004c213d01ea781a92a7c72145e53772cdaba0d0302ccf2585a3a20e6a2cb9c8f
data/ChangeLog CHANGED
@@ -1,3 +1,69 @@
1
+ 2026-07-13 Tatsuki Sugiura <sugi@nemui.org>
2
+
3
+ * Version: 2.1.0
4
+ * Shrink in-memory kyuureki calendar table to ~1/48 (bit-packed
5
+ one-integer-per-year representation in new internal module
6
+ Wareki::Calendar); calendar definitions now load ~30x faster
7
+ * Era definitions are now generated from manakai/data-locale
8
+ era-defs.json instead of the Wikipedia era list page
9
+ (build-util/gen-era-def.rb); corrects 正平/建徳/文中 boundaries,
10
+ 白雉/朱鳥 now cover until the end of their last kyuureki year,
11
+ and ERA_NORTH_DEFS 元徳 now ends at 正慶 transition
12
+ * Wareki.parse_to_date and patched Date.parse/_parse no longer
13
+ fall back to stdlib parsing on UnsupportedDateRange; the error
14
+ propagates again as in 1.x (reverts a 2.0.0 change)
15
+ * Cache kansuji conversion of small numbers for %J format
16
+ directives; Utils.to_simple_kan delegates non-Integer values
17
+ to YaKansuji
18
+ * Fast-path ASCII-only strings in patched Date.parse/Date._parse
19
+ * Remove internal constants Wareki::YEAR_DEFS, Wareki::YEAR_BY_NUM,
20
+ Wareki::Year and Wareki::Utils.find_year
21
+ * required_ruby_version is now >= 2.3
22
+
23
+ 2026-07-13 Tatsuki Sugiura <sugi@nemui.org>
24
+
25
+ * Version: 2.0.0
26
+ * Time.parse now understands Japanese kansuji/zenkaku time notations
27
+ (十二時三十四分五十六秒, 午後三時半, 正午, ...) via the patched
28
+ Date._parse; Date.parse no longer misreads bare 時分秒 strings
29
+ * Add %JT* time format directives (%JTf/%JTF/%JTH(k)/%JTM(k)/%JTS(k));
30
+ Time#strftime now supports all %J directives and DateTime#strftime
31
+ expands %JT too; add Time#to_wareki_date
32
+ * Strict date validation: nonexistent dates now raise
33
+ Wareki::InvalidDate (< ArgumentError) (month/day out of range,
34
+ nonexistent leap month, and the Meiji 5 December calendar-switch
35
+ gap for all era notations); patched Date.parse re-raises it
36
+ instead of falling back to stdlib misinterpretation
37
+ * README: document that conversion from Date/jd prefers northern court
38
+ eras during the Nanboku-cho period (previous statement was wrong;
39
+ behavior unchanged from 1.1.0)
40
+ * Update kyuureki calendar data (regenerated from
41
+ manakai/data-locale); corrects month boundaries of some
42
+ historical years
43
+ * Wareki::Date#- returns day count for date-like operands;
44
+ #+ raises TypeError for non-numeric operands (stdlib parity)
45
+ * Support date calculation with ActiveSupport::Duration
46
+ (was NotImplementedError)
47
+ * Add Wareki::Date#hash, #<=>, Comparable and #succ
48
+ * Invalidate cached jd on attribute writes; writers keep
49
+ year/era_year consistent
50
+ * Freeze era definition structs to prevent accidental mutation
51
+ of shared state
52
+ * Fix imperial_year= setter (sign error)
53
+ * Parse apostrophe leap-month markers so %Jf/%Jm output round-trips
54
+ * Fix misokka (晦日) parsing for 皇紀/西暦/紀元前/era-less dates
55
+ * DateTime#strftime now supports %J directives
56
+ * Escaped %%J or invalid %J sequences no longer trigger conversion;
57
+ Wareki::Date#strftime honors %% escapes too
58
+ * Fast-path non-wareki strings in patched Date.parse/_parse
59
+ * Wareki.parse_to_date falls back on UnsupportedDateRange too
60
+ * Fix deprecated Utils.i_to_kan (was calling undefined method)
61
+ * gemspec: SPDX license identifier (BSD-2-Clause)
62
+
63
+ [ by masa.kunikata <masa.kunikata@gmail.com> ]
64
+ * Add zero padding format feature
65
+ * Use zero padded format on default
66
+
1
67
  2019-12-24 Tatsuki Sugiura <sugi@nemui.org>
2
68
 
3
69
  * Version: 1.1.0
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # wareki - ruby 和暦ライブラリ
2
2
 
3
3
  [<img src="https://badge.fury.io/rb/wareki.svg" alt="Gem Version" />](https://badge.fury.io/rb/wareki)
4
- [<img src="https://travis-ci.org/sugi/wareki.svg?branch=master" alt="Build Status" />](https://travis-ci.org/sugi/wareki)
5
- [<img src="https://coveralls.io/repos/sugi/wareki/badge.svg?branch=master&service=github" alt="Coverage Status" />](https://coveralls.io/github/sugi/wareki?branch=master)
4
+ [<img src="https://github.com/sugi/wareki/actions/workflows/ci.yml/badge.svg" alt="Build Status" />](https://github.com/sugi/wareki/actions/workflows/ci.yml)
5
+ [<img src="https://coveralls.io/repos/github/sugi/wareki/badge.svg?branch=master" alt="Coverage Status" />](https://coveralls.io/github/sugi/wareki?branch=master)
6
6
  [<img src="https://api.codeclimate.com/v1/badges/c9209422700b526d2b45/maintainability" />](https://codeclimate.com/github/sugi/wareki/maintainability)
7
- [![Codacy Badge](https://api.codacy.com/project/badge/Grade/9242ddf1725c404aac2d9f82b9bc1d75)](https://www.codacy.com/app/sugi/wareki?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=sugi/wareki&amp;utm_campaign=Badge_Grade)
7
+ [![Codacy Badge](https://app.codacy.com/project/badge/Grade/a5e0b6022b6d485b86a195be7a392da5)](https://app.codacy.com/gh/sugi/wareki/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
8
8
 
9
9
  ## 概要
10
10
 
11
11
  日本の和暦をサポートするライブラリです。
12
12
  旧暦は445年から、元号は大化から全て処理できます。
13
- 元号はWikipedia、暦は日本暦日原典由来のデータを元にしています。
13
+ 元号・暦とも [manakai/data-locale](https://github.com/manakai/data-locale) のデータを元にしています (暦は日本暦日原典由来)。
14
14
 
15
15
  ## 機能
16
16
 
@@ -20,10 +20,15 @@
20
20
  * 閏月サポート
21
21
  * 月の別名のサポート (如月、弥生、師走など)
22
22
  * 元年、正月、朔日、晦日、廿一日、卅日 などの特殊な表記の日付サポート
23
+ * 時刻の漢数字・全角数字表記のサポート (十二時三十四分五十六秒、午後三時半、正午 など)
23
24
  * Date クラスの拡張
24
25
  * 標準の strftime に和暦へのフォーマット文字列を追加
25
26
  * to_wareki_date を追加
26
27
  * Date::JAPAN (明治改暦日)の追加
28
+ * Time / DateTime クラスの拡張
29
+ * strftime に時刻の全角・漢数字フォーマット文字列 (%JT 系) を追加
30
+ * Time.parse で和暦・漢数字時刻の文字列を解釈 (要 `require 'time'`)
31
+ * Time#to_wareki_date を追加
27
32
 
28
33
  ## インストール
29
34
 
@@ -71,6 +76,20 @@ Wareki::Date.parse("正嘉元年 うるう3月 12日") # => Wareki::Date イ
71
76
  Wareki::Date.new("明治", 8, 2, 1).to_date # => 標準 Date インスタンス 1875-02-01
72
77
  ```
73
78
 
79
+ ### 時刻のパースとフォーマット
80
+
81
+ 時刻の漢数字・全角数字表記も透過的に扱えます。時刻には暦のようなマッピングは
82
+ 存在しないため、単純な数字変換として処理されます。
83
+
84
+ ```ruby
85
+ require 'time'
86
+ t = Time.parse("平成元年五月四日 午後三時三十四分五十六秒")
87
+ # => 1989-05-04 15:34:56
88
+ t.strftime("%JF %JTF") # => "平成元年五月四日 十五時三十四分五十六秒"
89
+ t.strftime("%JTf") # => "15時34分56秒"
90
+ t.strftime("%JTHk時%JTMk分") # => "十五時三十四分"
91
+ ```
92
+
74
93
  ### 和暦・旧暦へのフォーマット
75
94
 
76
95
  日本では明治5年まで、グレゴリオ暦でもユリウス歴でもない旧暦が使われていました。これもフォーマット文字列経由で透過的に扱えます。
@@ -154,6 +173,18 @@ strftime が拡張されるので、 `config/locale/ja.yml` にそのままフ
154
173
  * %JDk: 和暦日の漢数字
155
174
  * %JDK: 和暦日の漢数字の特殊記法 (朔、晦)
156
175
 
176
+ 以下の時刻フォーマット文字列は Time / DateTime の strftime でのみ使用できます
177
+ (Date は時刻情報を持たないため展開されません)。
178
+
179
+ * %JTf: "%JTH時%JTM分%JTS秒" 相当の半角ゼロ埋め複合表記 (例: 15時04分05秒)
180
+ * %JTF: 漢数字の複合表記 (例: 十五時四分五秒)
181
+ * %JTH: 時の全角数字
182
+ * %JTHk: 時の漢数字
183
+ * %JTM: 分の全角数字
184
+ * %JTMk: 分の漢数字
185
+ * %JTS: 秒の全角数字
186
+ * %JTSk: 秒の漢数字
187
+
157
188
  ## 仕様、限界、制限など
158
189
 
159
190
  * 作者は暦の専門家ではまったくありません。ツッコミお待ちしています。
@@ -162,18 +193,25 @@ strftime が拡張されるので、 `config/locale/ja.yml` にそのままフ
162
193
  * 日本暦日原典の対照表が、どの暦法を元にしているのか分かっていません。単にデータだけを利用しています。
163
194
  * 内部的には全てユリウス日を経由して変換しています。
164
195
  * パース時には元号の存在しない年(例: 霊亀百年)を受け入れます。しかし現状、Date に変換した場合この情報は捨てられ、パース時の元号を復元することはできません。(Wareki::Date を直接扱えば可能です)
165
- * 改暦による存在しない日(明治5年12月3日〜31)は基本的に例外(ArgumentError)を上げます。が、現状このチェックは完全ではありません。
166
- * 北朝の元号も解釈できます。しかし現在の所、北朝の元号で文字列にフォーマットすることはできません。
196
+ * 存在しない日付(月・日の範囲超過、存在しない閏月、改暦により存在しない明治5年12月3日〜31日など)は例外(Wareki::InvalidDate, ArgumentError のサブクラス)を上げます。
197
+ * 南朝・北朝どちらの元号も解釈できます。Date やユリウス日からの変換では、南北朝期は通常の慣例に従い北朝の元号を優先します(元弘・延元など、北朝側の改元前で北朝に固有の元号がない期間は南朝の元号になります)。
198
+ * 元号の開始日より前の日付表記(例: 令和元年1月1日)は受理し、その元号の元年として解釈します。逆変換では実際の元号(平成31年1月1日)になります。
167
199
  * 10月の別名は「神無月」しかサポートしていません
168
200
  * 将来の日付に関しては、現在の元号がずっと継続しているとみなします
169
201
  * 日本でユリウス暦は使われていないので、Date::JAPAN は単にグレゴリオ暦への改暦日の目安、と言うだけです。Date.new で使う意味はほぼありません。
202
+ * 時刻の解釈・出力は単純な数字変換のみで、十二時辰などの伝統的時刻制度との対応はありません
203
+ * 時刻パース時、「午前」は無変換、「午後」は12時未満の時にのみ+12します (午後十二時 → 12:00)
204
+ * 「三時間」「13時代」のような「数字+時」を含む複合語も時刻として解釈されることがあります (このため、従来 Date.parse が数字を日として解釈して成功していた文字列がエラーになる場合があります)
205
+ * 範囲外の時刻 (二十五時など) は ASCII 表記 (25:00) と同様に扱われ、Time.parse では通常 ArgumentError になります
206
+ * 和暦・漢数字時刻のパース対応は Date.parse / Date._parse / Time.parse のみです。DateTime.parse は Ruby 内部のパーサを直接呼ぶため対象外です
170
207
 
171
208
  ## 参照元データ
172
209
 
173
210
  作成には以下のデータを参照しました。
174
211
 
175
- * 旧暦: [suikawiki - 旧暦](http://wiki.suikawiki.org/n/%E6%97%A7%E6%9A%A6#section-%E5%AF%BE%E7%85%A7%E8%A1%A8%E3%81%A8%E5%A4%89%E6%8F%9B%E3%83%84%E3%83%BC%E3%83%AB) にある「日本暦日原典」第4版準拠の先発グレゴリオ暦対照表
176
- * 元号: [Wikipedia - 元号一覧_(日本)](https://ja.wikipedia.org/wiki/%E5%85%83%E5%8F%B7%E4%B8%80%E8%A6%A7_%28%E6%97%A5%E6%9C%AC%29) のユリウス歴とグレゴリオ暦表記の物に基づいています
212
+ * Wakaba 氏による https://github.com/manakai/data-locale のデータを利用しています
213
+ * 旧暦: [suikawiki - 旧暦](http://wiki.suikawiki.org/n/%E6%97%A7%E6%9A%A6#section-%E5%AF%BE%E7%85%A7%E8%A1%A8%E3%81%A8%E5%A4%89%E6%8F%9B%E3%83%84%E3%83%BC%E3%83%AB) にある「日本暦日原典」第4版準拠の先発グレゴリオ暦対照表 (kyuureki-map.txt)
214
+ * 元号: 同リポジトリの元号定義データ (era-defs.json)
177
215
 
178
216
  ## ライセンス
179
217
 
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'wareki/calendar_def'
4
+
5
+ module Wareki
6
+ # 旧暦テーブルの参照ロジック。1年分の旧暦情報は整数1個 (40bit) に
7
+ # ビットパックされている。PACKED[西暦年 - YEAR_MIN]:
8
+ # bits 0..21 : その年の最初の月の初日のユリウス通日 (JD)
9
+ # bits 22..34 : 月の大小マスク (bit i = i 番目の月が大の月 = 30日)
10
+ # bit 35 : 閏月を含む13ヶ月の年なら1
11
+ # bits 36..39 : 閏月の月番号 (1..12、閏月がなければ0)
12
+ # 明治5年12月 (テーブル最終月) はグレゴリオ暦切替のため LAST_MONTH_DAYS
13
+ # (= 2) 日で打ち切られており、大小マスクには反映されていない。
14
+ module Calendar
15
+ module_function
16
+
17
+ def covers_year?(year)
18
+ year.between?(YEAR_MIN, YEAR_MAX)
19
+ end
20
+
21
+ def covers_jd?(jd)
22
+ jd.between?(JD_MIN, JD_MAX)
23
+ end
24
+
25
+ # 閏月の月番号。閏月のない年・範囲外の年は nil
26
+ def leap_month(year)
27
+ covers_year?(year) or return nil
28
+ lp = PACKED[year - YEAR_MIN] >> 36
29
+ lp == 0 ? nil : lp
30
+ end
31
+
32
+ # 月番号 (1始まり) を月配列添字 (閏月込み・0始まり) に変換する。
33
+ # year がテーブル範囲内であることは呼び出し側が保証すること。
34
+ def month_index(year, month, is_leap)
35
+ lp = leap_month(year)
36
+ idx = month - 1
37
+ is_leap || (lp && month > lp) and idx += 1
38
+ idx
39
+ end
40
+
41
+ def last_day_of_month(year, month, is_leap)
42
+ covers_year?(year) or return nil
43
+ packed = PACKED[year - YEAR_MIN]
44
+ idx = month_index(year, month, is_leap)
45
+ months = 12 + ((packed >> 35) & 1)
46
+ return LAST_MONTH_DAYS if year == YEAR_MAX && idx == months - 1
47
+
48
+ 29 + ((packed >> (22 + idx)) & 1)
49
+ end
50
+
51
+ def to_jd(year, month, day, is_leap)
52
+ covers_year?(year) or return nil
53
+ packed = PACKED[year - YEAR_MIN]
54
+ idx = month_index(year, month, is_leap)
55
+ jd = packed & 0x3fffff
56
+ i = 0
57
+ while i < idx
58
+ jd += 29 + ((packed >> (22 + i)) & 1)
59
+ i += 1
60
+ end
61
+ jd + day - 1
62
+ end
63
+
64
+ # jd を [西暦年, 月, 日, 閏月フラグ] に変換する。範囲外は nil
65
+ def find_date_ary(jd)
66
+ covers_jd?(jd) or return nil
67
+ i = PACKED.bsearch_index { |packed| (packed & 0x3fffff) > jd }
68
+ if i.nil?
69
+ i = PACKED.size - 1
70
+ else
71
+ i -= 1
72
+ end
73
+ packed = PACKED[i]
74
+ month_start = packed & 0x3fffff
75
+ months = 12 + ((packed >> 35) & 1)
76
+ lp = packed >> 36
77
+ idx = 0
78
+ while idx < months - 1
79
+ next_start = month_start + 29 + ((packed >> (22 + idx)) & 1)
80
+ break if jd < next_start
81
+
82
+ month_start = next_start
83
+ idx += 1
84
+ end
85
+ month = idx + 1
86
+ month -= 1 if lp != 0 && lp < month
87
+ [YEAR_MIN + i, month, jd - month_start + 1, lp != 0 && lp == idx]
88
+ end
89
+ end
90
+ end