BEACHSIDE BLOG

MicrosoftとかC#を好むレンジャーの個人的メモ

C# で 時間 の 計算 (切り上げ・切り捨て)

時間(DateTime型)を丸める処理を書いてみました。

果てーーーーーーーーーーしなく地味な内容です。。。

> Environment

System.DateTimeとSystem.TimeSpanしか使わないので、C#でこれ使えれば環境の依存はないです。

ただ、サンプルコードでは、C#6.0のExpression-bodied functionとstring-interpolation(文字列挿入)は使ってます。まだの方は、先生がわかりやすくまとめておりますので、ご参考にしていただければと。
C# 6.0 の新機能 expression-bodied な関数メンバー
C# 6.0 の新機能 文字列挿入

> Overview

やりたいことは、特定の時間に対して、既定のインターバルで切り上げたり切り下げたりしたい。
具体的には、
08:59:01に対して、インターバルが15分だと、

  • 切り上げ:09:00:00
  • 切り下げ:08:45:00


09:00:01に対して、インターバル15分だと、

  • 切り上げ:09:15:00
  • 切り下げ:09:00:00


09:00:00ちょうどの場合は、

  • 切り上げ:09:00:00
  • 切り下げ:09:00:00

をしたいだけです。

インターバルに入れる数値の前提は、以下とします。

  • 0はあかん。
  • 60で割り切れる数字

実装方法なんてなんぼでもあると思いますが、今回はDateTime.Ticks プロパティ (System)TimeSpan.Ticks プロパティ (System)を使います。
地味感に溢れているので、説明雑に行きます。

> implementation 切り上げ

現在のTicksからインターバルのTicksから1引いた数値を割って、それをインターバルのTicksで掛け算すればサクッとできちゃいます(説明が雑ですいません...)。

static DateTime RoundUp(DateTime input, TimeSpan interval) 
    => new DateTime(((input.Ticks + interval.Ticks - 1) / interval.Ticks) * interval.Ticks, input.Kind);

09:00:00のようなちょうどの時間に対応するため、1引いてます(説明が雑ですいませ...)。

> implementation 切り下げ

さっきの考え方を逆にしてあげればよいだけです(説明が雑で...)。

static DateTime RoundDown(DateTime input, TimeSpan interval) 
  => new DateTime((((input.Ticks + interval.Ticks) / interval.Ticks) - 1) * interval.Ticks, input.Kind);

インターバルのTicksからは1を引かず、割り算した結果から1ひいちゃえばサクサクッとできちゃいます(説.....)。

> implementation 動作確認

Consoleのプロジェクトで、試しに動かしてみると、

public void Main()
{
    var interval = TimeSpan.FromMinutes(15);
    var dt1 = DateTime.Parse("2016-01-03 08:59:01");
    Inspection1(dt1, interval);

    var girigiriSaaaafe = DateTime.Parse("2016-01-03 09:00:00");
    Inspection1(girigiriSaaaafe, interval);

    var chikooooooooku = DateTime.Parse("2016-01-03 09:00:01");
    Inspection1(chikooooooooku, interval);
}

static void Inspection1(DateTime input, TimeSpan interval)
{
    Console.WriteLine($"input:{input} (interval:{interval})");
    Console.WriteLine($"up   :{RoundUp(input, interval)}");
    Console.WriteLine($"down :{RoundDown(input, interval)}");
    Console.WriteLine();
}

結果は、こんな感じで無事に表示。
f:id:beachside:20160103195230p:plain

サンプルということでべたーっと書きましたが、用途に応じて拡張メソッドにしてあげるとかでしょうかね。
あと、インターバルに制約設けるなら、enumかなんかにした方がリスクないでしょうかね。
おしまい。