rs_klc/klc/
mod.rs

1/**
2 * The MIT License (MIT)
3 *
4 * Copyright (c) 2018 usingsky(usingsky@gmail.com)
5 * Copyright (c) 2022 chunghha(chunghha@users.noreply.github.com)
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in all
15 * copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25
26#[derive(Debug, Default)]
27pub struct LunarSolarConverter {
28    lunar_year: i32,
29    lunar_month: u32,
30    lunar_day: u32,
31    is_intercalation: bool,
32    solar_year: u32,
33    solar_month: u32,
34    solar_day: u32,
35    gapja_year_inx: [Option<usize>; 3],
36    gapja_month_inx: [Option<usize>; 3],
37    gapja_day_inx: [Option<usize>; 3],
38}
39
40const KOREAN_LUNAR_MIN_VALUE: u32 = 13910101;
41const KOREAN_LUNAR_MAX_VALUE: u32 = 20501118;
42const KOREAN_SOLAR_MIN_VALUE: u32 = 13910205;
43const KOREAN_SOLAR_MAX_VALUE: u32 = 20501231;
44
45const KOREAN_LUNAR_BASE_YEAR: i32 = 1391;
46const SOLAR_LUNAR_DAY_DIFF: u32 = 35;
47
48const LUNAR_SMALL_MONTH_DAY: u32 = 29;
49const LUNAR_BIG_MONTH_DAY: u32 = 30;
50const SOLAR_SMALL_YEAR_DAY: u32 = 365;
51const SOLAR_BIG_YEAR_DAY: u32 = 366;
52
53const SOLAR_DAYS: [u32; 13] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 29];
54const KOREAN_CHEONGAN: [char; 10] = [
55    '\u{ac11}', '\u{c744}', '\u{bcd1}', '\u{c815}', '\u{bb34}', '\u{ae30}', '\u{acbd}', '\u{c2e0}',
56    '\u{c784}', '\u{acc4}',
57];
58const KOREAN_GANJI: [char; 12] = [
59    '\u{c790}', '\u{cd95}', '\u{c778}', '\u{bb18}', '\u{c9c4}', '\u{c0ac}', '\u{c624}', '\u{bbf8}',
60    '\u{c2e0}', '\u{c720}', '\u{c220}', '\u{d574}',
61];
62const KOREAN_GAPJA_UNIT: [char; 3] = ['\u{b144}', '\u{c6d4}', '\u{c77c}'];
63
64const CHINESE_CHEONGAN: [char; 10] = [
65    '\u{7532}', '\u{4e59}', '\u{4e19}', '\u{4e01}', '\u{620a}', '\u{5df1}', '\u{5e9a}', '\u{8f9b}',
66    '\u{58ec}', '\u{7678}',
67];
68const CHINESE_GANJI: [char; 12] = [
69    '\u{5b50}', '\u{4e11}', '\u{5bc5}', '\u{536f}', '\u{8fb0}', '\u{5df3}', '\u{5348}', '\u{672a}',
70    '\u{7533}', '\u{9149}', '\u{620c}', '\u{4ea5}',
71];
72const CHINESE_GAPJA_UNIT: [char; 3] = ['\u{5e74}', '\u{6708}', '\u{65e5}'];
73
74const INTERCALATION_STR: [char; 2] = ['\u{c724}', '\u{958f}'];
75
76const KOREAN_LUNAR_DATA: [u32; 660] = [
77    0x82c40653, 0xc301c6a9, 0x82c405aa, 0x82c60ab5, 0x830092bd, 0xc2c402b6, 0x82c60c37, 0x82fe552e,
78    0x82c40c96, 0xc2c60e4b, 0x82fe3752, 0x82c60daa, 0x8301b5b4, 0xc2c6056d, 0x82c402ae, 0x83007a3d,
79    0x82c40a2d, 0xc2c40d15, 0x83004d95, 0x82c40b52, 0x8300cb69, 0xc2c60ada, 0x82c6055d, 0x8301925b,
80    0x82c4045b, 0xc2c40a2b, 0x83005aab, 0x82c40a95, 0x82c40b52, 0xc3001eaa, 0x82c60ab6, 0x8300c55b,
81    0x82c604b7, 0xc2c40457, 0x83007537, 0x82c4052b, 0x82c40695, 0xc3014695, 0x82c405aa, 0x8300c9b5,
82    0x82c60a6e, 0xc2c404ae, 0x83008a5e, 0x82c40a56, 0x82c40d2a, 0xc3006eaa, 0x82c60d55, 0x82c4056a,
83    0x8301295a, 0xc2c6095e, 0x8300b4af, 0x82c4049b, 0x82c40a4d, 0xc3007d2e, 0x82c40b2a, 0x82c60b55,
84    0x830045d5, 0xc2c402da, 0x82c6095b, 0x83011157, 0x82c4049b, 0xc3009a4f, 0x82c4064b, 0x82c406a9,
85    0x83006aea, 0xc2c606b5, 0x82c402b6, 0x83002aae, 0x82c60937, 0xc2ffb496, 0x82c40c96, 0x82c60e4b,
86    0x82fe76b2, 0xc2c60daa, 0x82c605ad, 0x8300336d, 0x82c4026e, 0xc2c4092e, 0x83002d2d, 0x82c40c95,
87    0x83009d4d, 0xc2c40b4a, 0x82c60b69, 0x8301655a, 0x82c6055b, 0xc2c4025d, 0x83002a5b, 0x82c4092b,
88    0x8300aa97, 0xc2c40695, 0x82c4074a, 0x83008b5a, 0x82c60ab6, 0xc2c6053b, 0x830042b7, 0x82c40257,
89    0x82c4052b, 0xc3001d2b, 0x82c40695, 0x830096ad, 0x82c405aa, 0xc2c60ab5, 0x830054ed, 0x82c404ae,
90    0x82c60a57, 0xc2ff344e, 0x82c40d2a, 0x8301bd94, 0x82c60b55, 0xc2c4056a, 0x8300797a, 0x82c6095d,
91    0x82c404ae, 0xc3004a9b, 0x82c40a4d, 0x82c40d25, 0x83011aaa, 0xc2c60b55, 0x8300956d, 0x82c402da,
92    0x82c6095b, 0xc30054b7, 0x82c40497, 0x82c40a4b, 0x83004b4b, 0xc2c406a9, 0x8300cad5, 0x82c605b5,
93    0x82c402b6, 0xc300895e, 0x82c6092f, 0x82c40497, 0x82fe4696, 0xc2c40d4a, 0x8300cea5, 0x82c60d69,
94    0x82c6056d, 0xc301a2b5, 0x82c4026e, 0x82c4052e, 0x83006cad, 0xc2c40c95, 0x82c40d4a, 0x83002f4a,
95    0x82c60b59, 0xc300c56d, 0x82c6055b, 0x82c4025d, 0x8300793b, 0xc2c4092b, 0x82c40a95, 0x83015b15,
96    0x82c406ca, 0xc2c60ad5, 0x830112b6, 0x82c604bb, 0x8300925f, 0xc2c40257, 0x82c4052b, 0x82fe6aaa,
97    0x82c60e95, 0xc2c406aa, 0x83003baa, 0x82c60ab5, 0x8300b4b7, 0xc2c404ae, 0x82c60a57, 0x82fe752d,
98    0x82c40d26, 0xc2c60d95, 0x830055d5, 0x82c4056a, 0x82c6096d, 0xc300255d, 0x82c404ae, 0x8300aa4f,
99    0x82c40a4d, 0xc2c40d25, 0x83006d69, 0x82c60b55, 0x82c4035a, 0xc3002aba, 0x82c6095b, 0x8301c49b,
100    0x82c40497, 0xc2c40a4b, 0x83008b2b, 0x82c406a5, 0x82c406d4, 0xc3034ab5, 0x82c402b6, 0x82c60937,
101    0x8300252f, 0xc2c40497, 0x82fe964e, 0x82c40d4a, 0x82c60ea5, 0xc30166a9, 0x82c6056d, 0x82c402b6,
102    0x8301385e, 0xc2c4092e, 0x8300bc97, 0x82c40a95, 0x82c40d4a, 0xc3008daa, 0x82c60b4d, 0x82c6056b,
103    0x830042db, 0xc2c4025d, 0x82c4092d, 0x83002d33, 0x82c40a95, 0xc3009b4d, 0x82c406aa, 0x82c60ad5,
104    0x83006575, 0xc2c604bb, 0x82c4025b, 0x83013457, 0x82c4052b, 0xc2ffba94, 0x82c60e95, 0x82c406aa,
105    0x83008ada, 0xc2c609b5, 0x82c404b6, 0x83004aae, 0x82c60a4f, 0xc2c20526, 0x83012d26, 0x82c60d55,
106    0x8301a5a9, 0xc2c4056a, 0x82c6096d, 0x8301649d, 0x82c4049e, 0xc2c40a4d, 0x83004d4d, 0x82c40d25,
107    0x8300bd53, 0xc2c40b54, 0x82c60b5a, 0x8301895a, 0x82c6095b, 0xc2c4049b, 0x83004a97, 0x82c40a4b,
108    0x82c40aa5, 0xc3001ea5, 0x82c406d4, 0x8302badb, 0x82c402b6, 0xc2c60937, 0x830064af, 0x82c40497,
109    0x82c4064b, 0xc2fe374a, 0x82c60da5, 0x8300b6b5, 0x82c6056d, 0xc2c402ba, 0x8300793e, 0x82c4092e,
110    0x82c40c96, 0xc3015d15, 0x82c40d4a, 0x82c60da5, 0x83013555, 0xc2c4056a, 0x83007a7a, 0x82c60a5d,
111    0x82c4092d, 0xc3006aab, 0x82c40a95, 0x82c40b4a, 0x83004baa, 0xc2c60ad5, 0x82c4055a, 0x830128ba,
112    0x82c60a5b, 0xc3007537, 0x82c4052b, 0x82c40693, 0x83015715, 0xc2c406aa, 0x82c60ad9, 0x830035b5,
113    0x82c404b6, 0xc3008a5e, 0x82c40a4e, 0x82c40d26, 0x83006ea6, 0xc2c40d52, 0x82c60daa, 0x8301466a,
114    0x82c6056d, 0xc2c404ae, 0x83003a9d, 0x82c40a4d, 0x83007d2b, 0xc2c40b25, 0x82c40d52, 0x83015d54,
115    0x82c60b5a, 0xc2c6055d, 0x8300355b, 0x82c4049d, 0x83007657, 0x82c40a4b, 0x82c40aa5, 0x83006b65,
116    0x82c406d2, 0xc2c60ada, 0x830045b6, 0x82c60937, 0x82c40497, 0xc3003697, 0x82c40a4d, 0x82fe76aa,
117    0x82c60da5, 0xc2c405aa, 0x83005aec, 0x82c60aae, 0x82c4092e, 0xc3003d2e, 0x82c40c96, 0x83018d45,
118    0x82c40d4a, 0xc2c60d55, 0x83016595, 0x82c4056a, 0x82c60a6d, 0xc300455d, 0x82c4052d, 0x82c40a95,
119    0x83003e95, 0xc2c40b4a, 0x83017b4a, 0x82c609d5, 0x82c4055a, 0xc3015a3a, 0x82c60a5b, 0x82c4052b,
120    0x83014a17, 0xc2c40693, 0x830096ab, 0x82c406aa, 0x82c60ab5, 0xc30064f5, 0x82c404b6, 0x82c60a57,
121    0x82fe452e, 0xc2c40d16, 0x82c60e93, 0x82fe3752, 0x82c60daa, 0xc30175aa, 0x82c6056d, 0x82c404ae,
122    0x83015a1b, 0xc2c40a2d, 0x82c40d15, 0x83004da5, 0x82c40b52, 0xc3009d6a, 0x82c60ada, 0x82c6055d,
123    0x8301629b, 0xc2c4045b, 0x82c40a2b, 0x83005b2b, 0x82c40a95, 0xc2c40b52, 0x83012ab2, 0x82c60ad6,
124    0x83017556, 0xc2c60537, 0x82c40457, 0x83005657, 0x82c4052b, 0xc2c40695, 0x83003795, 0x82c405aa,
125    0x8300aab6, 0xc2c60a6d, 0x82c404ae, 0x8300696e, 0x82c40a56, 0xc2c40d2a, 0x83005eaa, 0x82c60d55,
126    0x82c405aa, 0xc3003b6a, 0x82c60a6d, 0x830074bd, 0x82c404ab, 0xc2c40a8d, 0x83005d55, 0x82c40b2a,
127    0x82c60b55, 0xc30045d5, 0x82c404da, 0x82c6095d, 0x83002557, 0xc2c4049b, 0x83006a97, 0x82c4064b,
128    0x82c406a9, 0x83004baa, 0x82c606b5, 0x82c402ba, 0x83002ab6, 0xc2c60937, 0x82fe652e, 0x82c40d16,
129    0x82c60e4b, 0xc2fe56d2, 0x82c60da9, 0x82c605b5, 0x8300336d, 0xc2c402ae, 0x82c40a2e, 0x83002e2d,
130    0x82c40c95, 0xc3006d55, 0x82c40b52, 0x82c60b69, 0x830045da, 0xc2c6055d, 0x82c4025d, 0x83003a5b,
131    0x82c40a2b, 0xc3017a8b, 0x82c40a95, 0x82c40b4a, 0x83015b2a, 0xc2c60ad5, 0x82c6055b, 0x830042b7,
132    0x82c40257, 0xc300952f, 0x82c4052b, 0x82c40695, 0x830066d5, 0xc2c405aa, 0x82c60ab5, 0x8300456d,
133    0x82c404ae, 0xc2c60a57, 0x82ff3456, 0x82c40d2a, 0x83017e8a, 0xc2c60d55, 0x82c405aa, 0x83005ada,
134    0x82c6095d, 0xc2c404ae, 0x83004aab, 0x82c40a4d, 0x83008d2b, 0xc2c40b29, 0x82c60b55, 0x83007575,
135    0x82c402da, 0xc2c6095d, 0x830054d7, 0x82c4049b, 0x82c40a4b, 0xc3013a4b, 0x82c406a9, 0x83008ad9,
136    0x82c606b5, 0xc2c402b6, 0x83015936, 0x82c60937, 0x82c40497, 0xc2fe4696, 0x82c40e4a, 0x8300aea6,
137    0x82c60da9, 0xc2c605ad, 0x830162ad, 0x82c402ae, 0x82c4092e, 0xc3005cad, 0x82c40c95, 0x82c40d4a,
138    0x83013d4a, 0xc2c60b69, 0x8300757a, 0x82c6055b, 0x82c4025d, 0xc300595b, 0x82c4092b, 0x82c40a95,
139    0x83004d95, 0xc2c40b4a, 0x82c60b55, 0x830026d5, 0x82c6055b, 0xc3006277, 0x82c40257, 0x82c4052b,
140    0x82fe5aaa, 0xc2c60e95, 0x82c406aa, 0x83003baa, 0x82c60ab5, 0x830084bd, 0x82c404ae, 0x82c60a57,
141    0x82fe554d, 0xc2c40d26, 0x82c60d95, 0x83014655, 0x82c4056a, 0xc2c609ad, 0x8300255d, 0x82c404ae,
142    0x83006a5b, 0xc2c40a4d, 0x82c40d25, 0x83005da9, 0x82c60b55, 0xc2c4056a, 0x83002ada, 0x82c6095d,
143    0x830074bb, 0xc2c4049b, 0x82c40a4b, 0x83005b4b, 0x82c406a9, 0xc2c40ad4, 0x83024bb5, 0x82c402b6,
144    0x82c6095b, 0xc3002537, 0x82c40497, 0x82fe6656, 0x82c40e4a, 0xc2c60ea5, 0x830156a9, 0x82c605b5,
145    0x82c402b6, 0xc30138ae, 0x82c4092e, 0x83017c8d, 0x82c40c95, 0xc2c40d4a, 0x83016d8a, 0x82c60b69,
146    0x82c6056d, 0xc301425b, 0x82c4025d, 0x82c4092d, 0x83002d2b, 0xc2c40a95, 0x83007d55, 0x82c40b4a,
147    0x82c60b55, 0xc3015555, 0x82c604db, 0x82c4025b, 0x83013857, 0xc2c4052b, 0x83008a9b, 0x82c40695,
148    0x82c406aa, 0xc3006aea, 0x82c60ab5, 0x82c404b6, 0x83004aae, 0xc2c60a57, 0x82c40527, 0x82fe3726,
149    0x82c60d95, 0xc30076b5, 0x82c4056a, 0x82c609ad, 0x830054dd, 0xc2c404ae, 0x82c40a4e, 0x83004d4d,
150    0x82c40d25, 0xc3008d59, 0x82c40b54, 0x82c60d6a, 0x8301695a, 0xc2c6095b, 0x82c4049b, 0x83004a9b,
151    0x82c40a4b, 0xc300ab27, 0x82c406a5, 0x82c406d4, 0x83026b75, 0xc2c402b6, 0x82c6095b, 0x830054b7,
152    0x82c40497, 0xc2c4064b, 0x82fe374a, 0x82c60ea5, 0x830086d9, 0xc2c605ad, 0x82c402b6, 0x8300596e,
153    0x82c4092e, 0xc2c40c96, 0x83004e95, 0x82c40d4a, 0x82c60da5, 0xc3002755, 0x82c4056c, 0x83027abb,
154    0x82c4025d, 0xc2c4092d, 0x83005cab, 0x82c40a95, 0x82c40b4a, 0xc3013b4a, 0x82c60b55, 0x8300955d,
155    0x82c404ba, 0xc2c60a5b, 0x83005557, 0x82c4052b, 0x82c40a95, 0xc3004b95, 0x82c406aa, 0x82c60ad5,
156    0x830026b5, 0xc2c404b6, 0x83006a6e, 0x82c60a57, 0x82c40527, 0xc2fe56a6, 0x82c60d93, 0x82c405aa,
157    0x83003b6a, 0xc2c6096d, 0x8300b4af, 0x82c404ae, 0x82c40a4d, 0xc3016d0d, 0x82c40d25, 0x82c40d52,
158    0x83005dd4, 0xc2c60b6a, 0x82c6096d, 0x8300255b, 0x82c4049b, 0xc3007a57, 0x82c40a4b, 0x82c40b25,
159    0x83015b25, 0xc2c406d4, 0x82c60ada, 0x830138b6,
160];
161
162/// Represents the days of the week.
163#[derive(Debug, PartialEq, Eq, Copy, Clone)]
164pub enum DayOfWeek {
165    /// Monday (월요일)
166    Monday, // Corresponds to JDN % 7 = 0
167    /// Tuesday (화요일)
168    Tuesday, // Corresponds to JDN % 7 = 1
169    /// Wednesday (수요일)
170    Wednesday, // Corresponds to JDN % 7 = 2
171    /// Thursday (목요일)
172    Thursday, // Corresponds to JDN % 7 = 3
173    /// Friday (금요일)
174    Friday, // Corresponds to JDN % 7 = 4
175    /// Saturday (토요일)
176    Saturday, // Corresponds to JDN % 7 = 5
177    /// Sunday (일요일)
178    Sunday, // Corresponds to JDN % 7 = 6
179}
180
181impl LunarSolarConverter {
182    /// Creates a new, default `LunarSolarConverter` instance.
183    pub fn new() -> Self {
184        LunarSolarConverter::default()
185    }
186
187    fn get_lunar_data(year: i32) -> u32 {
188        if year < KOREAN_LUNAR_BASE_YEAR {
189            0
190        } else {
191            *KOREAN_LUNAR_DATA
192                .get((year - KOREAN_LUNAR_BASE_YEAR) as usize)
193                .unwrap_or(&0)
194        }
195    }
196
197    fn get_lunar_intercalation_month(lunar_data: u32) -> u32 {
198        (lunar_data >> 12) & 0x000F
199    }
200
201    fn shift_lunar_days(year: i32) -> u32 {
202        let lunar_data = Self::get_lunar_data(year);
203        if lunar_data == 0 {
204            return 0;
205        }
206
207        let mut total_days = 0;
208        let month_bits = lunar_data & 0xFFF;
209
210        for month in 1..=12 {
211            if ((month_bits >> (12 - month)) & 0x01) > 0 {
212                total_days += LUNAR_BIG_MONTH_DAY;
213            } else {
214                total_days += LUNAR_SMALL_MONTH_DAY;
215            }
216        }
217
218        let intercalation_month = Self::get_lunar_intercalation_month(lunar_data);
219        if intercalation_month > 0 {
220            if ((lunar_data >> 16) & 0x01) > 0 {
221                total_days += LUNAR_BIG_MONTH_DAY;
222            } else {
223                total_days += LUNAR_SMALL_MONTH_DAY;
224            }
225        }
226
227        total_days
228    }
229
230    fn get_lunar_days(year: i32, month: u32, is_intercalation: bool) -> u32 {
231        let mut days = 0;
232        if year < KOREAN_LUNAR_BASE_YEAR {
233            return 0;
234        }
235        let lunar_data = Self::get_lunar_data(year);
236        let intercalation_month = Self::get_lunar_intercalation_month(lunar_data);
237
238        if is_intercalation && intercalation_month == month {
239            if ((lunar_data >> 16) & 0x01) > 0 {
240                days = LUNAR_BIG_MONTH_DAY;
241            } else {
242                days = LUNAR_SMALL_MONTH_DAY;
243            }
244        } else if month > 0 && month < 13 {
245            if ((lunar_data >> (12 - month)) & 0x01) > 0 {
246                days = LUNAR_BIG_MONTH_DAY;
247            } else {
248                days = LUNAR_SMALL_MONTH_DAY;
249            }
250        }
251
252        days
253    }
254
255    fn get_lunar_days_before_base_year(year: i32) -> u32 {
256        let mut days = 0;
257
258        for base_year in KOREAN_LUNAR_BASE_YEAR..year {
259            days += Self::shift_lunar_days(base_year);
260        }
261
262        days
263    }
264
265    fn get_lunar_days_before_base_month(year: i32, month: u32, is_intercalation: bool) -> u32 {
266        let mut days = 0;
267        if year < KOREAN_LUNAR_BASE_YEAR || month == 0 {
268            return 0;
269        }
270
271        let lunar_data = Self::get_lunar_data(year);
272        let intercalation_month = Self::get_lunar_intercalation_month(lunar_data);
273
274        for base_month in 1..month {
275            days += Self::get_lunar_days(year, base_month, false);
276
277            if intercalation_month > 0 && intercalation_month == base_month {
278                days += Self::get_lunar_days(year, intercalation_month, true);
279            }
280        }
281
282        if is_intercalation && intercalation_month == month {
283            days += Self::get_lunar_days(year, month, false);
284        }
285
286        days
287    }
288
289    fn get_lunar_abs_days(year: i32, month: u32, day: u32, is_intercalation: bool) -> u32 {
290        if year < KOREAN_LUNAR_BASE_YEAR {
291            0
292        } else {
293            Self::get_lunar_days_before_base_year(year)
294                + Self::get_lunar_days_before_base_month(year, month, is_intercalation)
295                + day
296        }
297    }
298
299    fn is_gregorian_leap(year: i32) -> bool {
300        if year <= 1582 {
301            // Before Gregorian reform, Julian calendar used
302            year % 4 == 0
303        } else {
304            // Gregorian calendar rules
305            (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
306        }
307    }
308
309    fn shift_solar_days(year: i32) -> u32 {
310        let mut days;
311
312        // Use standard Gregorian leap year calculation
313        if Self::is_gregorian_leap(year) {
314            days = SOLAR_BIG_YEAR_DAY;
315        } else {
316            days = SOLAR_SMALL_YEAR_DAY;
317        }
318
319        if year == 1582 {
320            days -= 10;
321        }
322
323        days
324    }
325
326    fn get_solar_days(year: i32, month: u32) -> u32 {
327        let mut days = 0;
328
329        if year < KOREAN_LUNAR_BASE_YEAR {
330            return 0;
331        }
332
333        // Use standard Gregorian leap year calculation for February
334        if month == 2 && Self::is_gregorian_leap(year) {
335            days = *SOLAR_DAYS.get(12).unwrap_or(&0); // Index 12 is 29
336        } else if month > 0 && month < 13 {
337            days = *SOLAR_DAYS.get((month - 1) as usize).unwrap_or(&0);
338        }
339
340        if year == 1582 && month == 10 {
341            days -= 10;
342        }
343
344        days
345    }
346
347    fn get_solar_day_before_base_year(year: i32) -> u32 {
348        let mut days = 0;
349
350        for base_year in KOREAN_LUNAR_BASE_YEAR..year {
351            days += Self::shift_solar_days(base_year);
352        }
353
354        days
355    }
356
357    fn get_solar_days_before_base_month(year: i32, month: u32) -> u32 {
358        let mut days = 0;
359
360        if year < KOREAN_LUNAR_BASE_YEAR {
361            return 0;
362        }
363
364        for base_month in 1..month {
365            days += Self::get_solar_days(year, base_month);
366        }
367
368        days
369    }
370
371    fn get_solar_abs_days(year: i32, month: u32, day: u32) -> u32 {
372        if year < KOREAN_LUNAR_BASE_YEAR {
373            0
374        } else {
375            let mut days = Self::get_solar_day_before_base_year(year)
376                + Self::get_solar_days_before_base_month(year, month)
377                + day;
378            days -= SOLAR_LUNAR_DAY_DIFF;
379
380            days
381        }
382    }
383
384    /// Sets the converter's date based on a Lunar date.
385    ///
386    /// # Arguments
387    /// * `lunar_year` - The lunar year.
388    /// * `lunar_month` - The lunar month (1-12).
389    /// * `lunar_day` - The lunar day.
390    /// * `is_intercalation` - `true` if the month is an intercalary (leap) month (윤달).
391    ///
392    /// # Returns
393    /// `true` if the provided lunar date is valid and within the supported range,
394    /// `false` otherwise. If `true`, the corresponding solar date is calculated and stored.
395    pub fn set_lunar_date(
396        &mut self,
397        lunar_year: i32,
398        lunar_month: u32,
399        lunar_day: u32,
400        is_intercalation: bool,
401    ) -> bool {
402        let mut is_valid = false;
403
404        if Self::check_valid_date(
405            true,
406            is_intercalation,
407            lunar_year as u32,
408            lunar_month,
409            lunar_day,
410        ) {
411            self.lunar_year = lunar_year;
412            self.lunar_month = lunar_month;
413            self.lunar_day = lunar_day;
414            self.is_intercalation = is_intercalation
415                && (Self::get_lunar_intercalation_month(Self::get_lunar_data(lunar_year))
416                    == lunar_month);
417            self.set_solar_date_by_lunar_date(
418                lunar_year,
419                lunar_month,
420                lunar_day,
421                self.is_intercalation,
422            );
423
424            is_valid = true;
425        }
426
427        is_valid
428    }
429
430    /// Sets the converter's date based on a Solar (Gregorian) date.
431    ///
432    /// # Arguments
433    /// * `solar_year` - The solar year.
434    /// * `solar_month` - The solar month (1-12).
435    /// * `solar_day` - The solar day.
436    ///
437    /// # Returns
438    /// `true` if the provided solar date is valid and within the supported range
439    /// (handles the 1582 Gregorian reform gap), `false` otherwise. If `true`,
440    /// the corresponding lunar date is calculated and stored.
441    pub fn set_solar_date(&mut self, solar_year: u32, solar_month: u32, solar_day: u32) -> bool {
442        let mut is_valid = false;
443
444        if Self::check_valid_date(false, false, solar_year, solar_month, solar_day) {
445            self.solar_year = solar_year;
446            self.solar_month = solar_month;
447            self.solar_day = solar_day;
448            self.set_lunar_date_by_solar_date(solar_year, solar_month, solar_day);
449            is_valid = true;
450        }
451
452        is_valid
453    }
454
455    fn get_gapja(&mut self) {
456        let abs_days = Self::get_lunar_abs_days(
457            self.lunar_year,
458            self.lunar_month,
459            self.lunar_day,
460            self.is_intercalation,
461        );
462
463        if abs_days > 0 {
464            self.gapja_year_inx[0] = Some(
465                ((self.lunar_year + 7) - KOREAN_LUNAR_BASE_YEAR) as usize % KOREAN_CHEONGAN.len(),
466            );
467            self.gapja_year_inx[1] = Some(
468                ((self.lunar_year + 7) - KOREAN_LUNAR_BASE_YEAR) as usize % KOREAN_GANJI.len(),
469            );
470
471            let mut month_count = self.lunar_month;
472            month_count += 12 * ((self.lunar_year - KOREAN_LUNAR_BASE_YEAR) as u32);
473            self.gapja_month_inx[0] = Some((month_count + 5) as usize % KOREAN_CHEONGAN.len());
474            self.gapja_month_inx[1] = Some((month_count + 1) as usize % KOREAN_GANJI.len());
475
476            self.gapja_day_inx[0] = Some((abs_days + 4) as usize % KOREAN_CHEONGAN.len());
477            self.gapja_day_inx[1] = Some(abs_days as usize % KOREAN_GANJI.len());
478        } else {
479            self.gapja_year_inx = [None, None, None];
480            self.gapja_month_inx = [None, None, None];
481            self.gapja_day_inx = [None, None, None];
482        }
483    }
484
485    /// Returns the calculated Korean Gapja (간지) string for the current date.
486    /// Format: \"[Year]년 [Month]월 [Day]일\" (e.g., \"임인년 정미월 갑자일\").
487    /// Appends \" (윤월)\" if the current lunar month is intercalary.
488    /// Returns an empty string if the date is invalid.
489    pub fn get_gapja_string(&mut self) -> String {
490        self.get_gapja();
491
492        let mut gapja_string = String::new();
493
494        if let (Some(year_cheongan), Some(year_ganji)) =
495            (self.gapja_year_inx[0], self.gapja_year_inx[1])
496        {
497            gapja_string.push(KOREAN_CHEONGAN[year_cheongan]);
498            gapja_string.push(KOREAN_GANJI[year_ganji]);
499            gapja_string.push(KOREAN_GAPJA_UNIT[0]);
500        } else {
501            return "".to_string();
502        }
503
504        gapja_string.push(' ');
505
506        if let (Some(month_cheongan), Some(month_ganji)) =
507            (self.gapja_month_inx[0], self.gapja_month_inx[1])
508        {
509            gapja_string.push(KOREAN_CHEONGAN[month_cheongan]);
510            gapja_string.push(KOREAN_GANJI[month_ganji]);
511            gapja_string.push(KOREAN_GAPJA_UNIT[1]);
512        } else {
513            return "".to_string();
514        }
515
516        gapja_string.push(' ');
517
518        if let (Some(day_cheongan), Some(day_ganji)) =
519            (self.gapja_day_inx[0], self.gapja_day_inx[1])
520        {
521            gapja_string.push(KOREAN_CHEONGAN[day_cheongan]);
522            gapja_string.push(KOREAN_GANJI[day_ganji]);
523            gapja_string.push(KOREAN_GAPJA_UNIT[2]);
524        } else {
525            return "".to_string();
526        }
527
528        if self.is_intercalation {
529            gapja_string.push_str(" (");
530            gapja_string.push(INTERCALATION_STR[0]);
531            gapja_string.push(KOREAN_GAPJA_UNIT[1]);
532            gapja_string.push(')');
533        }
534
535        gapja_string
536    }
537
538    /// Returns the calculated Chinese Gapja string for the current date.
539    /// Format: \"[Year]年 [Month]月 [Day]日\" (e.g., \"壬寅年 丁未月 甲子日\").
540    /// Appends \" (閏月)\" if the current lunar month is intercalary.
541    /// Returns an empty string if the date is invalid.
542    pub fn get_chinese_gapja_string(&mut self) -> String {
543        self.get_gapja();
544
545        let mut gapja_string = String::new();
546
547        if let (Some(year_cheongan), Some(year_ganji)) =
548            (self.gapja_year_inx[0], self.gapja_year_inx[1])
549        {
550            gapja_string.push(CHINESE_CHEONGAN[year_cheongan]);
551            gapja_string.push(CHINESE_GANJI[year_ganji]);
552            gapja_string.push(CHINESE_GAPJA_UNIT[0]);
553        } else {
554            return "".to_string();
555        }
556
557        gapja_string.push(' ');
558
559        if let (Some(month_cheongan), Some(month_ganji)) =
560            (self.gapja_month_inx[0], self.gapja_month_inx[1])
561        {
562            gapja_string.push(CHINESE_CHEONGAN[month_cheongan]);
563            gapja_string.push(CHINESE_GANJI[month_ganji]);
564            gapja_string.push(CHINESE_GAPJA_UNIT[1]);
565        } else {
566            return "".to_string();
567        }
568
569        gapja_string.push(' ');
570
571        if let (Some(day_cheongan), Some(day_ganji)) =
572            (self.gapja_day_inx[0], self.gapja_day_inx[1])
573        {
574            gapja_string.push(CHINESE_CHEONGAN[day_cheongan]);
575            gapja_string.push(CHINESE_GANJI[day_ganji]);
576            gapja_string.push(CHINESE_GAPJA_UNIT[2]);
577        } else {
578            return "".to_string();
579        }
580
581        if self.is_intercalation {
582            gapja_string.push_str(" (");
583            gapja_string.push(INTERCALATION_STR[1]);
584            gapja_string.push(CHINESE_GAPJA_UNIT[1]);
585            gapja_string.push(')');
586        }
587
588        gapja_string
589    }
590
591    /// Returns the calculated Lunar date in ISO 8601 format (YYYY-MM-DD).
592    /// Appends " Intercalation" if the current lunar month is intercalary.
593    pub fn get_lunar_iso_format(&self) -> String {
594        let mut iso_str = format!(
595            "{:04}-{:02}-{:02}",
596            self.lunar_year, self.lunar_month, self.lunar_day
597        );
598
599        if self.is_intercalation {
600            iso_str.push_str(" Intercalation");
601        }
602
603        iso_str
604    }
605
606    /// Returns the calculated Solar date in ISO 8601 format (YYYY-MM-DD).
607    pub fn get_solar_iso_format(&self) -> String {
608        format!(
609            "{:04}-{:02}-{:02}",
610            self.solar_year, self.solar_month, self.solar_day
611        )
612    }
613
614    /// Calculates the Julian Day Number (JDN) for a given Solar date.
615    ///
616    /// The JDN is the integer number of days elapsed since noon UTC on January 1, 4713 BC (Julian calendar).
617    /// This implementation uses the algorithm described on Wikipedia and other sources,
618    /// correctly handling the transition from the Julian to the Gregorian calendar in October 1582.
619    ///
620    /// # Arguments
621    /// * `year` - The solar year.
622    /// * `month` - The solar month (1-12).
623    /// * `day` - The solar day.
624    ///
625    /// # Returns
626    /// `Some(u32)` containing the JDN if the date is valid, or `None` if the date
627    /// is invalid (e.g., within the 1582 Gregorian reform gap from Oct 5 to Oct 14).
628    ///
629    /// # Example
630    /// ```
631    /// use rs_klc::LunarSolarConverter;
632    /// assert_eq!(LunarSolarConverter::get_julian_day_number(2022, 7, 10), Some(2459771));
633    /// assert_eq!(LunarSolarConverter::get_julian_day_number(1582, 10, 4), Some(2299160)); // Last Julian day
634    /// assert_eq!(LunarSolarConverter::get_julian_day_number(1582, 10, 15), Some(2299161)); // First Gregorian day
635    /// assert_eq!(LunarSolarConverter::get_julian_day_number(1582, 10, 10), None); // Invalid date in gap
636    /// ```
637    pub fn get_julian_day_number(year: u32, month: u32, day: u32) -> Option<u32> {
638        // Check for invalid date in the Gregorian reform gap
639        if year == 1582 && month == 10 && day > 4 && day < 15 {
640            return None;
641        }
642        // Basic month/day validation (simplified, primarily for algorithm safety)
643        if month == 0 || month > 12 || day == 0 || day > 31 {
644            return None;
645        }
646
647        // Use i32 for calculations
648        let y = year as i32;
649        let m = month as i32;
650        let d = day as i32;
651
652        // Adjust month/year for Jan/Feb for calculation
653        let adj_y = if m <= 2 { y - 1 } else { y };
654        let adj_m = if m <= 2 { m + 12 } else { m };
655
656        // Calculate base Julian part using integer arithmetic
657        let julian_base = (1461 * (adj_y + 4716)) / 4 + (153 * (adj_m + 1)) / 5 + d;
658
659        // Determine Gregorian correction term 'b'
660        let b = if y > 1582 || (y == 1582 && m > 10) || (y == 1582 && m == 10 && d >= 15) {
661            // Apply correction only for Gregorian dates (starting from 1582-10-15)
662            let term1 = adj_y / 100; // Note: Use adj_y here consistent with algorithm derivations
663            2 - term1 + term1 / 4
664        } else {
665            // No correction for Julian dates (up to 1582-10-04)
666            0
667        };
668
669        // Combine base, correction, and standard offset (-1524)
670        let jdn = julian_base + b - 1524;
671
672        Some(jdn as u32)
673    }
674
675    /// Calculates the day of the week for a given Solar date.
676    ///
677    /// Uses the Julian Day Number calculation internally.
678    ///
679    /// # Arguments
680    /// * `year` - The solar year.
681    /// * `month` - The solar month (1-12).
682    /// * `day` - The solar day.
683    ///
684    /// # Returns
685    /// `Some(DayOfWeek)` if the date is valid, or `None` if the date is invalid (e.g., within the 1582 gap).
686    ///
687    /// # Example
688    /// ```
689    /// use rs_klc::{LunarSolarConverter, DayOfWeek};
690    /// assert_eq!(LunarSolarConverter::get_day_of_week(2022, 7, 10), Some(DayOfWeek::Sunday));
691    /// assert_eq!(LunarSolarConverter::get_day_of_week(1582, 10, 4), Some(DayOfWeek::Thursday));
692    /// assert_eq!(LunarSolarConverter::get_day_of_week(1582, 10, 15), Some(DayOfWeek::Friday));
693    /// assert_eq!(LunarSolarConverter::get_day_of_week(1582, 10, 10), None);
694    /// ```
695    pub fn get_day_of_week(year: u32, month: u32, day: u32) -> Option<DayOfWeek> {
696        Self::get_julian_day_number(year, month, day).map(|jdn| {
697            // JDN mod 7: 0=Mon, 1=Tue, 2=Wed, 3=Thu, 4=Fri, 5=Sat, 6=Sun
698            match jdn % 7 {
699                0 => DayOfWeek::Monday,
700                1 => DayOfWeek::Tuesday,
701                2 => DayOfWeek::Wednesday,
702                3 => DayOfWeek::Thursday,
703                4 => DayOfWeek::Friday,
704                5 => DayOfWeek::Saturday,
705                _ => DayOfWeek::Sunday, // 6 and any unexpected remainder
706            }
707        })
708    }
709
710    /// Checks if a given solar year is a leap year according to the Gregorian calendar rules.
711    ///
712    /// For years before or during 1582, the Julian calendar rule (divisible by 4) is used.
713    /// For years after 1582, the Gregorian rules apply: divisible by 4, unless divisible by 100 but not by 400.
714    ///
715    /// # Arguments
716    /// * `year` - The solar year.
717    ///
718    /// # Returns
719    /// `true` if the year is a leap year, `false` otherwise.
720    ///
721    /// # Example
722    /// ```
723    /// use rs_klc::LunarSolarConverter;
724    /// assert!(LunarSolarConverter::is_solar_leap_year(2024));
725    /// assert!(!LunarSolarConverter::is_solar_leap_year(2023));
726    /// assert!(!LunarSolarConverter::is_solar_leap_year(1900));
727    /// assert!(LunarSolarConverter::is_solar_leap_year(2000));
728    /// assert!(LunarSolarConverter::is_solar_leap_year(1500)); // Julian leap year
729    /// ```
730    pub fn is_solar_leap_year(year: u32) -> bool {
731        // Reuse the internal logic which handles the Gregorian reform
732        Self::is_gregorian_leap(year as i32)
733    }
734
735    /// Gets the intercalary (leap) month (윤달) for a given lunar year, if one exists.
736    ///
737    /// Based on the pre-calculated `KOREAN_LUNAR_DATA`.
738    ///
739    /// # Arguments
740    /// * `year` - The lunar year.
741    ///
742    /// # Returns
743    /// `Some(u32)` containing the intercalary month number (1-12) if the year has one,
744    /// or `None` if the year has no intercalary month or the year is outside the supported range.
745    ///
746    /// # Example
747    /// ```
748    /// use rs_klc::LunarSolarConverter;
749    /// assert_eq!(LunarSolarConverter::get_lunar_intercalary_month(2023), Some(2)); // 윤2월
750    /// assert_eq!(LunarSolarConverter::get_lunar_intercalary_month(2020), Some(4)); // 윤4월
751    /// assert_eq!(LunarSolarConverter::get_lunar_intercalary_month(2022), None);
752    /// ```
753    pub fn get_lunar_intercalary_month(year: i32) -> Option<u32> {
754        if year < KOREAN_LUNAR_BASE_YEAR
755            || year > KOREAN_LUNAR_BASE_YEAR + KOREAN_LUNAR_DATA.len() as i32 - 1
756        {
757            return None; // Year out of supported range
758        }
759        let lunar_data = Self::get_lunar_data(year);
760        let intercalary_month = Self::get_lunar_intercalation_month(lunar_data);
761        if intercalary_month > 0 {
762            Some(intercalary_month)
763        } else {
764            None
765        }
766    }
767
768    // --- Getters for date fields ---
769    /// Returns the currently stored solar year.
770    #[allow(dead_code)]
771    pub fn solar_year(&self) -> u32 {
772        self.solar_year
773    }
774    /// Returns the currently stored solar month.
775    #[allow(dead_code)]
776    pub fn solar_month(&self) -> u32 {
777        self.solar_month
778    }
779    /// Returns the currently stored solar day.
780    #[allow(dead_code)]
781    pub fn solar_day(&self) -> u32 {
782        self.solar_day
783    }
784    /// Returns the currently stored lunar year.
785    pub fn lunar_year(&self) -> i32 {
786        self.lunar_year
787    }
788    /// Returns the currently stored lunar month.
789    #[allow(dead_code)]
790    pub fn lunar_month(&self) -> u32 {
791        self.lunar_month
792    }
793    /// Returns the currently stored lunar day.
794    #[allow(dead_code)]
795    pub fn lunar_day(&self) -> u32 {
796        self.lunar_day
797    }
798    /// Returns `true` if the currently stored lunar date is an intercalary month.
799    #[allow(dead_code)]
800    pub fn is_intercalation(&self) -> bool {
801        self.is_intercalation
802    }
803    // ------------------------------
804
805    // --- Internal helper methods ---
806
807    fn set_solar_date_by_lunar_date(
808        &mut self,
809        lunar_year: i32,
810        lunar_month: u32,
811        lunar_day: u32,
812        is_intercalation: bool,
813    ) {
814        let abs_days =
815            Self::get_lunar_abs_days(lunar_year, lunar_month, lunar_day, is_intercalation);
816
817        if abs_days < Self::get_solar_abs_days(lunar_year + 1, 1, 1) {
818            self.solar_year = lunar_year as u32;
819        } else {
820            self.solar_year = (lunar_year + 1) as u32;
821        }
822
823        for month in (1..=12).rev() {
824            let abs_days_by_month = Self::get_solar_abs_days(self.solar_year as i32, month, 1);
825
826            if abs_days >= abs_days_by_month {
827                self.solar_month = month;
828                self.solar_day = abs_days - abs_days_by_month + 1;
829                break;
830            }
831        }
832
833        if self.solar_year == 1582 && self.solar_month == 10 && self.solar_day > 4 {
834            self.solar_day += 10;
835        }
836    }
837
838    fn set_lunar_date_by_solar_date(&mut self, solar_year: u32, solar_month: u32, solar_day: u32) {
839        let abs_days = Self::get_solar_abs_days(solar_year as i32, solar_month, solar_day);
840
841        self.is_intercalation = false;
842
843        if abs_days >= Self::get_lunar_abs_days(solar_year as i32, 1, 1, false) {
844            self.lunar_year = solar_year as i32;
845        } else {
846            self.lunar_year = solar_year as i32 - 1;
847        }
848
849        for month in (1..=12).rev() {
850            let abs_days_by_month = Self::get_lunar_abs_days(self.lunar_year, month, 1, false);
851
852            if abs_days >= abs_days_by_month {
853                self.lunar_month = month;
854
855                if Self::get_lunar_intercalation_month(Self::get_lunar_data(self.lunar_year))
856                    == month
857                {
858                    self.is_intercalation =
859                        abs_days >= Self::get_lunar_abs_days(self.lunar_year, month, 1, true);
860                }
861
862                self.lunar_day = abs_days
863                    - Self::get_lunar_abs_days(
864                        self.lunar_year,
865                        self.lunar_month,
866                        1,
867                        self.is_intercalation,
868                    )
869                    + 1;
870
871                break;
872            }
873        }
874    }
875
876    fn is_valid_min(is_lunar: bool, date_value: u32) -> bool {
877        if is_lunar {
878            KOREAN_LUNAR_MIN_VALUE <= date_value
879        } else {
880            KOREAN_SOLAR_MIN_VALUE <= date_value
881        }
882    }
883
884    fn is_valid_max(is_lunar: bool, date_value: u32) -> bool {
885        if is_lunar {
886            KOREAN_LUNAR_MAX_VALUE >= date_value
887        } else {
888            KOREAN_SOLAR_MAX_VALUE >= date_value
889        }
890    }
891
892    fn check_valid_date(
893        is_lunar: bool,
894        is_intercalation: bool,
895        year: u32,
896        month: u32,
897        day: u32,
898    ) -> bool {
899        let mut is_valid = false;
900        let date_value = year * 10000 + month * 100 + day;
901
902        //1582. 10. 5 ~ 1582. 10. 14 is not enable
903        if Self::is_valid_min(is_lunar, date_value) && Self::is_valid_max(is_lunar, date_value) {
904            let mut day_limit;
905
906            if month > 0 && month < 13 && day > 0 {
907                if is_lunar {
908                    if is_intercalation
909                        && Self::get_lunar_intercalation_month(Self::get_lunar_data(year as i32))
910                            != month
911                    {
912                        return false;
913                    }
914                    day_limit = Self::get_lunar_days(year as i32, month, is_intercalation);
915                } else {
916                    day_limit = Self::get_solar_days(year as i32, month);
917                }
918
919                if !is_lunar && year == 1582 && month == 10 {
920                    if day > 4 && day < 15 {
921                        return false;
922                    } else {
923                        day_limit += 10;
924                    }
925                }
926
927                if day <= day_limit {
928                    is_valid = true;
929                }
930            }
931        }
932
933        is_valid
934    }
935}
936
937#[cfg(test)]
938mod tests {
939    use super::DayOfWeek;
940    use crate::LunarSolarConverter;
941
942    #[test]
943    fn test_lunar_iso_format() {
944        let mut converter = LunarSolarConverter::new();
945        converter.set_solar_date(2022, 7, 10);
946        let lunar = converter.get_lunar_iso_format();
947        let want = "2022-06-12";
948
949        println!("{}", lunar);
950
951        assert_eq!(lunar, want, "got {:?} want {:?}", lunar, want);
952    }
953
954    #[test]
955    fn test_gapja_string() {
956        let mut converter = LunarSolarConverter::new();
957        converter.set_solar_date(2022, 7, 10);
958        let lunar_gapja = converter.get_gapja_string();
959        let want = "임인년 정미월 갑자일";
960
961        println!("{}", lunar_gapja);
962
963        assert_eq!(lunar_gapja, want, "got {:?} want {:?}", lunar_gapja, want);
964    }
965
966    #[test]
967    fn test_chinese_gapja_string() {
968        let mut converter = LunarSolarConverter::new();
969        converter.set_solar_date(2022, 7, 10);
970        let lunar_chinese_gapja = converter.get_chinese_gapja_string();
971        let want = "壬寅年 丁未月 甲子日";
972
973        println!("{}", lunar_chinese_gapja);
974
975        assert_eq!(
976            lunar_chinese_gapja, want,
977            "got {:?} want {:?}",
978            lunar_chinese_gapja, want
979        );
980    }
981
982    #[test]
983    fn test_solar_iso_format() {
984        let mut converter = LunarSolarConverter::new();
985        converter.set_lunar_date(2022, 6, 12, false);
986        let solar = converter.get_solar_iso_format();
987        let want = "2022-07-10";
988
989        println!("{}", solar);
990
991        assert_eq!(solar, want, "got {:?} want {:?}", solar, want);
992    }
993
994    #[test]
995    fn test_gapja_string_intercalation() {
996        let mut converter = LunarSolarConverter::new();
997        let is_valid = converter.set_lunar_date(2022, 6, 12, true);
998
999        assert!(
1000            !is_valid,
1001            "Expected set_lunar_date to return false for non-existent intercalary month"
1002        );
1003    }
1004
1005    #[test]
1006    fn test_chinese_gapja_string_intercalation() {
1007        let mut converter = LunarSolarConverter::new();
1008        let is_valid = converter.set_lunar_date(2022, 6, 12, true);
1009
1010        assert!(
1011            !is_valid,
1012            "Expected set_lunar_date to return false for non-existent intercalary month"
1013        );
1014    }
1015
1016    #[test]
1017    fn test_set_solar_date() {
1018        let mut converter = LunarSolarConverter::new();
1019        let is_valid = converter.set_solar_date(2022, 7, 10);
1020
1021        assert!(is_valid, "Expected solar date to be valid");
1022        assert_eq!(converter.lunar_year, 2022);
1023        assert_eq!(converter.lunar_month, 6);
1024        assert_eq!(converter.lunar_day, 12);
1025    }
1026
1027    #[test]
1028    fn test_set_lunar_date() {
1029        let mut converter = LunarSolarConverter::new();
1030        let is_valid = converter.set_lunar_date(2022, 6, 12, false);
1031
1032        assert!(is_valid, "Expected lunar date to be valid");
1033        assert_eq!(converter.solar_year, 2022);
1034        assert_eq!(converter.solar_month, 7);
1035        assert_eq!(converter.solar_day, 10);
1036    }
1037
1038    #[test]
1039    fn test_invalid_solar_date() {
1040        let mut converter = LunarSolarConverter::new();
1041        let is_valid = converter.set_solar_date(1582, 10, 10);
1042
1043        assert!(!is_valid, "Expected solar date to be invalid");
1044    }
1045
1046    #[test]
1047    fn test_invalid_lunar_date() {
1048        let mut converter = LunarSolarConverter::new();
1049        let is_valid = converter.set_lunar_date(1390, 12, 31, false);
1050
1051        assert!(!is_valid, "Expected lunar date to be invalid");
1052    }
1053
1054    #[test]
1055    fn test_get_lunar_days() {
1056        let days = LunarSolarConverter::get_lunar_days(2022, 6, false);
1057
1058        assert_eq!(days, 30, "Expected 30 days for June 2022");
1059    }
1060
1061    #[test]
1062    fn test_get_lunar_days_invalid_month() {
1063        let days = LunarSolarConverter::get_lunar_days(2022, 13, false);
1064
1065        assert_eq!(days, 0, "Expected 0 days for invalid month 2022");
1066    }
1067
1068    #[test]
1069    fn test_get_lunar_days_invalid_year() {
1070        let days = LunarSolarConverter::get_lunar_days(1390, 6, false);
1071        assert_eq!(days, 0, "Expected 0 days for invalid year 1390");
1072    }
1073
1074    #[test]
1075    fn test_get_solar_days() {
1076        let days = LunarSolarConverter::get_solar_days(2022, 2);
1077
1078        assert_eq!(days, 28, "Expected 28 days for February 2022");
1079    }
1080
1081    #[test]
1082    fn test_get_solar_days_invalid_month() {
1083        let days = LunarSolarConverter::get_solar_days(2022, 13);
1084
1085        assert_eq!(days, 0, "Expected 0 days for invalid month 2022");
1086    }
1087
1088    #[test]
1089    fn test_get_lunar_abs_days() {
1090        let days = LunarSolarConverter::get_lunar_abs_days(2022, 6, 12, false);
1091
1092        assert_eq!(days, 230616, "Expected 230616 absolute lunar days");
1093    }
1094
1095    #[test]
1096    fn test_get_lunar_abs_days_invalid_year() {
1097        let days = LunarSolarConverter::get_lunar_abs_days(1390, 6, 12, false);
1098
1099        assert_eq!(
1100            days, 0,
1101            "Expected 0 absolute lunar days for invalid year 1390"
1102        );
1103    }
1104
1105    #[test]
1106    fn test_get_solar_abs_days() {
1107        let days = LunarSolarConverter::get_solar_abs_days(2022, 7, 10);
1108
1109        // Correct assertion back to the library's internal relative day count
1110        assert_eq!(days, 230616, "Expected 230616 absolute solar days");
1111    }
1112
1113    #[test]
1114    fn test_get_solar_abs_days_invalid_year() {
1115        let days = LunarSolarConverter::get_solar_abs_days(1390, 7, 10);
1116
1117        assert_eq!(
1118            days, 0,
1119            "Expected 0 absolute solar days for invalid year 1390"
1120        );
1121    }
1122
1123    #[test]
1124    fn test_invalid_date_for_get_gapja_string() {
1125        let mut converter = LunarSolarConverter::new();
1126        converter.set_lunar_date(1390, 12, 31, false);
1127        let gapja = converter.get_gapja_string();
1128
1129        assert_eq!(gapja, "", "Expected empty string since the date is invalid");
1130    }
1131
1132    #[test]
1133    fn test_invalid_date_for_get_chinese_gapja_string() {
1134        let mut converter = LunarSolarConverter::new();
1135        converter.set_lunar_date(1390, 12, 31, false);
1136        let gapja = converter.get_chinese_gapja_string();
1137
1138        assert_eq!(gapja, "", "Expected empty string since the date is invalid");
1139    }
1140
1141    #[test]
1142    fn test_get_julian_day_number_gregorian() {
1143        let jdn = LunarSolarConverter::get_julian_day_number(2022, 7, 10);
1144        let want = Some(2459771);
1145        assert_eq!(
1146            jdn, want,
1147            "Expected JDN {:?} for 2022-07-10, got {:?}",
1148            want, jdn
1149        );
1150    }
1151
1152    #[test]
1153    fn test_get_julian_day_number_julian() {
1154        let jdn = LunarSolarConverter::get_julian_day_number(1500, 3, 1);
1155        let want = Some(2268993);
1156        assert_eq!(
1157            jdn, want,
1158            "Expected JDN {:?} for 1500-03-01, got {:?}",
1159            want, jdn
1160        );
1161    }
1162
1163    #[test]
1164    fn test_get_julian_day_number_reform_before() {
1165        let jdn = LunarSolarConverter::get_julian_day_number(1582, 10, 4);
1166        let want = Some(2299160);
1167        assert_eq!(
1168            jdn, want,
1169            "Expected JDN {:?} for 1582-10-04, got {:?}",
1170            want, jdn
1171        );
1172    }
1173
1174    #[test]
1175    fn test_get_julian_day_number_reform_after() {
1176        let jdn = LunarSolarConverter::get_julian_day_number(1582, 10, 15);
1177        let want = Some(2299161);
1178        assert_eq!(
1179            jdn, want,
1180            "Expected JDN {:?} for 1582-10-15, got {:?}",
1181            want, jdn
1182        );
1183    }
1184
1185    #[test]
1186    fn test_get_julian_day_number_min_date() {
1187        // Corresponds to KOREAN_SOLAR_MIN_VALUE
1188        let jdn = LunarSolarConverter::get_julian_day_number(1391, 2, 5);
1189        let want = Some(2229156);
1190        assert_eq!(
1191            jdn, want,
1192            "Expected JDN {:?} for 1391-02-05, got {:?}",
1193            want, jdn
1194        );
1195    }
1196
1197    #[test]
1198    fn test_get_julian_day_number_invalid_gap() {
1199        let jdn = LunarSolarConverter::get_julian_day_number(1582, 10, 10);
1200        assert_eq!(
1201            jdn, None,
1202            "Expected None for invalid date 1582-10-10 (Gregorian gap)"
1203        );
1204    }
1205
1206    #[test]
1207    fn test_get_julian_day_number_invalid_range_before() {
1208        // JDN function should work outside library range, only failing for 1582 gap.
1209        let jdn = LunarSolarConverter::get_julian_day_number(1391, 2, 4);
1210        let want = Some(2229155);
1211        assert_eq!(
1212            jdn, want,
1213            "Expected JDN {:?} for 1391-02-04, got {:?}",
1214            want, jdn
1215        );
1216    }
1217
1218    #[test]
1219    fn test_get_julian_day_number_invalid_range_after() {
1220        // Using a date beyond the KOREAN_SOLAR_MAX_VALUE
1221        // JDN function should work outside library range.
1222        let jdn = LunarSolarConverter::get_julian_day_number(2051, 1, 1);
1223        let want = Some(2470173);
1224        assert_eq!(
1225            jdn, want,
1226            "Expected JDN {:?} for 2051-01-01, got {:?}",
1227            want, jdn
1228        );
1229    }
1230
1231    #[test]
1232    fn test_get_day_of_week_monday() {
1233        // JDN 2459771 % 7 = 0 (Monday)
1234        let dow = LunarSolarConverter::get_day_of_week(2022, 7, 11);
1235        let want = Some(DayOfWeek::Monday);
1236        assert_eq!(
1237            dow, want,
1238            "Expected {:?} for 2022-07-11, got {:?}",
1239            want, dow
1240        );
1241    }
1242
1243    #[test]
1244    fn test_get_day_of_week_sunday() {
1245        // JDN 2459770 % 7 = 6 (Sunday)
1246        let dow = LunarSolarConverter::get_day_of_week(2022, 7, 10);
1247        let want = Some(DayOfWeek::Sunday);
1248        assert_eq!(
1249            dow, want,
1250            "Expected {:?} for 2022-07-10, got {:?}",
1251            want, dow
1252        );
1253    }
1254
1255    #[test]
1256    fn test_get_day_of_week_reform_before() {
1257        // October 4, 1582 was a Thursday (Julian)
1258        let result = LunarSolarConverter::get_day_of_week(1582, 10, 4);
1259        assert_eq!(result, Some(DayOfWeek::Thursday)); // Corrected from Friday
1260    }
1261
1262    #[test]
1263    fn test_get_day_of_week_reform_after() {
1264        // October 15, 1582 was a Friday (Gregorian)
1265        let result = LunarSolarConverter::get_day_of_week(1582, 10, 15);
1266        assert_eq!(result, Some(DayOfWeek::Friday)); // Corrected from Saturday
1267    }
1268
1269    #[test]
1270    fn test_get_day_of_week_invalid_gap() {
1271        let dow = LunarSolarConverter::get_day_of_week(1582, 10, 10);
1272        assert_eq!(dow, None, "Expected None for invalid date 1582-10-10");
1273    }
1274
1275    #[test]
1276    fn test_is_solar_leap_year() {
1277        assert!(
1278            LunarSolarConverter::is_solar_leap_year(2024),
1279            "2024 should be a leap year"
1280        );
1281        assert!(
1282            !LunarSolarConverter::is_solar_leap_year(2023),
1283            "2023 should not be a leap year"
1284        );
1285        assert!(
1286            !LunarSolarConverter::is_solar_leap_year(1900),
1287            "1900 should not be a leap year"
1288        );
1289        assert!(
1290            LunarSolarConverter::is_solar_leap_year(2000),
1291            "2000 should be a leap year"
1292        );
1293        assert!(
1294            LunarSolarConverter::is_solar_leap_year(1500),
1295            "1500 should be a leap year (Julian)"
1296        );
1297        assert!(
1298            !LunarSolarConverter::is_solar_leap_year(1582),
1299            "1582 should not be a leap year"
1300        );
1301        assert!(
1302            LunarSolarConverter::is_solar_leap_year(1600),
1303            "1600 should be a leap year"
1304        );
1305    }
1306
1307    #[test]
1308    fn test_get_lunar_intercalary_month() {
1309        // Year 2023 actually has intercalary month 2 (윤2월)
1310        assert_eq!(
1311            LunarSolarConverter::get_lunar_intercalary_month(2023),
1312            Some(2),
1313            "Year 2023 should have intercalary month 2"
1314        );
1315        // Year 2020 actually has intercalary month 4 (윤4월)
1316        assert_eq!(
1317            LunarSolarConverter::get_lunar_intercalary_month(2020),
1318            Some(4),
1319            "Year 2020 should have intercalary month 4"
1320        );
1321        // Year 2022 has no intercalary month
1322        assert_eq!(
1323            LunarSolarConverter::get_lunar_intercalary_month(2022),
1324            None,
1325            "Year 2022 should not have an intercalary month"
1326        );
1327        assert_eq!(
1328            LunarSolarConverter::get_lunar_intercalary_month(1391),
1329            None,
1330            "Year 1391 should not have an intercalary month"
1331        ); // First year in data
1332    }
1333
1334    #[test]
1335    fn test_get_lunar_intercalary_month_out_of_range() {
1336        assert_eq!(
1337            LunarSolarConverter::get_lunar_intercalary_month(1390),
1338            None,
1339            "Year 1390 is out of range"
1340        );
1341        // Max year is 2050 (1391 + 660 - 1)
1342        assert_eq!(
1343            LunarSolarConverter::get_lunar_intercalary_month(2051),
1344            None,
1345            "Year 2051 is out of range"
1346        );
1347    }
1348}