wareki 1.1.0 → 2.0.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: 23992b6b4cd96544832ea1db1e843279eddabc6d205d2a7af87c30e29079fc7b
4
+ data.tar.gz: 706f0a880da6c9a05a3fa013450aa04dbd15993d5144df7aeef92d9f49b19f4e
5
5
  SHA512:
6
- metadata.gz: 8b06ba92792afd6213be68dc40a836f1b7799e8e836052ce3ff17c036a7f33770a8319dc1e714a07168485c791b8e682622bfb5f21e2905696a35fa355a55892
7
- data.tar.gz: ad8e3fd61fe9a697d13c90eeead0f3fdc6b973591f9cb99624e609224c326b1a3f8c775ef0d55b8ffbfc408285eb841fbee918d76bcb33fd0c7a03a45e86e766
6
+ metadata.gz: d03b898c5bd3efc4087d9a9b6cfdb54526b30c7088e2a051b3bc31ae9c3994acca5607a8920ddd44f6acc1b9331ac4e62a6a82943689e3e41beb5633f165a5bc
7
+ data.tar.gz: 77d82b574ff6fee3fee4c19f27faf532ac040b2fb933b9c33d86ffd1fd6deb32a6aa9c5a2c08eb4ee039594e60032c97a2a0e95d26a9489b2790d7290480073c
data/ChangeLog CHANGED
@@ -1,3 +1,47 @@
1
+ 2026-07-13 Tatsuki Sugiura <sugi@nemui.org>
2
+
3
+ * Version: 2.0.0
4
+ * Time.parse now understands Japanese kansuji/zenkaku time notations
5
+ (十二時三十四分五十六秒, 午後三時半, 正午, ...) via the patched
6
+ Date._parse; Date.parse no longer misreads bare 時分秒 strings
7
+ * Add %JT* time format directives (%JTf/%JTF/%JTH(k)/%JTM(k)/%JTS(k));
8
+ Time#strftime now supports all %J directives and DateTime#strftime
9
+ expands %JT too; add Time#to_wareki_date
10
+ * Strict date validation: nonexistent dates now raise
11
+ Wareki::InvalidDate (< ArgumentError) (month/day out of range,
12
+ nonexistent leap month, and the Meiji 5 December calendar-switch
13
+ gap for all era notations); patched Date.parse re-raises it
14
+ instead of falling back to stdlib misinterpretation
15
+ * README: document that conversion from Date/jd prefers northern court
16
+ eras during the Nanboku-cho period (previous statement was wrong;
17
+ behavior unchanged from 1.1.0)
18
+ * Update kyuureki calendar data (regenerated from
19
+ manakai/data-locale); corrects month boundaries of some
20
+ historical years
21
+ * Wareki::Date#- returns day count for date-like operands;
22
+ #+ raises TypeError for non-numeric operands (stdlib parity)
23
+ * Support date calculation with ActiveSupport::Duration
24
+ (was NotImplementedError)
25
+ * Add Wareki::Date#hash, #<=>, Comparable and #succ
26
+ * Invalidate cached jd on attribute writes; writers keep
27
+ year/era_year consistent
28
+ * Freeze era definition structs to prevent accidental mutation
29
+ of shared state
30
+ * Fix imperial_year= setter (sign error)
31
+ * Parse apostrophe leap-month markers so %Jf/%Jm output round-trips
32
+ * Fix misokka (晦日) parsing for 皇紀/西暦/紀元前/era-less dates
33
+ * DateTime#strftime now supports %J directives
34
+ * Escaped %%J or invalid %J sequences no longer trigger conversion;
35
+ Wareki::Date#strftime honors %% escapes too
36
+ * Fast-path non-wareki strings in patched Date.parse/_parse
37
+ * Wareki.parse_to_date falls back on UnsupportedDateRange too
38
+ * Fix deprecated Utils.i_to_kan (was calling undefined method)
39
+ * gemspec: SPDX license identifier (BSD-2-Clause)
40
+
41
+ [ by masa.kunikata <masa.kunikata@gmail.com> ]
42
+ * Add zero padding format feature
43
+ * Use zero padded format on default
44
+
1
45
  2019-12-24 Tatsuki Sugiura <sugi@nemui.org>
2
46
 
3
47
  * Version: 1.1.0
data/README.md CHANGED
@@ -1,10 +1,10 @@
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
 
@@ -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,17 +193,24 @@ 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版準拠の先発グレゴリオ暦対照表
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版準拠の先発グレゴリオ暦対照表
176
214
  * 元号: [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) のユリウス歴とグレゴリオ暦表記の物に基づいています
177
215
 
178
216
  ## ライセンス
@@ -1,15 +1,14 @@
1
- # frozen_string_literal: true
2
1
  module Wareki
3
2
  Year = Struct.new(:year, :start, :end, :leap_month, :month_starts, :month_days)
4
3
  YEAR_DEFS = [
5
4
  Year.new(445, 1883618, 1884001, 5, [1883618, 1883648, 1883677, 1883707, 1883736, 1883766, 1883795, 1883825, 1883854, 1883884, 1883913, 1883943, 1883972], [30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30]),
6
5
  Year.new(446, 1884002, 1884355, nil, [1884002, 1884032, 1884061, 1884091, 1884120, 1884150, 1884179, 1884209, 1884238, 1884268, 1884297, 1884327], [30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29]),
7
- Year.new(447, 1884356, 1884710, nil, [1884356, 1884386, 1884415, 1884445, 1884475, 1884504, 1884534, 1884563, 1884593, 1884622, 1884652, 1884681], [30, 29, 30, 30, 29, 30, 29, 30, 29, 30, 29, 30]),
8
- Year.new(448, 1884711, 1885094, 2, [1884711, 1884740, 1884770, 1884799, 1884829, 1884858, 1884888, 1884917, 1884947, 1884977, 1885006, 1885036, 1885065], [29, 30, 29, 30, 29, 30, 29, 30, 30, 29, 30, 29, 30]),
6
+ Year.new(447, 1884356, 1884710, nil, [1884356, 1884386, 1884415, 1884445, 1884474, 1884504, 1884534, 1884563, 1884593, 1884622, 1884652, 1884681], [30, 29, 30, 29, 30, 30, 29, 30, 29, 30, 29, 30]),
7
+ Year.new(448, 1884711, 1885094, 2, [1884711, 1884740, 1884770, 1884799, 1884829, 1884858, 1884888, 1884917, 1884947, 1884976, 1885006, 1885036, 1885065], [29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 30, 29, 30]),
9
8
  Year.new(449, 1885095, 1885448, nil, [1885095, 1885124, 1885154, 1885183, 1885213, 1885242, 1885272, 1885301, 1885331, 1885360, 1885390, 1885419], [29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30]),
10
9
  Year.new(450, 1885449, 1885832, 10, [1885449, 1885479, 1885508, 1885538, 1885567, 1885597, 1885626, 1885656, 1885685, 1885715, 1885744, 1885774, 1885803], [30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30]),
11
- Year.new(451, 1885833, 1886186, nil, [1885833, 1885862, 1885892, 1885922, 1885951, 1885981, 1886010, 1886040, 1886069, 1886099, 1886128, 1886158], [29, 30, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29]),
12
- Year.new(452, 1886187, 1886541, nil, [1886187, 1886217, 1886246, 1886276, 1886305, 1886335, 1886364, 1886394, 1886424, 1886453, 1886483, 1886512], [30, 29, 30, 29, 30, 29, 30, 30, 29, 30, 29, 30]),
10
+ Year.new(451, 1885833, 1886186, nil, [1885833, 1885862, 1885892, 1885921, 1885951, 1885981, 1886010, 1886040, 1886069, 1886099, 1886128, 1886158], [29, 30, 29, 30, 30, 29, 30, 29, 30, 29, 30, 29]),
11
+ Year.new(452, 1886187, 1886541, nil, [1886187, 1886217, 1886246, 1886276, 1886305, 1886335, 1886364, 1886394, 1886423, 1886453, 1886483, 1886512], [30, 29, 30, 29, 30, 29, 30, 29, 30, 30, 29, 30]),
13
12
  Year.new(453, 1886542, 1886925, 6, [1886542, 1886571, 1886601, 1886630, 1886660, 1886689, 1886719, 1886748, 1886778, 1886807, 1886837, 1886866, 1886896], [29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 30]),
14
13
  Year.new(454, 1886926, 1887279, nil, [1886926, 1886955, 1886985, 1887014, 1887044, 1887073, 1887103, 1887132, 1887162, 1887191, 1887221, 1887250], [29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30]),
15
14
  Year.new(455, 1887280, 1887633, nil, [1887280, 1887309, 1887339, 1887369, 1887398, 1887428, 1887457, 1887487, 1887516, 1887546, 1887575, 1887605], [29, 30, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29]),
@@ -255,7 +254,7 @@ module Wareki
255
254
  Year.new(695, 1974927, 1975310, 2, [1974927, 1974956, 1974986, 1975015, 1975045, 1975074, 1975104, 1975133, 1975163, 1975192, 1975222, 1975252, 1975281], [29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 30, 29, 30]),
256
255
  Year.new(696, 1975311, 1975664, nil, [1975311, 1975340, 1975370, 1975399, 1975429, 1975458, 1975488, 1975517, 1975547, 1975576, 1975606, 1975636], [29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 30, 29]),
257
256
  Year.new(697, 1975665, 1976048, 12, [1975665, 1975694, 1975724, 1975753, 1975783, 1975813, 1975842, 1975871, 1975901, 1975931, 1975960, 1975990, 1976020], [29, 30, 29, 30, 30, 29, 29, 30, 30, 29, 30, 30, 29]),
258
- Year.new(698, 1976049, 1976403, nil, [1976049, 1976079, 1976108, 1976137, 1976167, 1976196, 1976226, 1976255, 1976285, 1976314, 1976344, 1976374], [30, 29, 29, 30, 29, 30, 29, 30, 29, 30, 30, 30]),
257
+ Year.new(698, 1976049, 1976403, nil, [1976049, 1976079, 1976108, 1976137, 1976167, 1976196, 1976226, 1976255, 1976285, 1976315, 1976344, 1976374], [30, 29, 29, 30, 29, 30, 29, 30, 30, 29, 30, 30]),
259
258
  Year.new(699, 1976404, 1976757, nil, [1976404, 1976433, 1976463, 1976492, 1976521, 1976551, 1976580, 1976609, 1976639, 1976669, 1976698, 1976728], [29, 30, 29, 29, 30, 29, 29, 30, 30, 29, 30, 30]),
260
259
  Year.new(700, 1976758, 1977141, 7, [1976758, 1976788, 1976817, 1976847, 1976876, 1976905, 1976935, 1976964, 1976993, 1977023, 1977052, 1977082, 1977112], [30, 29, 30, 29, 29, 30, 29, 29, 30, 29, 30, 30, 30]),
261
260
  Year.new(701, 1977142, 1977495, nil, [1977142, 1977171, 1977201, 1977231, 1977260, 1977289, 1977319, 1977348, 1977377, 1977407, 1977436, 1977466], [29, 30, 30, 29, 29, 30, 29, 29, 30, 29, 30, 30]),
@@ -300,7 +299,7 @@ module Wareki
300
299
  Year.new(740, 1991375, 1991729, nil, [1991375, 1991405, 1991434, 1991464, 1991493, 1991523, 1991552, 1991582, 1991612, 1991641, 1991671, 1991700], [30, 29, 30, 29, 30, 29, 30, 30, 29, 30, 29, 30]),
301
300
  Year.new(741, 1991730, 1992113, 3, [1991730, 1991759, 1991789, 1991818, 1991847, 1991877, 1991906, 1991936, 1991966, 1991995, 1992025, 1992055, 1992084], [29, 30, 29, 29, 30, 29, 30, 30, 29, 30, 30, 29, 30]),
302
301
  Year.new(742, 1992114, 1992467, nil, [1992114, 1992143, 1992173, 1992202, 1992231, 1992261, 1992290, 1992320, 1992349, 1992379, 1992409, 1992439], [29, 30, 29, 29, 30, 29, 30, 29, 30, 30, 30, 29]),
303
- Year.new(743, 1992468, 1992822, nil, [1992468, 1992498, 1992527, 1992557, 1992586, 1992615, 1992645, 1992674, 1992704, 1992734, 1992763, 1992793], [30, 29, 30, 29, 29, 30, 29, 30, 30, 29, 30, 30]),
302
+ Year.new(743, 1992468, 1992822, nil, [1992468, 1992498, 1992527, 1992557, 1992586, 1992615, 1992645, 1992674, 1992704, 1992733, 1992763, 1992793], [30, 29, 30, 29, 29, 30, 29, 30, 29, 30, 30, 30]),
304
303
  Year.new(744, 1992852, 1993205, 1, [1992823, 1992852, 1992882, 1992911, 1992941, 1992970, 1992999, 1993029, 1993058, 1993087, 1993117, 1993147, 1993176], [29, 30, 29, 30, 29, 29, 30, 29, 29, 30, 30, 29, 30]),
305
304
  Year.new(745, 1993206, 1993559, nil, [1993206, 1993236, 1993266, 1993295, 1993325, 1993354, 1993383, 1993413, 1993442, 1993471, 1993501, 1993531], [30, 30, 29, 30, 29, 29, 30, 29, 29, 30, 30, 29]),
306
305
  Year.new(746, 1993560, 1993943, 9, [1993560, 1993590, 1993620, 1993649, 1993679, 1993709, 1993738, 1993767, 1993797, 1993826, 1993856, 1993885, 1993915], [30, 30, 29, 30, 30, 29, 29, 30, 29, 30, 29, 30, 29]),
@@ -351,7 +350,7 @@ module Wareki
351
350
  Year.new(791, 2010009, 2010362, nil, [2010009, 2010038, 2010068, 2010098, 2010128, 2010157, 2010187, 2010216, 2010245, 2010275, 2010304, 2010334], [29, 30, 30, 30, 29, 30, 29, 29, 30, 29, 30, 29]),
352
351
  Year.new(792, 2010363, 2010746, 11, [2010363, 2010393, 2010423, 2010452, 2010481, 2010511, 2010541, 2010570, 2010600, 2010630, 2010659, 2010689, 2010718], [30, 30, 29, 29, 30, 30, 29, 30, 30, 29, 30, 29, 29]),
353
352
  Year.new(793, 2010747, 2011101, nil, [2010747, 2010777, 2010806, 2010836, 2010865, 2010895, 2010924, 2010954, 2010984, 2011013, 2011043, 2011072], [30, 29, 30, 29, 30, 29, 30, 30, 29, 30, 29, 30]),
354
- Year.new(794, 2011102, 2011456, nil, [2011102, 2011131, 2011161, 2011190, 2011219, 2011249, 2011278, 2011308, 2011338, 2011367, 2011397, 2011427], [29, 30, 29, 29, 30, 29, 30, 30, 29, 30, 30, 30]),
353
+ Year.new(794, 2011102, 2011456, nil, [2011102, 2011131, 2011161, 2011190, 2011220, 2011249, 2011278, 2011308, 2011338, 2011367, 2011397, 2011427], [29, 30, 29, 30, 29, 29, 30, 30, 29, 30, 30, 30]),
355
354
  Year.new(795, 2011457, 2011840, 7, [2011457, 2011486, 2011515, 2011545, 2011574, 2011603, 2011633, 2011662, 2011692, 2011722, 2011751, 2011781, 2011811], [29, 29, 30, 29, 29, 30, 29, 30, 30, 29, 30, 30, 30]),
356
355
  Year.new(796, 2011841, 2012194, nil, [2011841, 2011870, 2011899, 2011929, 2011958, 2011987, 2012017, 2012046, 2012076, 2012105, 2012135, 2012165], [29, 29, 30, 29, 29, 30, 29, 30, 29, 30, 30, 30]),
357
356
  Year.new(797, 2012195, 2012548, nil, [2012195, 2012224, 2012254, 2012283, 2012313, 2012342, 2012371, 2012401, 2012430, 2012460, 2012489, 2012519], [29, 30, 29, 30, 29, 29, 30, 29, 30, 29, 30, 30]),
@@ -377,7 +376,7 @@ module Wareki
377
376
  Year.new(817, 2019488, 2019871, 4, [2019488, 2019518, 2019547, 2019577, 2019607, 2019636, 2019666, 2019695, 2019725, 2019754, 2019784, 2019813, 2019842], [30, 29, 30, 30, 29, 30, 29, 30, 29, 30, 29, 29, 30]),
378
377
  Year.new(818, 2019872, 2020226, nil, [2019872, 2019902, 2019931, 2019961, 2019991, 2020020, 2020050, 2020079, 2020109, 2020138, 2020168, 2020197], [30, 29, 30, 30, 29, 30, 29, 30, 29, 30, 29, 30]),
379
378
  Year.new(819, 2020227, 2020580, nil, [2020227, 2020256, 2020286, 2020315, 2020345, 2020374, 2020404, 2020433, 2020463, 2020493, 2020522, 2020552], [29, 30, 29, 30, 29, 30, 29, 30, 30, 29, 30, 29]),
380
- Year.new(820, 2020611, 2020964, 1, [2020581, 2020611, 2020641, 2020670, 2020699, 2020728, 2020758, 2020788, 2020817, 2020847, 2020877, 2020906, 2020936], [30, 30, 29, 29, 29, 30, 30, 29, 30, 30, 29, 30, 29]),
379
+ Year.new(820, 2020611, 2020964, 1, [2020581, 2020611, 2020640, 2020670, 2020699, 2020728, 2020758, 2020788, 2020817, 2020847, 2020877, 2020906, 2020936], [30, 29, 30, 29, 29, 30, 30, 29, 30, 30, 29, 30, 29]),
381
380
  Year.new(821, 2020965, 2021319, nil, [2020965, 2020995, 2021024, 2021053, 2021083, 2021112, 2021142, 2021171, 2021201, 2021231, 2021260, 2021290], [30, 29, 29, 30, 29, 30, 29, 30, 30, 29, 30, 30]),
382
381
  Year.new(822, 2021320, 2021703, 9, [2021320, 2021350, 2021379, 2021408, 2021437, 2021467, 2021496, 2021525, 2021555, 2021585, 2021614, 2021644, 2021674], [30, 29, 29, 29, 30, 29, 29, 30, 30, 29, 30, 30, 30]),
383
382
  Year.new(823, 2021704, 2022057, nil, [2021704, 2021733, 2021763, 2021792, 2021821, 2021851, 2021880, 2021909, 2021939, 2021969, 2021998, 2022028], [29, 30, 29, 29, 30, 29, 29, 30, 30, 29, 30, 30]),
data/lib/wareki/common.rb CHANGED
@@ -9,6 +9,8 @@ module Wareki
9
9
  GREGORIAN_START_YEAR = 1873
10
10
  IMPERIAL_START = 1_480_041 # Date.new(-660, 2, 11, Date::GREGORIAN).jd
11
11
  IMPERIAL_START_YEAR = -660
12
+ WESTERN_ERA_NAMES = ['', '西暦', '紀元前'].freeze
13
+ IMPERIAL_ERA_NAMES = %w(皇紀 神武天皇即位紀元).freeze
12
14
  DATE_INFINITY = ::Date.new(280_000_000, 12, 31) # Use 280000000 for jruby limitation...
13
15
  YEAR_BY_NUM = Hash[*YEAR_DEFS.map { |y| [y.year, y] }.flatten].freeze
14
16
  KANJI_VARIANTS = {
@@ -42,8 +44,8 @@ module Wareki
42
44
  NORMALIZE_KANJI_VARIANTS_HASH = KANJI_VARIANTS.each_with_object({}) { |(n, s), h| s.each_char { |c| h[c] = n } }
43
45
  NORMALIZE_KANJI_VARIANTS = ->(str) { str.gsub(NORMALIZE_KANJI_VARIANTS_REGEX, NORMALIZE_KANJI_VARIANTS_HASH) }
44
46
  ERA_BY_NAME = Hash[*(ERA_NORTH_DEFS + ERA_DEFS).flat_map { |g| [g.name, g] }]
45
- ERA_BY_NAME['皇紀'] = ERA_BY_NAME['神武天皇即位紀元'] = Era.new('皇紀', -660, 1_480_041, DATE_INFINITY.jd)
46
- ERA_BY_NAME['西暦'] = ERA_BY_NAME[''] = Era.new('西暦', 1, 1_721_424, DATE_INFINITY.jd)
47
+ ERA_BY_NAME['皇紀'] = ERA_BY_NAME['神武天皇即位紀元'] = Era.new('皇紀', -660, 1_480_041, DATE_INFINITY.jd).freeze
48
+ ERA_BY_NAME['西暦'] = ERA_BY_NAME[''] = Era.new('西暦', 1, 1_721_424, DATE_INFINITY.jd).freeze
47
49
  ERA_BY_NAME.default_proc = ->(hash, key) { hash.fetch(SQUARE_ERAS[key] || NORMALIZE_KANJI_VARIANTS[key], nil) }
48
50
  ERA_BY_NAME.freeze
49
51
  ERA_REGEX = Regexp.new(
@@ -58,18 +60,36 @@ module Wareki
58
60
  (?:(?<era_name>紀元前|#{ERA_REGEX})?
59
61
  (?:(?<year>[元#{NUM_CHARS}]+)年))?
60
62
  (?:(?<is_leap>閏|潤|うるう)?
61
- (?:(?<month>[正#{NUM_CHARS}]+) |
63
+ (?:(?<month>[正#{NUM_CHARS}]+)(?<is_leap_post>['’])?月 |
62
64
  (?<alt_month>#{ALT_MONTH_NAME.join('|')})))?
63
65
  (?:(?<day>[元朔晦#{NUM_CHARS}]+)日|元旦)?
64
66
  }x.freeze
67
+ # REGEX が空でないマッチを返すには 年/月/日/元旦 か、「月」を含まない
68
+ # 月の別名 (弥生・師走) のいずれかが必要。それ以外は素の Date.parse に
69
+ # 直行させて monkey patch のオーバーヘッドを避ける。
70
+ PARSE_QUICK_FILTER = /[年月日]|元旦|弥生|師走/.freeze
71
+ TIME_REGEX = %r{
72
+ (?<noon>正午) |
73
+ (?:(?<ampm>午前|午後)[[:space:]]*)?
74
+ (?<hour>[#{NUM_CHARS}]+)[[:space:]]*時
75
+ (?:[[:space:]]*
76
+ (?:(?<half>半) |
77
+ (?<min>[#{NUM_CHARS}]+)[[:space:]]*分
78
+ (?:[[:space:]]*(?<sec>[#{NUM_CHARS}]+)[[:space:]]*秒)?))?
79
+ }x.freeze
80
+ # TIME_REGEX が非空マッチしうるのは「時」か「正午」を含む文字列のみ
81
+ TIME_PARSE_QUICK_FILTER = /時|正午/.freeze
65
82
 
66
83
  class UnsupportedDateRange < StandardError; end
84
+ class InvalidDate < ArgumentError; end
67
85
 
68
86
  module_function
69
87
 
70
88
  def parse_to_date(str, start = ::Date::ITALY)
71
89
  Date.parse(str).to_date(start)
72
- rescue ArgumentError
90
+ rescue InvalidDate
91
+ raise
92
+ rescue ArgumentError, UnsupportedDateRange
73
93
  ::Date.parse(str, true, start)
74
94
  end
75
95
  end
data/lib/wareki/date.rb CHANGED
@@ -8,33 +8,25 @@ require 'wareki/kansuji'
8
8
  module Wareki
9
9
  # Wareki date handling class, main implementation.
10
10
  class Date
11
- attr_accessor :year, :month, :day, :era_year, :era_name
11
+ include Comparable
12
+
13
+ attr_reader :year, :month, :day, :era_year, :era_name
12
14
 
13
15
  def self.today
14
16
  jd(::Date.today.jd)
15
17
  end
16
18
 
17
- def self._check_invalid_date(era, year, month, day)
18
- month == 12 or return true
19
- day > 2 or return true
20
- (era == '明治' && year == 5 ||
21
- %w(皇紀 神武天皇即位紀元).member?(era) &&
22
- year == GREGORIAN_START_YEAR - IMPERIAL_START_YEAR - 1) and
23
- return false
24
- true
25
- end
26
-
27
19
  def self._parse(str)
28
20
  str = str.to_s.gsub(/[[:space:]]/, '')
29
21
  match = REGEX.match(str)
30
- match && !match[0].empty? or
22
+ (match && !match[0].empty?) or
31
23
  raise ArgumentError, "Invaild Date: #{str}"
32
24
  era = match[:era_name]
33
25
  if (era.nil? || era == '') && match[:year].nil?
34
26
  year = Date.today.year
35
27
  else
36
- (year = Utils.k2i(match[:year])) > 0 or
37
- raise ArgumentError, "Invalid year: #{str}"
28
+ year = Utils.k2i(match[:year])
29
+ year > 0 or raise ArgumentError, "Invalid year: #{str}"
38
30
  end
39
31
  month = day = 1
40
32
 
@@ -48,20 +40,20 @@ module Wareki
48
40
  end
49
41
 
50
42
  month > 12 || month < 1 and
51
- raise ArgumentError, "Invalid month: #{str}"
43
+ raise InvalidDate, "invalid date (month out of range): #{str}"
44
+
45
+ is_leap = !!(match[:is_leap] || match[:is_leap_post])
52
46
 
53
47
  if match[:day]
54
48
  if match[:day] == '晦'
55
- day = Utils.last_day_of_month(ERA_BY_NAME[era].year + year - 1, month, match[:is_leap])
49
+ civil_year = Utils.era_year_to_civil(era, year)
50
+ day = Utils.last_day_of_era_month(era, civil_year, month, is_leap)
56
51
  else
57
52
  day = Utils.k2i(match[:day])
58
53
  end
59
54
  end
60
55
 
61
- _check_invalid_date(era, year, month, day) or
62
- raise ArgumentError, "Invaild Date: #{str}"
63
-
64
- {era: era, year: year, month: month, day: day, is_leap: !!match[:is_leap]}
56
+ {era: era, year: year, month: month, day: day, is_leap: is_leap}
65
57
  end
66
58
 
67
59
  def self.parse(str)
@@ -87,23 +79,13 @@ module Wareki
87
79
  end
88
80
 
89
81
  def initialize(era_name, era_year, month = 1, day = 1, is_leap_month = false)
90
- raise ArgumentError, "Undefined era '#{era_name}'" if
91
- era_name.to_s != '' && era_name != '紀元前' && !ERA_BY_NAME[era_name]
92
-
82
+ @era_name = era_name.to_s
83
+ @era_year = era_year
93
84
  @month = month
94
85
  @day = day
95
86
  @is_leap_month = is_leap_month
96
- @era_name = era_name
97
- @era_year = era_year
98
- if era_name.to_s == '' || era_name == '西暦'
99
- @year = @era_year
100
- elsif era_name.to_s == '紀元前'
101
- @year = -@era_year
102
- elsif %w(皇紀 神武天皇即位紀元).include? era_name
103
- @year = era_year + IMPERIAL_START_YEAR
104
- else
105
- @year = ERA_BY_NAME[era_name].year + era_year - 1
106
- end
87
+ @year = Utils.era_year_to_civil(@era_name, @era_year)
88
+ _validate_date!
107
89
  end
108
90
 
109
91
  def imperial_year
@@ -111,7 +93,7 @@ module Wareki
111
93
  end
112
94
 
113
95
  def imperial_year=(v)
114
- @year = v - IMPERIAL_START_YEAR
96
+ self.year = v + IMPERIAL_START_YEAR
115
97
  end
116
98
 
117
99
  def leap_month?
@@ -119,28 +101,86 @@ module Wareki
119
101
  end
120
102
 
121
103
  def leap_month=(v)
104
+ @jd = nil
122
105
  @is_leap_month = v
123
106
  end
124
107
 
108
+ def year=(v)
109
+ era_year = Utils.civil_to_era_year(@era_name, v)
110
+ @jd = nil
111
+ @year = v
112
+ @era_year = era_year
113
+ end
114
+
115
+ def month=(v)
116
+ @jd = nil
117
+ @month = v
118
+ end
119
+
120
+ def day=(v)
121
+ @jd = nil
122
+ @day = v
123
+ end
124
+
125
+ def era_year=(v)
126
+ year = Utils.era_year_to_civil(@era_name, v)
127
+ @jd = nil
128
+ @era_year = v
129
+ @year = year
130
+ end
131
+
132
+ def era_name=(v)
133
+ era_name = v.to_s
134
+ year = Utils.era_year_to_civil(era_name, @era_year)
135
+ @jd = nil
136
+ @era_name = era_name
137
+ @year = year
138
+ end
139
+
125
140
  def __set_jd(v)
126
141
  @jd = v
127
142
  end
128
143
 
129
144
  def month_index
130
145
  return month - 1 if
131
- ['', '西暦', '紀元前'].include?(@era_name) || @year >= GREGORIAN_START_YEAR
146
+ WESTERN_ERA_NAMES.include?(@era_name) || @year >= GREGORIAN_START_YEAR
132
147
 
133
148
  yobj = YEAR_BY_NUM[@year] or
134
149
  raise UnsupportedDateRange, "Cannot get year info of #{inspect}"
135
150
  idx = month - 1
136
- idx += 1 if leap_month? || yobj.leap_month && month > yobj.leap_month
151
+ idx += 1 if leap_month? || (yobj.leap_month && month > yobj.leap_month)
137
152
  idx
138
153
  end
139
154
 
155
+ def last_day_of_month
156
+ Utils.last_day_of_era_month(@era_name, @year, month, leap_month?)
157
+ end
158
+
159
+ def _validate_date!
160
+ (month.is_a?(Integer) && month >= 1 && month <= 12) or
161
+ raise InvalidDate, "invalid date (month out of range): #{inspect}"
162
+ (day.is_a?(Integer) && day >= 1) or
163
+ raise InvalidDate, "invalid date (day out of range): #{inspect}"
164
+ if !WESTERN_ERA_NAMES.include?(@era_name) && @year < GREGORIAN_START_YEAR
165
+ # 暦テーブル外の年は従来どおり jd 変換時の UnsupportedDateRange に委ねる
166
+ yobj = YEAR_BY_NUM[@year] or return
167
+ !leap_month? || yobj.leap_month == month or
168
+ raise InvalidDate, "invalid date (no leap month): #{inspect}"
169
+ day <= yobj.month_days[month_index] or
170
+ raise InvalidDate, "invalid date (day out of range): #{inspect}"
171
+ else
172
+ leap_month? and
173
+ raise InvalidDate, "invalid date (no leap month): #{inspect}"
174
+ day <= last_day_of_month or
175
+ raise InvalidDate, "invalid date (day out of range): #{inspect}"
176
+ end
177
+ end
178
+
140
179
  def jd
141
180
  @jd and return @jd
142
181
 
143
- ['', '西暦', '紀元前'].include?(@era_name) and
182
+ _validate_date!
183
+ WESTERN_ERA_NAMES.include?(@era_name) and
144
184
  return @jd = ::Date.new(@year, month, day, ::Date::ITALY).jd
145
185
 
146
186
  @year >= GREGORIAN_START_YEAR and
@@ -159,17 +199,28 @@ module Wareki
159
199
  to_date.to_time
160
200
  end
161
201
 
202
+ FORMAT_DIRECTIVE_REGEX = /%J(-|[_0]{0,2}[0-9]*|)([fFyYegGoOiImMsSlLdD][kK]?)/.freeze
203
+ FORMAT_EXPANSION_REGEX = /(?<!%)(?:%%)*\K#{FORMAT_DIRECTIVE_REGEX}/.freeze
204
+
205
+ def expand_wareki_format(format_str)
206
+ format_str.to_str.gsub(FORMAT_EXPANSION_REGEX) { format($2, $1) || $& }
207
+ end
208
+
162
209
  def strftime(format_str = '%JF')
163
- ret = format_str.to_str.gsub(/%J([fFyYegGoOiImMsSlLdD][kK]?)/) { format($1) || $& }
210
+ ret = expand_wareki_format(format_str)
164
211
  ret.index('%') or return ret
165
212
  d = to_date
166
213
  d.respond_to?(:_wareki_strftime_orig) ? d._wareki_strftime_orig(ret) : d.strftime(ret)
167
214
  end
168
215
 
169
- def format(key)
216
+ def _number_format(opt)
217
+ Utils.number_format(opt)
218
+ end
219
+
220
+ def format(key, opt = '')
170
221
  case key.to_sym
171
222
  when :e then era_name
172
- when :g then era_name.to_s == '' ? '' : era_year
223
+ when :g then era_name.to_s == '' ? '' : Kernel.format(_number_format(opt), era_year)
173
224
  when :G then era_name.to_s == '' ? '' : Utils.i2z(era_year)
174
225
  when :Gk then era_name.to_s == '' ? '' : YaKansuji.to_kan(era_year, :simple)
175
226
  when :GK
@@ -186,14 +237,14 @@ module Wareki
186
237
  when :i then imperial_year
187
238
  when :I then Utils.i2z(imperial_year)
188
239
  when :Ik then YaKansuji.to_kan(imperial_year, :simple)
189
- when :s then month
240
+ when :s then Kernel.format(_number_format(opt), month)
190
241
  when :S then Utils.i2z(month)
191
242
  when :Sk then YaKansuji.to_kan(month, :simple)
192
243
  when :SK then Utils.alt_month_name(month)
193
244
  when :l then leap_month? ? "'" : ''
194
245
  when :L then leap_month? ? '’' : ''
195
246
  when :Lk then leap_month? ? '閏' : ''
196
- when :d then day
247
+ when :d then Kernel.format(_number_format(opt), day)
197
248
  when :D then Utils.i2z(day)
198
249
  when :Dk then YaKansuji.to_kan(day, :simple)
199
250
  when :DK
@@ -201,19 +252,19 @@ module Wareki
201
252
  '元'
202
253
  elsif day == 1
203
254
  '朔'
204
- elsif day == Utils.last_day_of_month(year, month, leap_month?)
255
+ elsif day == last_day_of_month
205
256
  '晦'
206
257
  else
207
258
  YaKansuji.to_kan(day, :simple)
208
259
  end
209
- when :m then "#{format(:s)}#{format(:l)}"
260
+ when :m then "#{format(:s, opt)}#{format(:l)}"
210
261
  when :M then "#{format(:Lk)}#{format(:S)}"
211
262
  when :Mk then "#{format(:Lk)}#{format(:Sk)}"
212
- when :y then "#{format(:e)}#{format(:g)}"
263
+ when :y then "#{format(:e)}#{format(:g, opt)}"
213
264
  when :Y then "#{format(:e)}#{format(:G)}"
214
265
  when :Yk then "#{format(:e)}#{format(:Gk)}"
215
266
  when :YK then "#{format(:e)}#{format(:GK)}"
216
- when :f then "#{format(:e)}#{format(:g)}年#{format(:s)}#{format(:l)}月#{format(:d)}日"
267
+ when :f then "#{format(:e)}#{format(:g, opt)}年#{format(:s, opt)}#{format(:l)}月#{format(:d, opt)}日"
217
268
  when :F then "#{format(:e)}#{format(:GK)}年#{format(:Lk)}#{format(:Sk)}月#{format(:Dk)}日"
218
269
  end
219
270
  end
@@ -239,20 +290,48 @@ module Wareki
239
290
  true
240
291
  end
241
292
 
293
+ def hash
294
+ [self.class, @era_name, @era_year, @year, @month, @day, leap_month?].hash
295
+ end
296
+
297
+ def <=>(other)
298
+ ojd = _jd_if_date_like(other)
299
+ ojd = other if ojd.nil? && other.is_a?(Numeric)
300
+ ojd.nil? and return nil
301
+ jd <=> ojd
302
+ end
303
+
304
+ def succ
305
+ self + 1
306
+ end
307
+
242
308
  def -(other)
243
- self.class.jd jd - _to_jd_for_calc(other)
309
+ n = _to_days(other)
310
+ n.nil? or return self.class.jd(jd - n)
311
+ ojd = _jd_if_date_like(other)
312
+ ojd and return jd - ojd
313
+ raise TypeError, "Cannot subtract #{other.inspect} from Wareki::Date"
244
314
  end
245
315
 
246
316
  def +(other)
247
- self.class.jd jd + _to_jd_for_calc(other)
317
+ n = _to_days(other)
318
+ n.nil? and raise TypeError, "Cannot add #{other.inspect} to Wareki::Date"
319
+ self.class.jd(jd + n)
248
320
  end
249
321
 
250
- def _to_jd_for_calc(other)
251
- other.class.to_s == 'ActiveSupport::Duration' and
252
- raise NotImplementedError, 'Date calcration with ActiveSupport::Duration currently is not supported. Please use numeric.'
253
- other.respond_to?(:to_date) and other = other.to_date
254
- other.respond_to?(:jd) and other = other.jd
255
- other
322
+ def _to_days(other)
323
+ # rubocop:disable Style/ClassEqualityComparison
324
+ return other.in_days if other.class.name == 'ActiveSupport::Duration'
325
+ # rubocop:enable Style/ClassEqualityComparison
326
+
327
+ other.is_a?(Numeric) ? other : nil
328
+ end
329
+
330
+ def _jd_if_date_like(other)
331
+ return other.jd if other.respond_to?(:jd)
332
+
333
+ other.respond_to?(:to_date) && !other.is_a?(Numeric) and other = other.to_date
334
+ other.respond_to?(:jd) ? other.jd : nil
256
335
  end
257
336
  end
258
337
  end
@@ -252,7 +252,7 @@ module Wareki
252
252
  Era.new("昭和", 1926, 2424875, 2447534),
253
253
  Era.new("平成", 1989, 2447535, 2458604),
254
254
  Era.new("令和", 2019, 2458605, DAY_MAX),
255
- ].freeze
255
+ ].each(&:freeze).freeze
256
256
  ERA_NORTH_DEFS = [
257
257
  Era.new("大化", 645, 1956842, 1958551),
258
258
  Era.new("白雉", 650, 1958551, 1960259),
@@ -502,5 +502,5 @@ module Wareki
502
502
  Era.new("昭和", 1926, 2424875, 2447534),
503
503
  Era.new("平成", 1989, 2447535, 2458604),
504
504
  Era.new("令和", 2019, 2458605, DAY_MAX),
505
- ].freeze
505
+ ].each(&:freeze).freeze
506
506
  end
@@ -5,8 +5,26 @@ require 'wareki/date'
5
5
  module Wareki
6
6
  # :nodoc:
7
7
  module StdExt
8
+ module_function
9
+
10
+ def wareki_directive?(format)
11
+ !!(format.to_str =~ Wareki::Date::FORMAT_EXPANSION_REGEX)
12
+ end
13
+
14
+ def wareki_time_directive?(format)
15
+ !!(format.to_str =~ Wareki::Utils::TIME_FORMAT_EXPANSION_REGEX)
16
+ end
17
+
18
+ # %JT 時刻トークンと %J 日付トークンの両方を展開した文字列を返す
19
+ def expand_all_wareki_formats(format, datetime)
20
+ ret = format
21
+ ret = Wareki::Utils.expand_time_format(ret, datetime) if wareki_time_directive?(ret)
22
+ ret = datetime.to_wareki_date.expand_wareki_format(ret) if wareki_directive?(ret)
23
+ ret
24
+ end
8
25
  end
9
26
  end
27
+
10
28
  # :nodoc:
11
29
  class Date
12
30
  JAPAN = Wareki::GREGORIAN_START
@@ -17,23 +35,29 @@ class Date
17
35
 
18
36
  alias _wareki_strftime_orig strftime
19
37
  def strftime(format = '%F')
20
- if format.index('%J')
21
- to_wareki_date.strftime(format)
22
- else
23
- _wareki_strftime_orig(format)
24
- end
38
+ return _wareki_strftime_orig(format) unless Wareki::StdExt.wareki_directive?(format)
39
+
40
+ _wareki_strftime_orig(to_wareki_date.expand_wareki_format(format))
25
41
  end
26
42
 
27
43
  class << self
28
44
  alias _wareki_parse_orig parse
29
45
  def parse(str = '-4712-01-01', comp = true, start = ::Date::ITALY)
46
+ str = Wareki::Utils.normalize_time(str)
47
+ str.to_s =~ Wareki::PARSE_QUICK_FILTER or
48
+ return ::Date._wareki_parse_orig(str, comp, start)
30
49
  Wareki::Date.parse(str).to_date(start)
50
+ rescue Wareki::InvalidDate
51
+ raise
31
52
  rescue ArgumentError, Wareki::UnsupportedDateRange
32
53
  ::Date._wareki_parse_orig(str, comp, start)
33
54
  end
34
55
 
35
56
  alias _wareki__parse_orig _parse
36
57
  def _parse(str, comp = true)
58
+ str = Wareki::Utils.normalize_time(str)
59
+ str.to_s =~ Wareki::PARSE_QUICK_FILTER or
60
+ return ::Date._wareki__parse_orig(str, comp)
37
61
  di = Wareki::Date._parse(str)
38
62
  wdate = Wareki::Date.new(di[:era], di[:year], di[:month], di[:day], di[:is_leap])
39
63
  rescue ArgumentError, Wareki::UnsupportedDateRange
@@ -43,3 +67,23 @@ class Date
43
67
  end
44
68
  end
45
69
  end
70
+
71
+ # :nodoc:
72
+ class DateTime
73
+ alias _wareki_strftime_orig strftime
74
+ def strftime(format = '%FT%T%:z')
75
+ _wareki_strftime_orig(Wareki::StdExt.expand_all_wareki_formats(format, self))
76
+ end
77
+ end
78
+
79
+ # :nodoc:
80
+ class Time
81
+ def to_wareki_date
82
+ Wareki::Date.jd(to_date.jd)
83
+ end
84
+
85
+ alias _wareki_strftime_orig strftime
86
+ def strftime(format)
87
+ _wareki_strftime_orig(Wareki::StdExt.expand_all_wareki_formats(format, self))
88
+ end
89
+ end
data/lib/wareki/utils.rb CHANGED
@@ -5,6 +5,9 @@ require 'ya_kansuji'
5
5
  module Wareki
6
6
  # Static utility methods.
7
7
  module Utils
8
+ TIME_FORMAT_DIRECTIVE_REGEX = /%J(-|[_0]{0,2}[0-9]*|)(T(?:[fF]|[HMS]k?))/.freeze
9
+ TIME_FORMAT_EXPANSION_REGEX = /(?<!%)(?:%%)*\K#{TIME_FORMAT_DIRECTIVE_REGEX}/.freeze
10
+
8
11
  module_function
9
12
 
10
13
  def last_day_of_month(year, month, is_leap)
@@ -16,25 +19,47 @@ module Wareki
16
19
  end
17
20
 
18
21
  def _last_day_of_month_gregorian(year, month)
19
- tmp_y = year
20
- tmp_m = month
21
- if month == 12
22
- tmp_y += 1
23
- tmp_m = 1
24
- else
25
- tmp_m += 1
26
- end
27
- (::Date.new(tmp_y, tmp_m, 1, ::Date::GREGORIAN) - 1).day
22
+ ::Date.new(year, month, -1, ::Date::GREGORIAN).day
28
23
  end
29
24
 
30
25
  def _last_day_of_month_from_defs(year, month, is_leap)
31
26
  yobj = YEAR_BY_NUM[year] or
32
- raise UnsupportedDateRange, "Cannot find year #{inspect}"
27
+ raise UnsupportedDateRange, "Cannot find year #{year}"
33
28
  month_idx = month - 1
34
- month_idx += 1 if is_leap || yobj.leap_month && yobj.leap_month < month
29
+ month_idx += 1 if is_leap || (yobj.leap_month && yobj.leap_month < month)
35
30
  yobj.month_days[month_idx]
36
31
  end
37
32
 
33
+ def era_year_to_civil(era_name, era_year)
34
+ era_name = era_name.to_s
35
+ return era_year if ['', '西暦'].include?(era_name)
36
+ return -era_year if era_name == '紀元前'
37
+ return era_year + IMPERIAL_START_YEAR if IMPERIAL_ERA_NAMES.include?(era_name)
38
+
39
+ era = ERA_BY_NAME[era_name] or
40
+ raise ArgumentError, "Undefined era '#{era_name}'"
41
+ era.year + era_year - 1
42
+ end
43
+
44
+ def civil_to_era_year(era_name, year)
45
+ era_name = era_name.to_s
46
+ return year if ['', '西暦'].include?(era_name)
47
+ return -year if era_name == '紀元前'
48
+ return year - IMPERIAL_START_YEAR if IMPERIAL_ERA_NAMES.include?(era_name)
49
+
50
+ era = ERA_BY_NAME[era_name] or
51
+ raise ArgumentError, "Undefined era '#{era_name}'"
52
+ year - era.year + 1
53
+ end
54
+
55
+ def last_day_of_era_month(era_name, civil_year, month, is_leap)
56
+ if WESTERN_ERA_NAMES.include?(era_name.to_s)
57
+ ::Date.new(civil_year, month, -1, ::Date::ITALY).day
58
+ else
59
+ last_day_of_month(civil_year, month, is_leap)
60
+ end
61
+ end
62
+
38
63
  def alt_month_name_to_i(name)
39
64
  i = ALT_MONTH_NAME.index(name) or return false
40
65
  i + 1
@@ -85,6 +110,7 @@ module Wareki
85
110
 
86
111
  def find_year(d)
87
112
  jd = _to_jd(d)
113
+ jd < YEAR_DEFS.first.start and return nil
88
114
  YEAR_DEFS.bsearch { |y| y.end >= jd }
89
115
  end
90
116
 
@@ -111,6 +137,59 @@ module Wareki
111
137
  end
112
138
  end
113
139
 
140
+ # 日本語の時刻表記 (漢数字・全角数字の時分秒、午前/午後、半、正午) を
141
+ # 等価な "HH:MM(:SS)" 表記へ置換する。値の範囲チェックは行わず、
142
+ # 妥当性判断は Ruby 標準パーサに委ねる (二十五時 -> "25:00")。
143
+ def normalize_time(str)
144
+ str.to_s =~ TIME_PARSE_QUICK_FILTER or return str
145
+ str.to_s.sub(TIME_REGEX) { _time_match_to_s(Regexp.last_match) }
146
+ end
147
+
148
+ def _time_match_to_s(match)
149
+ return '12:00' if match[:noon]
150
+
151
+ hour = k2i(match[:hour])
152
+ hour += 12 if match[:ampm] == '午後' && hour < 12
153
+ min = 0
154
+ min = 30 if match[:half]
155
+ min = k2i(match[:min]) if match[:min]
156
+ return Kernel.format('%<hour>02d:%<min>02d', hour: hour, min: min) unless match[:sec]
157
+
158
+ Kernel.format('%<hour>02d:%<min>02d:%<sec>02d', hour: hour, min: min, sec: k2i(match[:sec]))
159
+ end
160
+
161
+ def number_format(opt)
162
+ case opt
163
+ when '', '0', '_0' then '%02d'
164
+ when '-' then '%d'
165
+ when /_\Z/ then '%2d'
166
+ when /0?_/ then "%#{opt.sub(/0?_/, '')}d"
167
+ when /_?0/ then "%#{opt.sub(/_?0/, '0')}d"
168
+ else "%0#{opt}d"
169
+ end
170
+ end
171
+
172
+ def expand_time_format(format_str, time)
173
+ format_str.to_str.gsub(TIME_FORMAT_EXPANSION_REGEX) { _format_time_directive($2, $1, time) || $& }
174
+ end
175
+
176
+ def _format_time_directive(key, opt, time)
177
+ case key.to_sym
178
+ when :Tf
179
+ nf = number_format(opt)
180
+ Kernel.format("#{nf}時#{nf}分#{nf}秒", time.hour, time.min, time.sec)
181
+ when :TF
182
+ "#{YaKansuji.to_kan(time.hour, :simple)}時#{YaKansuji.to_kan(time.min, :simple)}分" \
183
+ "#{YaKansuji.to_kan(time.sec, :simple)}秒"
184
+ when :TH then i2z(time.hour)
185
+ when :THk then YaKansuji.to_kan(time.hour, :simple)
186
+ when :TM then i2z(time.min)
187
+ when :TMk then YaKansuji.to_kan(time.min, :simple)
188
+ when :TS then i2z(time.sec)
189
+ when :TSk then YaKansuji.to_kan(time.sec, :simple)
190
+ end
191
+ end
192
+
114
193
  # DEPRECATED
115
194
  def kan_to_i(*args)
116
195
  warn '[DEPRECATED] Wareki::Utils.kan_to_i: Please use ya_kansuji gem to handle kansuji'
@@ -120,7 +199,7 @@ module Wareki
120
199
  # DEPRECATED
121
200
  def i_to_kan(*args)
122
201
  warn '[DEPRECATED] Wareki::Utils.i_to_kan: Please use ya_kansuji gem to handle kansuji'
123
- i2k(*args)
202
+ YaKansuji.to_kan(*args)
124
203
  end
125
204
  end
126
205
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wareki
4
- VERSION = '1.1.0'
4
+ VERSION = '2.0.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wareki
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tatsuki Sugiura
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-23 00:00:00.000000000 Z
11
+ date: 2026-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ya_kansuji
@@ -30,48 +30,6 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: 2.0.0
33
- - !ruby/object:Gem::Dependency
34
- name: bundler
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '1.9'
40
- type: :development
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '1.9'
47
- - !ruby/object:Gem::Dependency
48
- name: rake
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: '10.0'
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: '10.0'
61
- - !ruby/object:Gem::Dependency
62
- name: rspec
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- version: '0'
68
- type: :development
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- version: '0'
75
33
  description: |
76
34
  Pure ruby library of Wareki (Japanese calendar date) that supports string parsing,
77
35
  formatting, and bi-directional convertion with standard Date class.
@@ -95,9 +53,10 @@ files:
95
53
  - lib/wareki/version.rb
96
54
  homepage: https://github.com/sugi/wareki
97
55
  licenses:
98
- - BSD
99
- metadata: {}
100
- post_install_message:
56
+ - BSD-2-Clause
57
+ metadata:
58
+ rubygems_mfa_required: 'true'
59
+ post_install_message:
101
60
  rdoc_options: []
102
61
  require_paths:
103
62
  - lib
@@ -112,9 +71,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
71
  - !ruby/object:Gem::Version
113
72
  version: '0'
114
73
  requirements: []
115
- rubyforge_project:
116
- rubygems_version: 2.7.6.2
117
- signing_key:
74
+ rubygems_version: 3.1.6
75
+ signing_key:
118
76
  specification_version: 4
119
77
  summary: Pure ruby library of Wareki (Japanese calendar date)
120
78
  test_files: []