
点亮☆星标,不错过精彩分享
有个朋友问了个问题:需要给员工和客户发农历生日祝福,如何根据日期,返回农历的日期?


EXCEL中,默认的日期都是新历日期,没有现成的返回农历日期的函数。因为中国的农历日历,对于老外来说,是比较复杂的(对于我们来说,其实也不简单)。不说天干地支的年份命名,仅一个“闰月”的变量因素,就比较麻烦。
农历中,不是只有12个月,如果出现闰月,可能有13个月,比如2025年就有两个6月,但这个情况在新历中,是不可能出现的。
有点麻烦,但也不是不能实现,今天我们介绍两个方法,点赞转发收藏一下哦,这个问题的教程网上可不多见哦!
01_自定义格式
Excel中,其实有农历日期的转换方法,就是单元格自定义,操作也很简单快捷。选中要转换的日期,右键-设置单元格格式-自定义,输入这个代码:
[DBNum1][$-13000]"农历"m月d
或者用TEXT函数也可以
=text(A2,[DBNum1][$-13000]"农历"m月d)但是这个方法,不严谨,无法返回“闰月”,比如2025年有两个6月,这个自定义格式的方法,就无法处理。

02_VBA自定义函数
既然普通的函数公式和自定义格式无法处理,我们只能自己发明开发一个函数。
这个农历和规则和处理逻辑比较复杂,一句话总结:先算公历日期距 1900年1月31日的天数偏移,再拿着偏移逐农历年、逐月去「扣」,扣到哪天停就算哪天。
看不懂没关系,我们直接使用就行了,使用很简单,和正常的函数一样输入就行了。

=SolarToLunar(日期单元格,日期显示格式)=SolarToLunar(A2,1)'显示六月廿九=SolarToLunar(A2,2)'显示“2025/6/29=SolarToLunar(A2,3)'乙巳年六月廿八
这个函数有两个参数,第二个参数有三个定义,可以修改农历日期的显示格式:
1:显示中文简单:比如农历6月29,显示“六月廿九”;
2:显示阿拉伯数字日期,格式和新历一样,主要是方便我们的阅读习惯,比如农历6月29,显示“2025/6/29”;
3:中文大写全称:比如农历6月29,显示“乙巳年六月廿八”;

我们看结果对不对就行,这是电脑系统上的日期对比。

这是和百度上的农历日期相对

结果都是对的!如何创建这个函数呢?
打开你要使用这个函数的EXCEL或WPS表格; 按ALT+F11,打开VBA后台编辑器; 在左侧空白区,右键-新建一个模块; 在新建模块的右边空白代码窗口中,复制粘贴函数代码(代码我放在文章最后); 关闭VBA代码编辑窗口,返回表格中,就可以正常使用函数了; 如果想下次打开还能使用函数,需要将工作簿另存为“.xlsm”格式的工作簿;

完整VBA代码:
'====================================================================' 农历日期转换模块' 数据来源:紫金山天文台农历数据编码(1900-2100年)' 编码规则(每项4字节十六进制):' bit 0-3: 闰月月份(0=无闰月)' bit 4-15: 12个月大小(bit4=正月,...,bit15=腊月),1=30天,0=29天' bit 16-19: 闰月大小(1=30天,0=29天)'====================================================================Private Const LUNAR_DATA_STR As String = _"0x04bd8,0x04ae0,0x0a570,0x054d5,0x0d260,0x0d950,0x16554,0x056a0,0x09ad0,0x055d5," & _"0x04ae0,0x0a5b6,0x0a4d0,0x0d250,0x1d255,0x0b540,0x0d6a0,0x0ada2,0x095b0,0x14977," & _"0x04970,0x0a4b0,0x0b4b5,0x06a50,0x06d40,0x1ab54,0x02b60,0x09570,0x052f2,0x04970," & _"0x06566,0x0d4a0,0x0ea50,0x06e95,0x05ad0,0x02b60,0x186e3,0x092e0,0x1c8d7,0x0c950," & _"0x0d4a0,0x1d8a6,0x0b550,0x056a0,0x1a5b4,0x025d0,0x092d0,0x0d2b2,0x0a950,0x0b557," & _"0x06ca0,0x0b550,0x15355,0x04da0,0x0a5b0,0x14573,0x052b0,0x0a9a8,0x0e950,0x06aa0," & _"0x0aea6,0x0ab50,0x04b60,0x0aae4,0x0a570,0x05260,0x0f263,0x0d950,0x05b57,0x056a0," & _"0x096d0,0x04dd5,0x04ad0,0x0a4d0,0x0d4d4,0x0d250,0x0d558,0x0b540,0x0b6a0,0x195a6," & _"0x095b0,0x049b0,0x0a974,0x0a4b0,0x0b27a,0x06a50,0x06d40,0x0af46,0x0ab60,0x09570," & _"0x04af5,0x04970,0x064b0,0x074a3,0x0ea50,0x06b58,0x05ac0,0x0ab60,0x096d5,0x092e0," & _"0x0c960,0x0d954,0x0d4a0,0x0da50,0x07552,0x056a0,0x0abb7,0x025d0,0x092d0,0x0cab5," & _"0x0a950,0x0b4a0,0x0baa4,0x0ad50,0x055d9,0x04ba0,0x0a5b0,0x15176,0x052b0,0x0a930," & _"0x07954,0x06aa0,0x0ad50,0x05b52,0x04b60,0x0a666,0x0a4e0,0x0d260,0x0ea65,0x0d530," & _"0x05aa0,0x076a3,0x096d0,0x04afb,0x04ad0,0x0a4d0,0x1d0b6,0x0d250,0x0d520,0x0dd45," & _"0x0b5a0,0x056d0,0x055b2,0x049b0,0x0a577,0x0a4b0,0x0aa50,0x1b255,0x06d20,0x0ada0," & _"0x14b63,0x09370,0x049f8,0x04970,0x064b0,0x168a6,0x0ea50,0x06aa0,0x1a6c4,0x0aae0," & _"0x092e0,0x0d2e3,0x0c960,0x0d557,0x0d4a0,0x0da50,0x05d55,0x056a0,0x0a6d0,0x055d4," & _"0x052d0,0x0a9b8,0x0a950,0x0b4a0,0x0b6a6,0x0ad50,0x055a0,0x0aba4,0x0a5b0,0x052b0," & _"0x0b273,0x06930,0x07337,0x06aa0,0x0ad50,0x14b55,0x04b60,0x0a570,0x054e4,0x0d160," & _"0x0e968,0x0d520,0x0daa0,0x16aa6,0x056d0,0x04ae0,0x0a9d4,0x0a4d0,0x0d150,0x0f252," & _"0x0d520"Private m_LunarData(1900 To 2100) As LongPrivate m_DataReady As BooleanPrivate Const TIAN_GAN As String = "甲乙丙丁戊己庚辛壬癸"Private Const DI_ZHI As String = "子丑寅卯辰巳午未申酉戌亥"Private Const MONTH_CN As String = "正二三四五六七八九十冬腊"' 初始化农历数据表(1900-2100年),将字符串常量解析为Long型数组,只执行一次Private Sub InitData()If m_DataReady Then Exit SubDim arr As Variant: arr = Split(LUNAR_DATA_STR, ",")Dim i As LongFor i = 1900 To 2100m_LunarData(i) = CLng("&H" & Mid(arr(i - 1900), 3))Next im_DataReady = TrueEnd Sub' 根据农历年份数字返回干支纪年名称,如2025年返回"乙巳"Private Function GetYearName(ByVal y As Integer) As StringDim n As Long: n = (y - 4) Mod 60GetYearName = Mid(TIAN_GAN, (n Mod 10) + 1, 1) & Mid(DI_ZHI, (n Mod 12) + 1, 1)End Function' 返回农历月份的中文名称,isLeap=True时加"闰"前缀,如"六月"或"闰六月"Private Function GetMonthName(ByVal m As Integer, ByVal isLeap As Boolean) As StringDim s As StringIf m >= 1 And m <= 12 Then s = Mid(MONTH_CN, m, 1) & "月" Else s = m & "月"If isLeap Then s = "闰" & sGetMonthName = sEnd Function' 返回农历日期的中文名称,如"初一"、"廿三"等Private Function GetDayName(ByVal d As Integer) As StringIf d < 1 Or d > 30 Then GetDayName = CStr(d): Exit FunctionSelect Case dCase 1 To 10: GetDayName = "初" & Mid("一二三四五六七八九十", d, 1)Case 11 To 19: GetDayName = "十" & Mid("一二三四五六七八九", d - 10, 1)Case 20: GetDayName = "二十"Case 21 To 29: GetDayName = "廿" & Mid("一二三四五六七八九", d - 20, 1)Case 30: GetDayName = "三十"End SelectEnd Function' 返回指定农历年指定月份的天数(29或30天)Private Function MonthDays(ByVal y As Integer, ByVal m As Integer) As IntegerIf (m_LunarData(y) And (2 ^ (m + 3))) <> 0 Then MonthDays = 30 Else MonthDays = 29End Function' 返回指定农历年的闰月月份(0=无闰月)Private Function LeapM(ByVal y As Integer) As IntegerLeapM = m_LunarData(y) And &HFEnd Function' 返回指定农历年闰月的天数(29或30天),无闰月时返回0Private Function LeapD(ByVal y As Integer) As IntegerIf LeapM(y) > 0 ThenIf (m_LunarData(y) And &H10000) <> 0 Then LeapD = 30 Else LeapD = 29End IfEnd Function' 返回指定农历年的总天数(含闰月)Private Function YearDays(ByVal y As Integer) As LongDim t As Long, i As IntegerFor i = 1 To 12: t = t + MonthDays(y, i): Next iIf LeapM(y) > 0 Then t = t + LeapD(y)YearDays = tEnd Function' 核心转换:将公历日期转换为农历日期,通过引用参数返回农历年月日及闰月标志Private Sub ToLunar(ByVal dt As Date, ByRef ly As Integer, ByRef lm As Integer, ByRef ld As Integer, ByRef leap As Boolean)InitDataDim base As Date: base = DateSerial(1900, 1, 31)If dt < base Then ly = 0: Exit SubDim span As Long: span = CLng(dt - base)Dim y As Integer: y = 1900Do While y < 2100Dim yd As Long: yd = YearDays(y)If span < yd Then Exit Dospan = span - yd: y = y + 1LoopIf y >= 2100 Then ly = 0: Exit Subly = yDim lmT As Integer: lmT = LeapM(y)Dim ldT As Integer: ldT = LeapD(y)Dim fm As Boolean: fm = FalseDim m As IntegerFor m = 1 To 12Dim md As Integer: md = MonthDays(y, m)If span < md Thenlm = m: leap = False: ld = span + 1: Exit SubEnd Ifspan = span - mdIf lmT > 0 And m = lmT And Not fm ThenIf span < ldT Thenlm = lmT: leap = True: ld = span + 1: Exit SubEnd Ifspan = span - ldT: fm = TrueEnd IfNext mly = 0End Sub' ===== 公开接口 =====' SolarToLunar - 公历转农历' 参数:rng - 单元格引用(含公历日期)' fmt - 1=中文月日(如"六月初一"),2=数字格式(如"2025/6/1"),3=中文全称(如"乙巳年六月初一")' 公开接口:在Excel中作为自定义函数使用,根据fmt参数返回三种格式的农历日期字符串Public Function SolarToLunar(ByVal rng As Range, Optional ByVal fmt As Integer = 1) As StringOn Error GoTo ErrHIf rng Is Nothing Then SolarToLunar = "#N/A": Exit FunctionDim dt As Date: dt = rng.ValueIf Not IsDate(dt) Then SolarToLunar = "#VALUE!": Exit FunctionIf fmt < 1 Or fmt > 3 Then fmt = 1Dim ly As Integer, lm As Integer, ld As Integer, lp As BooleanToLunar dt, ly, lm, ld, lpIf ly = 0 Then SolarToLunar = "#DATE!": Exit FunctionIf fmt = 2 ThenSolarToLunar = ly & "/" & lm & "/" & ldExit FunctionEnd IfDim mStr As String: mStr = GetMonthName(lm, lp)Dim dStr As String: dStr = GetDayName(ld)If fmt = 1 ThenSolarToLunar = mStr & dStrElseSolarToLunar = GetYearName(ly) & "年" & mStr & dStrEnd IfExit FunctionErrH: SolarToLunar = "#ERR!"End Function
关注我,学习更多高效办公小技巧!有其它的VBA定制开发需求,也可私信咨询
往期干货文章学习推荐:
Excel输入【任意关键字】都能筛选查询结果!太高级了!怎么做?
Excel【一二三】级下拉菜单怎么做?99%人的只会设置一级,一个教程全讲清楚原理逻辑!
手把手教你用EXCEL手搓一个春晚收视率数据地图?地图根据数据自动变化!
夜雨聆风