公历转农历-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

  

posted @ 2025-06-05 17:05  Java一哥  阅读(305)  评论(0)    收藏  举报