lunar.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. // 农历(阴历)与节日计算:公历转农历采用 1900-2100 查表法,无需外部依赖。
  2. // 导出 getCalendarInfo(date):返回 { lunarText, festival },供 footer 显示用。
  3. // 农历数据表:每项表示 1900..2100 年当年的农历信息(闰月月份、大小月、闰月天数)
  4. const LUNAR_INFO = [
  5. 0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
  6. 0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
  7. 0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
  8. 0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
  9. 0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557,
  10. 0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0,
  11. 0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0,
  12. 0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b5a0, 0x195a6,
  13. 0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570,
  14. 0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x055c0, 0x0ab60, 0x096d5, 0x092e0,
  15. 0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5,
  16. 0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930,
  17. 0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530,
  18. 0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45,
  19. 0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0,
  20. 0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0,
  21. 0x092e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4,
  22. 0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0,
  23. 0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160,
  24. 0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252,
  25. 0x0d520,
  26. ];
  27. // 农历月份名(正月..腊月)
  28. const LUNAR_MONTHS = ['正', '二', '三', '四', '五', '六', '七', '八', '九', '十', '冬', '腊'];
  29. // 农历日名(初一..三十)
  30. const LUNAR_DAYS = [
  31. '初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
  32. '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
  33. '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十',
  34. ];
  35. // 农历节日:(月, 日) -> 名称
  36. const LUNAR_FESTIVALS = {
  37. '1,1': '春节',
  38. '1,15': '元宵节',
  39. '2,2': '龙抬头',
  40. '5,5': '端午节',
  41. '7,7': '七夕',
  42. '7,15': '中元节',
  43. '8,15': '中秋节',
  44. '9,9': '重阳节',
  45. '12,8': '腊八节',
  46. '12,23': '北方小年',
  47. '12,24': '南方小年',
  48. };
  49. // 公历节日:(月, 日) -> 名称
  50. const SOLAR_FESTIVALS = {
  51. '1,1': '元旦',
  52. '2,14': '情人节',
  53. '3,8': '妇女节',
  54. '3,12': '植树节',
  55. '4,1': '愚人节',
  56. '5,1': '劳动节',
  57. '5,4': '青年节',
  58. '6,1': '儿童节',
  59. '7,1': '建党节',
  60. '8,1': '建军节',
  61. '9,10': '教师节',
  62. '10,1': '国庆节',
  63. '12,24': '平安夜',
  64. '12,25': '圣诞节',
  65. };
  66. // 农历某年是否闰年
  67. function leapMonth(y) { return LUNAR_INFO[y - 1900] & 0xf; }
  68. // 农历某年闰月天数(无闰月返回 0)
  69. function leapDays(y) { return leapMonth(y) ? ((LUNAR_INFO[y - 1900] & 0x10000) ? 30 : 29) : 0; }
  70. // 农历某年某月天数(m 为 1..12)
  71. function monthDays(y, m) { return (LUNAR_INFO[y - 1900] & (0x10000 >> m)) ? 30 : 29; }
  72. // 农历某年总天数
  73. function lYearDays(y) {
  74. let sum = 348;
  75. for (let i = 0x8000; i > 0x8; i >>= 1) sum += (LUNAR_INFO[y - 1900] & i) ? 1 : 0;
  76. return sum + leapDays(y);
  77. }
  78. // 公历 Date -> 农历 { year, month, day, isLeap }
  79. function solar2lunar(date) {
  80. // 用 UTC 构造纯日期,消除时分秒与本地时区对天数差的影响
  81. const base = Date.UTC(1900, 0, 31); // 1900-01-31 为农历庚子年正月初一
  82. const target = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate());
  83. let offset = Math.round((target - base) / 86400000);
  84. let temp = 0;
  85. let year = 1900;
  86. for (; year < 2101 && offset > 0; year++) {
  87. temp = lYearDays(year);
  88. offset -= temp;
  89. }
  90. if (offset < 0) { offset += temp; year--; }
  91. const leap = leapMonth(year);
  92. let isLeap = false;
  93. let month;
  94. for (month = 1; month < 13 && offset > 0; month++) {
  95. if (leap > 0 && month === (leap + 1) && !isLeap) {
  96. --month;
  97. isLeap = true;
  98. temp = leapDays(year);
  99. } else {
  100. temp = monthDays(year, month);
  101. }
  102. if (isLeap && month === (leap + 1)) isLeap = false;
  103. offset -= temp;
  104. }
  105. if (offset === 0 && leap > 0 && month === leap + 1) {
  106. if (isLeap) { isLeap = false; } else { isLeap = true; --month; }
  107. }
  108. if (offset < 0) { offset += temp; --month; }
  109. return { year, month, day: offset + 1, isLeap };
  110. }
  111. // 返回该月最后一天(用于判断除夕)
  112. function lastDayOfMonth(y, m) { return monthDays(y, m); }
  113. // 计算某公历日期对应的农历文字与节日
  114. // date: Date 对象;返回 { lunarText, festival }
  115. export function getCalendarInfo(date) {
  116. const y = date.getFullYear();
  117. const m = date.getMonth() + 1; // 公历 1..12
  118. const d = date.getDate();
  119. const lunar = solar2lunar(date);
  120. // 农历文字:如「九月廿二」,闰月加「闰」前缀
  121. const lunarText = (lunar.isLeap ? '闰' : '') + LUNAR_MONTHS[lunar.month - 1] + '月' + LUNAR_DAYS[lunar.day - 1];
  122. // 节日:先查农历节日,其次公历节日,均无则空
  123. let festival = '';
  124. const lkey = `${lunar.month},${lunar.day}`;
  125. if (LUNAR_FESTIVALS[lkey]) {
  126. festival = LUNAR_FESTIVALS[lkey];
  127. } else if (lunar.month === 12 && lunar.day === lastDayOfMonth(lunar.year, 12)) {
  128. festival = '除夕'; // 腊月最后一天
  129. } else {
  130. festival = SOLAR_FESTIVALS[`${m},${d}`] || '';
  131. }
  132. return { lunarText, festival };
  133. }