2つのDateTimeから経過年数(満年数)などを求める方法について。

経過日数・経過時間

DateTime(またはDateTimeOffset)同士を減算すると、結果としてTimeSpan構造体が返される。 TimeSpanにはTotalDaysプロパティといったプロパティが用意されているので、これを参照することで経過日数・経過秒数などを求めることができる。

using System;

class Sample {
  static void Main()
  {
    var baseDay = new DateTime(2011, 6, 18);
    var day = new DateTime(2011, 6, 20);

    TimeSpan elapsedSpan = day - baseDay;

    Console.WriteLine("経過日数: {0}日", elapsedSpan.TotalDays);
    Console.WriteLine("経過時間: {0}時間", elapsedSpan.TotalHours);
    Console.WriteLine("経過秒数: {0}秒", elapsedSpan.TotalSeconds);
  }
}
Imports System

Class Sample
  Shared Sub Main()
    Dim baseDay As New DateTime(2011, 6, 18)
    Dim day As New DateTime(2011, 6, 20)

    Dim elapsedSpan As TimeSpan = day - baseDay

    Console.WriteLine("経過日数: {0}日", elapsedSpan.TotalDays)
    Console.WriteLine("経過時間: {0}時間", elapsedSpan.TotalHours)
    Console.WriteLine("経過秒数: {0}秒", elapsedSpan.TotalSeconds)
  End Sub
End Module
実行結果
経過日数: 2日
経過時間: 48時間
経過秒数: 172800秒

経過月数・経過年数

一方、TimeSpanには経過月数・経過年数を求めるプロパティは用意されていない。 経過日数(TimeSpan.TotalDays)から求めるにしても期間をまたがる月毎の日数の違いや閏年などを考慮して計算する必要がある。

経過月数(満月数)を求めるには、まず二つのDateTimeから月数のみの単純な差を求め、次に日付を比較し同日に達していなければ-1することで求めることができる。 なお以下の例では、日付の日部分が期間終了月の日数を超える場合には末日に達した時点で満1ヶ月としている(詳細はサンプル中のコメントおよび実行結果を参照)。

また経過年数(満年数)を求めるには、先に経過月数(満月数)を求め、満12ヶ月を満1年として求める。

満月数・満年数の取得
using System;

class Sample {
  // 基準日baseDayからdayまでの経過月数を求める
  // (DateTimeの時間部分に付いては考慮しない)
  public static int GetElapsedMonths(DateTime baseDay, DateTime day)
  {
    if (day < baseDay)
      // 日付が基準日より前の場合は例外とする
      throw new ArgumentException();

    // 経過月数を求める(満月数を考慮しない単純計算)
    var elapsedMonths = (day.Year - baseDay.Year) * 12 + (day.Month - baseDay.Month);

    if (baseDay.Day <= day.Day)
      // baseDayの日部分がdayの日部分以上の場合は、その月を満了しているとみなす
      // (例:1月30日→3月30日以降の場合は満(3-1)ヶ月)
      return elapsedMonths;
    else if (day.Day == DateTime.DaysInMonth(day.Year, day.Month) && day.Day <= baseDay.Day)
      // baseDayの日部分がdayの表す月の末日以降の場合は、その月を満了しているとみなす
      // (例:1月30日→2月28日(平年2月末日)/2月29日(閏年2月末日)以降の場合は満(2-1)ヶ月)
      return elapsedMonths;
    else
      // それ以外の場合は、その月を満了していないとみなす
      // (例:1月30日→3月29日以前の場合は(3-1)ヶ月未満、よって満(3-1-1)ヶ月)
      return elapsedMonths - 1;
  }

  // 基準日baseDayからdayまでの経過年数を求める
  public static int GetElapsedYears(DateTime baseDay, DateTime day)
  {
    // 経過月数÷12(端数切り捨て)したものを経過年数とする
    // (満12ヶ月で満1年とする)
    return GetElapsedMonths(baseDay, day) / 12;
  }

  static void Main()
  {
    var baseDay = new DateTime(2012, 1, 30);

    Console.WriteLine("基準日: {0:D}", baseDay);

    foreach (var day in new[] {
      new DateTime(2012, 1, 31),
      new DateTime(2012, 2, 28),
      new DateTime(2012, 2, 29),
      new DateTime(2012, 3, 29),
      new DateTime(2012, 3, 30),
      new DateTime(2012, 3, 31),
      new DateTime(2012, 4, 29),
      new DateTime(2012, 4, 30),
      new DateTime(2013, 1, 29),
      new DateTime(2013, 1, 30),
    }) {
      Console.WriteLine("日付: {0:D}, 経過月数: {1}", day, GetElapsedMonths(baseDay, day));
    }

    baseDay = new DateTime(2011, 6, 18);

    Console.WriteLine("基準日: {0:D}", baseDay);

    foreach (var day in new[] {
      new DateTime(2012, 6, 17),
      new DateTime(2012, 6, 18),
      new DateTime(2015, 6, 17),
      new DateTime(2015, 6, 18),
    }) {
      Console.WriteLine("日付: {0:D}, 経過年数: {1}", day, GetElapsedYears(baseDay, day));
    }

    baseDay = new DateTime(2012, 2, 29);

    Console.WriteLine("基準日: {0:D}", baseDay);

    foreach (var day in new[] {
      new DateTime(2012, 3, 28),
      new DateTime(2012, 3, 29),
      new DateTime(2013, 2, 27),
      new DateTime(2013, 2, 28),
      new DateTime(2013, 3, 1),
      new DateTime(2016, 2, 28),
      new DateTime(2016, 2, 29),
    }) {
      Console.WriteLine("日付: {0:D}, 経過月数: {1}, 経過年数: {2}", day, GetElapsedMonths(baseDay, day), GetElapsedYears(baseDay, day));
    }
  }
}
満月数・満年数の取得
Imports System

Class Sample
  ' 基準日baseDayからdayまでの経過月数を求める
  ' (DateTimeの時間部分に付いては考慮しない)
  Public Shared Function GetElapsedMonths(ByVal baseDay As DateTime, ByVal day As DateTime) As Integer
    ' 日付が基準日より前の場合は例外とする
    If day < baseDay Then Throw New ArgumentException()

    ' 経過月数を求める(満月数を考慮しない単純計算)
    Dim elapsedMonths As Integer = (day.Year - baseDay.Year) * 12 + (day.Month - baseDay.Month)

    If baseDay.Day <= day.Day Then
      ' baseDayの日部分がdayの日部分以上の場合は、その月を満了しているとみなす
      ' (例:1月30日→3月30日以降の場合は満(3-1)ヶ月)
      Return elapsedMonths
    Else If day.Day = DateTime.DaysInMonth(day.Year, day.Month) AndAlso day.Day <= baseDay.Day
      ' baseDayの日部分がdayの表す月の末日以降の場合は、その月を満了しているとみなす
      ' (例:1月30日→2月28日(平年2月末日)/2月29日(閏年2月末日)以降の場合は満(2-1)ヶ月)
      Return elapsedMonths
    Else
      ' それ以外の場合は、その月を満了していないとみなす
      ' (例:1月30日→3月29日以前の場合は(3-1)ヶ月未満、よって満(3-1-1)ヶ月)
      Return elapsedMonths - 1
    End If
  End Function

  ' 基準日baseDayからdayまでの経過年数を求める
  Public Shared Function GetElapsedYears(ByVal baseDay As DateTime, ByVal day As DateTime) As Integer
    ' 経過月数÷12(端数切り捨て)したものを経過年数とする
    ' (満12ヶ月で満1年とする)
    Return GetElapsedMonths(baseDay, day) \ 12
  End Function

  Shared Sub Main()
    Dim baseDay As New DateTime(2012, 1, 30)

    Console.WriteLine("基準日: {0:D}", baseDay)

    For Each day As DateTime In New DateTime() { _
      New DateTime(2012, 1, 31), _
      New DateTime(2012, 2, 28), _
      New DateTime(2012, 2, 29), _
      New DateTime(2012, 3, 29), _
      New DateTime(2012, 3, 30), _
      New DateTime(2012, 3, 31), _
      New DateTime(2012, 4, 29), _
      New DateTime(2012, 4, 30), _
      New DateTime(2013, 1, 29), _
      New DateTime(2013, 1, 30)
    }
      Console.WriteLine("日付: {0:D}, 経過月数: {1}", day, GetElapsedMonths(baseDay, day))
    Next

    baseDay = New DateTime(2011, 6, 18)

    Console.WriteLine("基準日: {0:D}", baseDay)

    For Each day As DateTime In New DateTime() { _
      New DateTime(2012, 6, 17), _
      New DateTime(2012, 6, 18), _
      New DateTime(2015, 6, 17), _
      New DateTime(2015, 6, 18) _
    }
      Console.WriteLine("日付: {0:D}, 経過年数: {1}", day, GetElapsedYears(baseDay, day))
    Next

    baseDay = New DateTime(2012, 2, 29)

    Console.WriteLine("基準日: {0:D}", baseDay)

    For Each day As DateTime In New DateTime() { _
      New DateTime(2012, 3, 28), _
      New DateTime(2012, 3, 29), _
      New DateTime(2013, 2, 27), _
      New DateTime(2013, 2, 28), _
      New DateTime(2013, 3, 1), _
      New DateTime(2016, 2, 28), _
      New DateTime(2016, 2, 29) _
    }
      Console.WriteLine("日付: {0:D}, 経過月数: {1}, 経過年数: {2}", day, GetElapsedMonths(baseDay, day), GetElapsedYears(baseDay, day))
    Next
  End Sub
End Module
出力例
基準日: 2012年1月30日
日付: 2012年1月31日, 経過月数: 0
日付: 2012年2月28日, 経過月数: 0
日付: 2012年2月29日, 経過月数: 1
日付: 2012年3月29日, 経過月数: 1
日付: 2012年3月30日, 経過月数: 2
日付: 2012年3月31日, 経過月数: 2
日付: 2012年4月29日, 経過月数: 2
日付: 2012年4月30日, 経過月数: 3
日付: 2013年1月29日, 経過月数: 11
日付: 2013年1月30日, 経過月数: 12
基準日: 2011年6月18日
日付: 2012年6月17日, 経過年数: 0
日付: 2012年6月18日, 経過年数: 1
日付: 2015年6月17日, 経過年数: 3
日付: 2015年6月18日, 経過年数: 4
基準日: 2012年2月29日
日付: 2012年3月28日, 経過月数: 0, 経過年数: 0
日付: 2012年3月29日, 経過月数: 1, 経過年数: 0
日付: 2013年2月27日, 経過月数: 11, 経過年数: 0
日付: 2013年2月28日, 経過月数: 12, 経過年数: 1
日付: 2013年3月1日, 経過月数: 12, 経過年数: 1
日付: 2016年2月28日, 経過月数: 47, 経過年数: 3
日付: 2016年2月29日, 経過月数: 48, 経過年数: 4