← Back to list

Dates for Finance in C++: Year Fractions

When calculating the value of money, a key factor is the time it is held. While this may seem obvious, there are many different ways this…

Rob Blackbourn · 2026-03-28 09:07 · 1 claps · 4.3 min read
#chrono #date #finance #cplusplus
Open on Medium ↗
Wiki topics: ECO · Economy · General

Dates for Finance in C++: Year Fractions

When calculating the value of money, a key factor is the time it is held. While this may seem obvious, there are many different ways this can be calculated.

This is the final article in a series, the last being on generating schedules. The source code can be found in GitHub, in particular the file called [terms.hpp](https://github.com/rob-blackbourn/jetblack-finance-cpp/blob/medium/src/dates/terms.hpp).

The number we are trying to get to is in units of years; so a whole year is 1, half a year is 0.5, etc.

Doing this kind of calculation now seems trivial, but these problems began before computers, so a number of conventions arose to make “pen and paper” solutions more simple. One of these is “30/360” where every month is given 30 days and every year has 360 days. Other conventions include “Actual/365” where the actual number of days in the period is used, but the days in the year is always 365.

There’s a nice discussion of the different day counts on wikipedia, so we’ll spend our time here looking at the code.

30/360

There are a few variations of this which attempt to account for the short month of February. The chrono types for days, months and years, don’t help us here, as we have a standard formula which uses the decomposed date parts.

Here is the basic calculation we’ll be using.

auto periodDays = 360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1);

auto term = periodDays / 360.0;

Lets write a helper function to decompose a date. Note the sequence of casts which lead to everything being an int.

std::tuple<int, int, int> decompose(const year_month_day& date)
{
  auto d = static_cast<unsigned int>(date.day());
  auto m = static_cast<unsigned int>(date.month());
  auto y = static_cast<int>(date.year());
  return {d, m, y};
}

Now we can write our first implementation. The “30/360 Bond Basis” (or 30A/360). For this rule d1 is reduced to 30 days if it is 31, and d2 is reduced to 30 if the adjusted d1 is greater than 29.

// 30/360 Bond Basis

auto [d1, m1, y1] = decompose(date1);
auto [d2, m2, y2] = decompose(date2);

d1 = std::min(d1, 30);

if (d1 > 29)
{
  d2 = std::min(d2, 30);
}

auto periodDays = days{
  360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1)
};

auto term = periodDays.count() / 360.0;

return { periodDays, term };

I decided to return both the days and the term (year fraction), as it makes testing the two components of the calculation possible.

The code for 30E/360 is very similar. I think the code describes the algorithm best.

// 30E/360

auto [d1, m1, y1] = decompose(date1);
auto [d2, m2, y2] = decompose(date2);

d1 = std::min(d1, 30);
d2 = std::min(d2, 30);

auto periodDays = days{
  360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1)
};

auto term = periodDays.count() / 360.0;

return { periodDays, term };

I won’t put all the code here. Checkout the code in GitHub for rest. The last one I’ll preset here is “30E/360 ISDA”. This requires knowing if a date is the last day of February.

The chrono library (and the functions we wrote earlier) provide a simple solution for this.

bool isLastDayOfFebruary(const year_month_day& date)
{
  return date.month() == February && isEndOfMonth(date);
}

With the above, we can implement the algorithm. Note this algorithm also needs to know if the second date was the maturity date.

// 30E/360 ISDA

auto [d1, m1, y1] = decompose(date1);
auto [d2, m2, y2] = decompose(date2);

d1 = std::min(d1, 30);
d2 = std::min(d2, 30);

if (isLastDayOfFebruary(date1))
{
  d1 = 30;
}

if (isLastDayOfFebruary(date2) && date2 != maturity)
{
  d2 = 30;
}

auto periodDays = days{
  360 * (y2 - y1) + 30 * (m2 - m1) + (d2 - d1)
};

auto term = periodDays.count() / 360.0;

return { periodDays, term };

There are a few more variations covered in the repo, including versions that use 365 days instead of 360.

Actual/365

Let’s not waste too much time on this one!

// Actual/360
auto periodDays = (sys_days{date2} - sys_days{date1});

auto term = periodDays.count() / 365.0;

return { periodDays, term };

We have to use sys_days to calculate the period days. I won’t bother with “Actual/360”, “Actual/366”, or “Actual/365.25” as the implementations simply require changing the denominator.

Actual/Actual

The “Actual/365” algorithm assumed there are 365 days in the year. If we want to use the actual days in the year we have to decide what to do with leap years. A moment’s reflection will reveal that we can count each whole year as 1, but the fragments at the start and the end present us with some decisions.

I’ll only present the most simple convention here, “Actual/Actual ISDA”.

// Actual/Actual ISDA

auto accrualDate = date1;
auto startPeriodDays = days{0};
auto startTerm = 0.0;

// Handle the start stub.
if (date1.year() < date2.year())
{
  // Set the accrual date to 1st January.
  accrualDate = (date1.year() + years{1}) / January / 1d;
  startPeriodDays = sys_days{accrualDate} - sys_days{date1};
  startTerm = startPeriodDays.count() /
      static_cast<double>(daysInYear(date1.year()).count());
}

// Get the whole years.
while (accrualDate.year() < date2.year())
{
  startTerm += 1;
  startPeriodDays += daysInYear(accrualDate.year());
  accrualDate += years {1};
}

// Handle the end stub.
auto endPeriodDays = sys_days{date2} - sys_days{accrualDate};
auto endTerm = endPeriodDays.count() /
    static_cast<double>(daysInYear(date2.year()).count());

auto periodDays = startPeriodDays + endPeriodDays;
double term = startTerm + endTerm;

return { periodDays, term };

There are many variations of the Actual/Actual conventions. The ISDA convention counts whole years from 1st January to 1st January. Another approach would be to count whole years from the start (or end) date, and add the end (or start) stub as the fractional part. Solutions for this are provided in the repo.

Year Fraction

We could package these routines up in a number of ways. I’ll take the obvious approach of a big long switch statement keyed by enums. Here’s a code fragment of how it’s going to look.

// Year Fraction

enum class EDayCount
{ 
  Actual_d360,
  Actual_d365,
  ...
};

std::tuple<days,double>
getTerm(
  const year_month_day& date1,
  const year_month_day& date2,
  EDayCount dayCount,
  const std::optional<year_month_day> maturity = {})
{
  switch(dayCount)
  {
    case EDayCount::Actual_d360:
    {
      auto periodDays = (sys_days{date2} - sys_days{date1});

      auto term = periodDays.count() / 360.0;

      return { periodDays, term };
    }
    case EDayCount::Actual_d365:
    {
      ...
    }

    ...

    default:
      throw std::invalid_argument("invalid daycount");
  }
}

As I mentioned previously, I decided to return both the days and the term. As we mostly want the term, it’s handy to have a convenience function.

double
yearFrac(
  const year_month_day& start,
  const year_month_day& end,
  EDayCount dayCount)
{
  const auto& [d, t] = getTerm(start, end, dayCount);
  return t;
}

Wrap Up

This turned in to quite a long sequence of articles. I learned a lot about chrono in the process.

If you notice any errors, please raise an isssue on the GitHub repo, or make a comment.

Good luck with your coding.


메타데이터
post_id
fdcb754e507f
slug
dates-for-finance-in-c-year-fractions-fdcb754e507f
url
https://medium.com/@rob-blackbourn/dates-for-finance-in-c-year-fractions-fdcb754e507f
canonical_url
https://medium.com/@rob-blackbourn/dates-for-finance-in-c-year-fractions-fdcb754e507f
author_url
https://medium.com/@rob-blackbourn
status
ok
fetched_at
2026-07-11 16:18:17