LoginSignup
0
0

More than 1 year has passed since last update.

2つの日時の差を、小数有りの月数で算出 (月数の小数部分は、1ヵ月を固定31日として算出)

Last updated at Posted at 2023-02-19

はじめに

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
image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0