zakuro 0.0.1 → 0.1.2

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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +4 -0
  3. data/.gitignore +7 -1
  4. data/.rubocop.yml +3 -0
  5. data/Gemfile +2 -0
  6. data/Makefile +2 -0
  7. data/README.md +126 -33
  8. data/Rakefile +1 -1
  9. data/doc/gengou.md +315 -0
  10. data/doc/operation.md +25 -0
  11. data/doc/operation/csv/month.csv +202 -0
  12. data/doc/operation/operation.xlsx +0 -0
  13. data/doc/operation/transfer.rb +77 -0
  14. data/lib/zakuro/condition.rb +19 -15
  15. data/lib/zakuro/cycle/abstract_remainder.rb +3 -4
  16. data/lib/zakuro/era/japan/gengou.rb +106 -0
  17. data/lib/zakuro/era/japan/gengou/parser.rb +167 -0
  18. data/lib/zakuro/era/japan/gengou/type.rb +178 -0
  19. data/lib/zakuro/era/japan/gengou/validator.rb +236 -0
  20. data/lib/zakuro/era/japan/reki.rb +91 -0
  21. data/lib/zakuro/era/japan/yaml/set-001-until-south.yaml +1121 -0
  22. data/lib/zakuro/era/japan/yaml/set-002-from-north.yaml +485 -0
  23. data/lib/zakuro/era/japan/yaml/set-003-modern.yaml +28 -0
  24. data/lib/zakuro/era/western.rb +11 -1
  25. data/lib/zakuro/operation/month/parser.rb +373 -0
  26. data/lib/zakuro/operation/month/type.rb +453 -0
  27. data/lib/zakuro/operation/month/validator.rb +802 -0
  28. data/lib/zakuro/operation/operation.rb +66 -0
  29. data/lib/zakuro/operation/yaml/month.yaml +6452 -0
  30. data/lib/zakuro/output/error.rb +2 -0
  31. data/lib/zakuro/output/logger.rb +2 -0
  32. data/lib/zakuro/output/response.rb +21 -19
  33. data/lib/zakuro/result/core.rb +52 -0
  34. data/lib/zakuro/result/data.rb +187 -0
  35. data/lib/zakuro/result/operation.rb +114 -0
  36. data/lib/zakuro/result/result.rb +37 -0
  37. data/lib/zakuro/{output → tools}/stringifier.rb +16 -9
  38. data/lib/zakuro/tools/typeof.rb +33 -0
  39. data/lib/zakuro/version.rb +1 -1
  40. data/lib/zakuro/version/senmyou/README.md +3 -1
  41. data/lib/zakuro/version/senmyou/base/era.rb +3 -1
  42. data/lib/zakuro/version/senmyou/base/multi_gengou.rb +98 -0
  43. data/lib/zakuro/version/senmyou/base/multi_gengou_roller.rb +217 -0
  44. data/lib/zakuro/version/senmyou/base/remainder.rb +4 -4
  45. data/lib/zakuro/version/senmyou/base/solar_term.rb +82 -0
  46. data/lib/zakuro/version/senmyou/base/year.rb +52 -6
  47. data/lib/zakuro/version/senmyou/monthly/first_day.rb +44 -0
  48. data/lib/zakuro/version/senmyou/monthly/initialized_month.rb +119 -0
  49. data/lib/zakuro/version/senmyou/monthly/lunar_phase.rb +3 -3
  50. data/lib/zakuro/version/senmyou/monthly/month.rb +136 -67
  51. data/lib/zakuro/version/senmyou/monthly/month_label.rb +87 -0
  52. data/lib/zakuro/version/senmyou/monthly/operated_month.rb +196 -0
  53. data/lib/zakuro/version/senmyou/{summary/annual_data.rb → range/annual_range.rb} +34 -61
  54. data/lib/zakuro/version/senmyou/range/full_range.rb +194 -0
  55. data/lib/zakuro/version/senmyou/range/operated_range.rb +126 -0
  56. data/lib/zakuro/version/senmyou/range/operated_solar_terms.rb +181 -0
  57. data/lib/zakuro/version/senmyou/range/western_date_allocation.rb +68 -0
  58. data/lib/zakuro/version/senmyou/range/year_boundary.rb +138 -0
  59. data/lib/zakuro/version/senmyou/senmyou.rb +2 -2
  60. data/lib/zakuro/version/senmyou/specifier/single_day_specifier.rb +102 -0
  61. data/lib/zakuro/version/senmyou/stella/lunar_orbit.rb +2 -2
  62. data/lib/zakuro/version/senmyou/stella/solar_average.rb +105 -128
  63. data/lib/zakuro/version/senmyou/stella/solar_location.rb +213 -0
  64. data/lib/zakuro/version/senmyou/stella/solar_orbit.rb +4 -189
  65. data/lib/zakuro/version/senmyou/summary/single.rb +125 -0
  66. data/lib/zakuro/version_factory.rb +1 -1
  67. metadata +46 -15
  68. data/lib/zakuro/era/gengou/set-001-until-south.yaml +0 -375
  69. data/lib/zakuro/era/gengou/set-002-from-north.yaml +0 -166
  70. data/lib/zakuro/era/gengou/set-003-modern.yaml +0 -12
  71. data/lib/zakuro/era/japan.rb +0 -630
  72. data/lib/zakuro/output/result.rb +0 -219
  73. data/lib/zakuro/version/senmyou/base/gengou.rb +0 -210
  74. data/lib/zakuro/version/senmyou/summary/gengou_data.rb +0 -294
@@ -3,7 +3,7 @@
3
3
  require 'date'
4
4
  require_relative '../abstract_version'
5
5
  require_relative '../../era/western'
6
- require_relative 'summary/gengou_data'
6
+ require_relative 'summary/single'
7
7
 
8
8
  # :nodoc:
9
9
  module Zakuro
@@ -27,7 +27,7 @@ module Zakuro
27
27
  #
28
28
  def self.to_japan_date(western_date:)
29
29
  date = Western::Calendar.create(date: western_date)
30
- GengouData.get_ancient_date(date: date)
30
+ Single.get(date: date)
31
31
  end
32
32
  end
33
33
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../era/western'
4
+ require_relative '../range/full_range'
5
+ require_relative '../base/multi_gengou_roller'
6
+ require_relative '../base/year'
7
+ require_relative '../../../output/response'
8
+ require_relative '../../../output/logger'
9
+
10
+ # :nodoc:
11
+ module Zakuro
12
+ # :nodoc:
13
+ module Senmyou
14
+ #
15
+ # SingleDaySpecifier 一日検索
16
+ #
17
+ module SingleDaySpecifier
18
+ # @return [Logger] ロガー
19
+ LOGGER = Logger.new(location: 'specifier')
20
+
21
+ #
22
+ # 取得する
23
+ #
24
+ # @param [Array<Year>] yeas 範囲
25
+ # @param [Western::Calendar] date 西暦日
26
+ #
27
+ # @return [Result::Data::SingleDay] 和暦日
28
+ #
29
+ def self.get(years: [], date: Western::Calendar.new)
30
+ year = specify_year(years: years, date: date)
31
+
32
+ year = transfer(year: year, date: date)
33
+
34
+ month = specify_month(year: year, date: date)
35
+ first_date = month.western_date
36
+
37
+ Response::SingleDay.save_single_day(
38
+ param: Response::SingleDay::Param.new(
39
+ year: year, month: month,
40
+ date: date, days: date - first_date
41
+ )
42
+ )
43
+ end
44
+
45
+ #
46
+ # 年を特定する
47
+ #
48
+ # @param [Array<Year>] years 範囲
49
+ # @param [Western::Calendar] date 西暦日
50
+ #
51
+ # @return [Year] 対象年
52
+ #
53
+ def self.specify_year(years:, date:)
54
+ years.reverse_each do |year|
55
+ return year if date >= year.new_year_date
56
+ end
57
+
58
+ raise ArgumentError, "invalid year range. date: #{date.format}"
59
+ end
60
+ private_class_method :specify_year
61
+
62
+ #
63
+ # 改元する
64
+ #
65
+ # @param [Year] year 年
66
+ # @param [Western::Calendar] date 西暦日
67
+ #
68
+ # @return [Year] 改元後の年
69
+ #
70
+ def self.transfer(year:, date:)
71
+ multi_gengou = MultiGengouRoller.transfer(multi_gengou: year.multi_gengou, date: date)
72
+ Year.new(multi_gengou: multi_gengou, new_year_date: year.new_year_date,
73
+ months: year.months, total_days: year.total_days)
74
+ end
75
+ private_class_method :transfer
76
+
77
+ # :reek:TooManyStatements { max_statements: 7 }
78
+
79
+ #
80
+ # 月を特定する
81
+ #
82
+ # @param [Year] year 年
83
+ # @param [Western::Calendar] date 西暦日
84
+ #
85
+ # @return [Month] 対象月
86
+ #
87
+ def self.specify_month(year:, date:)
88
+ months = year.months
89
+
90
+ current_month = months[0]
91
+ months.each do |month|
92
+ return current_month if month.western_date > date
93
+
94
+ current_month = month
95
+ end
96
+
97
+ current_month
98
+ end
99
+ private_class_method :specify_month
100
+ end
101
+ end
102
+ end
@@ -193,7 +193,7 @@ module Zakuro
193
193
  { |key, _| key.match(/^#{prefix}_#{format('%<day>02d', day: day)}_.*/) }
194
194
 
195
195
  targets.each do |key, value|
196
- # NOTE 境界値は上から順に引き当てた方を返す(7日の境界値7465は上のキーで返す)
196
+ # NOTE: 境界値は上から順に引き当てた方を返す(7日の境界値7465は上のキーで返す)
197
197
  matched, diff = \
198
198
  extract_data_from_moon_adjustment_key(key, minute)
199
199
  # 小余の下げ幅
@@ -255,7 +255,7 @@ module Zakuro
255
255
  #
256
256
  # 天正冬至(入暦前回未計算)を求める
257
257
  #
258
- # @param [Remainder] winter_solstice_age 昨年冬至
258
+ # @param [Remainder] winter_solstice_age 天正閏余
259
259
  # @param [Integer] western_year 西暦年
260
260
  #
261
261
  # @return [LunarRemainder] 入暦
@@ -7,186 +7,163 @@ module Zakuro
7
7
  #
8
8
  # SolarAverage 常気(太陽軌道平均)
9
9
  #
10
- module SolarAverage
10
+ class SolarAverage
11
11
  # @return [Remainder] 気策(24分の1年)
12
12
  SOLAR_TERM_AVERAGE = Remainder.new(day: 15, minute: 1835, second: 5)
13
13
 
14
- # :reek:TooManyStatements { max_statements: 6 }
15
-
16
14
  #
17
- # 冬至から数えた1年データの月ごとに二十四節気を割り当てる
15
+ # 初期化
18
16
  #
19
17
  # @param [Integer] western_year 西暦年
20
- # @param [Array<Month>] annual_data 1年データ
21
- #
22
- # @return [Array<Month>] 1年データ
23
18
  #
24
- def self.set_solar_terms_into_annual_data(western_year:, annual_data:)
25
- # 天正冬至
26
- winter_solstice = WinterSolstice.calc(western_year: western_year)
27
-
28
- # 前年冬至からの二十四節気
29
- solar_terms = collect_solar_terms_from_last_winter_solstice(
30
- winter_solstice: winter_solstice
31
- )
32
-
33
- apply_solar_terms_from_last_winter_solstice(annual_data: annual_data,
34
- solar_terms: solar_terms)
35
-
36
- # 前後の二十四節気
37
- rest_solar_terms = \
38
- collect_solar_terms_before_and_after(solar_terms: solar_terms)
39
-
40
- apply_solar_terms_before_and_after(annual_data: annual_data,
41
- rest_solar_terms: rest_solar_terms)
42
-
43
- annual_data
19
+ def initialize(western_year:)
20
+ @solar_term = SolarAverage.first_solar_term(western_year: western_year)
44
21
  end
45
22
 
46
- # :reek:TooManyStatements { max_statements: 6 }
47
-
48
23
  #
49
- # 前年冬至から当年大雪までの二十四節気を計算する
24
+ # 冬至から数えた1年データの月ごとに二十四節気を割り当てる
50
25
  #
51
- # @param [Remainder] winter_solstice 冬至
26
+ # @param [Array<Month>] annual_range 1年データ
52
27
  #
53
- # @return [Array<Remainder>] 二十四節気
28
+ # @return [Array<Month>] 1年データ
54
29
  #
55
- def self.collect_solar_terms_from_last_winter_solstice(winter_solstice:)
56
- result = []
57
- term = winter_solstice
58
- (0...24).each do |_i|
59
- result.push(term)
60
- term = term.add(SOLAR_TERM_AVERAGE)
30
+ def set(annual_range:)
31
+ # 次月と比較しながら当月の二十四節気を決める
32
+ # NOTE: 最後の月は処理できない(=計算外の余分な月が最後に必要である)
33
+ annual_range.each_cons(2) do |(current_month, next_month)|
34
+ set_solar_term(
35
+ current_month: current_month,
36
+ next_month: next_month
37
+ )
61
38
  end
62
39
 
63
- result
40
+ annual_range
64
41
  end
65
- private_class_method :collect_solar_terms_from_last_winter_solstice
66
-
67
- # :reek:TooManyStatements { max_statements: 9 }
68
42
 
69
43
  #
70
- # 各月の二十四節気を設定する
44
+ # 計算開始する二十四節気を求める
71
45
  #
72
- # @param [Array<Month>] annual_data 1年データ
73
- # @param [Array<Remainder>] solar_terms 1年データ内の全二十四節気
46
+ # @param [Integer] western_year 西暦年
47
+ #
48
+ # @return [SolarTerm] 二十四節気
74
49
  #
75
- def self.apply_solar_terms_from_last_winter_solstice(annual_data:,
76
- solar_terms:)
77
- c_idx = 0
78
- st_idx = 0
79
- while c_idx < annual_data.size && st_idx < solar_terms.size
80
- current_month = annual_data[c_idx]
81
- next_month = annual_data[c_idx + 1]
82
- solar_term = solar_terms[st_idx]
50
+ def self.first_solar_term(western_year:)
51
+ # 天正冬至
52
+ winter_solstice = WinterSolstice.calc(western_year: western_year)
83
53
 
84
- if in_range_solar_term?(target: solar_term, min: current_month.remainder,
85
- max: next_month.remainder)
86
- set_solar_term(month: current_month,
87
- solar_term: solar_term, solar_term_index: st_idx)
88
- st_idx += 1
89
- next
90
- end
91
- c_idx += 1
92
- end
54
+ # 二十四節気(冬至)
55
+ solar_term = SolarTerm.new(index: 0, remainder: winter_solstice)
56
+
57
+ first_solar_term_index = SolarAverage.calc_fist_solar_term_index(western_year: western_year)
58
+
59
+ # 対象の二十四節気まで戻す
60
+ solar_term.prev_by_index(first_solar_term_index)
61
+
62
+ solar_term
93
63
  end
94
- private_class_method :apply_solar_terms_from_last_winter_solstice
95
64
 
96
65
  #
97
- # 1年データ内の二十四節気の前後を収集する
66
+ # 計算開始する二十四節気番号を求める
67
+ #
68
+ # * 前提として入定気は冬至の手前にある
69
+ # * 例えば、定気が大雪であれば入定気は大雪の範囲内にある
70
+ # * 入定気は、定気の開始位置に重複しない限り、常に定気より後にある
71
+ # * 基本的に定気の一つ前から起算すれば、当時から求めた11月(閏10/閏11月)に二十四節気を割り当てられる
98
72
  #
99
- # @param [Array<Remainder>] solar_terms 1年データ内の全二十四節気
73
+ # @param [Integer] western_year 西暦年
100
74
  #
101
- # @return [Hash<Integer, Hash<Symbol, Integer>>, Hash<Integer, Hash<Symbol, Remainder>>] 前後
75
+ # @return [Integer] 二十四節気番号
102
76
  #
103
- def self.collect_solar_terms_before_and_after(solar_terms:)
104
- raise ArgumentError, 'parameter must be 24 solar terms' unless solar_terms.size == 24
77
+ def self.calc_fist_solar_term_index(western_year:)
78
+ # 天正閏余
79
+ winter_solstice_age = \
80
+ WinterSolstice.calc_moon_age(western_year: western_year)
105
81
 
106
- touji = solar_terms[-1].add(SOLAR_TERM_AVERAGE)
107
- {
108
- # 前年大雪
109
- 23 => { index: 0, solar_term: solar_terms[0].sub(SOLAR_TERM_AVERAGE) },
110
- # 当年冬至
111
- 0 => { index: -2, solar_term: touji },
112
- # 当年小寒
113
- 1 => { index: -2, solar_term: touji.add(SOLAR_TERM_AVERAGE) }
114
- }
115
- end
116
- private_class_method :collect_solar_terms_before_and_after
82
+ # 入定気を求める
83
+ solar_location = SolarLocation.get(
84
+ solar_term: SolarTerm.new(remainder: winter_solstice_age)
85
+ )
117
86
 
118
- # :reek:TooManyStatements { max_statements: 6 }
87
+ solar_term_index = solar_location.index
119
88
 
120
- #
121
- # 1年データ前後の二十四節気を適用する
122
- #
123
- # @param [Array<Month>] annual_data 1年データ
124
- # @param [Hash<Integer, Hash<Symbol, Integer>>, Hash<Integer, Hash<Symbol, Remainder>>]
125
- # rest_solar_terms 前後
126
- #
127
- def self.apply_solar_terms_before_and_after(annual_data:, rest_solar_terms:)
128
- rest_solar_terms.each do |key, value|
129
- index = value[:index]
130
- solar_term = value[:solar_term]
131
- data = annual_data[index]
132
- next unless in_range_solar_term?(
133
- target: solar_term,
134
- min: data.remainder, max: annual_data[index + 1].remainder
135
- )
89
+ # 入定気の一つ後の二十四節気まで戻す(ただし11月経朔が二十四節気上にある場合は戻さない)
90
+ solar_term_index += 1 unless solar_location.remainder == Remainder.new(total: 0)
136
91
 
137
- set_solar_term(month: data,
138
- solar_term: solar_term, solar_term_index: key)
139
- end
92
+ solar_term_index
140
93
  end
141
- private_class_method :apply_solar_terms_before_and_after
142
94
 
143
95
  # :reek:TooManyStatements { max_statements: 7 }
144
96
 
145
97
  #
146
- # 月内(currentからnextの間)に二十四節気があるか
98
+ # 月内(当月朔日から当月末日(来月朔日の前日)の間)に二十四節気があるか
147
99
  # @note 大余60で一巡するため 以下2パターンがある
148
- # * current(min) <= next(max) : (二十四節気) >= current && (二十四節気) < next
149
- # * current(min) > next(max) : (二十四節気) >= current || (二十四節気) < next
100
+ # * current_month <= next_month : (二十四節気) >= current_month && (二十四節気) < next_month
101
+ # * current_month > next_month : (二十四節気) >= current_month || (二十四節気) < next_month
150
102
  #
151
- # @param [Remainder] target 対象の二十四節気
152
- # @param [Remainder] min 月初
153
- # @param [Remainder] max 月末
103
+ # @param [Remainder] solar_term 二十四節気
104
+ # @param [Remainder] current_month 月初
105
+ # @param [Remainder] next_month 月末
154
106
  #
155
107
  # @return [True] 対象の二十四節気がある
156
108
  # @return [False] 対象の二十四節気がない
157
109
  #
158
- def self.in_range_solar_term?(target:, min:, max:)
110
+ def self.in_solar_term?(solar_term:, current_month:, next_month:)
159
111
  # 大余で比較する
160
- target_time = target.day
161
- min_time = min.day
162
- max_time = max.day
163
- min_over = (target_time >= min_time)
164
- max_under = (target_time < max_time)
112
+ target_time = solar_term.day
113
+ current_month_time = current_month.day
114
+ next_month_time = next_month.day
115
+ current_month_over = (target_time >= current_month_time)
116
+ next_month_under = (target_time < next_month_time)
117
+
118
+ return current_month_over && next_month_under if current_month_time <= next_month_time
165
119
 
166
- (min_time <= max_time ? min_over && max_under : min_over || max_under)
120
+ current_month_over || next_month_under
167
121
  end
168
- private_class_method :in_range_solar_term?
122
+
123
+ private
124
+
125
+ # :reek:TooManyStatements { max_statements: 8 }
169
126
 
170
127
  #
171
128
  # 二十四節気を設定する
172
129
  #
173
- # @param [Month] month
174
- # @param [Remainder] solar_term 二十四節気
175
- # @param [Integer] solar_term_index 連番(二十四節気)
176
- #
177
- def self.set_solar_term(month:, solar_term:, solar_term_index:)
178
- term = SolarTerm.new(remainder: solar_term, index: solar_term_index)
179
- if solar_term_index.even?
180
- # 中気
181
- month.even_term = term
182
- else
183
- # 節気
184
- month.odd_term = term
130
+ # @param [Month] current_month 当月
131
+ # @param [Month] next_month 次月
132
+ #
133
+ def set_solar_term(current_month:, next_month:)
134
+ # 安全策として無限ループは回避する
135
+ # * 最大試行回数:4回(設定なし => 設定あり => 設定あり => 設定なし)
136
+ # * 閏月は1回しか設定しない
137
+ # * 最大2回設定する(中気・節気)
138
+ (0..3).each do |_index|
139
+ in_range = SolarAverage.in_solar_term?(
140
+ solar_term: @solar_term.remainder, current_month: current_month.remainder,
141
+ next_month: next_month.remainder
142
+ )
143
+
144
+ # 範囲外
145
+ unless in_range
146
+ # 1つ以上設定されていれば切り上げる(一つ飛ばしで二十四節気を設定することはない)
147
+ break unless current_month.empty_solar_term?
148
+
149
+ next_solar_term
150
+ next
151
+ end
152
+
153
+ current_month.add_term(term: @solar_term.clone)
154
+ next_solar_term
155
+
156
+ # 宣明暦は最大2つまで
157
+ break if current_month.solar_term_size == 2
185
158
  end
159
+ end
186
160
 
187
- nil
161
+ #
162
+ # 次の二十四節気に移る
163
+ #
164
+ def next_solar_term
165
+ @solar_term.next!
188
166
  end
189
- private_class_method :set_solar_term
190
167
  end
191
168
  end
192
169
  end
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :nodoc:
4
+ module Zakuro
5
+ # :nodoc:
6
+ module Senmyou
7
+ #
8
+ # SolarLocation 入定気演算
9
+ #
10
+ # 入定気とは、太陽がどの二十四節気に属するか、またその二十四節気の開始点からどれだけ離れているかを示す
11
+ #
12
+ module SolarLocation
13
+ #
14
+ # Interval 入気定日加減数(二十四節気の間隔)
15
+ #
16
+ module Interval
17
+ # @return [Hash<Symbol, Remainder>] 一覧
18
+ LIST = {
19
+ # 冬至(とうじ)・大雪(たいせつ)
20
+ touji: Remainder.new(day: 14, minute: 4235, second: 5),
21
+ taisetsu: Remainder.new(day: 14, minute: 4235, second: 5),
22
+ # 小寒(しょうかん)・小雪(しょうせつ)
23
+ shoukan: Remainder.new(day: 14, minute: 5235, second: 5),
24
+ shousetsu: Remainder.new(day: 14, minute: 5235, second: 5),
25
+ # 大寒(だいかん)・立冬(りっとう)
26
+ daikan: Remainder.new(day: 14, minute: 6235, second: 5),
27
+ rittou: Remainder.new(day: 14, minute: 6235, second: 5),
28
+ # 立春(りっしゅん)・霜降(そうこう)
29
+ risshun: Remainder.new(day: 14, minute: 7235, second: 5),
30
+ soukou: Remainder.new(day: 14, minute: 7235, second: 5),
31
+ # 雨水(うすい)・寒露(かんろ)
32
+ usui: Remainder.new(day: 15, minute: 35, second: 5),
33
+ kanro: Remainder.new(day: 15, minute: 35, second: 5),
34
+ # 啓蟄(けいちつ)・秋分(しゅうぶん)
35
+ keichitsu: Remainder.new(day: 15, minute: 1235, second: 5),
36
+ shuubun: Remainder.new(day: 15, minute: 1235, second: 5),
37
+ # 春分(しゅんぶん)・白露(はくろ)
38
+ shunbun: Remainder.new(day: 15, minute: 2435, second: 5),
39
+ hakuro: Remainder.new(day: 15, minute: 2435, second: 5),
40
+ # 清明(せいめい)・処暑(しょしょ)
41
+ seimei: Remainder.new(day: 15, minute: 3635, second: 5),
42
+ shosho: Remainder.new(day: 15, minute: 3635, second: 5),
43
+ # 穀雨(こくう)・立秋(りっしゅう)
44
+ kokuu: Remainder.new(day: 15, minute: 4835, second: 5),
45
+ risshuu: Remainder.new(day: 15, minute: 4835, second: 5),
46
+ # 立夏(りっか)・大暑(たいしょ)
47
+ rikka: Remainder.new(day: 15, minute: 5835, second: 5),
48
+ taisho: Remainder.new(day: 15, minute: 5835, second: 5),
49
+ # 小満(しょうまん)・小暑(しょうしょ)
50
+ shouman: Remainder.new(day: 15, minute: 6835, second: 5),
51
+ shousho: Remainder.new(day: 15, minute: 6835, second: 5),
52
+ # 芒種(ぼうしゅ)・夏至(げし)
53
+ boushu: Remainder.new(day: 15, minute: 7835, second: 5),
54
+ geshi: Remainder.new(day: 15, minute: 7835, second: 5)
55
+ }.freeze
56
+
57
+ # @return [Array<Remainder>] 索引
58
+ INDEXES = [
59
+ LIST[:touji], # 0
60
+ LIST[:shoukan], # 1
61
+ LIST[:daikan], # 2
62
+ LIST[:risshun], # 3
63
+ LIST[:usui], # 4
64
+ LIST[:keichitsu], # 5
65
+ LIST[:shunbun], # 6
66
+ LIST[:seimei], # 7
67
+ LIST[:kokuu], # 8
68
+ LIST[:rikka], # 9
69
+ LIST[:shouman], # 10
70
+ LIST[:boushu], # 11
71
+ LIST[:geshi], # 12
72
+ LIST[:shousho], # 13
73
+ LIST[:taisho], # 14
74
+ LIST[:risshuu], # 15
75
+ LIST[:shosho], # 16
76
+ LIST[:hakuro], # 17
77
+ LIST[:shuubun], # 18
78
+ LIST[:kanro], # 19
79
+ LIST[:soukou], # 20
80
+ LIST[:rittou], # 21
81
+ LIST[:shousetsu], # 22
82
+ LIST[:taisetsu] # 23
83
+ ].freeze
84
+ end
85
+
86
+ #
87
+ # 入定気を計算する
88
+ #
89
+ # * 定気(index)の指定がない場合は、11月の入定気を求める
90
+ # * 定気(index)の指定がある場合は、大余小余から適切な入定気を再計算する
91
+ #
92
+ # @param [SolarTerm] solar_term 入定気
93
+ #
94
+ # @return [SolarTerm] 入定気
95
+ #
96
+ def self.get(solar_term:)
97
+ if solar_term.invalid?
98
+ return calc_first_solar_term(
99
+ winter_solstice_age: solar_term.remainder
100
+ )
101
+ end
102
+
103
+ calc_next_solar_term_recursively(
104
+ solar_term: solar_term
105
+ )
106
+ end
107
+
108
+ # :reek:TooManyStatements { max_statements: 7 }
109
+
110
+ #
111
+ # 入定気(定気の開始点からの日時)と属する定気を計算する
112
+ #
113
+ # @param [Remainder] winter_solstice_age 天正冬至
114
+ #
115
+ # @return [SolarTerm] 二十四節気
116
+ # SolarTerm.remainder : 入定気(定気の開始点からの日時)
117
+ # SolarTerm.index : 定気
118
+ #
119
+ def self.calc_first_solar_term(winter_solstice_age:)
120
+ # 入定気の起算方法
121
+ # 概要:
122
+ # * 太陽の運行による補正値は、二十四節気の気ごとに定められる
123
+ # * 11月経朔の前にある気を求め、それから11月経朔との間隔を求める
124
+ # * 気ごとの補正値と、気から11月経朔までにかかる補正値を求める
125
+ # 前提:
126
+ # * 11月経朔に関わる二十四節気は、時系列から順に、小雪・大雪・冬至である
127
+ # * 小雪〜大雪の間隔は小雪定数で、大雪〜冬至の間隔は大雪定数で決められている(24気損益眺朒(ちょうじく)数のこと)
128
+ # * 11月経朔は、この小雪〜冬至の間のいずれかにある
129
+ # 計算:
130
+ # 2パターンある
131
+ # (a) 大雪〜冬至にある場合
132
+ # *「大雪定数 >= 天正閏余」の場合を指す
133
+ # * * NOTE 資料では「より大きい(>)」とされるが、大雪そのものの場合は大雪から起算すべき
134
+ # * この場合は、大雪〜経朔の間隔を求める
135
+ # (b) 小雪〜大雪にある場合
136
+ # *「大雪定数 < 天正閏余」の場合を指す
137
+ # * この場合は、小雪〜経朔の間隔を求める
138
+
139
+ # NOTE: 上記パターンとは別に、稀だが立冬のパターンも存在する
140
+ # この場合は比較方法はそのままに立冬〜経朔の間隔を求める
141
+
142
+ rest = winter_solstice_age.clone
143
+ # 大雪(23)/小雪(22)/立冬(21)
144
+ [23, 22, 21].each do |index|
145
+ solar_term = prev_solar_term(winter_solstice_age: rest, index: index)
146
+
147
+ if solar_term.invalid?
148
+ rest = solar_term.remainder
149
+ next
150
+ end
151
+
152
+ return solar_term
153
+ end
154
+
155
+ # 立冬(21)を超える天正閏余は成立し得ない(1朔望月をはるかに超えることになる)
156
+ raise ArgumentError.new, 'invalid winster solstice age'
157
+ end
158
+ private_class_method :calc_first_solar_term
159
+
160
+ #
161
+ # 入気定日加減数で入定気を遡る
162
+ #
163
+ # @param [Remainder] winter_solstice_age 天正冬至
164
+ # @param [Integer] index 二十四節気の連番
165
+ #
166
+ # @return [SolarTerm] 二十四節気
167
+ # SolarTerm.remainder : 入定気(定気の開始点からの日時)
168
+ # SolarTerm.index : 定気(範囲外であれば-1とする)
169
+ #
170
+ def self.prev_solar_term(winter_solstice_age:, index:)
171
+ interval = Interval::INDEXES[index]
172
+ if winter_solstice_age > interval
173
+ # 入定気が確定しない(さらに前の定気まで遡れる)
174
+ return SolarTerm.new(
175
+ remainder: winter_solstice_age.sub(interval),
176
+ index: -1
177
+ )
178
+ end
179
+
180
+ # 入定気が確定する
181
+ SolarTerm.new(
182
+ remainder: interval.sub(winter_solstice_age),
183
+ index: index
184
+ )
185
+ end
186
+ private_class_method :prev_solar_term
187
+
188
+ # :reek:TooManyStatements { max_statements: 8 }
189
+
190
+ #
191
+ # 次の二十四節気を計算する
192
+ #
193
+ # @param [SolarTerm] solar_term 今回の二十四節気
194
+ #
195
+ # @return [SolarTerm] 次回の二十四節気
196
+ #
197
+ def self.calc_next_solar_term_recursively(solar_term:)
198
+ remainder = solar_term.remainder
199
+ index = solar_term.index
200
+ interval = Interval::INDEXES[index]
201
+ return solar_term if remainder < interval
202
+
203
+ remainder.sub!(interval)
204
+ index += 1
205
+ index = 0 if index >= Interval::INDEXES.size
206
+ calc_next_solar_term_recursively(
207
+ solar_term: SolarTerm.new(remainder: remainder, index: index)
208
+ )
209
+ end
210
+ private_class_method :calc_next_solar_term_recursively
211
+ end
212
+ end
213
+ end