1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include "i18n_hilog.h"
16 #include "unicode/gregocal.h"
17 #include "lunar_calendar.h"
18 
19 namespace OHOS {
20 namespace Global {
21 namespace I18n {
22 std::unordered_map<int32_t, int32_t> LunarCalendar::daysOfMonth {
23     { 1, 31 },
24     { 2, 28 },
25     { 3, 31 },
26     { 4, 30 },
27     { 5, 31 },
28     { 6, 30 },
29     { 7, 31 },
30     { 8, 31 },
31     { 9, 30 },
32     { 10, 31 },
33     { 11, 30 },
34     { 12, 31 }
35 };
36 
37 std::unordered_map<int32_t, int32_t> LunarCalendar::accDaysOfMonth {
38     { 1, 0 },
39     { 2, 31 },
40     { 3, 59 },
41     { 4, 90 },
42     { 5, 120 },
43     { 6, 151 },
44     { 7, 181 },
45     { 8, 212 },
46     { 9, 243 },
47     { 10, 273 },
48     { 11, 304 },
49     { 12, 334 }
50 };
51 
52 std::vector<uint32_t> LunarCalendar::lunarDateInfo {
53     0x4bd8, 0x4ae0, 0xa570, 0x54d5, 0xd260, 0xd950, 0x5554, 0x56af, 0x9ad0, 0x55d2,
54     0x4ae0, 0xa5b6, 0xa4d0, 0xd250, 0xd295, 0xb54f, 0xd6a0, 0xada2, 0x95b0, 0x4977,
55     0x497f, 0xa4b0, 0xb4b5, 0x6a50, 0x6d40, 0xab54, 0x2b6f, 0x9570, 0x52f2, 0x4970,
56     0x6566, 0xd4a0, 0xea50, 0x6a95, 0x5adf, 0x2b60, 0x86e3, 0x92ef, 0xc8d7, 0xc95f,
57     0xd4a0, 0xd8a6, 0xb55f, 0x56a0, 0xa5b4, 0x25df, 0x92d0, 0xd2b2, 0xa950, 0xb557,
58     0x6ca0, 0xb550, 0x5355, 0x4daf, 0xa5b0, 0x4573, 0x52bf, 0xa9a8, 0xe950, 0x6aa0,
59     0xaea6, 0xab50, 0x4b60, 0xaae4, 0xa570, 0x5260, 0xf263, 0xd950, 0x5b57, 0x56a0,
60     0x96d0, 0x4dd5, 0x4ad0, 0xa4d0, 0xd4d4, 0xd250, 0xd558, 0xb540, 0xb6a0, 0x95a6,
61     0x95bf, 0x49b0, 0xa974, 0xa4b0, 0xb27a, 0x6a50, 0x6d40, 0xaf46, 0xab60, 0x9570,
62     0x4af5, 0x4970, 0x64b0, 0x74a3, 0xea50, 0x6b58, 0x5ac0, 0xab60, 0x96d5, 0x92e0,
63     0xc960, 0xd954, 0xd4a0, 0xda50, 0x7552, 0x56a0, 0xabb7, 0x25d0, 0x92d0, 0xcab5,
64     0xa950, 0xb4a0, 0xbaa4, 0xad50, 0x55d9, 0x4ba0, 0xa5b0, 0x5176, 0x52bf, 0xa930,
65     0x7954, 0x6aa0, 0xad50, 0x5b52, 0x4b60, 0xa6e6, 0xa4e0, 0xd260, 0xea65, 0xd530,
66     0x5aa0, 0x76a3, 0x96d0, 0x4afb, 0x4ad0, 0xa4d0, 0xd0b6, 0xd25f, 0xd520, 0xdd45,
67     0xb5a0, 0x56d0, 0x55b2, 0x49b0, 0xa577, 0xa4b0, 0xaa50, 0xb255, 0x6d2f, 0xada0,
68     0x4b63, 0x937f, 0x49f8, 0x4970, 0x64b0, 0x68a6, 0xea5f, 0x6b20, 0xa6c4, 0xaaef,
69     0x92e0, 0xd2e3, 0xc960, 0xd557, 0xd4a0, 0xda50, 0x5d55, 0x56a0, 0xa6d0, 0x55d4,
70     0x52d0, 0xa9b8, 0xa950, 0xb4a0, 0xb6a6, 0xad50, 0x55a0, 0xaba4, 0xa5b0, 0x52b0,
71     0xb273, 0x6930, 0x7337, 0x6aa0, 0xad50, 0x4b55, 0x4b6f, 0xa570, 0x54e4, 0xd260,
72     0xe968, 0xd520, 0xdaa0, 0x6aa6, 0x56df, 0x4ae0, 0xa9d4, 0xa4d0, 0xd150, 0xf252,
73     0xd520,
74 };
75 
LunarCalendar()76 LunarCalendar::LunarCalendar()
77 {
78     UErrorCode status = U_ZERO_ERROR;
79     calendar_ = new icu::GregorianCalendar(status);
80     if (U_FAILURE(status)) {
81         HILOG_ERROR_I18N("LunarCalendar: create GregorianCalendar failed");
82         if (calendar_ != nullptr) {
83             delete calendar_;
84         }
85         calendar_ = nullptr;
86     }
87 }
88 
~LunarCalendar()89 LunarCalendar::~LunarCalendar()
90 {
91     if (calendar_ != nullptr) {
92         delete calendar_;
93     }
94     calendar_ = nullptr;
95 }
96 
SetGregorianDate(int32_t year,int32_t month,int32_t day)97 bool LunarCalendar::SetGregorianDate(int32_t year, int32_t month, int32_t day)
98 {
99     ConvertDate(year, month, day);
100     isGregorianLeapYear = false;
101     isValidDate = VerifyDate(year, month, day);
102     if (!isValidDate) {
103         return false;
104     }
105     solorYear = year;
106     solorMonth = month;
107     solorDay = day;
108     CalcDaysFromBaseDate();
109     SolorDateToLunarDate();
110     return true;
111 }
112 
ConvertDate(int32_t & year,int32_t & month,int32_t & day)113 void LunarCalendar::ConvertDate(int32_t& year, int32_t& month, int32_t& day)
114 {
115     if (calendar_ == nullptr) {
116         return;
117     }
118     calendar_->set(year, month - 1, day);
119     UErrorCode status = U_ZERO_ERROR;
120     int32_t tempYear = calendar_->get(UCAL_YEAR, status);
121     if (U_FAILURE(status)) {
122         HILOG_ERROR_I18N("ConvertDate: get year failed");
123         return;
124     }
125     int32_t tempMonth = calendar_->get(UCAL_MONTH, status) + 1;
126     if (U_FAILURE(status)) {
127         HILOG_ERROR_I18N("ConvertDate: get month failed");
128         return;
129     }
130     int32_t tempDay = calendar_->get(UCAL_DATE, status);
131     if (U_FAILURE(status)) {
132         HILOG_ERROR_I18N("ConvertDate: get day failed");
133         return;
134     }
135     year = tempYear;
136     month = tempMonth;
137     day = tempDay;
138 }
139 
CalcDaysFromBaseDate()140 void LunarCalendar::CalcDaysFromBaseDate()
141 {
142     daysCounts = DAYS_OF_YEAR * (solorYear - START_YEAR);
143     daysCounts += accDaysOfMonth[solorMonth];
144     if (isGregorianLeapYear && solorMonth > MONTH_FEB) {
145         daysCounts++;
146     }
147     daysCounts--;
148     daysCounts += solorDay;
149     daysCounts += (solorYear - START_YEAR) / FREQ_LEAP_YEAR;
150     if (isGregorianLeapYear) {
151         daysCounts--;
152     }
153     if (solorYear >= VALID_END_YEAR) {
154         daysCounts--;
155     }
156 }
157 
SolorDateToLunarDate()158 void LunarCalendar::SolorDateToLunarDate()
159 {
160     int32_t daysInPerLunarYear = 0;
161     int32_t daysInPerLunarMonth = 0;
162     int32_t leapMonth = 0xf;
163     int tempDaysCounts = daysCounts;
164     tempDaysCounts -= DAYS_FROM_SOLAR_TO_LUNAR;
165     int32_t i = 0;
166     for (i = START_YEAR; (tempDaysCounts > 0) && (i < END_YEAR); i++) {
167         daysInPerLunarYear = GetDaysPerLunarYear(i);
168         tempDaysCounts -= daysInPerLunarYear;
169     }
170     if (tempDaysCounts < 0) {
171         tempDaysCounts += daysInPerLunarYear;
172         --i;
173     }
174     lunarYear = i;
175 
176     leapMonth = lunarDateInfo[lunarYear - START_YEAR] & 0xf;
177     leapMonth = (leapMonth == 0xf) ? 0 : leapMonth;
178     isLeapMonth = false;
179     for (i = 1; i <= VALID_END_MONTH && tempDaysCounts > 0; i++) {
180         if (leapMonth > 0 && (leapMonth + 1) == i && !isLeapMonth) {
181             --i;
182             isLeapMonth = true;
183             daysInPerLunarMonth = ((lunarDateInfo[lunarYear - START_YEAR + 1] & 0xf) == 0xf) ? DAYS_IN_BIG_MONTH :
184                 DAYS_IN_SMALL_MONTH;
185         } else {
186             daysInPerLunarMonth = ((lunarDateInfo[lunarYear - START_YEAR] &
187                 (0x8000 >> (i - 1))) == (0x8000 >> (i - 1))) ? DAYS_IN_BIG_MONTH : DAYS_IN_SMALL_MONTH;
188         }
189         if (isLeapMonth && (leapMonth + 1) == i) {
190             isLeapMonth = false;
191         }
192         tempDaysCounts -= daysInPerLunarMonth;
193     }
194     AdjustLeapMonth(i, tempDaysCounts, leapMonth);
195     if (tempDaysCounts < 0) {
196         tempDaysCounts += daysInPerLunarMonth;
197         --i;
198     }
199     lunarMonth = i;
200     lunarDay = tempDaysCounts + 1;
201 }
202 
AdjustLeapMonth(int32_t & i,int32_t tempDaysCounts,int32_t leapMonth)203 void LunarCalendar::AdjustLeapMonth(int32_t& i, int32_t tempDaysCounts, int32_t leapMonth)
204 {
205     if (tempDaysCounts == 0 && leapMonth > 0 && i == leapMonth + 1) {
206         if (isLeapMonth) {
207             isLeapMonth = false;
208         } else {
209             isLeapMonth = true;
210             --i;
211         }
212     }
213 }
214 
GetDaysPerLunarYear(int32_t lunarYear)215 int32_t LunarCalendar::GetDaysPerLunarYear(int32_t lunarYear)
216 {
217     int32_t daysPerLunarYear = 0;
218     if ((lunarYear < START_YEAR) || (lunarYear > END_YEAR)) {
219         return 0;
220     }
221     daysPerLunarYear += BASE_DAYS_PER_LUNAR_YEAR;
222     for (uint32_t i = 0x8000; i > 0x8; i = i >> 1) {
223         daysPerLunarYear += ((lunarDateInfo[lunarYear - START_YEAR] & i) == i) ? 1 : 0;
224     }
225     if (((lunarDateInfo[lunarYear - START_YEAR] & 0xf) != 0) &&
226         ((lunarDateInfo[lunarYear - START_YEAR] & 0xf) != 0xf)) {
227         daysPerLunarYear += ((lunarDateInfo[lunarYear - START_YEAR + 1] & 0xf) == 0xf) ? DAYS_IN_BIG_MONTH :
228             DAYS_IN_SMALL_MONTH;
229     }
230     return daysPerLunarYear;
231 }
232 
VerifyDate(int32_t year,int32_t month,int32_t day)233 bool LunarCalendar::VerifyDate(int32_t year, int32_t month, int32_t day)
234 {
235     if ((year < VALID_START_YEAR) || (year > VALID_END_YEAR)) {
236         HILOG_ERROR_I18N("VerifyDate: %{public}d is an invalid year", year);
237         return false;
238     }
239 
240     if ((month < VALID_START_MONTH) || (month > VALID_END_MONTH)) {
241         HILOG_ERROR_I18N("VerifyDate: %{public}d is an invalid month", month);
242         return false;
243     }
244 
245     int32_t validEndDay = daysOfMonth[month];
246     isGregorianLeapYear = IsGregorianLeapYear(year);
247     if (month == MONTH_FEB) {
248         validEndDay = isGregorianLeapYear ? validEndDay + 1 : validEndDay;
249     }
250 
251     if ((month < VALID_START_DAY) || (month > validEndDay)) {
252         HILOG_ERROR_I18N("VerifyDate: %{public}d is an invalid day", day);
253         return false;
254     }
255     return true;
256 }
257 
IsGregorianLeapYear(int32_t year)258 bool LunarCalendar::IsGregorianLeapYear(int32_t year)
259 {
260     if (year % YEAR_ERA == 0) {
261         if (year % (YEAR_ERA * FREQ_LEAP_YEAR) == 0) {
262             return true;
263         }
264         return false;
265     }
266     if (year % FREQ_LEAP_YEAR == 0) {
267         return true;
268     }
269     return false;
270 }
271 
GetLunarYear()272 int32_t LunarCalendar::GetLunarYear()
273 {
274     if (!isValidDate) {
275         HILOG_ERROR_I18N("GetLunarYear: invalid date");
276         return -1;
277     }
278     return lunarYear;
279 }
280 
GetLunarMonth()281 int32_t LunarCalendar::GetLunarMonth()
282 {
283     if (!isValidDate) {
284         HILOG_ERROR_I18N("GetLunarYear: invalid date");
285         return -1;
286     }
287     return lunarMonth;
288 }
289 
GetLunarDay()290 int32_t LunarCalendar::GetLunarDay()
291 {
292     if (!isValidDate) {
293         HILOG_ERROR_I18N("GetLunarYear: invalid date");
294         return -1;
295     }
296     return lunarDay;
297 }
298 
IsLeapMonth()299 bool LunarCalendar::IsLeapMonth()
300 {
301     if (!isValidDate) {
302         HILOG_ERROR_I18N("GetLunarYear: invalid date");
303         return false;
304     }
305     return isLeapMonth;
306 }
307 } // namespace I18n
308 } // namespace Global
309 } // namespace OHOS