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
19namespace OHOS {
20namespace Global {
21namespace I18n {
22std::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
37std::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
52std::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
76LunarCalendar::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
89LunarCalendar::~LunarCalendar()
90{
91    if (calendar_ != nullptr) {
92        delete calendar_;
93    }
94    calendar_ = nullptr;
95}
96
97bool 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
113void 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
140void 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
158void 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
203void 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
215int32_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
233bool 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
258bool 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
272int32_t LunarCalendar::GetLunarYear()
273{
274    if (!isValidDate) {
275        HILOG_ERROR_I18N("GetLunarYear: invalid date");
276        return -1;
277    }
278    return lunarYear;
279}
280
281int32_t LunarCalendar::GetLunarMonth()
282{
283    if (!isValidDate) {
284        HILOG_ERROR_I18N("GetLunarYear: invalid date");
285        return -1;
286    }
287    return lunarMonth;
288}
289
290int32_t LunarCalendar::GetLunarDay()
291{
292    if (!isValidDate) {
293        HILOG_ERROR_I18N("GetLunarYear: invalid date");
294        return -1;
295    }
296    return lunarDay;
297}
298
299bool 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