在 Python 中复制 Excel 中的 YEARFRAC() 函数

问题描述 投票:0回答:5

所以我使用 python 来自动化一些我必须在 excel 中完成的重复性任务。我需要做的计算之一需要使用yearfrac()。这在Python中被复制了吗?

我找到了这个 但对于我尝试的值来说这是不正确的。

(从 12/19/2011 到 3/31/17,yearfrac() 给出 5.2833333333333300000,但链接中的 python 函数给出 5.2807978099335156。)

python excel algorithm xlwings
5个回答
7
投票

我发现 来自实际 Office 开发人员的电子邮件线程提供了

YEARFRAC
算法的 VBA 实现。

Public Function FIsLeapYear(Year As Integer) As Boolean
    If (Year Mod 4) > 0 Then
        FIsLeapYear = False
    ElseIf (Year Mod 100) > 0 Then
        FIsLeapYear = True
    ElseIf (Year Mod 400) = 0 Then
        FIsLeapYear = True
    Else
        FIsLeapYear = False
    End If

End Function

Public Function FIsEndOfMonth(Day As Integer, Month As Integer, Year As Integer) As Boolean
    Select Case Month
        Case 1, 3, 5, 7, 8, 10, 12
            FIsEndOfMonth = (Day = 31)
        Case 4, 6, 9, 11
            FIsEndOfMonth = (Day = 30)
        Case 2
            If FIsLeapYear(Year) Then
                FIsEndOfMonth = (Day = 29)
            Else
                FIsEndOfMonth = (Day = 28)
            End If
    End Select

End Function



Public Function Days360(StartYear As Integer, EndYear As Integer, StartMonth As Integer, EndMonth As Integer, StartDay As Integer, EndDay As Integer) As Integer

    Days360 = ((EndYear - StartYear) * 360) + ((EndMonth - StartMonth) * 30) + (EndDay - StartDay)
End Function


Public Function TmpDays360Nasd(StartDate As Date, EndDate As Date, Method As Integer, UseEom As Boolean) As Integer
    Dim StartDay As Integer
    Dim StartMonth As Integer
    Dim StartYear As Integer
    Dim EndDay As Integer
    Dim EndMonth As Integer
    Dim EndYear As Integer

    StartDay = Day(StartDate)
    StartMonth = Month(StartDate)
    StartYear = Year(StartDate)
    EndDay = Day(EndDate)
    EndMonth = Month(EndDate)
    EndYear = Year(EndDate)

    If (EndMonth = 2 And FIsEndOfMonth(EndDay, EndMonth, EndYear)) And ((StartMonth = 2 And FIsEndOfMonth(StartDay, StartMonth, StartYear)) Or Method = 3) Then
        EndDay = 30
    End If
    If EndDay = 31 And (StartDay >= 30 Or Method = 3) Then
        EndDay = 30
    End If
    If StartDay = 31 Then
       StartDay = 30
    End If
    If (UseEom And StartMonth = 2 And FIsEndOfMonth(StartDay, StartMonth, StartYear)) Then
        StartDay = 30
    End If
    TmpDays360Nasd = Days360(StartYear, EndYear, StartMonth, EndMonth, StartDay, EndDay)

End Function

Public Function TmpDays360Euro(StartDate As Date, EndDate As Date)
    Dim StartDay As Integer
    Dim StartMonth As Integer
    Dim StartYear As Integer
    Dim EndDay As Integer
    Dim EndMonth As Integer
    Dim EndYear As Integer

    StartDay = Day(StartDate)
    StartMonth = Month(StartDate)
    StartYear = Year(StartDate)
    EndDay = Day(EndDate)
    EndMonth = Month(EndDate)
    EndYear = Year(EndDate)

    If (StartDay = 31) Then
        StartDay = 30
    End If
    If (EndDay = 31) Then
        EndDay = 30
    End If
    TmpDays360Euro = Days360(StartYear, EndYear, StartMonth, EndMonth, StartDay, EndDay)
End Function

Public Function TmpDiffDates(StartDate As Date, EndDate As Date, Basis As Integer) As Integer


    Select Case Basis
        Case 0 'atpmBasis30360
            TmpDiffDates = TmpDays360Nasd(StartDate, EndDate, 0, True)

        Case 1, 2, 3 'atpmBasisActual atpmBasisActual360 atpmBasisActual365 -- use actual count of days
            TmpDiffDates = DateDiff("d", StartDate, EndDate)

        Case 4 'atpmBasisE30360
            TmpDiffDates = TmpDays360Euro(StartDate, EndDate)
    End Select

End Function

Public Function TmpCalcAnnualBasis(StartDate As Date, EndDate As Date, Basis As Integer) As Double
    Dim StartDay As Integer
    Dim StartMonth As Integer
    Dim StartYear As Integer
    Dim EndDay As Integer
    Dim EndMonth As Integer
    Dim EndYear As Integer
    Dim iYear As Integer

    Select Case Basis
        Case 0, 2, 4  'atpmBasis30360 atpmBasisActual360 atpmBasisE30360
            TmpCalcAnnualBasis = 360
        Case 3 'atpmBasisActual365
            TmpCalcAnnualBasis = 365
        Case 1 ' atpmBasisActual
            StartDay = Day(StartDate)
            StartMonth = Month(StartDate)
            StartYear = Year(StartDate)
            EndDay = Day(EndDate)
            EndMonth = Month(EndDate)
            EndYear = Year(EndDate)

            If (StartYear = EndYear) Then
                If FIsLeapYear(StartYear) Then
                    TmpCalcAnnualBasis = 366
                Else
                    TmpCalcAnnualBasis = 365
                End If
            ElseIf ((EndYear - 1) = StartYear) And ((StartMonth > EndMonth) Or ((StartMonth = EndMonth) And StartDay >= EndDay)) Then
                If FIsLeapYear(StartYear) Then
                    If StartMonth < 2 Or (StartMonth = 2 And StartDay <= 29) Then
                        TmpCalcAnnualBasis = 366
                    Else
                        TmpCalcAnnualBasis = 365
                    End If
                ElseIf FIsLeapYear(EndYear) Then
                    If EndMonth > 2 Or (EndMonth = 2 And EndDay = 29) Then
                        TmpCalcAnnualBasis = 366
                    Else
                        TmpCalcAnnualBasis = 365
                    End If
                Else
                    TmpCalcAnnualBasis = 365
                End If
            Else
                For iYear = StartYear To EndYear
                    If FIsLeapYear(iYear) Then
                        TmpCalcAnnualBasis = TmpCalcAnnualBasis + 366
                    Else
                        TmpCalcAnnualBasis = TmpCalcAnnualBasis + 365
                    End If
                Next iYear
                TmpCalcAnnualBasis = TmpCalcAnnualBasis / (EndYear - StartYear + 1)

            End If
    End Select

End Function


Public Function TmpYearFrac(StartDate As Date, EndDate As Date, Basis As Integer)
    Dim nNumerator As Integer
    Dim nDenom As Double

    nNumerator = TmpDiffDates(StartDate, EndDate, Basis)
    nDenom = TmpCalcAnnualBasis(StartDate, EndDate, Basis)

    TmpYearFrac = nNumerator / nDenom
End Function

=end VBA source code for YearFrac

#-----------------------------------------------------------------------------
# Ruby version starts here, with VBA code in comment blocks for comparison ...
#-----------------------------------------------------------------------------

Public Function FIsLeapYear(Year As Integer) As Boolean
    If (Year Mod 4) > 0 Then
        FIsLeapYear = False
    ElseIf (Year Mod 100) > 0 Then
        FIsLeapYear = True
    ElseIf (Year Mod 400) = 0 Then
        FIsLeapYear = True
    Else
        FIsLeapYear = False
    End If

End Function

Public Function FIsEndOfMonth(Day As Integer, Month As Integer, Year As Integer) As Boolean
    Select Case Month
        Case 1, 3, 5, 7, 8, 10, 12
            FIsEndOfMonth = (Day = 31)
        Case 4, 6, 9, 11
            FIsEndOfMonth = (Day = 30)
        Case 2
            If FIsLeapYear(Year) Then
                FIsEndOfMonth = (Day = 29)
            Else
                FIsEndOfMonth = (Day = 28)
            End If
    End Select

End Function



Public Function Days360(StartYear As Integer, EndYear As Integer, StartMonth As Integer, EndMonth As Integer, StartDay As Integer, EndDay As Integer) As Integer

    Days360 = ((EndYear - StartYear) * 360) + ((EndMonth - StartMonth) * 30) + (EndDay - StartDay)
End Function


Public Function TmpDays360Nasd(StartDate As Date, EndDate As Date, Method As Integer, UseEom As Boolean) As Integer
    Dim StartDay As Integer
    Dim StartMonth As Integer
    Dim StartYear As Integer
    Dim EndDay As Integer
    Dim EndMonth As Integer
    Dim EndYear As Integer

    StartDay = Day(StartDate)
    StartMonth = Month(StartDate)
    StartYear = Year(StartDate)
    EndDay = Day(EndDate)
    EndMonth = Month(EndDate)
    EndYear = Year(EndDate)

    If (EndMonth = 2 And FIsEndOfMonth(EndDay, EndMonth, EndYear)) And ((StartMonth = 2 And FIsEndOfMonth(StartDay, StartMonth, StartYear)) Or Method = 3) Then
        EndDay = 30
    End If
    If EndDay = 31 And (StartDay >= 30 Or Method = 3) Then
        EndDay = 30
    End If
    If StartDay = 31 Then
       StartDay = 30
    End If
    If (UseEom And StartMonth = 2 And FIsEndOfMonth(StartDay, StartMonth, StartYear)) Then
        StartDay = 30
    End If
    TmpDays360Nasd = Days360(StartYear, EndYear, StartMonth, EndMonth, StartDay, EndDay)

End Function

Public Function TmpDays360Euro(StartDate As Date, EndDate As Date)
    Dim StartDay As Integer
    Dim StartMonth As Integer
    Dim StartYear As Integer
    Dim EndDay As Integer
    Dim EndMonth As Integer
    Dim EndYear As Integer

    StartDay = Day(StartDate)
    StartMonth = Month(StartDate)
    StartYear = Year(StartDate)
    EndDay = Day(EndDate)
    EndMonth = Month(EndDate)
    EndYear = Year(EndDate)

    If (StartDay = 31) Then
        StartDay = 30
    End If
    If (EndDay = 31) Then
        EndDay = 30
    End If
    TmpDays360Euro = Days360(StartYear, EndYear, StartMonth, EndMonth, StartDay, EndDay)
End Function

Public Function TmpDiffDates(StartDate As Date, EndDate As Date, Basis As Integer) As Integer


    Select Case Basis
        Case 0 'atpmBasis30360
            TmpDiffDates = TmpDays360Nasd(StartDate, EndDate, 0, True)

        Case 1, 2, 3 'atpmBasisActual atpmBasisActual360 atpmBasisActual365 -- use actual count of days
            TmpDiffDates = DateDiff("d", StartDate, EndDate)

        Case 4 'atpmBasisE30360
            TmpDiffDates = TmpDays360Euro(StartDate, EndDate)
    End Select

End Function

Public Function TmpCalcAnnualBasis(StartDate As Date, EndDate As Date, Basis As Integer) As Double
    Dim StartDay As Integer
    Dim StartMonth As Integer
    Dim StartYear As Integer
    Dim EndDay As Integer
    Dim EndMonth As Integer
    Dim EndYear As Integer
    Dim iYear As Integer

    Select Case Basis
        Case 0, 2, 4  'atpmBasis30360 atpmBasisActual360 atpmBasisE30360
            TmpCalcAnnualBasis = 360
        Case 3 'atpmBasisActual365
            TmpCalcAnnualBasis = 365
        Case 1 ' atpmBasisActual
            StartDay = Day(StartDate)
            StartMonth = Month(StartDate)
            StartYear = Year(StartDate)
            EndDay = Day(EndDate)
            EndMonth = Month(EndDate)
            EndYear = Year(EndDate)

            If (StartYear = EndYear) Then
                If FIsLeapYear(StartYear) Then
                    TmpCalcAnnualBasis = 366
                Else
                    TmpCalcAnnualBasis = 365
                End If
            ElseIf ((EndYear - 1) = StartYear) And ((StartMonth > EndMonth) Or ((StartMonth = EndMonth) And StartDay >= EndDay)) Then
                If FIsLeapYear(StartYear) Then
                    If StartMonth < 2 Or (StartMonth = 2 And StartDay <= 29) Then
                        TmpCalcAnnualBasis = 366
                    Else
                        TmpCalcAnnualBasis = 365
                    End If
                ElseIf FIsLeapYear(EndYear) Then
                    If EndMonth > 2 Or (EndMonth = 2 And EndDay = 29) Then
                        TmpCalcAnnualBasis = 366
                    Else
                        TmpCalcAnnualBasis = 365
                    End If
                Else
                    TmpCalcAnnualBasis = 365
                End If
            Else
                For iYear = StartYear To EndYear
                    If FIsLeapYear(iYear) Then
                        TmpCalcAnnualBasis = TmpCalcAnnualBasis + 366
                    Else
                        TmpCalcAnnualBasis = TmpCalcAnnualBasis + 365
                    End If
                Next iYear
                TmpCalcAnnualBasis = TmpCalcAnnualBasis / (EndYear - StartYear + 1)

            End If
    End Select

End Function


Public Function TmpYearFrac(StartDate As Date, EndDate As Date, Basis As Integer)
    Dim nNumerator As Integer
    Dim nDenom As Double

    nNumerator = TmpDiffDates(StartDate, EndDate, Basis)
    nDenom = TmpCalcAnnualBasis(StartDate, EndDate, Basis)

    TmpYearFrac = nNumerator / nDenom
End Function

更深入地挖掘,我发现了一篇文章,它提供了一个看起来非常像 Python 的伪代码实现。没有时间测试它,下面是伪代码:

def appears_le_year(date1, date2):
    # Returns True if date1 and date2 "appear" to be 1 year or less apart.
    # This compares the values of year, month, and day directly to each other.
    # Requires date1 <= date2; returns boolean. Used by basis 1.
    if date1.year == date2.year:
        return True
    if (((date1.year + 1) == date2.year) and
        ((date1.month > date2.month) or
        ((date1.month == date2.month) and (date1.day >= date2.day)))):
        return True
    return False

def basis0(date1,date2):
    # Swap so date1 <= date2 in all cases:
    if date1 > date2:
        date1, date2 = date2, date1
    if date1 == date2:
        return 0.0

    # Change day-of-month for purposes of calculation.
    date1day, date1month, date1year = date1.day, date1.month, date1.year
    date2day, date2month, date2year = date2.day, date2.month, date2.year
    if (date1day == 31 and date2day == 31):
        date1day = 30
        date2day = 30
    elif date1day == 31:
        date1day = 30
    elif (date1day == 30 and date2day == 31):
        date2day = 30
    # Note: If date2day==31, it STAYS 31 if date1day < 30.
    # Special fixes for February:
    elif (date1month == 2 and date2month == 2 and
          last_day_of_month(date1) and
          last_day_of_month(date2)):
        date1day = 30 # Set the day values to be equal
        date2day = 30
    elif date1month == 2 and last_day_of_month(date1):
        date1day = 30 # "Illegal" Feb 30 date.

    daydiff360 = ((date2day + date2month * 30 + date2year * 360) -
                  (date1day + date1month * 30 + date1year * 360))
    return daydiff360 / 360


def basis1(date1,date2):
    # Swap so date1 <= date2 in all cases:
    if date1 > date2:
        date1, date2 = date2, date1
    if date1 == date2:
        return 0.0
    if appears_le_year(date1, date2):
        if (date1.year == date2.year and is_leap_year(date1.year)):
            year_length = 366.
        elif (feb29_between(date1, date2) or
            (date2.month == 2 and date2.day == 29)): # fixed, 2008-04-18
    year_length = 366.
    else:
    year_length = 365.
    return diffdays(date1, date2) / year_length
    else:
    num_years = (date2.year - date1.year) + 1
    days_in_years = diffdays(date(date1.year, 1, 1), date(date2.year+1, 1, 1))
    average_year_length = days_in_years / num_years
    return diffdays(date1, date2) / average_year_length

def basis2(date1,date2):
    # Swap so date1 <= date2 in all cases:
    if date1 > date2:
        date1, date2 = date2, date1
    return diffdays(date1, date2) / 360.

def basis3(date1,date2):
    # Swap so date1 <= date2 in all cases:
    if date1 > date2:
        date1, date2 = date2, date1
    return diffdays(date1, date2) / 365.

def basis4(date1,date2):
    # Swap so date1 <= date2 in all cases:
    if date1 > date2:
        date1, date2 = date2, date1
    if date1 == date2:
        return 0.0
    # Change day-of-month for purposes of calculation.
    date1day, date1month, date1year = date1.day, date1.month, date1.year
    date2day, date2month, date2year = date2.day, date2.month, date2.year
    if date1day == 31:
        date1day = 30
    if date2day == 31:
        date2day = 30
    # Remarkably, do NOT change Feb. 28 or 29 at ALL.

    daydiff360 = ( (date2day + date2month * 30 + date2year * 360) -
    (date1day + date1month * 30 + date1year * 360))
    return daydiff360 / 360

2
投票
import datetime

def IsLeapYear(year):
    if year % 4 > 0:
        IsLeapYear = False
    elif year % 100 > 0:
        IsLeapYear = True
    elif year % 400 == 0:
        IsLeapYear = True
    else:
        IsLeapYear = False
    return IsLeapYear

def IsEndOfMonth(day, month, year):
    if month in [1,3,5,7,8,10,12]:
        IsEndOfMonth = (day == 31)
    if month in [4,6,9,11]:
        IsEndOfMonth = (day == 30)
    if month in [2]:
        if IsLeapYear(year):
            IsEndOfMonth = (day == 29)
        else:
            IsEndOfMonth = (day == 28)
    return IsEndOfMonth

def Days360(StartYear, EndYear, StartMonth, EndMonth, StartDay, EndDay):
    Days360 = ((EndYear - StartYear) * 360) + ((EndMonth - StartMonth) * 30) + (EndDay - StartDay)
    return Days360

def TmpDays360Nasd(StartDate, EndDate, Method, UseEom):
    StartDay = StartDate.day
    StartMonth = StartDate.month
    StartYear = StartDate.year
    EndDay = EndDate.day
    EndMonth = EndDate.month
    EndYear = EndDate.year
    if (EndMonth == 2 and IsEndOfMonth(EndDay, EndMonth, EndYear)) and ((StartMonth == 2 and IsEndOfMonth(StartDay, StartMonth, StartYear)) or Method == 3):
        EndDay = 30     
    if EndDay == 31 and (StartDay >= 30 or Method == 3):
        EndDay = 30
    if StartDay == 31:
        StartDay = 30
    if (UseEom and StartMonth == 2 and IsEndOfMonth(StartDay, StartMonth, StartYear)):
        StartDay = 30   
    return Days360(StartYear, EndYear, StartMonth, EndMonth, StartDay, EndDay)

def TmpDays360Euro(StartDate, EndDate):
    StartDay = StartDate.day
    StartMonth = StartDate.month
    StartYear = StartDate.year
    EndDay = EndDate.day
    EndMonth = EndDate.month
    EndYear = EndDate.year
    if StartDay == 31:
        StartDay = 30
    if EndDay == 31:
        EndDay = 30
    return Days360(StartYear, EndYear, StartMonth, EndMonth, StartDay, EndDay)

def DateDiff(StartDate, EndDate):
    return abs((StartDate - EndDate).days)

def TmpDiffDates(StartDate, EndDate, Basis):
    if Basis in [0]:
        TmpDiffDates = TmpDays360Nasd(StartDate, EndDate, 0, True)
    elif Basis in [1,2,3]:
        TmpDiffDates = DateDiff(StartDate, EndDate)
    elif Basis in [4]:
       TmpDiffDates = TmpDays360Euro(StartDate, EndDate)
    return TmpDiffDates

def TmpCalcAnnualBasis(StartDate, EndDate, Basis):
    if Basis in [0,2,4]:
        TmpCalcAnnualBasis = 360
    elif Basis in [3]:
        TmpCalcAnnualBasis = 365
    elif Basis in [1]:
        StartDay = StartDate.day
        StartMonth = StartDate.month
        StartYear = StartDate.year
        EndDay = EndDate.day
        EndMonth = EndDate.month
        EndYear = EndDate.year

        if StartYear == EndYear:
            if IsLeapYear(StartYear):
                TmpCalcAnnualBasis = 366
            else:
                TmpCalcAnnualBasis = 365
        elif (EndYear - 1) == StartYear and (StartMonth > EndMonth or (StartMonth == EndMonth and StartDay >= EndDay)):
            if IsLeapYear(StartYear):        
                if StartMonth < 2 or (StartMonth == 2 and StartDay <= 29):
                    TmpCalcAnnualBasis = 366
                else:
                    TmpCalcAnnualBasis = 365
            elif IsLeapYear(EndYear):
                if EndMonth > 2 or (EndMonth == 2 and EndDay == 29):
                    TmpCalcAnnualBasis = 366
                else:
                    TmpCalcAnnualBasis = 365  
            else:
                TmpCalcAnnualBasis = 365
        else:
            TmpCalcAnnualBasis = 0
            for iYear in range(StartYear, EndYear + 1 ): 
                if IsLeapYear(iYear):
                    TmpCalcAnnualBasis = TmpCalcAnnualBasis + 366
                else:
                    TmpCalcAnnualBasis = TmpCalcAnnualBasis + 365
            TmpCalcAnnualBasis = TmpCalcAnnualBasis / (EndYear - StartYear + 1)
    return TmpCalcAnnualBasis

def YearFrac(StartDate, EndDate, Basis):
    Numerator = TmpDiffDates(StartDate, EndDate, Basis)
    Denom = TmpCalcAnnualBasis(StartDate, EndDate, Basis)
    YearFrac = Numerator/Denom
    return YearFrac


# TESTING    
start = datetime.datetime(1978, 2, 28)
end = datetime.datetime(2020, 5, 17)

x0 = YearFrac(start, end, 0)
x1 = YearFrac(start, end, 1)
x2 = YearFrac(start, end, 2)
x3 = YearFrac(start, end, 3)
x4 = YearFrac(start, end, 4)

1
投票

Excel 的 YEARFRAC 有多种模式 (

basis
)。如果您将
basis
设置为 0 或省略,则默认使用 US NASD 30/360 计算。

您可以在 python 中执行此操作,因为该公式很容易遵循下面的等式

这是一个例子

In [1]: import datetime as dt

In [2]: d1 = dt.date(2015, 12, 19)

In [3]: d2 = dt.date(2020, 10, 19)

In [4]: def date_factor(date1, date2):
...:     """
...:     This formula follows the US (NASD) calculation
...:     that is used in Excel's YEARFRAC by default
...:     https://www.fundmanagersoftware.com/help/def_accrued_interest.html
...:     """
...:     num = (360 * (date2.year - date1.year) + (30*(date2.month - date1.month) + (date2.day - date1.day)))
...:     return num / 360
...:

In [5]: date_factor(d1, d2)
Out[5]: 4.833333333333333

In [6]:

与 Excel 匹配


1
投票

这里是来自所提供的伪代码的 Python 代码的完整实现。它需要日期时间库。输入是日期时间对象。

### YEARFRAC() ###
from datetime import datetime

def appears_le_year(date1, date2):
    ## Used by basis 1 ##
    # Returns True if date1 and date2 "appear" to be 1 year or less apart.
    # This compares the values of year, month, and day directly to each other.
    # Requires date1 <= date2; returns boolean.
    if date1.year == date2.year:
        return True
    if (((date1.year + 1) == date2.year) and
        ((date1.month > date2.month) or
        ((date1.month == date2.month) and (date1.day >= date2.day)))):
        return True
    return False

def is_leap_year(year):
    return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)

def diffdays(date1, date2):
    return abs((date2 - date1).days)

def feb29_between(date1, date2):
    year1 = date1.year
    year2 = date2.year
    if not year1 < year2:
        year1, year2 = year2, year1
    if year1 == year2:
        if not is_leap_year(year1):
            return False
        else:
            feb29 = datetime.strptime(str(year1) + '-02-29', '%Y-%m-%d')
            return (date1 <= feb29 and date2 >= feb29)
    else:
        month1 = date1.month
        month2 = date2.month
        if month1 > 2:
            if month2 < 2:
                return False
            elif month2 == 2:
                return date2.day == 29
            else:
                return True
        else:
            return is_leap_year(year1)

def last_day_of_month(date):
    next_month = date.replace(day=28) + timedelta(days=4)
    last_day = next_month - timedelta(days=next_month.day)
    return date == last_day

def basis0(date1, date2): #US 30360
        # Swap so date1 <= date2 in all cases:
    if date1 > date2:
        date1, date2 = date2, date1
    if date1 == date2:
        return 0.0

    # Change day-of-month for purposes of calculation.
    date1day, date1month, date1year = date1.day, date1.month, date1.year
    date2day, date2month, date2year = date2.day, date2.month, date2.year
    if (date1day == 31 and date2day == 31):
        date1day = 30
        date2day = 30
    elif date1day == 31:
        date1day = 30
    elif (date1day == 30 and date2day == 31):
        date2day = 30
    # Note: If date2day==31, it STAYS 31 if date1day < 30.
    # Special fixes for February:
    elif (date1month == 2 and date2month == 2 and last_day_of_month(date1) and last_day_of_month(date2)):
        date1day = 30 # Set the day values to be equal
        date2day = 30
    elif date1month == 2 and last_day_of_month(date1):
        date1day = 30 # "Illegal" Feb 30 date.

    daydiff360 = ((date2day + date2month * 30 + date2year * 360) - (date1day + date1month * 30 + date1year * 360))
    return daydiff360 / 360


def basis1(date1,date2): #Actual/actual
    # Swap so date1 <= date2 in all cases:
    if date1 > date2:
        date1, date2 = date2, date1
    if date1 == date2:
        return 0.0
    if appears_le_year(date1, date2):
        if (date1.year == date2.year and is_leap_year(date1.year)):
            year_length = 366.
        elif (feb29_between(date1, date2) or (date2.month == 2 and date2.day == 29)): # fixed, 2008-04-18
            year_length = 366.
        else:
            year_length = 365.
        return diffdays(date1, date2) / year_length
    else:
        num_years = (date2.year - date1.year) + 1
        days_in_years = diffdays(date(date1.year, 1, 1), date(date2.year+1, 1, 1))
        average_year_length = days_in_years / num_years
    return diffdays(date1, date2) / average_year_length

def basis2(date1,date2): #Actual/360
    # Swap so date1 <= date2 in all cases:
    if date1 > date2:
        date1, date2 = date2, date1
    return diffdays(date1, date2) / 360.

def basis3(date1,date2): #Actual/365
    # Swap so date1 <= date2 in all cases:
    if date1 > date2:
        date1, date2 = date2, date1
    return diffdays(date1, date2) / 365.

def basis4(date1,date2): #EU 30360
    # Swap so date1 <= date2 in all cases:
    if date1 > date2:
        date1, date2 = date2, date1
    if date1 == date2:
        return 0.0
    # Change day-of-month for purposes of calculation.
    date1day, date1month, date1year = date1.day, date1.month, date1.year
    date2day, date2month, date2year = date2.day, date2.month, date2.year
    if date1day == 31:
        date1day = 30
    if date2day == 31:
        date2day = 30
    # Remarkably, do NOT change Feb. 28 or 29 at ALL.

    daydiff360 = ( (date2day + date2month * 30 + date2year * 360) - (date1day + date1month * 30 + date1year * 360))
    return daydiff360 / 360

使用示例:

date1 = datetime.strptime('2000-01-01', '%Y-%m-%d')
date2 = datetime.strptime('2000-06-06', '%Y-%m-%d')
print(basis0(date1, date2),
      basis1(date1, date2),
      basis2(date1, date2),
      basis3(date1, date2),
      basis4(date1, date2))

0
投票
`appears_le_year <- function(date1, date2) {
  if (year(date1) == year(date2)) {
    return(TRUE)
  }
  if (((year(date1) + 1) == year(date2)) &&
      ((month(date1) > month(date2)) ||
       ((month(date1) == month(date2)) && (mday(date1) >= mday(date2))))) {
    return(TRUE)
  }
  return(FALSE)
}

is_leap_year <- function(year) {
  return(year %% 4 == 0 && (year %% 100 != 0 || year %% 400 == 0))
}

diffdays <- function(date1, date2) {
  return(abs(as.numeric(date2 - date1, units = "days")))
}

feb29_between <- function(date1, date2) {
  year1 <- year(date1)
  year2 <- year(date2)
  if (!(year1 < year2)) {
    year1 <- year2
    year2 <- year(date1)
  }
  if (year1 == year2) {
    if (!is_leap_year(year1)) {
      return(FALSE)
    } else {
      feb29 <- as.Date(paste(year1, '-02-29'))
      return(date1 <= feb29 & date2 >= feb29)
    }
  } else {
    month1 <- month(date1)
    month2 <- month(date2)
    if (month1 > 2) {
      if (month2 < 2) {
        return(FALSE)
      } else if (month2 == 2) {
        return(mday(date2) == 29)
      } else {
        return(TRUE)
      }
    } else {
      return(is_leap_year(year1))
    }
  }
}

last_day_of_month <- function(date) {
  next_month <- date + months(1)
  last_day <- next_month - days(mday(next_month))
  return(date == last_day)
}

YearFracBasis0 <- function(date1, date2) {
  if (date1 > date2) {
    temp <- date1
    date1 <- date2
    date2 <- temp
  }
  if (date1 == date2) {
    return(0.0)
  }
  
  date1day <- mday(date1)
  date1month <- month(date1)
  date1year <- year(date1)
  
  date2day <- mday(date2)
  date2month <- month(date2)
  date2year <- year(date2)
  
  if ((date1day == 31 & date2day == 31)) {
    date1day <- 30
    date2day <- 30
  } else if (date1day == 31) {
    date1day <- 30
  } else if ((date1day == 30 & date2day == 31)) {
    date2day <- 30
  }
  
  if ((month(date1) == 2 & month(date2) == 2 & last_day_of_month(date1) & last_day_of_month(date2))) {
    date1day <- 30
    date2day <- 30
  } else if (month(date1) == 2 & last_day_of_month(date1)) {
    date1day <- 30
  }
  
  daydiff360 <- ((date2day + date2month * 30 + date2year * 360) - 
                   (date1day + date1month * 30 + date1year * 360))
  
  return(daydiff360 / 360)
}

YearFracBasis1 <- function(date1, date2) {
  if (date1 > date2) {
    temp <- date1
    date1 <- date2
    date2 <- temp
  }
  if (date1 == date2) {
    return(0.0)
  }
  
  if (appears_le_year(date1, date2)) {
    if ((year(date1) == year(date2) & is_leap_year(year(date1)))) {
      year_length <- 366
    } else if (feb29_between(date1, date2) || (month(date2) == 2 & mday(date2) == 29)) {
      year_length <- 366
    } else {
      year_length <- 365
    }
    return(diffdays(date1, date2) / year_length)
  } else {
    num_years <- (year(date2) - year(date1)) + 1
    days_in_years <- diffdays(as.Date(paste(year(date1), '01-01')), as.Date(paste(year(date2) + 1, '01-01')))
    average_year_length <- days_in_years / num_years
    return(diffdays(date1, date2) / average_year_length)
  }
}

YearFracBasis2 <- function(date1, date2) {
  if (date1 > date2) {
    temp <- date1
    date1 <- date2
    date2 <- temp
  }
  return(diffdays(date1, date2) / 360)
}

YearFracBasis3 <- function(date1, date2) {
  if (date1 > date2) {
    temp <- date1
    date1 <- date2
    date2 <- temp
  }
  return(diffdays(date1, date2) / 365)
}

YearFracBasis4 <- function(date1, date2) {
  if (date1 > date2) {
    temp <- date1
    date1 <- date2
    date2 <- temp
  }
  if (date1 == date2) {
    return(0.0)
  }
  
  date1day <- mday(date1)
  date1month <- month(date1)
  date1year <- year(date1)
  
  date2day <- mday(date2)
  date2month <- month(date2)
  date2year <- year(date2)
  
  if (date1day == 31) {
    date1day <- 30
  }
  if (date2day == 31) {
    date2day <- 30
  }
  
  daydiff360 <- ((date2day + date2month * 30 + date2year * 360) - 
                   (date1day + date1month * 30 + date1year * 360))
  
  return(daydiff360 / 360)
}`
© www.soinside.com 2019 - 2024. All rights reserved.