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.is_intercalation = is_intercalation
412                && (Self::get_lunar_intercalation_month(Self::get_lunar_data(lunar_year))
413                    == lunar_month);
414            self.set_solar_date_by_lunar_date(
415                lunar_year,
416                lunar_month,
417                lunar_day,
418                self.is_intercalation,
419            );
420
421            is_valid = true;
422        }
423
424        is_valid
425    }
426
427    /// Sets the converter's date based on a Solar (Gregorian) date.
428    ///
429    /// # Arguments
430    /// * `solar_year` - The solar year.
431    /// * `solar_month` - The solar month (1-12).
432    /// * `solar_day` - The solar day.
433    ///
434    /// # Returns
435    /// `true` if the provided solar date is valid and within the supported range
436    /// (handles the 1582 Gregorian reform gap), `false` otherwise. If `true`,
437    /// the corresponding lunar date is calculated and stored.
438    pub fn set_solar_date(&mut self, solar_year: u32, solar_month: u32, solar_day: u32) -> bool {
439        let mut is_valid = false;
440
441        if Self::check_valid_date(false, false, solar_year, solar_month, solar_day) {
442            self.set_lunar_date_by_solar_date(solar_year, solar_month, solar_day);
443            is_valid = true;
444        }
445
446        is_valid
447    }
448
449    fn get_gapja(&mut self) {
450        let abs_days = Self::get_lunar_abs_days(
451            self.lunar_year,
452            self.lunar_month,
453            self.lunar_day,
454            self.is_intercalation,
455        );
456
457        if abs_days > 0 {
458            self.gapja_year_inx[0] = Some(
459                ((self.lunar_year + 7) - KOREAN_LUNAR_BASE_YEAR) as usize % KOREAN_CHEONGAN.len(),
460            );
461            self.gapja_year_inx[1] = Some(
462                ((self.lunar_year + 7) - KOREAN_LUNAR_BASE_YEAR) as usize % KOREAN_GANJI.len(),
463            );
464
465            let mut month_count = self.lunar_month;
466            month_count += 12 * ((self.lunar_year - KOREAN_LUNAR_BASE_YEAR) as u32);
467            self.gapja_month_inx[0] = Some((month_count + 5) as usize % KOREAN_CHEONGAN.len());
468            self.gapja_month_inx[1] = Some((month_count + 1) as usize % KOREAN_GANJI.len());
469
470            self.gapja_day_inx[0] = Some((abs_days + 4) as usize % KOREAN_CHEONGAN.len());
471            self.gapja_day_inx[1] = Some(abs_days as usize % KOREAN_GANJI.len());
472        } else {
473            self.gapja_year_inx = [None, None, None];
474            self.gapja_month_inx = [None, None, None];
475            self.gapja_day_inx = [None, None, None];
476        }
477    }
478
479    /// Returns the calculated Korean Gapja (간지) string for the current date.
480    /// Format: \"[Year]년 [Month]월 [Day]일\" (e.g., \"임인년 정미월 갑자일\").
481    /// Appends \" (윤월)\" if the current lunar month is intercalary.
482    /// Returns an empty string if the date is invalid.
483    pub fn get_gapja_string(&mut self) -> String {
484        self.get_gapja();
485
486        let mut gapja_string = String::new();
487
488        if let (Some(year_cheongan), Some(year_ganji)) =
489            (self.gapja_year_inx[0], self.gapja_year_inx[1])
490        {
491            gapja_string.push(KOREAN_CHEONGAN[year_cheongan]);
492            gapja_string.push(KOREAN_GANJI[year_ganji]);
493            gapja_string.push(KOREAN_GAPJA_UNIT[0]);
494        } else {
495            return "".to_string();
496        }
497
498        gapja_string.push(' ');
499
500        if let (Some(month_cheongan), Some(month_ganji)) =
501            (self.gapja_month_inx[0], self.gapja_month_inx[1])
502        {
503            gapja_string.push(KOREAN_CHEONGAN[month_cheongan]);
504            gapja_string.push(KOREAN_GANJI[month_ganji]);
505            gapja_string.push(KOREAN_GAPJA_UNIT[1]);
506        } else {
507            return "".to_string();
508        }
509
510        gapja_string.push(' ');
511
512        if let (Some(day_cheongan), Some(day_ganji)) =
513            (self.gapja_day_inx[0], self.gapja_day_inx[1])
514        {
515            gapja_string.push(KOREAN_CHEONGAN[day_cheongan]);
516            gapja_string.push(KOREAN_GANJI[day_ganji]);
517            gapja_string.push(KOREAN_GAPJA_UNIT[2]);
518        } else {
519            return "".to_string();
520        }
521
522        if self.is_intercalation {
523            gapja_string.push_str(" (");
524            gapja_string.push(INTERCALATION_STR[0]);
525            gapja_string.push(KOREAN_GAPJA_UNIT[1]);
526            gapja_string.push(')');
527        }
528
529        gapja_string
530    }
531
532    /// Returns the calculated Chinese Gapja string for the current date.
533    /// Format: \"[Year]年 [Month]月 [Day]日\" (e.g., \"壬寅年 丁未月 甲子日\").
534    /// Appends \" (閏月)\" if the current lunar month is intercalary.
535    /// Returns an empty string if the date is invalid.
536    pub fn get_chinese_gapja_string(&mut self) -> String {
537        self.get_gapja();
538
539        let mut gapja_string = String::new();
540
541        if let (Some(year_cheongan), Some(year_ganji)) =
542            (self.gapja_year_inx[0], self.gapja_year_inx[1])
543        {
544            gapja_string.push(CHINESE_CHEONGAN[year_cheongan]);
545            gapja_string.push(CHINESE_GANJI[year_ganji]);
546            gapja_string.push(CHINESE_GAPJA_UNIT[0]);
547        } else {
548            return "".to_string();
549        }
550
551        gapja_string.push(' ');
552
553        if let (Some(month_cheongan), Some(month_ganji)) =
554            (self.gapja_month_inx[0], self.gapja_month_inx[1])
555        {
556            gapja_string.push(CHINESE_CHEONGAN[month_cheongan]);
557            gapja_string.push(CHINESE_GANJI[month_ganji]);
558            gapja_string.push(CHINESE_GAPJA_UNIT[1]);
559        } else {
560            return "".to_string();
561        }
562
563        gapja_string.push(' ');
564
565        if let (Some(day_cheongan), Some(day_ganji)) =
566            (self.gapja_day_inx[0], self.gapja_day_inx[1])
567        {
568            gapja_string.push(CHINESE_CHEONGAN[day_cheongan]);
569            gapja_string.push(CHINESE_GANJI[day_ganji]);
570            gapja_string.push(CHINESE_GAPJA_UNIT[2]);
571        } else {
572            return "".to_string();
573        }
574
575        if self.is_intercalation {
576            gapja_string.push_str(" (");
577            gapja_string.push(INTERCALATION_STR[1]);
578            gapja_string.push(CHINESE_GAPJA_UNIT[1]);
579            gapja_string.push(')');
580        }
581
582        gapja_string
583    }
584
585    /// Returns the calculated Lunar date in ISO 8601 format (YYYY-MM-DD).
586    /// Appends " Intercalation" if the current lunar month is intercalary.
587    pub fn get_lunar_iso_format(&self) -> String {
588        let mut iso_str = format!(
589            "{:04}-{:02}-{:02}",
590            self.lunar_year, self.lunar_month, self.lunar_day
591        );
592
593        if self.is_intercalation {
594            iso_str.push_str(" Intercalation");
595        }
596
597        iso_str
598    }
599
600    /// Returns the calculated Solar date in ISO 8601 format (YYYY-MM-DD).
601    pub fn get_solar_iso_format(&self) -> String {
602        format!(
603            "{:04}-{:02}-{:02}",
604            self.solar_year, self.solar_month, self.solar_day
605        )
606    }
607
608    /// Calculates the Julian Day Number (JDN) for a given Solar date.
609    ///
610    /// The JDN is the integer number of days elapsed since noon UTC on January 1, 4713 BC (Julian calendar).
611    /// This implementation uses the algorithm described on Wikipedia and other sources,
612    /// correctly handling the transition from the Julian to the Gregorian calendar in October 1582.
613    ///
614    /// # Arguments
615    /// * `year` - The solar year.
616    /// * `month` - The solar month (1-12).
617    /// * `day` - The solar day.
618    ///
619    /// # Returns
620    /// `Some(u32)` containing the JDN if the date is valid, or `None` if the date
621    /// is invalid (e.g., within the 1582 Gregorian reform gap from Oct 5 to Oct 14).
622    ///
623    /// # Example
624    /// ```
625    /// use korean_lunar_calendar::LunarSolarConverter;
626    /// assert_eq!(LunarSolarConverter::get_julian_day_number(2022, 7, 10), Some(2459771));
627    /// assert_eq!(LunarSolarConverter::get_julian_day_number(1582, 10, 4), Some(2299160)); // Last Julian day
628    /// assert_eq!(LunarSolarConverter::get_julian_day_number(1582, 10, 15), Some(2299161)); // First Gregorian day
629    /// assert_eq!(LunarSolarConverter::get_julian_day_number(1582, 10, 10), None); // Invalid date in gap
630    /// ```
631    pub fn get_julian_day_number(year: u32, month: u32, day: u32) -> Option<u32> {
632        // Check for invalid date in the Gregorian reform gap
633        if year == 1582 && month == 10 && day > 4 && day < 15 {
634            return None;
635        }
636        // Basic month/day validation (simplified, primarily for algorithm safety)
637        if month == 0 || month > 12 || day == 0 || day > 31 {
638            return None;
639        }
640
641        // Use i32 for calculations
642        let y = year as i32;
643        let m = month as i32;
644        let d = day as i32;
645
646        // Adjust month/year for Jan/Feb for calculation
647        let adj_y = if m <= 2 { y - 1 } else { y };
648        let adj_m = if m <= 2 { m + 12 } else { m };
649
650        // Calculate base Julian part using integer arithmetic
651        let julian_base = (1461 * (adj_y + 4716)) / 4 + (153 * (adj_m + 1)) / 5 + d;
652
653        // Determine Gregorian correction term 'b'
654        let b = if y > 1582 || (y == 1582 && m > 10) || (y == 1582 && m == 10 && d >= 15) {
655            // Apply correction only for Gregorian dates (starting from 1582-10-15)
656            let term1 = adj_y / 100; // Note: Use adj_y here consistent with algorithm derivations
657            2 - term1 + term1 / 4
658        } else {
659            // No correction for Julian dates (up to 1582-10-04)
660            0
661        };
662
663        // Combine base, correction, and standard offset (-1524)
664        let jdn = julian_base + b - 1524;
665
666        Some(jdn as u32)
667    }
668
669    /// Calculates the day of the week for a given Solar date.
670    ///
671    /// Uses the Julian Day Number calculation internally.
672    ///
673    /// # Arguments
674    /// * `year` - The solar year.
675    /// * `month` - The solar month (1-12).
676    /// * `day` - The solar day.
677    ///
678    /// # Returns
679    /// `Some(DayOfWeek)` if the date is valid, or `None` if the date is invalid (e.g., within the 1582 gap).
680    ///
681    /// # Example
682    /// ```
683    /// use korean_lunar_calendar::{LunarSolarConverter, DayOfWeek};
684    /// assert_eq!(LunarSolarConverter::get_day_of_week(2022, 7, 10), Some(DayOfWeek::Sunday));
685    /// assert_eq!(LunarSolarConverter::get_day_of_week(1582, 10, 4), Some(DayOfWeek::Thursday));
686    /// assert_eq!(LunarSolarConverter::get_day_of_week(1582, 10, 15), Some(DayOfWeek::Friday));
687    /// assert_eq!(LunarSolarConverter::get_day_of_week(1582, 10, 10), None);
688    /// ```
689    pub fn get_day_of_week(year: u32, month: u32, day: u32) -> Option<DayOfWeek> {
690        Self::get_julian_day_number(year, month, day).map(|jdn| {
691            // JDN mod 7: 0=Mon, 1=Tue, 2=Wed, 3=Thu, 4=Fri, 5=Sat, 6=Sun
692            match jdn % 7 {
693                0 => DayOfWeek::Monday,
694                1 => DayOfWeek::Tuesday,
695                2 => DayOfWeek::Wednesday,
696                3 => DayOfWeek::Thursday,
697                4 => DayOfWeek::Friday,
698                5 => DayOfWeek::Saturday,
699                _ => DayOfWeek::Sunday, // 6 and any unexpected remainder
700            }
701        })
702    }
703
704    /// Checks if a given solar year is a leap year according to the Gregorian calendar rules.
705    ///
706    /// For years before or during 1582, the Julian calendar rule (divisible by 4) is used.
707    /// For years after 1582, the Gregorian rules apply: divisible by 4, unless divisible by 100 but not by 400.
708    ///
709    /// # Arguments
710    /// * `year` - The solar year.
711    ///
712    /// # Returns
713    /// `true` if the year is a leap year, `false` otherwise.
714    ///
715    /// # Example
716    /// ```
717    /// use korean_lunar_calendar::LunarSolarConverter;
718    /// assert!(LunarSolarConverter::is_solar_leap_year(2024));
719    /// assert!(!LunarSolarConverter::is_solar_leap_year(2023));
720    /// assert!(!LunarSolarConverter::is_solar_leap_year(1900));
721    /// assert!(LunarSolarConverter::is_solar_leap_year(2000));
722    /// assert!(LunarSolarConverter::is_solar_leap_year(1500)); // Julian leap year
723    /// ```
724    pub fn is_solar_leap_year(year: u32) -> bool {
725        // Reuse the internal logic which handles the Gregorian reform
726        Self::is_gregorian_leap(year as i32)
727    }
728
729    /// Gets the intercalary (leap) month (윤달) for a given lunar year, if one exists.
730    ///
731    /// Based on the pre-calculated `KOREAN_LUNAR_DATA`.
732    ///
733    /// # Arguments
734    /// * `year` - The lunar year.
735    ///
736    /// # Returns
737    /// `Some(u32)` containing the intercalary month number (1-12) if the year has one,
738    /// or `None` if the year has no intercalary month or the year is outside the supported range.
739    ///
740    /// # Example
741    /// ```
742    /// use korean_lunar_calendar::LunarSolarConverter;
743    /// assert_eq!(LunarSolarConverter::get_lunar_intercalary_month(2023), Some(2)); // 윤2월
744    /// assert_eq!(LunarSolarConverter::get_lunar_intercalary_month(2020), Some(4)); // 윤4월
745    /// assert_eq!(LunarSolarConverter::get_lunar_intercalary_month(2022), None);
746    /// ```
747    pub fn get_lunar_intercalary_month(year: i32) -> Option<u32> {
748        if year < KOREAN_LUNAR_BASE_YEAR
749            || year > KOREAN_LUNAR_BASE_YEAR + KOREAN_LUNAR_DATA.len() as i32 - 1
750        {
751            return None; // Year out of supported range
752        }
753        let lunar_data = Self::get_lunar_data(year);
754        let intercalary_month = Self::get_lunar_intercalation_month(lunar_data);
755        if intercalary_month > 0 {
756            Some(intercalary_month)
757        } else {
758            None
759        }
760    }
761
762    // --- Getters for date fields ---
763    /// Returns the currently stored solar year.
764    #[allow(dead_code)]
765    pub fn solar_year(&self) -> u32 {
766        self.solar_year
767    }
768    /// Returns the currently stored solar month.
769    #[allow(dead_code)]
770    pub fn solar_month(&self) -> u32 {
771        self.solar_month
772    }
773    /// Returns the currently stored solar day.
774    #[allow(dead_code)]
775    pub fn solar_day(&self) -> u32 {
776        self.solar_day
777    }
778    /// Returns the currently stored lunar year.
779    pub fn lunar_year(&self) -> i32 {
780        self.lunar_year
781    }
782    /// Returns the currently stored lunar month.
783    #[allow(dead_code)]
784    pub fn lunar_month(&self) -> u32 {
785        self.lunar_month
786    }
787    /// Returns the currently stored lunar day.
788    #[allow(dead_code)]
789    pub fn lunar_day(&self) -> u32 {
790        self.lunar_day
791    }
792    /// Returns `true` if the currently stored lunar date is an intercalary month.
793    #[allow(dead_code)]
794    pub fn is_intercalation(&self) -> bool {
795        self.is_intercalation
796    }
797    // ------------------------------
798
799    // --- Internal helper methods ---
800
801    fn set_solar_date_by_lunar_date(
802        &mut self,
803        lunar_year: i32,
804        lunar_month: u32,
805        lunar_day: u32,
806        is_intercalation: bool,
807    ) {
808        let abs_days =
809            Self::get_lunar_abs_days(lunar_year, lunar_month, lunar_day, is_intercalation);
810
811        if abs_days < Self::get_solar_abs_days(lunar_year + 1, 1, 1) {
812            self.solar_year = lunar_year as u32;
813        } else {
814            self.solar_year = (lunar_year + 1) as u32;
815        }
816
817        for month in (1..=12).rev() {
818            let abs_days_by_month = Self::get_solar_abs_days(self.solar_year as i32, month, 1);
819
820            if abs_days >= abs_days_by_month {
821                self.solar_month = month;
822                self.solar_day = abs_days - abs_days_by_month + 1;
823                break;
824            }
825        }
826
827        if self.solar_year == 1582 && self.solar_month == 10 && self.solar_day > 4 {
828            self.solar_day += 10;
829        }
830    }
831
832    fn set_lunar_date_by_solar_date(&mut self, solar_year: u32, solar_month: u32, solar_day: u32) {
833        let abs_days = Self::get_solar_abs_days(solar_year as i32, solar_month, solar_day);
834
835        self.is_intercalation = false;
836
837        if abs_days >= Self::get_lunar_abs_days(solar_year as i32, 1, 1, false) {
838            self.lunar_year = solar_year as i32;
839        } else {
840            self.lunar_year = solar_year as i32 - 1;
841        }
842
843        for month in (1..=12).rev() {
844            let abs_days_by_month = Self::get_lunar_abs_days(self.lunar_year, month, 1, false);
845
846            if abs_days >= abs_days_by_month {
847                self.lunar_month = month;
848
849                if Self::get_lunar_intercalation_month(Self::get_lunar_data(self.lunar_year))
850                    == month
851                {
852                    self.is_intercalation =
853                        abs_days >= Self::get_lunar_abs_days(self.lunar_year, month, 1, true);
854                }
855
856                self.lunar_day = abs_days
857                    - Self::get_lunar_abs_days(
858                        self.lunar_year,
859                        self.lunar_month,
860                        1,
861                        self.is_intercalation,
862                    )
863                    + 1;
864
865                break;
866            }
867        }
868    }
869
870    fn is_valid_min(is_lunar: bool, date_value: u32) -> bool {
871        if is_lunar {
872            KOREAN_LUNAR_MIN_VALUE <= date_value
873        } else {
874            KOREAN_SOLAR_MIN_VALUE <= date_value
875        }
876    }
877
878    fn is_valid_max(is_lunar: bool, date_value: u32) -> bool {
879        if is_lunar {
880            KOREAN_LUNAR_MAX_VALUE >= date_value
881        } else {
882            KOREAN_SOLAR_MAX_VALUE >= date_value
883        }
884    }
885
886    fn check_valid_date(
887        is_lunar: bool,
888        is_intercalation: bool,
889        year: u32,
890        month: u32,
891        day: u32,
892    ) -> bool {
893        let mut is_valid = false;
894        let date_value = year * 10000 + month * 100 + day;
895
896        //1582. 10. 5 ~ 1582. 10. 14 is not enable
897        if Self::is_valid_min(is_lunar, date_value) && Self::is_valid_max(is_lunar, date_value) {
898            let mut day_limit;
899
900            if month > 0 && month < 13 && day > 0 {
901                if is_lunar {
902                    if is_intercalation
903                        && Self::get_lunar_intercalation_month(Self::get_lunar_data(year as i32))
904                            != month
905                    {
906                        return false;
907                    }
908                    day_limit = Self::get_lunar_days(year as i32, month, is_intercalation);
909                } else {
910                    day_limit = Self::get_solar_days(year as i32, month);
911                }
912
913                if !is_lunar && year == 1582 && month == 10 {
914                    if day > 4 && day < 15 {
915                        return false;
916                    } else {
917                        day_limit += 10;
918                    }
919                }
920
921                if day <= day_limit {
922                    is_valid = true;
923                }
924            }
925        }
926
927        is_valid
928    }
929}
930
931#[cfg(test)]
932mod tests {
933    use super::DayOfWeek;
934    use crate::LunarSolarConverter;
935
936    #[test]
937    fn test_lunar_iso_format() {
938        let mut converter = LunarSolarConverter::new();
939        converter.set_solar_date(2022, 7, 10);
940        let lunar = converter.get_lunar_iso_format();
941        let want = "2022-06-12";
942
943        println!("{}", lunar);
944
945        assert_eq!(lunar, want, "got {:?} want {:?}", lunar, want);
946    }
947
948    #[test]
949    fn test_gapja_string() {
950        let mut converter = LunarSolarConverter::new();
951        converter.set_solar_date(2022, 7, 10);
952        let lunar_gapja = converter.get_gapja_string();
953        let want = "임인년 정미월 갑자일";
954
955        println!("{}", lunar_gapja);
956
957        assert_eq!(lunar_gapja, want, "got {:?} want {:?}", lunar_gapja, want);
958    }
959
960    #[test]
961    fn test_chinese_gapja_string() {
962        let mut converter = LunarSolarConverter::new();
963        converter.set_solar_date(2022, 7, 10);
964        let lunar_chinese_gapja = converter.get_chinese_gapja_string();
965        let want = "壬寅年 丁未月 甲子日";
966
967        println!("{}", lunar_chinese_gapja);
968
969        assert_eq!(
970            lunar_chinese_gapja, want,
971            "got {:?} want {:?}",
972            lunar_chinese_gapja, want
973        );
974    }
975
976    #[test]
977    fn test_solar_iso_format() {
978        let mut converter = LunarSolarConverter::new();
979        converter.set_lunar_date(2022, 6, 12, false);
980        let solar = converter.get_solar_iso_format();
981        let want = "2022-07-10";
982
983        println!("{}", solar);
984
985        assert_eq!(solar, want, "got {:?} want {:?}", solar, want);
986    }
987
988    #[test]
989    fn test_gapja_string_intercalation() {
990        let mut converter = LunarSolarConverter::new();
991        let is_valid = converter.set_lunar_date(2022, 6, 12, true);
992
993        assert!(
994            !is_valid,
995            "Expected set_lunar_date to return false for non-existent intercalary month"
996        );
997    }
998
999    #[test]
1000    fn test_chinese_gapja_string_intercalation() {
1001        let mut converter = LunarSolarConverter::new();
1002        let is_valid = converter.set_lunar_date(2022, 6, 12, true);
1003
1004        assert!(
1005            !is_valid,
1006            "Expected set_lunar_date to return false for non-existent intercalary month"
1007        );
1008    }
1009
1010    #[test]
1011    fn test_set_solar_date() {
1012        let mut converter = LunarSolarConverter::new();
1013        let is_valid = converter.set_solar_date(2022, 7, 10);
1014
1015        assert!(is_valid, "Expected solar date to be valid");
1016        assert_eq!(converter.lunar_year, 2022);
1017        assert_eq!(converter.lunar_month, 6);
1018        assert_eq!(converter.lunar_day, 12);
1019    }
1020
1021    #[test]
1022    fn test_set_lunar_date() {
1023        let mut converter = LunarSolarConverter::new();
1024        let is_valid = converter.set_lunar_date(2022, 6, 12, false);
1025
1026        assert!(is_valid, "Expected lunar date to be valid");
1027        assert_eq!(converter.solar_year, 2022);
1028        assert_eq!(converter.solar_month, 7);
1029        assert_eq!(converter.solar_day, 10);
1030    }
1031
1032    #[test]
1033    fn test_invalid_solar_date() {
1034        let mut converter = LunarSolarConverter::new();
1035        let is_valid = converter.set_solar_date(1582, 10, 10);
1036
1037        assert!(!is_valid, "Expected solar date to be invalid");
1038    }
1039
1040    #[test]
1041    fn test_invalid_lunar_date() {
1042        let mut converter = LunarSolarConverter::new();
1043        let is_valid = converter.set_lunar_date(1390, 12, 31, false);
1044
1045        assert!(!is_valid, "Expected lunar date to be invalid");
1046    }
1047
1048    #[test]
1049    fn test_get_lunar_days() {
1050        let days = LunarSolarConverter::get_lunar_days(2022, 6, false);
1051
1052        assert_eq!(days, 30, "Expected 30 days for June 2022");
1053    }
1054
1055    #[test]
1056    fn test_get_lunar_days_invalid_month() {
1057        let days = LunarSolarConverter::get_lunar_days(2022, 13, false);
1058
1059        assert_eq!(days, 0, "Expected 0 days for invalid month 2022");
1060    }
1061
1062    #[test]
1063    fn test_get_lunar_days_invalid_year() {
1064        let days = LunarSolarConverter::get_lunar_days(1390, 6, false);
1065        assert_eq!(days, 0, "Expected 0 days for invalid year 1390");
1066    }
1067
1068    #[test]
1069    fn test_get_solar_days() {
1070        let days = LunarSolarConverter::get_solar_days(2022, 2);
1071
1072        assert_eq!(days, 28, "Expected 28 days for February 2022");
1073    }
1074
1075    #[test]
1076    fn test_get_solar_days_invalid_month() {
1077        let days = LunarSolarConverter::get_solar_days(2022, 13);
1078
1079        assert_eq!(days, 0, "Expected 0 days for invalid month 2022");
1080    }
1081
1082    #[test]
1083    fn test_get_lunar_abs_days() {
1084        let days = LunarSolarConverter::get_lunar_abs_days(2022, 6, 12, false);
1085
1086        assert_eq!(days, 230616, "Expected 230616 absolute lunar days");
1087    }
1088
1089    #[test]
1090    fn test_get_lunar_abs_days_invalid_year() {
1091        let days = LunarSolarConverter::get_lunar_abs_days(1390, 6, 12, false);
1092
1093        assert_eq!(
1094            days, 0,
1095            "Expected 0 absolute lunar days for invalid year 1390"
1096        );
1097    }
1098
1099    #[test]
1100    fn test_get_solar_abs_days() {
1101        let days = LunarSolarConverter::get_solar_abs_days(2022, 7, 10);
1102
1103        // Correct assertion back to the library's internal relative day count
1104        assert_eq!(days, 230616, "Expected 230616 absolute solar days");
1105    }
1106
1107    #[test]
1108    fn test_get_solar_abs_days_invalid_year() {
1109        let days = LunarSolarConverter::get_solar_abs_days(1390, 7, 10);
1110
1111        assert_eq!(
1112            days, 0,
1113            "Expected 0 absolute solar days for invalid year 1390"
1114        );
1115    }
1116
1117    #[test]
1118    fn test_invalid_date_for_get_gapja_string() {
1119        let mut converter = LunarSolarConverter::new();
1120        converter.set_lunar_date(1390, 12, 31, false);
1121        let gapja = converter.get_gapja_string();
1122
1123        assert_eq!(gapja, "", "Expected empty string since the date is invalid");
1124    }
1125
1126    #[test]
1127    fn test_invalid_date_for_get_chinese_gapja_string() {
1128        let mut converter = LunarSolarConverter::new();
1129        converter.set_lunar_date(1390, 12, 31, false);
1130        let gapja = converter.get_chinese_gapja_string();
1131
1132        assert_eq!(gapja, "", "Expected empty string since the date is invalid");
1133    }
1134
1135    #[test]
1136    fn test_get_julian_day_number_gregorian() {
1137        let jdn = LunarSolarConverter::get_julian_day_number(2022, 7, 10);
1138        let want = Some(2459771);
1139        assert_eq!(
1140            jdn, want,
1141            "Expected JDN {:?} for 2022-07-10, got {:?}",
1142            want, jdn
1143        );
1144    }
1145
1146    #[test]
1147    fn test_get_julian_day_number_julian() {
1148        let jdn = LunarSolarConverter::get_julian_day_number(1500, 3, 1);
1149        let want = Some(2268993);
1150        assert_eq!(
1151            jdn, want,
1152            "Expected JDN {:?} for 1500-03-01, got {:?}",
1153            want, jdn
1154        );
1155    }
1156
1157    #[test]
1158    fn test_get_julian_day_number_reform_before() {
1159        let jdn = LunarSolarConverter::get_julian_day_number(1582, 10, 4);
1160        let want = Some(2299160);
1161        assert_eq!(
1162            jdn, want,
1163            "Expected JDN {:?} for 1582-10-04, got {:?}",
1164            want, jdn
1165        );
1166    }
1167
1168    #[test]
1169    fn test_get_julian_day_number_reform_after() {
1170        let jdn = LunarSolarConverter::get_julian_day_number(1582, 10, 15);
1171        let want = Some(2299161);
1172        assert_eq!(
1173            jdn, want,
1174            "Expected JDN {:?} for 1582-10-15, got {:?}",
1175            want, jdn
1176        );
1177    }
1178
1179    #[test]
1180    fn test_get_julian_day_number_min_date() {
1181        // Corresponds to KOREAN_SOLAR_MIN_VALUE
1182        let jdn = LunarSolarConverter::get_julian_day_number(1391, 2, 5);
1183        let want = Some(2229156);
1184        assert_eq!(
1185            jdn, want,
1186            "Expected JDN {:?} for 1391-02-05, got {:?}",
1187            want, jdn
1188        );
1189    }
1190
1191    #[test]
1192    fn test_get_julian_day_number_invalid_gap() {
1193        let jdn = LunarSolarConverter::get_julian_day_number(1582, 10, 10);
1194        assert_eq!(
1195            jdn, None,
1196            "Expected None for invalid date 1582-10-10 (Gregorian gap)"
1197        );
1198    }
1199
1200    #[test]
1201    fn test_get_julian_day_number_invalid_range_before() {
1202        // JDN function should work outside library range, only failing for 1582 gap.
1203        let jdn = LunarSolarConverter::get_julian_day_number(1391, 2, 4);
1204        let want = Some(2229155);
1205        assert_eq!(
1206            jdn, want,
1207            "Expected JDN {:?} for 1391-02-04, got {:?}",
1208            want, jdn
1209        );
1210    }
1211
1212    #[test]
1213    fn test_get_julian_day_number_invalid_range_after() {
1214        // Using a date beyond the KOREAN_SOLAR_MAX_VALUE
1215        // JDN function should work outside library range.
1216        let jdn = LunarSolarConverter::get_julian_day_number(2051, 1, 1);
1217        let want = Some(2470173);
1218        assert_eq!(
1219            jdn, want,
1220            "Expected JDN {:?} for 2051-01-01, got {:?}",
1221            want, jdn
1222        );
1223    }
1224
1225    #[test]
1226    fn test_get_day_of_week_monday() {
1227        // JDN 2459771 % 7 = 0 (Monday)
1228        let dow = LunarSolarConverter::get_day_of_week(2022, 7, 11);
1229        let want = Some(DayOfWeek::Monday);
1230        assert_eq!(
1231            dow, want,
1232            "Expected {:?} for 2022-07-11, got {:?}",
1233            want, dow
1234        );
1235    }
1236
1237    #[test]
1238    fn test_get_day_of_week_sunday() {
1239        // JDN 2459770 % 7 = 6 (Sunday)
1240        let dow = LunarSolarConverter::get_day_of_week(2022, 7, 10);
1241        let want = Some(DayOfWeek::Sunday);
1242        assert_eq!(
1243            dow, want,
1244            "Expected {:?} for 2022-07-10, got {:?}",
1245            want, dow
1246        );
1247    }
1248
1249    #[test]
1250    fn test_get_day_of_week_reform_before() {
1251        // October 4, 1582 was a Thursday (Julian)
1252        let result = LunarSolarConverter::get_day_of_week(1582, 10, 4);
1253        assert_eq!(result, Some(DayOfWeek::Thursday)); // Corrected from Friday
1254    }
1255
1256    #[test]
1257    fn test_get_day_of_week_reform_after() {
1258        // October 15, 1582 was a Friday (Gregorian)
1259        let result = LunarSolarConverter::get_day_of_week(1582, 10, 15);
1260        assert_eq!(result, Some(DayOfWeek::Friday)); // Corrected from Saturday
1261    }
1262
1263    #[test]
1264    fn test_get_day_of_week_invalid_gap() {
1265        let dow = LunarSolarConverter::get_day_of_week(1582, 10, 10);
1266        assert_eq!(dow, None, "Expected None for invalid date 1582-10-10");
1267    }
1268
1269    #[test]
1270    fn test_is_solar_leap_year() {
1271        assert!(
1272            LunarSolarConverter::is_solar_leap_year(2024),
1273            "2024 should be a leap year"
1274        );
1275        assert!(
1276            !LunarSolarConverter::is_solar_leap_year(2023),
1277            "2023 should not be a leap year"
1278        );
1279        assert!(
1280            !LunarSolarConverter::is_solar_leap_year(1900),
1281            "1900 should not be a leap year"
1282        );
1283        assert!(
1284            LunarSolarConverter::is_solar_leap_year(2000),
1285            "2000 should be a leap year"
1286        );
1287        assert!(
1288            LunarSolarConverter::is_solar_leap_year(1500),
1289            "1500 should be a leap year (Julian)"
1290        );
1291        assert!(
1292            !LunarSolarConverter::is_solar_leap_year(1582),
1293            "1582 should not be a leap year"
1294        );
1295        assert!(
1296            LunarSolarConverter::is_solar_leap_year(1600),
1297            "1600 should be a leap year"
1298        );
1299    }
1300
1301    #[test]
1302    fn test_get_lunar_intercalary_month() {
1303        // Year 2023 actually has intercalary month 2 (윤2월)
1304        assert_eq!(
1305            LunarSolarConverter::get_lunar_intercalary_month(2023),
1306            Some(2),
1307            "Year 2023 should have intercalary month 2"
1308        );
1309        // Year 2020 actually has intercalary month 4 (윤4월)
1310        assert_eq!(
1311            LunarSolarConverter::get_lunar_intercalary_month(2020),
1312            Some(4),
1313            "Year 2020 should have intercalary month 4"
1314        );
1315        // Year 2022 has no intercalary month
1316        assert_eq!(
1317            LunarSolarConverter::get_lunar_intercalary_month(2022),
1318            None,
1319            "Year 2022 should not have an intercalary month"
1320        );
1321        assert_eq!(
1322            LunarSolarConverter::get_lunar_intercalary_month(1391),
1323            None,
1324            "Year 1391 should not have an intercalary month"
1325        ); // First year in data
1326    }
1327
1328    #[test]
1329    fn test_get_lunar_intercalary_month_out_of_range() {
1330        assert_eq!(
1331            LunarSolarConverter::get_lunar_intercalary_month(1390),
1332            None,
1333            "Year 1390 is out of range"
1334        );
1335        // Max year is 2050 (1391 + 660 - 1)
1336        assert_eq!(
1337            LunarSolarConverter::get_lunar_intercalary_month(2051),
1338            None,
1339            "Year 2051 is out of range"
1340        );
1341    }
1342}