公历转农历-js方式-详解
- 一、摘要
日历中的历法分为阴历、阳历和阴阳合历。
阳历亦即太阳历,其历年为一个回归年,现时国际通用的公历(格里高利历)和中国的干支历即属于太阳历这类。
阴历亦称月亮历,或称太阴历,其历月是一个朔望月,历年为12个朔望月,其大月30天,小月29天,伊斯兰历即为阴历的一种。
阴阳历的有非常明显的平年和闰年之分,年天数差异较大,历月为朔望月,因为12个朔望月与回归年存在一定的差别(少11天左右),所以阴阳历中设置闰月,用以协调回归年和朔望月之间的关系,存在闰月的年份中一年为十三个月(朔望月),是闰年。一般每经过十九年就会有七个闰年。区别于一年有十二个朔望月的平年。因此这种历法即与月相相符又与地球绕太阳周期运动相符合。
农历作为阴阳历,能够反映季节、 农时、潮汐规律,这使得它在日常生活、农业生产、渔业生产、防汛 抗洪等方面也具有广泛的实用价值。但由于农历的特殊性,其规律性并不像公历那样规则,找不到一个固定算法能够推演产生。所以市面上的万年历均采用查表法得到,即预先存储农历,然后查询某一天。
- 二、数据准备
1.数据分析
一年有12或13个月(闰月),月份最大值为12,每月有29天或30天。
可以用0表示29天即小月,用1表示30天即大月
得到的数字转16进制保存
以2024年为例,
| 正月(小) | 二月(大) | 三月(小) | 四月(小) | 五月(大) | 六月(小) | 七月(大) | 八月(大) | 九月(小) | 十月(大) | 十一月(大) | 腊月(小) |
| 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
所以得到数字 0100 1011 0110 ,转16进制 0x4b6
2025年数据,润六月
| 正月(大) | 二月(小) | 三月(大) | 四月(小) | 五月(小) | 六月(大) | 七月(大) | 八月(小) | 九月(大) | 十月(大) | 十一月(大) | 腊月(小) | 闰月:六月(小) |
| 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 0 |
得到数字 1010 0110 1110 ,
我们将闰月的0保存在第13位,即 0 1010 0110 1110。
然后再将闰月的数值6转2进制,0110 保存到 最左侧 ,即2025年数据为 0110 0 1010 0110 1110,转16进制 0xca6e
2.解析数据
数据来源 香港天文台 https://www.hko.gov.hk/sc/gts/time/conversion1_text.htm
1 var lunarInfo = [ 2 0x104bd,0x004ae,0x00a57,0x0a54d,0x00d26,0x00d95,0x09655,0x0056a,0x009ad,0x0455d, // 1900~1909 3 0x004ae,0x0ca5b,0x00a4d,0x00d25,0x0bd25,0x00b54,0x00d6a,0x04ada,0x0095b,0x0f497, // 1910~1919 4 0x00497,0x00a4b,0x0ab4b,0x006a5,0x006d4,0x09ab5,0x002b6,0x00957,0x0452f,0x00497, // 1920~1929 5 0x0c656,0x00d4a,0x00ea5,0x0b6a9,0x005ad,0x002b6,0x0786e,0x0092e,0x0fc8d,0x00c95, // 1930~1939 6 0x00d4a,0x0dd8a,0x00b55,0x0056a,0x09a5b,0x0025d,0x0092d,0x04d2b,0x00a95,0x0eb55, // 1940~1949 7 0x006ca,0x00b55,0x0b535,0x004da,0x00a5b,0x07457,0x0052b,0x10a9a,0x00e95,0x006aa, // 1950~1959 8 0x0caea,0x00ab5,0x004b6,0x08aae,0x00a57,0x00526,0x06f26,0x00d95,0x0e5b5,0x0056a, // 1960~1969 9 0x0096d,0x0a4dd,0x004ad,0x00a4d,0x08d4d,0x00d25,0x10d55,0x00b54,0x00b6a,0x0d95a, // 1970~1979 10 0x0095b,0x0049b,0x08a97,0x00a4b,0x14b27,0x006a5,0x006d4,0x0caf4,0x00ab6,0x00957, // 1980~1989 11 0x0a4af,0x00497,0x0064b,0x0674a,0x00ea5,0x106b5,0x005ac,0x00ab6,0x0a96d,0x0092e, // 1990~1999 12 0x00c96,0x08d95,0x00d4a,0x00da5,0x04755,0x0056a,0x0eabb,0x0025d,0x0092d,0x0acab, // 2000~2009 13 0x00a95,0x00b4a,0x08baa,0x00ad5,0x1255d,0x004ba,0x00a5b,0x0d517,0x0052b,0x00a93, // 2010~2019 14 0x08795,0x006aa,0x00ad5,0x045b5,0x004b6,0x0ca6e,0x00a4e,0x00d26,0x0aea6,0x00d53, // 2020~2029 15 0x005aa,0x0676a,0x0096d,0x164af,0x004ad,0x00a4d,0x0dd0b,0x00d25,0x00d52,0x0add4, // 2030~2039 16 0x00b5a,0x0056d,0x0455b,0x0049b,0x0ea57,0x00a4b,0x00aa5,0x0bb25,0x006d2,0x00ada, // 2040~2049 17 0x074b6,0x00937,0x1049f,0x00497,0x0064b,0x0d68a,0x00ea5,0x006aa,0x09a6c,0x00aae, // 2050~2059 18 0x0092e,0x06d2e,0x00c96,0x0ed55,0x00d4a,0x00da5,0x0a5d5,0x0056a,0x00a6d,0x0855d, // 2060~2069 19 0x0052d,0x10a9b,0x00a95,0x00b4a,0x0cb6a,0x00ad5,0x0055a,0x08aba,0x00a5b,0x0052b, // 2070~2079 20 0x06b27,0x00693,0x0e733,0x006aa,0x00ad5,0x0b4b5,0x004b6,0x00a57,0x0854e,0x00d16, // 2080~2089 21 0x10e96,0x00d52,0x00daa,0x0d6aa,0x0056d,0x004ae,0x08a9d,0x00a2d,0x00d15,0x04f25, // 2090~2099 22 0x00d52 // 2100 23 ]
- 三、代码实现
1 /** 2 * 公历转换 3 * 1900~2100区间内的公历转农历 4 */ 5 var chineseCalendar = { 6 7 /** 8 * 农历1900~2100信息表 9 */ 10 lunarInfo: [ 11 0x104bd, 0x4ae, 0xa57, 0xa54d, 0xd26, 0xd95, 0x9655, 0x56a, 0x9ad, 0x455d, // 1900~1909 12 0x4ae, 0xca5b, 0xa4d, 0xd25, 0xbd25, 0xb54, 0xd6a, 0x4ada, 0x95b, 0xf497, // 1910~1919 13 0x497, 0xa4b, 0xab4b, 0x6a5, 0x6d4, 0x9ab5, 0x2b6, 0x957, 0x452f, 0x497, // 1920~1929 14 0xc656, 0xd4a, 0xea5, 0xb6a9, 0x5ad, 0x2b6, 0x786e, 0x92e, 0xfc8d, 0xc95, // 1930~1939 15 0xd4a, 0xdd8a, 0xb55, 0x56a, 0x9a5b, 0x25d, 0x92d, 0x4d2b, 0xa95, 0xeb55, // 1940~1949 16 0x6ca, 0xb55, 0xb535, 0x4da, 0xa5b, 0x7457, 0x52b, 0x10a9a, 0xe95, 0x6aa, // 1950~1959 17 0xcaea, 0xab5, 0x4b6, 0x8aae, 0xa57, 0x526, 0x6f26, 0xd95, 0xe5b5, 0x56a, // 1960~1969 18 0x96d, 0xa4dd, 0x4ad, 0xa4d, 0x8d4d, 0xd25, 0x10d55, 0xb54, 0xb6a, 0xd95a, // 1970~1979 19 0x95b, 0x49b, 0x8a97, 0xa4b, 0x14b27, 0x6a5, 0x6d4, 0xcaf4, 0xab6, 0x957, // 1980~1989 20 0xa4af, 0x497, 0x64b, 0x674a, 0xea5, 0x106b5, 0x5ac, 0xab6, 0xa96d, 0x92e, // 1990~1999 21 0xc96, 0x8d95, 0xd4a, 0xda5, 0x4755, 0x56a, 0xeabb, 0x25d, 0x92d, 0xacab, // 2000~2009 22 0xa95, 0xb4a, 0x8baa, 0xad5, 0x1255d, 0x4ba, 0xa5b, 0xd517, 0x52b, 0xa93, // 2010~2019 23 0x8795, 0x6aa, 0xad5, 0x45b5, 0x4b6, 0xca6e, 0xa4e, 0xd26, 0xaea6, 0xd53, // 2020~2029 24 0x5aa, 0x676a, 0x96d, 0x164af, 0x4ad, 0xa4d, 0xdd0b, 0xd25, 0xd52, 0xadd4, // 2030~2039 25 0xb5a, 0x56d, 0x455b, 0x49b, 0xea57, 0xa4b, 0xaa5, 0xbb25, 0x6d2, 0xada, // 2040~2049 26 0x74b6, 0x937, 0x1049f, 0x497, 0x64b, 0xd68a, 0xea5, 0x6aa, 0x9a6c, 0xaae, // 2050~2059 27 0x92e, 0x6d2e, 0xc96, 0xed55, 0xd4a, 0xda5, 0xa5d5, 0x56a, 0xa6d, 0x855d, // 2060~2069 28 0x52d, 0x10a9b, 0xa95, 0xb4a, 0xcb6a, 0xad5, 0x55a, 0x8aba, 0xa5b, 0x52b, // 2070~2079 29 0x6b27, 0x693, 0xe733, 0x6aa, 0xad5, 0xb4b5, 0x4b6, 0xa57, 0x854e, 0xd16, // 2080~2089 30 0x10e96, 0xd52, 0xdaa, 0xd6aa, 0x56d, 0x4ae, 0x8a9d, 0xa2d, 0xd15, 0x4f25, // 2090~2099 31 0xd52 // 2100 32 ], 33 34 /** 35 * 二十四节气速算表 36 * 节气点时间(单位是分钟) 37 * 从0小寒起算 38 */ 39 termInfo :[ 40 0,21208,42467,63836, 41 85337,107014,128867,150921, 42 173149,195551,218072,240693, 43 263343,285989,308563,331033, 44 353350,375494,397447,419210, 45 440795,462224,483532,504758 46 ], 47 48 /** 49 * 二十四节气对应表 50 * 从0小寒起算 51 */ 52 solarTerm: [ 53 '小寒', '大寒', '立春', '雨水', 54 '惊蛰', '春分', '清明', '谷雨', 55 '立夏', '小满', '芒种', '夏至', 56 '小暑', '大暑', '立秋', '处暑', 57 '白露', '秋分', '寒露', '霜降', 58 '立冬', '小雪', '大雪', '冬至' 59 ], 60 61 /** 62 * 农历月份 63 */ 64 chineseMonthCN: ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊'], 65 66 /** 67 * 农历日期 68 */ 69 chineseDayCN: ['初', '十', '廿', '卅'], 70 71 /** 72 * 中文数字 73 */ 74 numberCN: ['日', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十'], 75 76 /** 77 * 获取农历年一年的天数 78 */ 79 lunarYearDays: function(y) { 80 var i, sum = 12 * 29; 81 for (i = 1; i < 1 << 12; i <<= 1) { 82 sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0; 83 } 84 return (sum + this.leapDays(y)); 85 }, 86 87 /** 88 * 获取农历年闰月月份,不存在返回0 89 */ 90 leapMonth: function(y) { 91 return ((this.lunarInfo[y - 1900] >> 13) & 0xf); 92 }, 93 94 /** 95 * 获取农历年闰月的天数,不存在闰月返回0 96 */ 97 leapDays: function(y) { 98 if (this.leapMonth(y)) { 99 return ((this.lunarInfo[y - 1900] & 0x1000) ? 30 : 29); 100 } 101 return 0; 102 }, 103 104 /** 105 * 获取农历年某月的天数,正常月份 106 */ 107 lunarDaysOfMonth: function(y, m) { 108 if (m > 12 || m < 1) 109 return -1; 110 return ((this.lunarInfo[y - 1900] & (0x1000 >> m)) ? 30 : 29); 111 }, 112 113 /** 114 * 月份转汉字 115 */ 116 toChinaMonth: function(m) { 117 if (m > 12 || m < 1) 118 return -1; 119 return this.chineseMonthCN[m - 1] + "月"; 120 }, 121 122 /** 123 * 农历天转汉字 124 */ 125 toChinaDay: function(d) { 126 if(d > 30) 127 return -1; 128 if(d == 10) 129 return this.chineseDayCN[0]+this.chineseDayCN[1]; 130 return this.chineseDayCN[Math.floor(d / 10)] + this.numberCN[d % 10]; 131 }, 132 133 /** 134 * 获取一年中的第几个节气 135 */ 136 termForIndex(y,n) { 137 if(n>24){ 138 return -1; 139 } 140 var offDate = new Date(( 31556925974.7*(y-1900) + this.termInfo[n]*60000 ) + Date.UTC(1900,0,6,2,3,57)); 141 return offDate.getUTCDate(); 142 }, 143 144 /** 145 * 获取某日的节气,不是节气返回 undefined 146 */ 147 termOfDay(y,m,d){ 148 149 var mon = parseInt(m) - 1; 150 y = parseInt(y); 151 d = parseInt(d); 152 var solarTerms = undefined; 153 // 先计算当月第一个节气 154 var index = mon*2 155 if (this.termForIndex(y, index) == d) { 156 solarTerms = this.solarTerm[index]; 157 } 158 index ++; 159 if (this.termForIndex(y, index ) == d) { 160 solarTerms = this.solarTerm[index]; 161 } 162 return solarTerms; 163 }, 164 165 /** 166 * 公历转农历 167 * 公历年月日获得详细的公历、农历信息 168 */ 169 gregorian2lunar: function(y, m, d) { 170 171 // 默认获得当天 172 var objDate = new Date(); 173 if (y && m && d) { 174 objDate = new Date(y, parseInt(m) - 1, d) 175 } 176 177 // 校验传参区间,1900-1-31 ~ 2100-12-31 178 var minDate = new Date(1900, 0, 31), 179 maxDate = new Date(2100, 11, 31); 180 if (objDate < minDate || objDate > maxDate) { 181 return -1; 182 } 183 184 // 重新获取年月日数值 185 var y = objDate.getFullYear(), 186 m = objDate.getMonth() + 1, 187 d = objDate.getDate(); 188 var i, leap = 0, 189 temp = 0; 190 // 计算日期到 1900-1-31 之间天数 191 var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 192 31)) / 8.64e7; 193 for (i = 1900; i <= 2100 && offset > 0; i++) { 194 temp = this.lunarYearDays(i); 195 offset -= temp; 196 } 197 // 修正偏移量 198 if (offset < 0) { 199 offset += temp; 200 i--; 201 } 202 203 // 取星期 204 var dayOfWeek = objDate.getDay(), 205 cWeek = this.numberCN[dayOfWeek]; 206 if (dayOfWeek == 0) { 207 dayOfWeek = 7; 208 } 209 210 // 农历年 211 var year = i; 212 var leapMonth = this.leapMonth(i); 213 var isLeap = false; 214 // 根据偏移量计算天数 215 for (i = 1; i < 13 && offset > 0; i++) { 216 217 if (leapMonth > 0 && i == (leapMonth + 1) && isLeap == false) { 218 // 循环闰月,取闰月天数,月份减1 219 --i; 220 isLeap = true; 221 temp = this.leapDays(year); 222 } else { 223 // 取普通月天数 224 temp = this.lunarDaysOfMonth(year, i); 225 } 226 // 循环到闰月下个月,解除闰月 227 if (isLeap == true && i == (leapMonth + 1)) { 228 isLeap = false; 229 } 230 offset -= temp; 231 } 232 // offset为0时,并且刚才计算的月份是闰月,要校正 233 if (offset == 0 && leapMonth > 0 && i == leapMonth + 1) { 234 if (isLeap) { 235 isLeap = false; 236 } else { 237 isLeap = true; 238 --i; 239 } 240 } 241 // offset小于0时,也要校正 242 if (offset < 0) { 243 offset += temp; 244 --i; 245 } 246 247 // 获取农历大小尽 248 var lunation = (isLeap ? this.leapDays(year) : this.lunarDaysOfMonth(year, i)) > 29 249 250 // 农历月 251 var month = i; 252 // 农历日 253 var day = offset + 1; 254 255 return { 256 'chinaYear': year, 257 'chinaMonth': month, 258 'chinaDay': day, 259 'chinaMonthCn': (isLeap ? "润" : '') + this.toChinaMonth(month), 260 'chinaDayCn': this.toChinaDay(day), 261 'solarYear': y, 262 'solarMonth': m, 263 'solarDay': d, 264 'lunation': lunation ? '大' : '小', 265 'isLeap': isLeap, 266 'term': this.termOfDay(y,m,d), 267 'dayOfWeek': dayOfWeek, 268 'week': "星期" + cWeek 269 } 270 } 271 272 };
调用方式
console.log(chineseCalendar.gregorian2lunar(2025,6,5))
{ "chinaYear": 2025, // 农历年份 "chinaMonth": 5, // 农历月份 "chinaDay": 10, // 农历日期 "chinaMonthCn": "五月", // 农历月份汉字 "chinaDayCn": "初十", // 农历日期汉字 "solarYear": 2025, // 公历年份 "solarMonth": 6, // 公历月份 "solarDay": 5, // 公历日期 "lunation": "小", // 大尽(大建) / 小尽(小建) "isLeap": false, // 是否闰月 "term": "芒种", // 二十四节气 "dayOfWeek": 4, // 星期第几天,周一开始 "week": "星期四" // 星期 }
- 四、数据来源
香港天文台 https://www.hko.gov.hk/sc/gts/time/conversion1_text.htm
紫金山天文台 http://pmo.cas.cn/xwdt2019/kpdt2019/202203/t20220309_6386774.html

浙公网安备 33010602011771号