为跨闰年的日期计算 Excel 中的 YEARFRAC
Calculating YEARFRAC in Excel for dates spanning leap year
如果使用 Excel 的 YEARFRAC(date1,date2,1)
函数,那么 Excel 将根据第二个参数使用基础。如果 date1 和 date2 在同一年,这无关紧要,但如果 date1 是闰年(例如 2020)并且 date2 是非闰年(例如 2021)就很重要
我认为更准确的计算将考虑到 date1
的基础可能与 date2
的基础不同。
比如YEARFRAC(15/12/2020,15/01/2021,1)
returns0.08493151
但是真正的计算是(31/12/2020-15/12/2020)/366 + (15/01/2020-31/12/2020)/365 = 0.084811737
我在 VBA 中实现了以下内容。有没有人有任何根本上更好的方法(在 Excel 或 VBA 中)? (我不是在寻找对我匆忙的 VBA 代码的微小改进)
使用yearFracVBA("AA1","AA2")
调用
Function yearFracVBA(aDate1, aDate2)
result = 0
y1 = Application.Evaluate("=YEAR(" & aDate1 & ")")
y2 = Application.Evaluate("=YEAR(" & aDate2 & ")")
For Y = y1 To y2
fraction = 0
If Y = y1 And Y = y2 Then fraction = Application.Evaluate("=YEARFRAC(" & aDate1 & "," & aDate2 & ",1)")
If Y = y1 And Y < y2 Then fraction = Application.Evaluate("=YEARFRAC(" & aDate1 & ",DATE(YEAR(" & aDate1 & "),12,31),1)")
If Y > y1 And Y < y2 Then fraction = 1
If Y > y1 And Y = y2 Then fraction = Application.Evaluate("=YEARFRAC(DATE(YEAR(" & aDate2 & ")-1,12,31)," & aDate2 & ",1)")
result = result + fraction
Next Y
yearFracVBA = result
End Function
以下代码不需要Excel。它适用于任何 VBA 应用程序:
Option Explicit
Public Function YearFracActual(ByVal aDate1 As Date, ByVal aDate2 As Date) As Double
Dim lowerDate As Date
Dim upperDate As Date
Dim year1 As Integer
Dim year2 As Integer
'Get dates in order
If aDate1 > aDate2 Then
lowerDate = aDate2
upperDate = aDate1
Else
lowerDate = aDate1
upperDate = aDate2
End If
'Round down (floor) - exclude any time (hours, minutes, seconds)
lowerDate = VBA.Int(lowerDate)
upperDate = VBA.Int(upperDate)
'Get years
year1 = Year(lowerDate)
year2 = Year(upperDate)
If year1 = year2 Then
YearFracActual = (upperDate - lowerDate) / GetDaysInYear(year1)
Else
Dim lowerFrac As Double
Dim upperFrac As Double
Dim midFrac As Double
lowerFrac = (DateSerial(year1, 12, 31) - lowerDate) / GetDaysInYear(year1)
midFrac = year2 - year1 - 1
upperFrac = (upperDate - DateSerial(year2 - 1, 12, 31)) / GetDaysInYear(year2)
YearFracActual = lowerFrac + midFrac + upperFrac
End If
End Function
Private Function GetDaysInYear(ByVal year_ As Integer) As Integer
If IsLeapYear(year_) Then
GetDaysInYear = 366
Else
GetDaysInYear = 365
End If
End Function
Private Function IsLeapYear(ByVal year_ As Integer) As Boolean
If year_ Mod 400 = 0 Then
IsLeapYear = True
ElseIf year_ Mod 100 = 0 Then
IsLeapYear = False
Else
IsLeapYear = (year_ Mod 4 = 0)
End If
End Function
如果您不想使用 VBA,那么在 Excel 中您可以使用这个公式:
=ABS(YEARFRAC(A1,EOMONTH(A1,12-MONTH(A1)),1)+YEAR(B1)-YEAR(A1)-1+YEARFRAC(B1,EOMONTH(B1,-MONTH(B1)),1))
其中 A1 和 B1 是包含 2 个日期的单元格
如果使用 Excel 的 YEARFRAC(date1,date2,1)
函数,那么 Excel 将根据第二个参数使用基础。如果 date1 和 date2 在同一年,这无关紧要,但如果 date1 是闰年(例如 2020)并且 date2 是非闰年(例如 2021)就很重要
我认为更准确的计算将考虑到 date1
的基础可能与 date2
的基础不同。
比如YEARFRAC(15/12/2020,15/01/2021,1)
returns0.08493151
但是真正的计算是(31/12/2020-15/12/2020)/366 + (15/01/2020-31/12/2020)/365 = 0.084811737
我在 VBA 中实现了以下内容。有没有人有任何根本上更好的方法(在 Excel 或 VBA 中)? (我不是在寻找对我匆忙的 VBA 代码的微小改进)
使用yearFracVBA("AA1","AA2")
Function yearFracVBA(aDate1, aDate2)
result = 0
y1 = Application.Evaluate("=YEAR(" & aDate1 & ")")
y2 = Application.Evaluate("=YEAR(" & aDate2 & ")")
For Y = y1 To y2
fraction = 0
If Y = y1 And Y = y2 Then fraction = Application.Evaluate("=YEARFRAC(" & aDate1 & "," & aDate2 & ",1)")
If Y = y1 And Y < y2 Then fraction = Application.Evaluate("=YEARFRAC(" & aDate1 & ",DATE(YEAR(" & aDate1 & "),12,31),1)")
If Y > y1 And Y < y2 Then fraction = 1
If Y > y1 And Y = y2 Then fraction = Application.Evaluate("=YEARFRAC(DATE(YEAR(" & aDate2 & ")-1,12,31)," & aDate2 & ",1)")
result = result + fraction
Next Y
yearFracVBA = result
End Function
以下代码不需要Excel。它适用于任何 VBA 应用程序:
Option Explicit
Public Function YearFracActual(ByVal aDate1 As Date, ByVal aDate2 As Date) As Double
Dim lowerDate As Date
Dim upperDate As Date
Dim year1 As Integer
Dim year2 As Integer
'Get dates in order
If aDate1 > aDate2 Then
lowerDate = aDate2
upperDate = aDate1
Else
lowerDate = aDate1
upperDate = aDate2
End If
'Round down (floor) - exclude any time (hours, minutes, seconds)
lowerDate = VBA.Int(lowerDate)
upperDate = VBA.Int(upperDate)
'Get years
year1 = Year(lowerDate)
year2 = Year(upperDate)
If year1 = year2 Then
YearFracActual = (upperDate - lowerDate) / GetDaysInYear(year1)
Else
Dim lowerFrac As Double
Dim upperFrac As Double
Dim midFrac As Double
lowerFrac = (DateSerial(year1, 12, 31) - lowerDate) / GetDaysInYear(year1)
midFrac = year2 - year1 - 1
upperFrac = (upperDate - DateSerial(year2 - 1, 12, 31)) / GetDaysInYear(year2)
YearFracActual = lowerFrac + midFrac + upperFrac
End If
End Function
Private Function GetDaysInYear(ByVal year_ As Integer) As Integer
If IsLeapYear(year_) Then
GetDaysInYear = 366
Else
GetDaysInYear = 365
End If
End Function
Private Function IsLeapYear(ByVal year_ As Integer) As Boolean
If year_ Mod 400 = 0 Then
IsLeapYear = True
ElseIf year_ Mod 100 = 0 Then
IsLeapYear = False
Else
IsLeapYear = (year_ Mod 4 = 0)
End If
End Function
如果您不想使用 VBA,那么在 Excel 中您可以使用这个公式:
=ABS(YEARFRAC(A1,EOMONTH(A1,12-MONTH(A1)),1)+YEAR(B1)-YEAR(A1)-1+YEARFRAC(B1,EOMONTH(B1,-MONTH(B1)),1))
其中 A1 和 B1 是包含 2 个日期的单元格