はじめに
C#で、DateTime型の差を求めると、TimeSpan型が得られて、
TimeSpan型 ← DateTime型 - DateTime型
TimeSpan型のTotalDaysや、TotalHours、TotalMinutes、TotalSeconds、TotalMillisecondsで、小数有りの日数や時間数が得られるのに対して、
月数は、1ヵ月の長さが年月によって変わるため(28~31日)、小数有りの月数が得られません。
(同様に、年数は、1年の長さが閏年によって変わるため(365~366日)、小数有りの年数が得られません。)
ここでは、2つの日時の差を、年月に応じた月数を小数有りで算出する関数を自作してみます。
処理方法
- 関数名DiffMonthsAsFloatで、2つの日時の差を月数(小数有り)で算出。
- 2つの日時の差を、年月に応じた月数で、かつ、小数有りで算出する関数。月数の小数部分は、1ヵ月を固定31日として算出。
- 月数を小数有りで算出する為、小数部分は1ヵ月を月日数の最大値である31日として算出。小数部を31で掛けると、1ヵ月以下の端数となる日数が計算できる。
- t1 - t2を考えて、単純な月表示部での月数の差をまず計算、t2から月表示の差だけまず進める。これがt1を過ぎる場合は、1ヵ月繰り下げて、整数部分の月数とする。月数の小数部分は、1ヵ月を固定31日として、単純に日数を31で割って、月数の小数部を決める。
当処理部分のコード抜粋:
// 2つの日時の差を月数(小数有り)で算出 (小数部分は1ヵ月を固定31日として算出)
public static double DiffMonthsAsFloat(this DateTime t1, DateTime t2, // t1 - t2
bool keep_last = true) { // T:月末の日であれば次の月末までを1ヵ月として月数を算出
// assume: 月数を小数有りで算出する為、小数部分は1ヵ月を月日数の最大値である31日として算出
// note: 小数部を31で掛けると、1ヵ月以下の端数となる日数が計算できる
// note: 1ヵ月が固定31日(最大値)の為、"1 - 当月数"等の引き算で差を取る事は不適
var n_M = t1.DiffMonthsAsSeen(t2); // 単純な月表示の差(整数)
var t2_M = t2.AddMonths(n_M, keep_last); // 月表示の差だけまず進める
if (t1 < t2_M) { // if: t1を過ぎた
n_M -= 1; // 1ヵ月繰り下げの分
t2_M = t2.AddMonths(n_M, keep_last); // 再計算 // NOTE: 月末の日を考慮するので-1の減算は不可
}
var t_D = (t1 - t2_M).TotalDays;
return n_M + t_D / 31; // 1ヵ月を固定31日として月数の小数部を決める
}
// 単純な月表示部での月数の差(整数) 見たままの月数の差
public static int DiffMonthsAsSeen(this DateTime t1, DateTime t2) { // t1 - t2
return (t1.Year * 12 + t1.Month) - (t2.Year * 12 + t2.Month);
}
テストコード
Program 2301-3 月数(小数有り)を算出.cs
using System;
namespace ConsoleApp1 {
class Program {
static void Main(string[] args) {
// sec: 月数(小数有り)を算出
/* 結果:
// 同日となる1ヵ月は、ぴったり1ヵ月となる
1[month]
2[month]
// 月末から月末は、ぴったり1ヵ月単位となる
1[month]
3[month]
// 月末から始まる場合は、次の月末でぴったり1ヵ月となる
1.870967741935484[month]
1.903225806451613[month]
1.935483870967742[month]
2[month]
// 月末でない日から始まる場合は、次の同日でぴったり1ヵ月となる
1.967741935483871[month]
2[month]
2.032258064516129[month]
2.096774193548387[month]
// 算出した月数の小数部(31を掛けて日数に変換)
0.03225806451612903[month]
1[days](月数の小数部x31)
1.870967741935484[month]
27[days](月数の小数部x31)
1.935483870967742[month]
29[days](月数の小数部x31)
// 秒単位の変化でも月数を算出可
11.935483870594386[month]
12.000000000373356[month]
*/
Action<string> WL = Console.WriteLine; // 短縮
Func<string, DateTime> DT = DateTime.Parse;
// 同日となる1ヵ月は、ぴったり1ヵ月となる
WL($"{DT("2023/02/01").DiffMonthsAsFloat(DT("2023/01/01"))}[month]");
WL($"{DT("2023/03/02").DiffMonthsAsFloat(DT("2023/01/02"))}[month]");
WL("");
// 月末から月末は、ぴったり1ヵ月単位となる
WL($"{DT("2023/02/28").DiffMonthsAsFloat(DT("2023/01/31"))}[month]");
WL($"{DT("2023/04/30").DiffMonthsAsFloat(DT("2023/01/31"))}[month]");
WL("");
// 月末から始まる場合は、次の月末でぴったり1ヵ月となる
WL($"{DT("2023/04/27").DiffMonthsAsFloat(DT("2023/02/28"))}[month]");
WL($"{DT("2023/04/28").DiffMonthsAsFloat(DT("2023/02/28"))}[month]");
WL($"{DT("2023/04/29").DiffMonthsAsFloat(DT("2023/02/28"))}[month]");
WL($"{DT("2023/04/30").DiffMonthsAsFloat(DT("2023/02/28"))}[month]");
WL("");
// 月末でない日から始まる場合は、次の同日でぴったり1ヵ月となる
WL($"{DT("2023/04/26").DiffMonthsAsFloat(DT("2023/02/27"))}[month]");
WL($"{DT("2023/04/27").DiffMonthsAsFloat(DT("2023/02/27"))}[month]");
WL($"{DT("2023/04/28").DiffMonthsAsFloat(DT("2023/02/27"))}[month]");
WL($"{DT("2023/04/30").DiffMonthsAsFloat(DT("2023/02/27"))}[month]");
WL("");
// 算出した月数の小数部(31を掛けて日数に変換)
WL($"{DT("2023/01/03").DiffMonthsAsFloat(DT("2023/01/02"))}[month]");
WL($"{DT("2023/01/03").DiffMonthsAsFloat(DT("2023/01/02")) % 1 * 31}[days](月数の小数部x31)");
WL($"{DT("2023/03/01").DiffMonthsAsFloat(DT("2023/01/02"))}[month]");
WL($"{DT("2023/03/01").DiffMonthsAsFloat(DT("2023/01/02")) % 1 * 31}[days](月数の小数部x31)");
WL($"{DT("2023/04/29").DiffMonthsAsFloat(DT("2023/02/28"))}[month]");
WL($"{DT("2023/04/29").DiffMonthsAsFloat(DT("2023/02/28")) % 1 * 31}[days](月数の小数部x31)");
WL("");
// 秒単位の変化でも月数を算出可
WL($"{DT("2024/02/29").DiffMonthsAsFloat(DT("2023/02/28 0:00:00.001"))}[month]");
WL($"{DT("2024/02/29 0:00:00.001").DiffMonthsAsFloat(DT("2023/02/28"))}[month]");
WL("");
// sec: 月数(小数有り)を算出 マイナスの期間となる場合
/* 結果:
// 同日となる1ヵ月は、ぴったり1ヵ月となる
-1[month]
-2[month]
// 月末から月末は、ぴったり1ヵ月単位となる
-1[month]
-3[month]
// 月末から始まる場合は、次の月末でぴったり1ヵ月となる
-1.967741935483871[month]
-2[month]
-2[month]
-2[month]
// 月末でない日から始まる場合は、次の同日でぴったり1ヵ月となる
-1.967741935483871[month]
-2[month]
-2.032258064516129[month]
-2.129032258064516[month]
// 算出した月数の小数部(31を掛けて日数に変換)
-0.032258064516129004[month]
-1[days](月数の小数部x31)
-1.967741935483871[month]
-30[days](月数の小数部x31)
-2[month]
-0[days](月数の小数部x31)
// 秒単位の変化でも月数を算出可
-11.999999999626644[month]
-12.096774193921744[month]
*/
// 同日となる1ヵ月は、ぴったり1ヵ月となる
WL($"{DT("2023/01/01").DiffMonthsAsFloat(DT("2023/02/01"))}[month]");
WL($"{DT("2023/01/02").DiffMonthsAsFloat(DT("2023/03/02"))}[month]");
WL("");
// 月末から月末は、ぴったり1ヵ月単位となる
WL($"{DT("2023/01/31").DiffMonthsAsFloat(DT("2023/02/28"))}[month]");
WL($"{DT("2023/01/31").DiffMonthsAsFloat(DT("2023/04/30"))}[month]");
WL("");
// 月末から始まる場合は、次の月末でぴったり1ヵ月となる
WL($"{DT("2023/02/28").DiffMonthsAsFloat(DT("2023/04/27"))}[month]");
WL($"{DT("2023/02/28").DiffMonthsAsFloat(DT("2023/04/28"))}[month]");
WL($"{DT("2023/02/28").DiffMonthsAsFloat(DT("2023/04/29"))}[month]");
WL($"{DT("2023/02/28").DiffMonthsAsFloat(DT("2023/04/30"))}[month]");
WL("");
// 月末でない日から始まる場合は、次の同日でぴったり1ヵ月となる
WL($"{DT("2023/02/27").DiffMonthsAsFloat(DT("2023/04/26"))}[month]");
WL($"{DT("2023/02/27").DiffMonthsAsFloat(DT("2023/04/27"))}[month]");
WL($"{DT("2023/02/27").DiffMonthsAsFloat(DT("2023/04/28"))}[month]");
WL($"{DT("2023/02/27").DiffMonthsAsFloat(DT("2023/04/30"))}[month]");
WL("");
// 算出した月数の小数部(31を掛けて日数に変換)
WL($"{DT("2023/01/02").DiffMonthsAsFloat(DT("2023/01/03"))}[month]");
WL($"{DT("2023/01/02").DiffMonthsAsFloat(DT("2023/01/03")) % 1 * 31}[days](月数の小数部x31)");
WL($"{DT("2023/01/02").DiffMonthsAsFloat(DT("2023/03/01"))}[month]");
WL($"{DT("2023/01/02").DiffMonthsAsFloat(DT("2023/03/01")) % 1 * 31}[days](月数の小数部x31)");
WL($"{DT("2023/02/28").DiffMonthsAsFloat(DT("2023/04/29"))}[month]");
WL($"{DT("2023/02/28").DiffMonthsAsFloat(DT("2023/04/29")) % 1 * 31}[days](月数の小数部x31)");
WL("");
// 秒単位の変化でも月数を算出可
WL($"{DT("2023/02/28 0:00:00.001").DiffMonthsAsFloat(DT("2024/02/29"))}[month]");
WL($"{DT("2023/02/28").DiffMonthsAsFloat(DT("2024/02/29 0:00:00.001"))}[month]");
}
}
public static class DateTime_ExtensionMethods {
// 2つの日時の差を月数(小数有り)で算出 (小数部分は1ヵ月を固定31日として算出)
public static double DiffMonthsAsFloat(this DateTime t1, DateTime t2, // t1 - t2
bool keep_last = true) { // T:月末の日であれば次の月末までを1ヵ月として月数を算出
// assume: 月数を小数有りで算出する為、小数部分は1ヵ月を月日数の最大値である31日として算出
// note: 小数部を31で掛けると、1ヵ月以下の端数となる日数が計算できる
// note: 1ヵ月が固定31日(最大値)の為、"1 - 当月数"等の引き算で差を取る事は不適
var n_M = t1.DiffMonthsAsSeen(t2); // 単純な月表示の差(整数)
var t2_M = t2.AddMonths(n_M, keep_last); // 月表示の差だけまず進める
if (t1 < t2_M) { // if: t1を過ぎた
n_M -= 1; // 1ヵ月繰り下げの分
t2_M = t2.AddMonths(n_M, keep_last); // 再計算 // NOTE: 月末の日を考慮するので-1の減算は不可
}
var t_D = (t1 - t2_M).TotalDays;
return n_M + t_D / 31; // 1ヵ月を固定31日として月数の小数部を決める
}
// 単純な月表示部での月数の差(整数) 見たままの月数の差
public static int DiffMonthsAsSeen(this DateTime t1, DateTime t2) { // t1 - t2
return (t1.Year * 12 + t1.Month) - (t2.Year * 12 + t2.Month);
}
// 月を加算(月末の日であれば加算後も月末となるような加算)
public static DateTime AddMonths(this DateTime t1, int n_M,
bool keep_last = true) { // T:月末の日であれば次の月末までを1ヵ月として月数を算出
if (!keep_last)
return t1.AddMonths(n_M); // 標準の月加算
var t_1d = t1.AddDays(1);
var is_last = t_1d.Day == 1; // 月末か?
if (!is_last)
return t1.AddMonths(n_M); // 標準の月加算
// if: 月末の時
return t_1d.AddMonths(n_M).AddDays(-1); // 月末を維持して月加算
}
}
}
環境
Microsoft Visual Studio Community 2019 Version 16.11.22
VisualStudio.16.Release
Microsoft .NET Framework
Version 4.8.04084