ICU-22736 Fix Persian calendar

ICU-22736 Add tests for java and make correction

Update icu4c/source/i18n/persncal.cpp

Co-authored-by: Markus Scherer <markus.icu@gmail.com>
This commit is contained in:
Frank Tang 2024-09-13 00:21:49 -07:00 committed by Frank Yung-Fong Tang
parent 36b552737f
commit 73956e9cf3
6 changed files with 985 additions and 13 deletions

View file

@ -25,6 +25,9 @@
#include "umutex.h"
#include "gregoimp.h" // Math
#include <float.h>
#include "cmemory.h"
#include "ucln_in.h"
#include "unicode/uniset.h"
static const int16_t kPersianNumDays[]
= {0,31,62,93,124,155,186,216,246,276,306,336}; // 0-based, for day-in-year
@ -62,6 +65,45 @@ static const int32_t kPersianCalendarLimits[UCAL_FIELD_COUNT][4] = {
{ 0, 0, 11, 11}, // ORDINAL_MONTH
};
namespace { // anonymous
static icu::UnicodeSet *gLeapCorrection = nullptr;
static icu::UInitOnce gCorrectionInitOnce {};
static int32_t gMinCorrection;
} // namespace
U_CDECL_BEGIN
static UBool calendar_persian_cleanup() {
if (gLeapCorrection) {
delete gLeapCorrection;
gLeapCorrection = nullptr;
}
gCorrectionInitOnce.reset();
return true;
}
U_CDECL_END
namespace { // anonymous
static void U_CALLCONV initLeapCorrection() {
static int16_t nonLeapYears[] = {
1502, 1601, 1634, 1667, 1700, 1733, 1766, 1799, 1832, 1865, 1898, 1931, 1964, 1997, 2030, 2059,
2063, 2096, 2129, 2158, 2162, 2191, 2195, 2224, 2228, 2257, 2261, 2290, 2294, 2323, 2327, 2356,
2360, 2389, 2393, 2422, 2426, 2455, 2459, 2488, 2492, 2521, 2525, 2554, 2558, 2587, 2591, 2620,
2624, 2653, 2657, 2686, 2690, 2719, 2723, 2748, 2752, 2756, 2781, 2785, 2789, 2818, 2822, 2847,
2851, 2855, 2880, 2884, 2888, 2913, 2917, 2921, 2946, 2950, 2954, 2979, 2983, 2987,
};
gMinCorrection = nonLeapYears[0];
icu::UnicodeSet prefab;
for (auto year : nonLeapYears) {
prefab.add(year);
}
gLeapCorrection = prefab.cloneAsThawed();
ucln_i18n_registerCleanup(UCLN_I18N_PERSIAN_CALENDAR, calendar_persian_cleanup);
}
const icu::UnicodeSet* getLeapCorrection() {
umtx_initOnce(gCorrectionInitOnce, &initLeapCorrection);
return gLeapCorrection;
}
} // namespace anonymous
U_NAMESPACE_BEGIN
static const int32_t PERSIAN_EPOCH = 1948320;
@ -111,8 +153,15 @@ int32_t PersianCalendar::handleGetLimit(UCalendarDateFields field, ELimitType li
*/
UBool PersianCalendar::isLeapYear(int32_t year)
{
if (year >= gMinCorrection && getLeapCorrection()->contains(year)) {
return false;
}
if (year > gMinCorrection && getLeapCorrection()->contains(year-1)) {
return true;
}
int64_t y = static_cast<int64_t>(year) * 25LL + 11LL;
return (y % 33L < 8);
bool res = (y % 33L < 8);
return res;
}
/**
@ -165,6 +214,15 @@ int32_t PersianCalendar::handleGetYearLength(int32_t extendedYear) const {
// Functions for converting from field values to milliseconds....
//-------------------------------------------------------------------------
static int64_t firstJulianOfYear(int64_t year) {
int64_t julianDay = 365LL * (year - 1LL) + ClockMath::floorDivide(8LL * year + 21, 33);
if (year > gMinCorrection && getLeapCorrection()->contains(year-1)) {
julianDay--;
}
return julianDay;
}
// Return JD of start of given month/year
int64_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool /*useMonth*/, UErrorCode& status) const {
if (U_FAILURE(status)) {
@ -179,7 +237,7 @@ int64_t PersianCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U
}
}
int64_t julianDay = PERSIAN_EPOCH - 1LL + 365LL * (eyear - 1LL) + ClockMath::floorDivide(8LL * eyear + 21, 33);
int64_t julianDay = PERSIAN_EPOCH - 1LL + firstJulianOfYear(eyear);
if (month != 0) {
julianDay += kPersianNumDays[month];
@ -219,6 +277,7 @@ int32_t PersianCalendar::handleGetExtendedYear(UErrorCode& status) {
void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) {
int64_t daysSinceEpoch = julianDay;
daysSinceEpoch -= PERSIAN_EPOCH;
int64_t year = ClockMath::floorDivideInt64(
33LL * daysSinceEpoch + 3LL, 12053LL) + 1LL;
if (year > INT32_MAX || year < INT32_MIN) {
@ -226,11 +285,16 @@ void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status)
return;
}
int64_t farvardin1 = 365LL * (year - 1) + ClockMath::floorDivide(8LL * year + 21, 33);
int64_t farvardin1 = firstJulianOfYear(year);
int32_t dayOfYear = daysSinceEpoch - farvardin1; // 0-based
U_ASSERT(dayOfYear >= 0);
U_ASSERT(dayOfYear < 366);
//
if (dayOfYear == 365 && year >= gMinCorrection && getLeapCorrection()->contains(year)) {
year++;
dayOfYear = 0;
}
int32_t month;
if (dayOfYear < 216) { // Compute 0-based month
month = dayOfYear / 31;
@ -240,11 +304,11 @@ void PersianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status)
U_ASSERT(month >= 0);
U_ASSERT(month < 12);
int32_t dayOfMonth = dayOfYear - kPersianNumDays[month] + 1;
++dayOfYear; // Make it 1-based now
int32_t dayOfMonth = dayOfYear - kPersianNumDays[month];
U_ASSERT(dayOfMonth > 0);
U_ASSERT(dayOfMonth <= 31);
++dayOfYear; // Make it 1-based now
internalSet(UCAL_ERA, 0);
internalSet(UCAL_YEAR, year);

View file

@ -39,6 +39,7 @@ typedef enum ECleanupI18NType {
UCLN_I18N_HEBREW_CALENDAR,
UCLN_I18N_ASTRO_CALENDAR,
UCLN_I18N_DANGI_CALENDAR,
UCLN_I18N_PERSIAN_CALENDAR,
UCLN_I18N_CALENDAR,
UCLN_I18N_TIMEZONEFORMAT,
UCLN_I18N_TZDBTIMEZONENAMES,

View file

@ -86,6 +86,10 @@ void IntlCalendarTest::runIndexedTest( int32_t index, UBool exec, const char* &n
TESTCASE_AUTO(TestJapanese3860);
TESTCASE_AUTO(TestForceGannenNumbering);
TESTCASE_AUTO(TestPersian);
TESTCASE_AUTO(TestPersianJulianDayToYMD);
TESTCASE_AUTO(TestPersianYMDToJulianDay);
TESTCASE_AUTO(TestPersianJan1ToGregorian);
TESTCASE_AUTO(TestGregorianToPersian);
TESTCASE_AUTO(TestPersianFormat);
TESTCASE_AUTO(TestTaiwan);
TESTCASE_AUTO(TestConsistencyGregorian);
@ -896,6 +900,433 @@ void IntlCalendarTest::TestPersian() {
delete grego;
}
// Test data copy from
// https://github.com/unicode-org/icu4x/blob/main/components/calendar/src/persian.rs#L299
static struct PersianTestCase1 {
int32_t rd;
int32_t year;
int32_t month;
int32_t day;
} persianTestCases1[]{
{656786, 1178, 1, 1},
{664224, 1198, 5, 10},
{671401, 1218, 1, 7},
{694799, 1282, 1, 29},
{702806, 1304, 1, 1},
{704424, 1308, 6, 3},
{708842, 1320, 7, 7},
{709409, 1322, 1, 29},
{709580, 1322, 7, 14},
{727274, 1370, 12, 27},
{728714, 1374, 12, 6},
{739330, 1403, 12, 30},
{739331, 1404, 1, 1},
{744313, 1417, 8, 19},
{763436, 1469, 12, 30},
{763437, 1470, 1, 1},
{764652, 1473, 4, 28},
{775123, 1501, 12, 29},
{775488, 1502, 12, 29},
{775487, 1502, 12, 28},
{775488, 1502, 12, 29},
{775489, 1503, 1, 1},
{775490, 1503, 1, 2},
{1317873, 2987, 12, 29},
{1317874, 2988, 1, 1},
{1317875, 2988, 1, 2},
};
void IntlCalendarTest::TestPersianJulianDayToYMD() {
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<Calendar> cal(Calendar::createInstance(TimeZone::createTimeZone("Asia/Tehran"), "fa_IR@calendar=persian", status));
for (const auto &testCase : persianTestCases1) {
status = U_ZERO_ERROR;
int32_t jday = testCase.rd + 1721425;
cal->clear();
cal->set(UCAL_JULIAN_DAY, jday);
int32_t actualYear = cal->get(UCAL_YEAR, status);
int32_t actualMonth = cal->get(UCAL_MONTH, status)+1;
int32_t actualDay = cal->get(UCAL_DAY_OF_MONTH, status);
if (actualYear != testCase.year || actualMonth != testCase.month || actualDay != testCase.day) {
errln(UnicodeString("rd ") + testCase.rd + " = jday " + jday + " -> expect Persian(" +
testCase.year + "/" + testCase.month + "/" + testCase.day + ") " +
"actual Persian(" + actualYear + "/" + actualMonth + "/" + actualDay + ")");
}
}
}
void IntlCalendarTest::TestPersianYMDToJulianDay() {
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<Calendar> cal(Calendar::createInstance(TimeZone::createTimeZone("Asia/Tehran"), "fa_IR@calendar=persian", status));
for (const auto &testCase : persianTestCases1) {
status = U_ZERO_ERROR;
cal->clear();
cal->set(UCAL_YEAR, testCase.year);
cal->set(UCAL_MONTH, testCase.month-1);
cal->set(UCAL_DAY_OF_MONTH, testCase.day);
int32_t actualJday = cal->get(UCAL_JULIAN_DAY, status);
int32_t actualRD = actualJday - 1721425;
if (actualRD != testCase.rd) {
errln(UnicodeString("Persian(") + testCase.year + "/" + testCase.month + "/" + testCase.day + ") => "+
"expect rd " + testCase.rd + " but actual jd: " + actualJday + " = rd " + actualRD);
}
}
}
// Test data copy from
// https://github.com/unicode-org/icu4x/blob/main/components/calendar/src/persian.rs#L534
// From https://calendar.ut.ac.ir/Fa/News/Data/Doc/KabiseShamsi1206-1498-new.pdf
// Plain text version at https://github.com/roozbehp/persiancalendar/blob/main/kabise.txt
static struct PersianTestCase2 {
int32_t pYear;
bool pLeap;
int32_t year;
int32_t month;
int32_t day;
} persianTestCases2[]{
{1206, false, 1827, 3, 22},
{1207, false, 1828, 3, 21},
{1208, false, 1829, 3, 21},
{1209, false, 1830, 3, 21},
{1210, true, 1831, 3, 21},
{1211, false, 1832, 3, 21},
{1212, false, 1833, 3, 21},
{1213, false, 1834, 3, 21},
{1214, true, 1835, 3, 21},
{1215, false, 1836, 3, 21},
{1216, false, 1837, 3, 21},
{1217, false, 1838, 3, 21},
{1218, true, 1839, 3, 21},
{1219, false, 1840, 3, 21},
{1220, false, 1841, 3, 21},
{1221, false, 1842, 3, 21},
{1222, true, 1843, 3, 21},
{1223, false, 1844, 3, 21},
{1224, false, 1845, 3, 21},
{1225, false, 1846, 3, 21},
{1226, true, 1847, 3, 21},
{1227, false, 1848, 3, 21},
{1228, false, 1849, 3, 21},
{1229, false, 1850, 3, 21},
{1230, true, 1851, 3, 21},
{1231, false, 1852, 3, 21},
{1232, false, 1853, 3, 21},
{1233, false, 1854, 3, 21},
{1234, true, 1855, 3, 21},
{1235, false, 1856, 3, 21},
{1236, false, 1857, 3, 21},
{1237, false, 1858, 3, 21},
{1238, true, 1859, 3, 21},
{1239, false, 1860, 3, 21},
{1240, false, 1861, 3, 21},
{1241, false, 1862, 3, 21},
{1242, false, 1863, 3, 21},
{1243, true, 1864, 3, 20},
{1244, false, 1865, 3, 21},
{1245, false, 1866, 3, 21},
{1246, false, 1867, 3, 21},
{1247, true, 1868, 3, 20},
{1248, false, 1869, 3, 21},
{1249, false, 1870, 3, 21},
{1250, false, 1871, 3, 21},
{1251, true, 1872, 3, 20},
{1252, false, 1873, 3, 21},
{1253, false, 1874, 3, 21},
{1254, false, 1875, 3, 21},
{1255, true, 1876, 3, 20},
{1256, false, 1877, 3, 21},
{1257, false, 1878, 3, 21},
{1258, false, 1879, 3, 21},
{1259, true, 1880, 3, 20},
{1260, false, 1881, 3, 21},
{1261, false, 1882, 3, 21},
{1262, false, 1883, 3, 21},
{1263, true, 1884, 3, 20},
{1264, false, 1885, 3, 21},
{1265, false, 1886, 3, 21},
{1266, false, 1887, 3, 21},
{1267, true, 1888, 3, 20},
{1268, false, 1889, 3, 21},
{1269, false, 1890, 3, 21},
{1270, false, 1891, 3, 21},
{1271, true, 1892, 3, 20},
{1272, false, 1893, 3, 21},
{1273, false, 1894, 3, 21},
{1274, false, 1895, 3, 21},
{1275, false, 1896, 3, 20},
{1276, true, 1897, 3, 20},
{1277, false, 1898, 3, 21},
{1278, false, 1899, 3, 21},
{1279, false, 1900, 3, 21},
{1280, true, 1901, 3, 21},
{1281, false, 1902, 3, 22},
{1282, false, 1903, 3, 22},
{1283, false, 1904, 3, 21},
{1284, true, 1905, 3, 21},
{1285, false, 1906, 3, 22},
{1286, false, 1907, 3, 22},
{1287, false, 1908, 3, 21},
{1288, true, 1909, 3, 21},
{1289, false, 1910, 3, 22},
{1290, false, 1911, 3, 22},
{1291, false, 1912, 3, 21},
{1292, true, 1913, 3, 21},
{1293, false, 1914, 3, 22},
{1294, false, 1915, 3, 22},
{1295, false, 1916, 3, 21},
{1296, true, 1917, 3, 21},
{1297, false, 1918, 3, 22},
{1298, false, 1919, 3, 22},
{1299, false, 1920, 3, 21},
{1300, true, 1921, 3, 21},
{1301, false, 1922, 3, 22},
{1302, false, 1923, 3, 22},
{1303, false, 1924, 3, 21},
{1304, true, 1925, 3, 21},
{1305, false, 1926, 3, 22},
{1306, false, 1927, 3, 22},
{1307, false, 1928, 3, 21},
{1308, false, 1929, 3, 21},
{1309, true, 1930, 3, 21},
{1310, false, 1931, 3, 22},
{1311, false, 1932, 3, 21},
{1312, false, 1933, 3, 21},
{1313, true, 1934, 3, 21},
{1314, false, 1935, 3, 22},
{1315, false, 1936, 3, 21},
{1316, false, 1937, 3, 21},
{1317, true, 1938, 3, 21},
{1318, false, 1939, 3, 22},
{1319, false, 1940, 3, 21},
{1320, false, 1941, 3, 21},
{1321, true, 1942, 3, 21},
{1322, false, 1943, 3, 22},
{1323, false, 1944, 3, 21},
{1324, false, 1945, 3, 21},
{1325, true, 1946, 3, 21},
{1326, false, 1947, 3, 22},
{1327, false, 1948, 3, 21},
{1328, false, 1949, 3, 21},
{1329, true, 1950, 3, 21},
{1330, false, 1951, 3, 22},
{1331, false, 1952, 3, 21},
{1332, false, 1953, 3, 21},
{1333, true, 1954, 3, 21},
{1334, false, 1955, 3, 22},
{1335, false, 1956, 3, 21},
{1336, false, 1957, 3, 21},
{1337, true, 1958, 3, 21},
{1338, false, 1959, 3, 22},
{1339, false, 1960, 3, 21},
{1340, false, 1961, 3, 21},
{1341, false, 1962, 3, 21},
{1342, true, 1963, 3, 21},
{1343, false, 1964, 3, 21},
{1344, false, 1965, 3, 21},
{1345, false, 1966, 3, 21},
{1346, true, 1967, 3, 21},
{1347, false, 1968, 3, 21},
{1348, false, 1969, 3, 21},
{1349, false, 1970, 3, 21},
{1350, true, 1971, 3, 21},
{1351, false, 1972, 3, 21},
{1352, false, 1973, 3, 21},
{1353, false, 1974, 3, 21},
{1354, true, 1975, 3, 21},
{1355, false, 1976, 3, 21},
{1356, false, 1977, 3, 21},
{1357, false, 1978, 3, 21},
{1358, true, 1979, 3, 21},
{1359, false, 1980, 3, 21},
{1360, false, 1981, 3, 21},
{1361, false, 1982, 3, 21},
{1362, true, 1983, 3, 21},
{1363, false, 1984, 3, 21},
{1364, false, 1985, 3, 21},
{1365, false, 1986, 3, 21},
{1366, true, 1987, 3, 21},
{1367, false, 1988, 3, 21},
{1368, false, 1989, 3, 21},
{1369, false, 1990, 3, 21},
{1370, true, 1991, 3, 21},
{1371, false, 1992, 3, 21},
{1372, false, 1993, 3, 21},
{1373, false, 1994, 3, 21},
{1374, false, 1995, 3, 21},
{1375, true, 1996, 3, 20},
{1376, false, 1997, 3, 21},
{1377, false, 1998, 3, 21},
{1378, false, 1999, 3, 21},
{1379, true, 2000, 3, 20},
{1380, false, 2001, 3, 21},
{1381, false, 2002, 3, 21},
{1382, false, 2003, 3, 21},
{1383, true, 2004, 3, 20},
{1384, false, 2005, 3, 21},
{1385, false, 2006, 3, 21},
{1386, false, 2007, 3, 21},
{1387, true, 2008, 3, 20},
{1388, false, 2009, 3, 21},
{1389, false, 2010, 3, 21},
{1390, false, 2011, 3, 21},
{1391, true, 2012, 3, 20},
{1392, false, 2013, 3, 21},
{1393, false, 2014, 3, 21},
{1394, false, 2015, 3, 21},
{1395, true, 2016, 3, 20},
{1396, false, 2017, 3, 21},
{1397, false, 2018, 3, 21},
{1398, false, 2019, 3, 21},
{1399, true, 2020, 3, 20},
{1400, false, 2021, 3, 21},
{1401, false, 2022, 3, 21},
{1402, false, 2023, 3, 21},
{1403, true, 2024, 3, 20},
{1404, false, 2025, 3, 21},
{1405, false, 2026, 3, 21},
{1406, false, 2027, 3, 21},
{1407, false, 2028, 3, 20},
{1408, true, 2029, 3, 20},
{1409, false, 2030, 3, 21},
{1410, false, 2031, 3, 21},
{1411, false, 2032, 3, 20},
{1412, true, 2033, 3, 20},
{1413, false, 2034, 3, 21},
{1414, false, 2035, 3, 21},
{1415, false, 2036, 3, 20},
{1416, true, 2037, 3, 20},
{1417, false, 2038, 3, 21},
{1418, false, 2039, 3, 21},
{1419, false, 2040, 3, 20},
{1420, true, 2041, 3, 20},
{1421, false, 2042, 3, 21},
{1422, false, 2043, 3, 21},
{1423, false, 2044, 3, 20},
{1424, true, 2045, 3, 20},
{1425, false, 2046, 3, 21},
{1426, false, 2047, 3, 21},
{1427, false, 2048, 3, 20},
{1428, true, 2049, 3, 20},
{1429, false, 2050, 3, 21},
{1430, false, 2051, 3, 21},
{1431, false, 2052, 3, 20},
{1432, true, 2053, 3, 20},
{1433, false, 2054, 3, 21},
{1434, false, 2055, 3, 21},
{1435, false, 2056, 3, 20},
{1436, true, 2057, 3, 20},
{1437, false, 2058, 3, 21},
{1438, false, 2059, 3, 21},
{1439, false, 2060, 3, 20},
{1440, false, 2061, 3, 20},
{1441, true, 2062, 3, 20},
{1442, false, 2063, 3, 21},
{1443, false, 2064, 3, 20},
{1444, false, 2065, 3, 20},
{1445, true, 2066, 3, 20},
{1446, false, 2067, 3, 21},
{1447, false, 2068, 3, 20},
{1448, false, 2069, 3, 20},
{1449, true, 2070, 3, 20},
{1450, false, 2071, 3, 21},
{1451, false, 2072, 3, 20},
{1452, false, 2073, 3, 20},
{1453, true, 2074, 3, 20},
{1454, false, 2075, 3, 21},
{1455, false, 2076, 3, 20},
{1456, false, 2077, 3, 20},
{1457, true, 2078, 3, 20},
{1458, false, 2079, 3, 21},
{1459, false, 2080, 3, 20},
{1460, false, 2081, 3, 20},
{1461, true, 2082, 3, 20},
{1462, false, 2083, 3, 21},
{1463, false, 2084, 3, 20},
{1464, false, 2085, 3, 20},
{1465, true, 2086, 3, 20},
{1466, false, 2087, 3, 21},
{1467, false, 2088, 3, 20},
{1468, false, 2089, 3, 20},
{1469, true, 2090, 3, 20},
{1470, false, 2091, 3, 21},
{1471, false, 2092, 3, 20},
{1472, false, 2093, 3, 20},
{1473, false, 2094, 3, 20},
{1474, true, 2095, 3, 20},
{1475, false, 2096, 3, 20},
{1476, false, 2097, 3, 20},
{1477, false, 2098, 3, 20},
{1478, true, 2099, 3, 20},
{1479, false, 2100, 3, 21},
{1480, false, 2101, 3, 21},
{1481, false, 2102, 3, 21},
{1482, true, 2103, 3, 21},
{1483, false, 2104, 3, 21},
{1484, false, 2105, 3, 21},
{1485, false, 2106, 3, 21},
{1486, true, 2107, 3, 21},
{1487, false, 2108, 3, 21},
{1488, false, 2109, 3, 21},
{1489, false, 2110, 3, 21},
{1490, true, 2111, 3, 21},
{1491, false, 2112, 3, 21},
{1492, false, 2113, 3, 21},
{1493, false, 2114, 3, 21},
{1494, true, 2115, 3, 21},
{1495, false, 2116, 3, 21},
{1496, false, 2117, 3, 21},
{1497, false, 2118, 3, 21},
{1498, true, 2119, 3, 21},
};
void IntlCalendarTest::TestPersianJan1ToGregorian() {
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<Calendar> gcal(Calendar::createInstance(TimeZone::createTimeZone("Asia/Tehran"), "en", status));
std::unique_ptr<Calendar> cal(Calendar::createInstance(TimeZone::createTimeZone("Asia/Tehran"), "fa_IR@calendar=persian", status));
for (const auto &testCase : persianTestCases2) {
status = U_ZERO_ERROR;
cal->clear();
cal->set(UCAL_YEAR, testCase.pYear);
cal->set(UCAL_MONTH, 0);
cal->set(UCAL_DAY_OF_MONTH, 1);
gcal->setTime(cal->getTime(status), status);
int32_t actualYear = gcal->get(UCAL_YEAR, status);
int32_t actualMonth = gcal->get(UCAL_MONTH, status)+1;
int32_t actualDay = gcal->get(UCAL_DAY_OF_MONTH, status);
if (actualYear != testCase.year || actualMonth != testCase.month || actualDay != testCase.day) {
errln(UnicodeString("Persian(") + testCase.pYear + ", 1, 1) => " +
"expect Gregorian(" + testCase.year + "/" + testCase.month + "/" + testCase.day + ") " +
"actual Gregorian(" + actualYear + "/" + actualMonth + "/" + actualDay + ")");
}
}
}
void IntlCalendarTest::TestGregorianToPersian() {
UErrorCode status = U_ZERO_ERROR;
std::unique_ptr<Calendar> gcal(Calendar::createInstance(TimeZone::createTimeZone("Asia/Tehran"), "en", status));
std::unique_ptr<Calendar> cal(Calendar::createInstance(TimeZone::createTimeZone("Asia/Tehran"), "fa_IR@calendar=persian", status));
for (const auto &testCase : persianTestCases2) {
status = U_ZERO_ERROR;
gcal->clear();
gcal->set(UCAL_YEAR, testCase.year);
gcal->set(UCAL_MONTH, testCase.month-1);
gcal->set(UCAL_DAY_OF_MONTH, testCase.day);
cal->setTime(gcal->getTime(status), status);
int32_t persianYear = cal->get(UCAL_YEAR, status);
int32_t persianMonth = cal->get(UCAL_MONTH, status)+1;
int32_t persianDay = cal->get(UCAL_DAY_OF_MONTH, status);
if (persianYear != testCase.pYear || persianMonth != 1 || persianDay != 1) {
errln(UnicodeString("Gregorian(") + testCase.year + "/" + testCase.month + "/" + testCase.day + ") "+
" => expect Persian(" + testCase.pYear + "/1/1) actual " +
"Persian(" + persianYear + "/" + persianMonth + "/" + persianDay + ")");
}
}
}
void IntlCalendarTest::TestPersianFormat() {
UErrorCode status = U_ZERO_ERROR;
SimpleDateFormat fmt(UnicodeString("MMMM d, yyyy G"), Locale(" en_US@calendar=persian"), status);

View file

@ -41,6 +41,10 @@ public:
void TestForceGannenNumbering();
void TestPersian();
void TestPersianJulianDayToYMD();
void TestPersianYMDToJulianDay();
void TestPersianJan1ToGregorian();
void TestGregorianToPersian();
void TestPersianFormat();
void TestConsistencyGregorian();

View file

@ -10,7 +10,9 @@
package com.ibm.icu.util;
import java.util.Date;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import com.ibm.icu.util.ULocale.Category;
@ -93,6 +95,22 @@ public class PersianCalendar extends Calendar {
private static final int PERSIAN_EPOCH = 1948320;
private static final int NON_LEAP_YEARS[] = {
1502, 1601, 1634, 1667, 1700, 1733, 1766, 1799, 1832, 1865, 1898, 1931, 1964, 1997, 2030, 2059,
2063, 2096, 2129, 2158, 2162, 2191, 2195, 2224, 2228, 2257, 2261, 2290, 2294, 2323, 2327, 2356,
2360, 2389, 2393, 2422, 2426, 2455, 2459, 2488, 2492, 2521, 2525, 2554, 2558, 2587, 2591, 2620,
2624, 2653, 2657, 2686, 2690, 2719, 2723, 2748, 2752, 2756, 2781, 2785, 2789, 2818, 2822, 2847,
2851, 2855, 2880, 2884, 2888, 2913, 2917, 2921, 2946, 2950, 2954, 2979, 2983, 2987,
};
private static Set<Integer> LEAP_CORRECTION = null;
{
Set<Integer> prefab = new HashSet<Integer>(NON_LEAP_YEARS.length);
for (int nonLeap : NON_LEAP_YEARS) {
prefab.add(nonLeap);
}
LEAP_CORRECTION = prefab;
}
//-------------------------------------------------------------------------
// Constructors...
//-------------------------------------------------------------------------
@ -312,10 +330,15 @@ public class PersianCalendar extends Calendar {
*/
private final static boolean isLeapYear(int year)
{
if (year >= NON_LEAP_YEARS[0] && LEAP_CORRECTION.contains(year)) {
return false;
}
if (year > NON_LEAP_YEARS[0] && LEAP_CORRECTION.contains(year-1)) {
return true;
}
int[] remainder = new int[1];
floorDivide(25 * year + 11, 33, remainder);
return remainder[0] < 8;
}
//----------------------------------------------------------------------
@ -375,12 +398,12 @@ public class PersianCalendar extends Calendar {
month = rem[0];
}
int julianDay = PERSIAN_EPOCH - 1 + 365 * (eyear - 1) + floorDivide(8 * eyear + 21, 33);
long julianDay = PERSIAN_EPOCH - 1L + firstJulianOfYear(eyear);
if (month != 0) {
julianDay += MONTH_COUNT[month][2];
}
return julianDay;
}
return (int)julianDay;
}
//-------------------------------------------------------------------------
// Functions for converting from milliseconds to field values
@ -401,6 +424,13 @@ public class PersianCalendar extends Calendar {
return year;
}
private static long firstJulianOfYear(int year) {
long julianDay = 365L * (year - 1L) + floorDivide(8L * year + 21, 33L);
if (year > NON_LEAP_YEARS[0] && LEAP_CORRECTION.contains(year-1)) {
julianDay--;
}
return julianDay;
}
/**
* Override Calendar to compute several fields specific to the Persian
* calendar system. These are:
@ -425,16 +455,22 @@ public class PersianCalendar extends Calendar {
long daysSinceEpoch = julianDay - PERSIAN_EPOCH;
year = 1 + (int) floorDivide(33 * daysSinceEpoch + 3, 12053);
long farvardin1 = 365L * (year - 1L) + floorDivide(8L * year + 21, 33L);
long farvardin1 = firstJulianOfYear(year);
dayOfYear = (int)(daysSinceEpoch - farvardin1); // 0-based
if (dayOfYear == 365 && year >= NON_LEAP_YEARS[0] && LEAP_CORRECTION.contains(year)) {
year++;
dayOfYear = 0;
}
if (dayOfYear < 216) { // Compute 0-based month
month = dayOfYear / 31;
} else {
month = (dayOfYear - 6) / 30;
}
dayOfMonth = dayOfYear - MONTH_COUNT[month][2] + 1;
++dayOfYear; // Make it 1-based now
dayOfMonth = dayOfYear - MONTH_COUNT[month][2];
internalSet(ERA, 0);
internalSet(YEAR, year);
internalSet(EXTENDED_YEAR, year);

View file

@ -15,6 +15,7 @@ import org.junit.runners.JUnit4;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.PersianCalendar;
import com.ibm.icu.util.TimeZone;
import com.ibm.icu.util.ULocale;
@RunWith(JUnit4.class)
@ -143,4 +144,439 @@ public class PersianTest extends CalendarTestFmwk {
new StubCalendar();
}
// Test data copy from
// https://github.com/unicode-org/icu4x/blob/main/components/calendar/src/persian.rs#L299
final int[] PERSIAN_TEST_CASE_1 = {
// rd, year, month, day
656786, 1178, 1, 1,
664224, 1198, 5, 10,
671401, 1218, 1, 7,
694799, 1282, 1, 29,
702806, 1304, 1, 1,
704424, 1308, 6, 3,
708842, 1320, 7, 7,
709409, 1322, 1, 29,
709580, 1322, 7, 14,
727274, 1370, 12, 27,
728714, 1374, 12, 6,
739330, 1403, 12, 30,
739331, 1404, 1, 1,
744313, 1417, 8, 19,
763436, 1469, 12, 30,
763437, 1470, 1, 1,
764652, 1473, 4, 28,
775123, 1501, 12, 29,
775488, 1502, 12, 29,
775487, 1502, 12, 28,
775488, 1502, 12, 29,
775489, 1503, 1, 1,
775490, 1503, 1, 2,
1317873, 2987, 12, 29,
1317874, 2988, 1, 1,
1317875, 2988, 1, 2,
};
@Test
public void TestPersianJulianDayToYMD() {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tehran"),
new ULocale("fa_IR@calendar=persian"));
for (int i = 0; i < PERSIAN_TEST_CASE_1.length;) {
int rd = PERSIAN_TEST_CASE_1[i++];
int year = PERSIAN_TEST_CASE_1[i++];
int month = PERSIAN_TEST_CASE_1[i++];
int day = PERSIAN_TEST_CASE_1[i++];
int jday = rd + 1721425;
cal.clear();
cal.set(Calendar.JULIAN_DAY, jday);
int actualYear = cal.get(Calendar.YEAR);
int actualMonth = cal.get(Calendar.MONTH)+1;
int actualDay = cal.get(Calendar.DAY_OF_MONTH);
if (actualYear != year || actualMonth != month || actualDay != day) {
errln("Fail: rd " + rd + " = jday " + jday + " -> expect Persian(" +
year + "/" + month + "/" + day + ") " +
"actual Persian(" + actualYear + "/" + actualMonth + "/" + actualDay + ")");
}
}
}
@Test
public void TestPersianYMDToJulianDay() {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tehran"),
new ULocale("fa_IR@calendar=persian"));
for (int i = 0; i < PERSIAN_TEST_CASE_1.length;) {
int rd = PERSIAN_TEST_CASE_1[i++];
int year = PERSIAN_TEST_CASE_1[i++];
int month = PERSIAN_TEST_CASE_1[i++];
int day = PERSIAN_TEST_CASE_1[i++];
int jday = rd + 1721425;
cal.clear();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month-1);
cal.set(Calendar.DAY_OF_MONTH, day);
int actualJday = cal.get(Calendar.JULIAN_DAY);
int actualRD = actualJday - 1721425;
if (actualRD != rd) {
errln("Fail: Persian(" + year + "/" + month + "/" + day + ") => "+
"expect rd " + rd + " but actual jd: " + actualJday + " = rd " + actualRD);
}
}
}
// Test data copy from
// https://github.com/unicode-org/icu4x/blob/main/components/calendar/src/persian.rs#L534
// From https://calendar.ut.ac.ir/Fa/News/Data/Doc/KabiseShamsi1206-1498-new.pdf
// Plain text version at https://github.com/roozbehp/persiancalendar/blob/main/kabise.txt
final int[] PERSIAN_TEST_CASE_2 = {
// pYear, pLeap, year, month, day
1206, 0, 1827, 3, 22,
1207, 0, 1828, 3, 21,
1208, 0, 1829, 3, 21,
1209, 0, 1830, 3, 21,
1210, 1, 1831, 3, 21,
1211, 0, 1832, 3, 21,
1212, 0, 1833, 3, 21,
1213, 0, 1834, 3, 21,
1214, 1, 1835, 3, 21,
1215, 0, 1836, 3, 21,
1216, 0, 1837, 3, 21,
1217, 0, 1838, 3, 21,
1218, 1, 1839, 3, 21,
1219, 0, 1840, 3, 21,
1220, 0, 1841, 3, 21,
1221, 0, 1842, 3, 21,
1222, 1, 1843, 3, 21,
1223, 0, 1844, 3, 21,
1224, 0, 1845, 3, 21,
1225, 0, 1846, 3, 21,
1226, 1, 1847, 3, 21,
1227, 0, 1848, 3, 21,
1228, 0, 1849, 3, 21,
1229, 0, 1850, 3, 21,
1230, 1, 1851, 3, 21,
1231, 0, 1852, 3, 21,
1232, 0, 1853, 3, 21,
1233, 0, 1854, 3, 21,
1234, 1, 1855, 3, 21,
1235, 0, 1856, 3, 21,
1236, 0, 1857, 3, 21,
1237, 0, 1858, 3, 21,
1238, 1, 1859, 3, 21,
1239, 0, 1860, 3, 21,
1240, 0, 1861, 3, 21,
1241, 0, 1862, 3, 21,
1242, 0, 1863, 3, 21,
1243, 1, 1864, 3, 20,
1244, 0, 1865, 3, 21,
1245, 0, 1866, 3, 21,
1246, 0, 1867, 3, 21,
1247, 1, 1868, 3, 20,
1248, 0, 1869, 3, 21,
1249, 0, 1870, 3, 21,
1250, 0, 1871, 3, 21,
1251, 1, 1872, 3, 20,
1252, 0, 1873, 3, 21,
1253, 0, 1874, 3, 21,
1254, 0, 1875, 3, 21,
1255, 1, 1876, 3, 20,
1256, 0, 1877, 3, 21,
1257, 0, 1878, 3, 21,
1258, 0, 1879, 3, 21,
1259, 1, 1880, 3, 20,
1260, 0, 1881, 3, 21,
1261, 0, 1882, 3, 21,
1262, 0, 1883, 3, 21,
1263, 1, 1884, 3, 20,
1264, 0, 1885, 3, 21,
1265, 0, 1886, 3, 21,
1266, 0, 1887, 3, 21,
1267, 1, 1888, 3, 20,
1268, 0, 1889, 3, 21,
1269, 0, 1890, 3, 21,
1270, 0, 1891, 3, 21,
1271, 1, 1892, 3, 20,
1272, 0, 1893, 3, 21,
1273, 0, 1894, 3, 21,
1274, 0, 1895, 3, 21,
1275, 0, 1896, 3, 20,
1276, 1, 1897, 3, 20,
1277, 0, 1898, 3, 21,
1278, 0, 1899, 3, 21,
1279, 0, 1900, 3, 21,
1280, 1, 1901, 3, 21,
1281, 0, 1902, 3, 22,
1282, 0, 1903, 3, 22,
1283, 0, 1904, 3, 21,
1284, 1, 1905, 3, 21,
1285, 0, 1906, 3, 22,
1286, 0, 1907, 3, 22,
1287, 0, 1908, 3, 21,
1288, 1, 1909, 3, 21,
1289, 0, 1910, 3, 22,
1290, 0, 1911, 3, 22,
1291, 0, 1912, 3, 21,
1292, 1, 1913, 3, 21,
1293, 0, 1914, 3, 22,
1294, 0, 1915, 3, 22,
1295, 0, 1916, 3, 21,
1296, 1, 1917, 3, 21,
1297, 0, 1918, 3, 22,
1298, 0, 1919, 3, 22,
1299, 0, 1920, 3, 21,
1300, 1, 1921, 3, 21,
1301, 0, 1922, 3, 22,
1302, 0, 1923, 3, 22,
1303, 0, 1924, 3, 21,
1304, 1, 1925, 3, 21,
1305, 0, 1926, 3, 22,
1306, 0, 1927, 3, 22,
1307, 0, 1928, 3, 21,
1308, 0, 1929, 3, 21,
1309, 1, 1930, 3, 21,
1310, 0, 1931, 3, 22,
1311, 0, 1932, 3, 21,
1312, 0, 1933, 3, 21,
1313, 1, 1934, 3, 21,
1314, 0, 1935, 3, 22,
1315, 0, 1936, 3, 21,
1316, 0, 1937, 3, 21,
1317, 1, 1938, 3, 21,
1318, 0, 1939, 3, 22,
1319, 0, 1940, 3, 21,
1320, 0, 1941, 3, 21,
1321, 1, 1942, 3, 21,
1322, 0, 1943, 3, 22,
1323, 0, 1944, 3, 21,
1324, 0, 1945, 3, 21,
1325, 1, 1946, 3, 21,
1326, 0, 1947, 3, 22,
1327, 0, 1948, 3, 21,
1328, 0, 1949, 3, 21,
1329, 1, 1950, 3, 21,
1330, 0, 1951, 3, 22,
1331, 0, 1952, 3, 21,
1332, 0, 1953, 3, 21,
1333, 1, 1954, 3, 21,
1334, 0, 1955, 3, 22,
1335, 0, 1956, 3, 21,
1336, 0, 1957, 3, 21,
1337, 1, 1958, 3, 21,
1338, 0, 1959, 3, 22,
1339, 0, 1960, 3, 21,
1340, 0, 1961, 3, 21,
1341, 0, 1962, 3, 21,
1342, 1, 1963, 3, 21,
1343, 0, 1964, 3, 21,
1344, 0, 1965, 3, 21,
1345, 0, 1966, 3, 21,
1346, 1, 1967, 3, 21,
1347, 0, 1968, 3, 21,
1348, 0, 1969, 3, 21,
1349, 0, 1970, 3, 21,
1350, 1, 1971, 3, 21,
1351, 0, 1972, 3, 21,
1352, 0, 1973, 3, 21,
1353, 0, 1974, 3, 21,
1354, 1, 1975, 3, 21,
1355, 0, 1976, 3, 21,
1356, 0, 1977, 3, 21,
1357, 0, 1978, 3, 21,
1358, 1, 1979, 3, 21,
1359, 0, 1980, 3, 21,
1360, 0, 1981, 3, 21,
1361, 0, 1982, 3, 21,
1362, 1, 1983, 3, 21,
1363, 0, 1984, 3, 21,
1364, 0, 1985, 3, 21,
1365, 0, 1986, 3, 21,
1366, 1, 1987, 3, 21,
1367, 0, 1988, 3, 21,
1368, 0, 1989, 3, 21,
1369, 0, 1990, 3, 21,
1370, 1, 1991, 3, 21,
1371, 0, 1992, 3, 21,
1372, 0, 1993, 3, 21,
1373, 0, 1994, 3, 21,
1374, 0, 1995, 3, 21,
1375, 1, 1996, 3, 20,
1376, 0, 1997, 3, 21,
1377, 0, 1998, 3, 21,
1378, 0, 1999, 3, 21,
1379, 1, 2000, 3, 20,
1380, 0, 2001, 3, 21,
1381, 0, 2002, 3, 21,
1382, 0, 2003, 3, 21,
1383, 1, 2004, 3, 20,
1384, 0, 2005, 3, 21,
1385, 0, 2006, 3, 21,
1386, 0, 2007, 3, 21,
1387, 1, 2008, 3, 20,
1388, 0, 2009, 3, 21,
1389, 0, 2010, 3, 21,
1390, 0, 2011, 3, 21,
1391, 1, 2012, 3, 20,
1392, 0, 2013, 3, 21,
1393, 0, 2014, 3, 21,
1394, 0, 2015, 3, 21,
1395, 1, 2016, 3, 20,
1396, 0, 2017, 3, 21,
1397, 0, 2018, 3, 21,
1398, 0, 2019, 3, 21,
1399, 1, 2020, 3, 20,
1400, 0, 2021, 3, 21,
1401, 0, 2022, 3, 21,
1402, 0, 2023, 3, 21,
1403, 1, 2024, 3, 20,
1404, 0, 2025, 3, 21,
1405, 0, 2026, 3, 21,
1406, 0, 2027, 3, 21,
1407, 0, 2028, 3, 20,
1408, 1, 2029, 3, 20,
1409, 0, 2030, 3, 21,
1410, 0, 2031, 3, 21,
1411, 0, 2032, 3, 20,
1412, 1, 2033, 3, 20,
1413, 0, 2034, 3, 21,
1414, 0, 2035, 3, 21,
1415, 0, 2036, 3, 20,
1416, 1, 2037, 3, 20,
1417, 0, 2038, 3, 21,
1418, 0, 2039, 3, 21,
1419, 0, 2040, 3, 20,
1420, 1, 2041, 3, 20,
1421, 0, 2042, 3, 21,
1422, 0, 2043, 3, 21,
1423, 0, 2044, 3, 20,
1424, 1, 2045, 3, 20,
1425, 0, 2046, 3, 21,
1426, 0, 2047, 3, 21,
1427, 0, 2048, 3, 20,
1428, 1, 2049, 3, 20,
1429, 0, 2050, 3, 21,
1430, 0, 2051, 3, 21,
1431, 0, 2052, 3, 20,
1432, 1, 2053, 3, 20,
1433, 0, 2054, 3, 21,
1434, 0, 2055, 3, 21,
1435, 0, 2056, 3, 20,
1436, 1, 2057, 3, 20,
1437, 0, 2058, 3, 21,
1438, 0, 2059, 3, 21,
1439, 0, 2060, 3, 20,
1440, 0, 2061, 3, 20,
1441, 1, 2062, 3, 20,
1442, 0, 2063, 3, 21,
1443, 0, 2064, 3, 20,
1444, 0, 2065, 3, 20,
1445, 1, 2066, 3, 20,
1446, 0, 2067, 3, 21,
1447, 0, 2068, 3, 20,
1448, 0, 2069, 3, 20,
1449, 1, 2070, 3, 20,
1450, 0, 2071, 3, 21,
1451, 0, 2072, 3, 20,
1452, 0, 2073, 3, 20,
1453, 1, 2074, 3, 20,
1454, 0, 2075, 3, 21,
1455, 0, 2076, 3, 20,
1456, 0, 2077, 3, 20,
1457, 1, 2078, 3, 20,
1458, 0, 2079, 3, 21,
1459, 0, 2080, 3, 20,
1460, 0, 2081, 3, 20,
1461, 1, 2082, 3, 20,
1462, 0, 2083, 3, 21,
1463, 0, 2084, 3, 20,
1464, 0, 2085, 3, 20,
1465, 1, 2086, 3, 20,
1466, 0, 2087, 3, 21,
1467, 0, 2088, 3, 20,
1468, 0, 2089, 3, 20,
1469, 1, 2090, 3, 20,
1470, 0, 2091, 3, 21,
1471, 0, 2092, 3, 20,
1472, 0, 2093, 3, 20,
1473, 0, 2094, 3, 20,
1474, 1, 2095, 3, 20,
1475, 0, 2096, 3, 20,
1476, 0, 2097, 3, 20,
1477, 0, 2098, 3, 20,
1478, 1, 2099, 3, 20,
1479, 0, 2100, 3, 21,
1480, 0, 2101, 3, 21,
1481, 0, 2102, 3, 21,
1482, 1, 2103, 3, 21,
1483, 0, 2104, 3, 21,
1484, 0, 2105, 3, 21,
1485, 0, 2106, 3, 21,
1486, 1, 2107, 3, 21,
1487, 0, 2108, 3, 21,
1488, 0, 2109, 3, 21,
1489, 0, 2110, 3, 21,
1490, 1, 2111, 3, 21,
1491, 0, 2112, 3, 21,
1492, 0, 2113, 3, 21,
1493, 0, 2114, 3, 21,
1494, 1, 2115, 3, 21,
1495, 0, 2116, 3, 21,
1496, 0, 2117, 3, 21,
1497, 0, 2118, 3, 21,
1498, 1, 2119, 3, 21,
};
@Test
public void TestPersianJan1ToGregorian() {
Calendar gcal = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tehran"),
new ULocale("en"));
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tehran"),
new ULocale("fa_IR@calendar=persian"));
for (int i = 0; i < PERSIAN_TEST_CASE_2.length;) {
int pYear = PERSIAN_TEST_CASE_2[i++];
boolean pLeap = PERSIAN_TEST_CASE_2[i++] != 0;
int year = PERSIAN_TEST_CASE_2[i++];
int month = PERSIAN_TEST_CASE_2[i++];
int day = PERSIAN_TEST_CASE_2[i++];
cal.clear();
cal.set(Calendar.YEAR, pYear);
cal.set(Calendar.MONTH, 0);
cal.set(Calendar.DAY_OF_MONTH, 1);
gcal.setTime(cal.getTime());
int actualYear = gcal.get(Calendar.YEAR);
int actualMonth = gcal.get(Calendar.MONTH)+1;
int actualDay = gcal.get(Calendar.DAY_OF_MONTH);
if (actualYear != year || actualMonth != month || actualDay != day) {
errln("Fail: Persian(" + pYear + ", 1, 1) => " +
"expect Gregorian(" + year + "/" + month + "/" + day + ") " +
"actual Gregorian(" + actualYear + "/" + actualMonth + "/" + actualDay + ")");
}
}
}
@Test
public void TestGregorianToPersian() {
Calendar gcal = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tehran"),
new ULocale("en"));
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tehran"),
new ULocale("fa_IR@calendar=persian"));
for (int i = 0; i < PERSIAN_TEST_CASE_2.length;) {
int pYear = PERSIAN_TEST_CASE_2[i++];
boolean pLeap = PERSIAN_TEST_CASE_2[i++] != 0;
int year = PERSIAN_TEST_CASE_2[i++];
int month = PERSIAN_TEST_CASE_2[i++];
int day = PERSIAN_TEST_CASE_2[i++];
gcal.clear();
gcal.set(Calendar.YEAR, year);
gcal.set(Calendar.MONTH, month-1);
gcal.set(Calendar.DAY_OF_MONTH, day);
cal.setTime(gcal.getTime());
int persianYear = cal.get(Calendar.YEAR);
int persianMonth = cal.get(Calendar.MONTH)+1;
int persianDay = cal.get(Calendar.DAY_OF_MONTH);
if (persianYear != pYear || persianMonth != 1 || persianDay != 1) {
errln("Fail: Gregorian(" + year + "/" + month + "/" + day + ") "+
" => expect Persian(" + pYear + "/1/1) actual " +
"Persian(" + persianYear + "/" + persianMonth + "/" + persianDay + ")");
}
}
}
}