Background
Recently achieved a project, need to convert the lunar calendar and the solar calendar, the Internet to find a lot of information, found a lot of is not allowed, but gave me the reference value
Algorithm
To borrow Baidu Encyclopedia:
Gregorian calendar
Solar calendar, also known as the Gregorian calendar, is based on the cycle of the Earth's orbit around the sun.
Solar Calendar's calendar year is approximately equal to the annual return, 12 months a year, this "month", in fact, is unrelated to lunar month.
Solar calendar month, date and the sun in the position of the ecliptic better match, according to the date of the Gregorian calendar, in a year can be clearly seen in the four seasons cold and warm changes, but in each month, see the Moon's Shuo, Wang, two strings.
Now the world's Gregorian calendar is a Gregorian calendar, common year 365 days, leap year 366 days, every four years a leap, every hundred young leap, to the No. 400 year again, that is, every 400 years there are 97 leap years. The average length of the Gregorian calendar year is only 26 seconds from the annual regression, and it will take 3,300 years to accumulate.
Lunar calendar
Sigi The history of the lunar calendar, its calculation method is: The Etheric Yin Circle missing a week for January, lasted 29th 12 hours 44 minutes 2.8 seconds, the lunar circle 12 weeks is a year, lasted 354 days 8 hours 48 minutes 33.6 seconds. In each of the 12 months of each year, the 6 singular months (i.e. 1, 3, 5, 7, 9, November) are "Dajian", 30 days per month, 6 even-numbered months (2, 4, 6, 8, 10, December) for "small Build", 29 days per month, and in a leap year, December Otsuki to 30 days. The calendar is a 30-year period, each of the 2nd, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29, a total of 11 years for a leap year, do not set a leap month, and in the end of December a leap day, leap years for 355 days, and 19 for common year, 354 days per year. Therefore, the average annual is 354 days and 8 hours 48 minutes. According to the actual days of the calendar year, 21 hours and 1 minutes less than the year of reunification, and the product of 2.7 regression year January, the product 32.6 regression year difference of one year. The calendar calculates day and night, with sunset as the beginning of the day, to the day after sunset, usually referred to as a night before, that is, the night in front, and the days in the back, forming one. Sigi history every September (Ramadan) is the month of fasting for Islam, and the commencement of this month is determined by the presence of the new moon, in addition to calculations. That is, on August 29, the day of observation, such as the New Moon, the second day is September 1, before the dawn of fasting, August is still small, if no new moon, the third day is September 1, August becomes "Dajian". By the evening of September 29, also need to look at the moon, such as See Crescent, the next day is October 1, that is, Eid al-Fitr day, so that September into a "small build", if not seen New moon, fasting must be extended another day, September is "Dajian". December (Zuller Hige) is the date of Hajj, December 10 is the Eid al-Adha day. The week of the calendar uses the Sunday Law of seven (day, month, fire, water, wood, gold, earth) to remember the day. Every week Jin Yu is the "main hemp day", Muslims on this day hold "gathering ceremony".
Ideas
Borrowing: https://blog.csdn.net/hsd2012 ... Here's the explanation: To calculate the day of the lunar calendar for a given time, we need to find a reference time and then calculate the time later with that reference time. First calculate the number of days between the current time and the reference time, and then calculate the day of the year in which the current time corresponds by finding the number of days of the lunar calendar, and finally calculating which date belongs to that month.
Calculate Zodiac Zodiac
The current practice is the lunar year-1900 + 36 and then divided by 12 to get the remainder, the number of the Zodiac Zodiac, as for why this calculation, I do not understand, the internet also searched a lot of also did not say clearly, here Reference article: https://juejin.im/entry/59904 ...
private function yearShengXiao ($ lunarYear)
{
// TODO As to why this is done, I didn't figure it out
return self :: SHENG_XIAO [($ lunarYear-self :: MIN_YEAR + 36)% 12]; // year of the zodiac
}
Year-end Branch Algorithm
The method found on the Internet is to use the year of the year to calculate, but it is not correct, and then I replaced the lunar calendar with the calendar of Baidu. I didn't figure it out, but I can figure it out. , The remainder of division is 10, the base is changed to 12, and the remainder is the year of the Earth branch (if the remainder is 0, the largest serial number is taken)
private function yearGanZhi ($ lunarYear)
{
// The number of years is reduced by three first. The remainder is divided by 10, and the base is divided by 12. The remainder is the year of the Earthly Branch. (If the remainder is 0, the largest serial number is used.)
$ yJiShu = $ lunarYear-3;
$ yTianGan = ($ yJiShu% 10 == 0)? 10: $ yJiShu% 10;
$ yDiZhi = ($ yJiShu% 12 == 0)? 10: $ yJiShu% 12;
$ yGanZhi = self :: TIAN_GAN [$ yTianGan-1]. self :: DI_ZHI [$ yDiZhi-1]; // // Since it starts from 0, subtract one here
return $ yGanZhi;
}
Dry branch algorithm
I searched the Internet, and I didn't find a good way to implement it.
Day-to-day algorithm
Found online:
G = 4C + [C / 4] + 5y + [y / 4] + [3 * (M + 1) / 5] + d-3
Z = 8C + [C / 4] + 5y + [y / 4] + [3 * (M + 1) / 5] + d + 7 + i
Where C is the century minus one, y is the last two digits of the year, M is the month, and d is the number of days. January and February are counted as the previous year's 13 and 14 months. Odd months i = 0 and even months i = 6. The remainder of G divided by 10 is Tiangan, and the remainder of Z divided by 12 is Earthly Branch.
But it ’s not right, let me know if you have trouble
PHP implementation complete code
<? php
/ **
* Conversion of solar and new calendars
* /
class SolarLunar {
// minimum year
const MIN_YEAR = 1900;
// maximum year
const MAX_YEAR = 2100;
// start date
const START_DATE_STR = "1900-01-30";
const CHINESE_NUMBER = ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", " twelve"];
const CHINESE_NUMBER_SPECIAL = ["zheng", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "winter", "wax "];
const TIAN_GAN = ["A", "B", "B", "Ding", "pent", "Ji", "Geng", "Xin", "Ren", "Dec" ";
const DI_ZHI = ["子", "ugly", "yin", "卯", "chen", "巳", "noon", "wei", "shen", "酉", "戌", "hai "];
const SHENG_XIAO = ["Rat", "Bull", "Tiger", "Rabbit", "Dragon", "Snake", "Horse", "Sheep", "Monkey", "Dog", "Pig "];
// Data for the lunar year
const LUNAR_INFO = [
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977, // 1910-1919
0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970, // 1920-1929
0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950, // 1930-1939
0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557, // 1940-1949
0x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0, // 1950-1959
0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0, // 1960-1969
0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6, // 1970-1979
0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a, 0x06a50,0x06d40,0x0af46,0x0ab60,0x09570, // 1980-1989
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930, // 2010-2019
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
0x05aa0,0x076a3,0x096d0,0x04bd7,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45, // 2030-2039
0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0, // 2040-2049
0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50, 0x06b20,0x1a6c4,0x0aae0, // 2050-2059
0x0a2e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4, // 2060-2069
0x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0, // 2070-2079
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
0x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a2d0,0x0d150,0x0f252, // 2090-2099
0x0d520];
/ **
* Lunar Calendar to Solar Calendar
* @param string $ date
* @param bool $ leapMonthFlag
* @return string
* /
public function lunarToSolar ($ date = "", $ leapMonthFlag = false) {
try {
$ dateTime = new \ DateTime ($ date);
} catch (\ Exception $ exception) {
return "";
}
$ lunarYear = $ dateTime-> format ("Y");
$ lunarMonth = $ dateTime-> format ("n");
$ lunarDay = $ dateTime-> format ("j");
// check if it is legal
if (! $ this-> checkLunarDate ($ lunarYear, $ lunarMonth, $ lunarDay, $ leapMonthFlag)) {
return "";
}
$ offset = 0;
for ($ i = self :: MIN_YEAR; $ i <$ lunarYear; $ i ++) {
$ yearDaysCount = $ this-> getYearDays ($ i); // find the number of days in the lunar calendar
$ offset + = $ yearDaysCount;
}
// Calculate the months of the year
$ leapMonth = $ this-> getLeapMonth ($ lunarYear);
if ($ leapMonthFlag && $ leapMonth! = $ lunarMonth) {
// The leap month sign you entered is incorrect
return "";
}
if ($ leapMonth == 0 || ($ lunarMonth <$ leapMonth) || ($ lunarMonth == $ leapMonth &&! $ leapMonthFlag)) {
for ($ i = 1; $ i <$ lunarMonth; $ i ++) {
$ tempMonthDaysCount = $ this-> getMonthDays ($ lunarYear, $ i);
$ offset + = $ tempMonthDaysCount;
}
// check if the date is greater than the maximum day
if ($ lunarDay> $ this-> getMonthDays ($ lunarYear, $ lunarMonth)) {
// illegal lunar date
return "";
}
$ offset + = intval ($ lunarDay); // plus the number of days in the month
} else {// There is a leap month, and the month is later than or equal to the leap month
for ($ i = 1; $ i <$ lunarMonth; $ i ++) {
$ tempMonthDaysCount = $ this-> getMonthDays ($ lunarYear, $ i);
$ offset + = $ tempMonthDaysCount;
}
if ($ lunarMonth> $ leapMonth) {
$ temp = $ this-> getLeapMonthDays ($ lunarYear); // calculate the number of leap month days
$ offset + = $ temp; // plus days in leap month
if ($ lunarDay> $ this-> getMonthDays ($ lunarYear, $ lunarMonth)) {
// illegal lunar date
return "";
}
$ offset + = intval ($ lunarDay);
} else {// If you need to calculate a leap month, you should first add the number of days in the ordinary month corresponding to the leap month
// Calculate the month as a leap month
$ temp = $ this-> getMonthDays ($ lunarYear, $ lunarMonth); // calculate the number of non-leap month days
$ offset + = $ temp;
if ($ lunarDay> $ this-> getLeapMonthDays ($ lunarYear)) {
// illegal lunar date
return "";
}
$ offset + = intval ($ lunarDay);
}
}
try {
$ newDateTime = new \ DateTime (self :: START_DATE_STR);
} catch (\ Exception $ exception) {
return "";
}
$ sumH = 24 * $ offset;
$ newDateTime-> add (new \ DateInterval ('PT'. $ sumH. 'H'));
return $ newDateTime-> format ("Y-m-d");
}
/ **
* Convert solar calendar to Chinese calendar
* @param string $ date
* @return string
* /
public function solarToChineseLunar ($ date)
{
$ dateTime = new \ DateTime ($ date);
list ($ lunarYear, $ lunarMonth, $ lunarDay, $ leapMonth, $ leapMonthFlag) = $ this-> calculateLunar ($ dateTime);
$ result = "";
if ($ leapMonthFlag && $ lunarMonth == $ leapMonth) {
$ result. = "闰";
}
$ solarYear = (int) $ dateTime-> format ("Y");
$ solarMoth = (int) $ dateTime-> format ("n");
$ solarDay = (int) $ dateTime-> format ("j");
$ result. = self :: CHINESE_NUMBER_SPECIAL [$ lunarMonth-1]. "month";
$ result. = $ this-> chineseDayString ($ lunarDay). "日";
// year of the zodiac
$ yGanZhi = $ this-> yearGanZhi ($ lunarYear);
// Zodiac signs
$ yShengXiao = $ this-> yearShengXiao ($ lunarYear);
$ result. = ",". $ yGanZhi. "year [". $ yShengXiao. 'year]';
// The zodiac of the month
$ mTianGanDiZhi = $ this-> mothGanZhi ($ solarYear, $ solarMoth, $ solarDay);
$ result. = ",". $ mTianGanDiZhi. 'month';
// day's zodiac
$ dTianGanDiZhi = $ this-> dayGanZhi ($ solarYear, $ solarMoth, $ solarDay);
$ result. = ",". $ dTianGanDiZhi. 'Day';
return $ result;
}
/ **
* Conversion of solar calendar to simple Chinese calendar
* @param string $ date
* @return string
* /
public function solarToSimpleLunar ($ date)
{
$ dateTime = new \ DateTime ($ date);
list ($ lunarYear, $ lunarMonth, $ lunarDay, $ leapMonth, $ leapMonthFlag) = $ this-> calculateLunar ($ dateTime);
$ result = $ lunarYear. "year";
if ($ leapMonthFlag && $ lunarMonth == $ leapMonth) {
$ result. = "闰";
}
if ($ lunarMonth <10) {
$ result. = "0". $ lunarMonth. "month";
} else {
$ result. = $ lunarMonth. "month";
}
if ($ lunarDay <10) {
$ result. = "0". $ lunarDay. "日";
} else {
$ result. = $ lunarDay. "日";
}
return $ result;
}
/ **
* The solar calendar turns into a normal lunar calendar
* @param string $ date
* @param bool $ isLeapMonth is a leap month
* @return string
* /
public function solarToLunar ($ date, & $ isLeapMonth = false)
{
$ dateTime = new \ DateTime ($ date);
list ($ lunarYear, $ lunarMonth, $ lunarDay, $ leapMonth, $ leapMonthFlag) = $ this-> calculateLunar ($ dateTime);
$ result = $ lunarYear. "-";
if ($ lunarMonth <10) {
$ result. = "0". $ lunarMonth. "-";
} else {
$ result. = $ lunarMonth. "-";
}
if ($ lunarDay <10) {
$ result. = "0". $ lunarDay;
} else {
$ result. = $ lunarDay;
}
$ isLeapMonth = false;
if ($ leapMonthFlag && $ lunarMonth == $ leapMonth) {
$ isLeapMonth = true;
}
return $ result;
}
/ **
* Calculate the lunar calendar for the current date
* @param \ DateTime $ dateTime
* @return array
* /
private function calculateLunar ($ dateTime)
{
$ i = 0;
$ temp = 0;
$ leapMonthFlag = false;
$ isLeapYear = false;
$ startDate = new \ DateTime (self :: START_DATE_STR);
$ offset = $ this-> daysBwteen ($ dateTime, $ startDate);
for ($ i = self :: MIN_YEAR; $ i <self :: MAX_YEAR; $ i ++) {
$ temp = $ this-> getYearDays ($ i); // Find the number of days in the lunar year
if ($ offset-$ temp <1) {
break;
} else {
$ offset-= $ temp;
}
}
$ lunarYear = $ i;
$ leapMonth = $ this-> getLeapMonth ($ lunarYear); // Calculate which month of the year
// Set whether there is a leap month
if ($ leapMonth> 0) {
$ isLeapYear =true;
} else {
$ isLeapYear = false;
}
for ($ i = 1; $ i <= 12; $ i ++) {
if ($ i == $ leapMonth + 1 && $ isLeapYear) {
$ temp = $ this-> getLeapMonthDays ($ lunarYear);
$ isLeapYear = false;
$ leapMonthFlag = true;
$ i--;
} else {
$ temp = $ this-> getMonthDays ($ lunarYear, $ i);
}
$ offset-= $ temp;
if ($ offset <= 0) {
break;
}
}
$ offset + = $ temp;
$ lunarMonth = $ i;
$ lunarDay = $ offset;
return [$ lunarYear, $ lunarMonth, $ lunarDay, $ leapMonth, $ leapMonthFlag];
}
/ **
* Check if the lunar calendar is legal
* @param int $ lunarYear
* @param int $ lunarMonth
* @param int $ lunarDay
* @param bool $ leapMonthFlag
* @return bool
* @throws Exception
* /
private function checkLunarDate ($ lunarYear, $ lunarMonth, $ lunarDay, $ leapMonthFlag = false) {
if ($ lunarYear <self :: MIN_YEAR || $ lunarYear> self :: MAX_YEAR) {
// illegal lunar year
return false;
}
if ($ lunarMonth <1 || $ lunarMonth> 12) {
// illegal lunar month
return false;
}
if ($ lunarDay <1 || $ lunarDay> 30) {// up to 30 days in China
// illegal lunar days
return false;
}
$ leap = $ this-> getLeapMonth ($ lunarYear); // calculate which month the year should be
if ($ leapMonthFlag == true && $ lunarMonth! = $ leap) {
// Illegal leap month
return false;
}
return true;
}
/ **
* Calculate the total number of days in the month
* @param int $ year
* @param int $ month
* @return int
* /
private function getMonthDays ($ year, $ month) {
if ($ month> 31 || $ month <0) {
// error month
return 0;
}
// 0X0FFFF [0000 {1111 1111 1111} 1111] The 12 digits in the middle represent 12 months, 1 is the big month and 0 is the small month
$ bit = 1 << (16-$ month);
if (((self :: LUNAR_INFO [$ year-1900] & 0x0FFFF) & $ bit) == 0) {
return 29;
}
return 30;
}
/ **
* Calculate the total number of days in the lunar year
* @param int $ year
* @return int
* /
private function getYearDays ($ year) {
$ sum = 29 * 12;
for ($ i = 0x8000; $ i> = 0x8; $ i >> = 1) {
if ((self :: LUNAR_INFO [$ year-1900] & 0xfff0 & $ i)! = 0) {
$ sum ++;
}
}
return $ sum + $ this-> getLeapMonthDays ($ year);
}
/ **
* Calculate the number of days in the lunar month
* @param int $ year
* @return int
* /
private function getLeapMonthDays ($ year) {
if ($ this-> getLeapMonth ($ year)! = 0) {
if ((self :: LUNAR_INFO [$ year-1900] & 0xf0000) == 0) {
return 29;
}
return 30;
}
return 0;
}
/ **
* Calculate which month of the lunar calendar is 1-12, no return 0
* @param int $ year
* @return int
* /
private function getLeapMonth ($ year) {
return (int) (self :: LUNAR_INFO [$ year-1900] & 0xf);
}
/ **
* Calculate bad days
* @param \ DateTime $ date
* @param \ DateTime $ startDate
* @return int
* /
private function daysBwteen ($ date, $ startDate)
{
$ subValue = floatval ($ date-> getTimestamp ()-$ startDate-> getTimestamp ()) / 86400.0 + 0.5;
return intval ($ subValue);
}
/ **
* Calculate the annual dry earth branch
* @param $ lunarYear
* @return string
* /
private function yearGanZhi ($ lunarYear)
{
// The number of years is reduced by three first. The remainder is divided by 10, and the base is divided by 12. The remainder is the year of the Earthly Branch. (If the remainder is 0, the largest serial number is used.)
$ yJiShu = $ lunarYear-3;
$ yTianGan = ($ yJiShu% 10 == 0)? 10: $ yJiShu% 10;
$ yDiZhi = ($ yJiShu% 12 == 0)? 10: $ yJiShu% 12;
$ yGanZhi = self :: TIAN_GAN [$ yTianGan-1]. self :: DI_ZHI [$ yDiZhi-1]; // // Since it starts from 0, subtract one here
return $ yGanZhi;
}
/ **
* Calculate the zodiac sign of the year
* @param $ lunarYear
* @return mixed
* /
private function yearShengXiao ($ lunarYear)
{
// TODO As to why this is done, I didn't figure it out
return self :: SHENG_XIAO [($ lunarYear-self :: MIN_YEAR + 36)% 12]; // year of the zodiac
}
// TODO has not been implemented
/ **
* Calculus Day
* @param $ solarYear
* @param $ solarDay
* @param $ solarDay
* @return string
* /
private function dayGanZhi ($ solarYear, $ solarDay, $ solarDay)
{
return "";
}
// TODO has not been implemented
/ **
* Calculate the zodiac of the month
* @param $ solarYear
* @param $ solarDay
* @param $ solarDay
* @return string
* /
private function mothGanZhi ($ solarYear, $ solarDay, $ solarDay)
{
return "";
}
/ **
* Convert days to Chinese characters
* @param int $ day
* @return mixed | string
* /
private function chineseDayString ($ day)
{
$ chineseTen = ["chu", "ten", "廿", "three"];
$ n = 0;
if ($ day% 10 == 0) {
$ n = 9;
} else {
$ n = $ day% 10-1;
}
if ($ day> 30) {
return "";
}
if ($ day == 20) {
return "twenty";
} else if ($ day == 10) {
return "first ten";
} else {
return $ chineseTen [$ day / 10]. self :: CHINESE_NUMBER [$ n];
}
}
}
$ solarLunar = new SolarLunar ();
$ solarDate = "2010-01-05";
$ new_date = $ solarLunar-> solarToChineseLunar ($ solarDate);
var_dump ($ solarDate. "Converted to the lunar calendar:". $ new_date);
$ new_date = $ solarLunar-> solarToSimpleLunar ($ solarDate);
var_dump ($ solarDate. "Convert to lunar calendar, Chinese:". $ new_date);
$ new_date = $ solarLunar-> solarToLunar ($ solarDate, $ isLeapMonth);
var_dump ("Is it a leap month:". ($ isLeapMonth? "Yes": "No"));
var_dump ($ solarDate. "Converted to the lunar calendar:". $ new_date);
$ lunarDate = "2099-11-25";
$ new_date = $ solarLunar-> lunarToSolar ($ lunarDate, false);
var_dump ($ lunarDate. "Transfer new calendar to:". $ new_date);
Output:
string (67) "2010-01-05 converted to the lunar calendar: the first day of the winter month, the ugly year [year of the bull], month, day"
string (50) "2010-01-05 converted to the lunar calendar, Chinese: November 21, 2009"
string (20) "Whether it is a leap month: No"
string (35) "2010-01-05 to Lunar Calendar: 2009-11-21"
string (35) "2099-11-25 To New Calendar: 2100-01-05"
GO implementation
https: //github.com/nosixtools ...