連載
» 2015年05月27日 05時00分 UPDATE

.NET TIPS:月初/月末の日付を求めるには?[C#、VB]

ある日が含まれる月の最初と最後の日付を求める処理と、それをライブラリ化して拡張メソッドの形で再利用できるようにする方法を説明する。

[山本康彦,BluewaterSoft/Microsoft MVP for Windows Platform Development]
.NET TIPS
Insider.NET

 

「.NET TIPS」のインデックス

連載目次

対象:.NET 2.0以降
ただし、DateTime構造体を使う方法は.NET 1.0から可能。
拡張メソッドを使ってライブラリ化できるのはVisual Studio 2008以降。


 日付処理で頻繁に登場するのが、月の最初と最後の日の算出である。標準で備わっていてもおかしくないのに、.NET Frameworkには用意されていないのだ。そこで自前で実装することになるのだが、月初はともかくとして、月末の日付はどのようにして求めたらよいのだろうか? 本稿では、月初/月末の日付を求める方法を説明し、さらにライブラリ化する方法も紹介する。

 本稿では、.NET Frameworkの初期から提供されているDateTime構造体(System名前空間)を使った処理だけでなく、.NET 2.0で追加されたDateTimeOffset構造体(System名前空間)を使う場合も紹介する。なお、本稿のコードはVisual Studio(以降、VSと略す)2012で検証しており、VS 2008以降で登場した言語機能も使用していることをお断りしておく。

月初の日付を求めるには?

 ある日付が与えられたときに、その日付の月の最初の日(例えば、5月のいずれかの日なら5月1日)を求めるのは簡単だ。与えられた日付から年と月を取り出し、日は1日として新しく日付のオブジェクトを作ればよい。

 日付がDateTime構造体で与えられる場合は、次のコードのようになる。

var theDay = new DateTime(2015, 5, 27);
var firstDayOfMonth = new DateTime(theDay.Year, theDay.Month, 1);
Console.WriteLine("{0}月の最初の日は {1}"
                  theDay.Month, firstDayOfMonth.ToString());
// 出力結果:
// 5月の最初の日は 2015/05/01 0:00:00

Dim theDay = New DateTime(2015, 5, 27)
Dim firstDayOfMonth = New DateTime(theDay.Year, theDay.Month, 1)
Console.WriteLine("{0}月の最初の日は {1}",
                  theDay.Month, firstDayOfMonth.ToString())
' 出力結果:
' 5月の最初の日は 2015/05/01 0:00:00

DateTime構造体で与えられた日付から、その月初の日付を求めるコードの例(上:C#、下:VB)
与えられた日付の年と月、そして日としては1日を使って、新しくDateTime構造体を作成すればよい(太字の部分)。
このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2008以前の環境で試すには、適宜修正していただきたい。C#も同様に、C# 3.0(=VS 2008)から利用できるローカル型推論を使用している。

 .NET 2.0で導入されたDateTimeOffset構造体で日付が与えられる場合は、次のコードのようになる。

var theDay = new DateTimeOffset(2015, 5, 27, 0, 0, 0, TimeSpan.FromHours(9.0));
var firstDayOfMonth = new DateTimeOffset(theDay.Year, theDay.Month, 1,
                                          0, 0, 0, theDay.Offset);
Console.WriteLine("{0}月の最初の日は {1}"
                  theDay.Month, firstDayOfMonth.ToString());
// 出力結果:
// 5月の最初の日は 2015/05/01 0:00:00 +09:00

Dim theDay = New DateTimeOffset(2015, 5, 27,
                                0, 0, 0, TimeSpan.FromHours(9.0))
Dim firstDayOfMonth = New DateTimeOffset(theDay.Year, theDay.Month, 1,
                                         0, 0, 0, theDay.Offset)
Console.WriteLine("{0}月の最初の日は {1}",
                  theDay.Month, firstDayOfMonth.ToString())
' 出力結果:
' 5月の最初の日は 2015/05/01 0:00:00 +09:00

DateTimeOffset構造体で与えられた日付から、その月初の日付を求めるコードの例(上:C#、下:VB)
与えられた日付の年と月、そして日としては1日を使って、新しくDateTimeOffset構造体を作成すればよい(太字の部分)。DateTimeOffset構造体のインスタンスを作るときには、必ずUTC(=協定世界時、グリニッジ標準時とほぼ同義)からの時差を設定しなければならない(DateTime構造体のインスタンスを与えてDateTimeOffset構造体のインスタンスを作る場合には、DateTime構造体のKindプロパティから算出される)。ローカル変数「theDay」では、日本標準時を想定して+9時間としている。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2008以前の環境で試すには、適宜修正していただきたい。C#も同様に、C# 3.0(=VS 2008)から利用できるローカル型推論を使用している。

月末の日付を求めるには?

 月末の日付を求めるには、ちょっと工夫が必要だ。ここでは二通りの方法を紹介しよう。

 一つ目は、その月の日数を使って算出する方法だ。月ごとの日数を配列に持たせて処理しているプログラムを見たこともあるが、.NETではそんなことをする必要はない。DateTime構造体のDaysInMonthメソッドを使えば、ある月の日数が取得できるのである(次のコード)。

var theDay = new DateTime(2016, 2, 14);  // うるう年
int days = DateTime.DaysInMonth(theDay.Year, theDay.Month); // その月の日数

// DateTime構造体の場合
var lastDayOfMonth1 = new DateTime(theDay.Year, theDay.Month, days);
Console.WriteLine("{0}年{1}月の最後の日は {2}"
                  theDay.Year, theDay.Month, lastDayOfMonth1.ToString());
// 出力結果:
// 2016年2月の最後の日は 2016/02/29 0:00:00

// DateTimeOffset構造体の場合
var lastDayOfMonth2 = new DateTimeOffset(theDay.Year, theDay.Month, days, 
                                         0, 0, 0, TimeSpan.FromHours(9.0));
Console.WriteLine("{0}年{1}月の最後の日は {2}"
                  theDay.Year, theDay.Month, lastDayOfMonth2.ToString());
// 出力結果:
// 2016年2月の最後の日は 2016/02/29 0:00:00 +09:00

Dim theDay = New DateTime(2016, 2, 14)  ' うるう年
Dim days As Integer = DateTime.DaysInMonth(theDay.Year, theDay.Month) ' その月の日数

' DateTime構造体の場合
Dim lastDayOfMonth1 = New DateTime(theDay.Year, theDay.Month, days)
Console.WriteLine("{0}年{1}月の最後の日は {2}",
                  theDay.Year, theDay.Month, lastDayOfMonth1.ToString())
' 出力結果:
' 2016年2月の最後の日は 2016/02/29 0:00:00

' DateTimeOffset構造体の場合
Dim lastDayOfMonth2 = New DateTimeOffset(theDay.Year, theDay.Month, days,
                                         0, 0, 0, TimeSpan.FromHours(9.0))
Console.WriteLine("{0}年{1}月の最後の日は {2}",
                  theDay.Year, theDay.Month, lastDayOfMonth2.ToString())
' 出力結果:
' 2016年2月の最後の日は 2016/02/29 0:00:00 +09:00

DaysInMonthメソッドを利用して月末の日付を求めるコードの例(上:C#、下:VB)
ある月の日数は、DateTime構造体のDaysInMonthメソッドで求められる(太字にした部分)。DaysInMonthメソッドは、うるう年に対応している(そのため引数に年も必要なのだ)。あとは、その日数を日として、日付オブジェクトを生成すればよいのである。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2008以前の環境で試すには、適宜修正していただきたい。C#も同様に、C# 3.0(=VS 2008)から利用できるローカル型推論を使用している。

 もう一つは、翌月の1日を求め、そこから1日戻す方法だ。例えば、5月の末日を求めるには、6月1日を作り、そこから1日を引いて5月31日とするのである(次のコード)。

var theDay = new DateTime(2015, 12, 24);

// DateTime構造体の場合
var lastDayOfMonth1 = (new DateTime(theDay.Year, theDay.Month, 1)) 
                      .AddMonths(1) 
                      .AddDays(-1.0);
Console.WriteLine("{0}年{1}月の最後の日は {2}"
                  theDay.Year, theDay.Month, lastDayOfMonth1.ToString());
// 出力結果:
// 2015年12月の最後の日は 2015/12/31 0:00:00

// DateTimeOffset構造体の場合
var lastDayOfMonth2 = (new DateTimeOffset(theDay.Year, theDay.Month, 1, 
                                          0, 0, 0, TimeSpan.FromHours(9.0)))
                      .AddMonths(1)
                      .AddDays(-1.0);
Console.WriteLine("{0}年{1}月の最後の日は {2}"
                  theDay.Year, theDay.Month, lastDayOfMonth2.ToString());
// 出力結果:
// 2015年12月の最後の日は 2015/12/31 0:00:00 +09:00

Dim theDay = New DateTime(2015, 12, 24)

' DateTime構造体の場合
Dim lastDayOfMonth1 = (New DateTime(theDay.Year, theDay.Month, 1)) _
                      .AddMonths(1) _
                      .AddDays(-1.0)
Console.WriteLine("{0}年{1}月の最後の日は {2}",
                  theDay.Year, theDay.Month, lastDayOfMonth1.ToString())
' 出力結果:
' 2015年12月の最後の日は 2015/12/31 0:00:00

' DateTimeOffset構造体の場合
Dim lastDayOfMonth2 = (New DateTimeOffset(theDay.Year, theDay.Month,
                                          1, 0, 0, 0, TimeSpan.FromHours(9.0))) _
                      .AddMonths(1) _
                      .AddDays(-1.0)
Console.WriteLine("{0}年{1}月の最後の日は {2}",
                  theDay.Year, theDay.Month, lastDayOfMonth2.ToString())
' 出力結果:
' 2015年12月の最後の日は 2015/12/31 0:00:00 +09:00

翌月の1日を求めてから1日を引いて月末の日付とするコードの例(上:C#、下:VB)
翌月の1日を求めるには、まずその月の1日を作ってから1月を足す。月の数字に1を加えてから日付オブジェクトを作ると(「new DateTimeOffset(theDay.Year, theDay.Month + 1, 1, ……」のようにすると)、12月の日付が与えられたときに13月1日という日付を作ることになり、例外が発生してしまう。そこで、その月の1日を作り、1月を足し(AddMonths(1)呼び出し)、1日を引く(AddDays(-1.0)呼び出し)ことで月末の日付を求めている。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2008以前の環境で試すには、適宜修正していただきたい。C#も同様に、C# 3.0(=VS 2008)から利用できるローカル型推論を使用している。

ライブラリにするには?

 このようなよく使う日付の処理は、拡張メソッドとしてライブラリ化しておくと便利である*1。ただし、この方法が使えるのはVisual Studio 2008からである。

 そのライブラリ専用の名前空間を決めて(ここでは「dotNetTips1108VS2012CS/VB.MyExtensions」とした)、静的クラス(VBではモジュール)を作り、そこに拡張メソッドを実装する(次のコード)。

using System;

namespace dotNetTips1108VS2012CS.MyExtensions
{
  public static class DateTimeUtil
  {
    // 月の最初の日を求める(DateTime版)
    public static DateTime GetFirstDayOfMonth(this DateTime theDay)
    {
      return new DateTime(theDay.Year, theDay.Month, 1);
    }

    // 月の最後の日を求める(DateTime版)
    public static DateTime GetLastDayOfMonth(this DateTime theDay)
    {
      int days = DateTime.DaysInMonth(theDay.Year, theDay.Month);
      return new DateTime(theDay.Year, theDay.Month, days);
    }

    // 月の最初の日を求める(DateTimeOffset版)
    public static DateTimeOffset GetFirstDayOfMonth(this DateTimeOffset theDay)
    {
      return new DateTimeOffset(theDay.Year, theDay.Month, 1,
                                0, 0, 0, theDay.Offset);
    }

    // 月の最後の日を求める(DateTimeOffset版)
    public static DateTimeOffset GetLastDayOfMonth(this DateTimeOffset theDay)
    {
      int days = DateTime.DaysInMonth(theDay.Year, theDay.Month);
      return new DateTimeOffset(theDay.Year, theDay.Month, days, 
                                0, 0, 0, theDay.Offset);
    }
  }
}

Imports System.Runtime.CompilerServices

Namespace MyExtensions
' プロジェクト名は「dotNetTips1108VS2012VB」としてある。
' 従って、名前空間のフルネームは「dotNetTips1108VS2012VB. MyExtensions」となる。

  Module DateTimeUtil
    ' 月の最初の日を求める(DateTime版)
    <Extension()>
    Public Function GetFirstDayOfMonth(theDay As DateTime) As DateTime
      Return New DateTime(theDay.Year, theDay.Month, 1)
    End Function

    ' 月の最後の日を求める(DateTime版)
    <Extension()>
    Public Function GetLastDayOfMonth(theDay As DateTime) As DateTime
      Dim days As Integer = DateTime.DaysInMonth(theDay.Year, theDay.Month)
      Return New DateTime(theDay.Year, theDay.Month, days)
    End Function

    ' 月の最初の日を求める(DateTimeOffset版)
    <Extension()>
    Public Function GetFirstDayOfMonth(theDay As DateTimeOffset) As DateTimeOffset
      Return New DateTimeOffset(theDay.Year, theDay.Month, 1,
                                0, 0, 0, theDay.Offset)
    End Function

    ' 月の最後の日を求める(DateTimeOffset版)
    <Extension()>
    Public Function GetLastDayOfMonth(theDay As DateTimeOffset) As DateTimeOffset
      Dim days As Integer = DateTime.DaysInMonth(theDay.Year, theDay.Month)
      Return New DateTimeOffset(theDay.Year, theDay.Month, days,
                                0, 0, 0, theDay.Offset)
    End Function
  End Module
End Namespace

月初/月末の日付を求めるライブラリクラス(VBではモジュール)の例(上:C#、下:VB)
月初/月末の日付を求めるコードを拡張メソッドにして一つのクラス/モジュールにまとめた*1。メソッド名が同じでも、引数の型が違えば同じクラス/モジュール内に同居できるのである(オーバーロード)。
なお、このVBのコードは、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。また、拡張メソッドは、Visual Basic 2008/C# 3.0(=VS 2008)で導入された機能である。それ以前の環境では利用できない。

 上の拡張メソッドを利用して月初/月末の日付を求めるコードは次のようになる。拡張メソッドを使うには、その名前空間をファイルの先頭でusing/Importする必要がある。

using System;
using dotNetTips1108VS2012CS.MyExtensions; // 拡張メソッドのある名前空間

……省略……

// DateTime構造体の場合
var theDay1 = new DateTime(2015, 5, 27);
var firstDayOfMonth1 = theDay1.GetFirstDayOfMonth();
Console.WriteLine("{0}年{1}月の最初の日は {2}"
                  theDay1.Year, theDay1.Month, firstDayOfMonth1.ToString());
var theDay2 = new DateTime(2016, 2, 14);  // うるう年
var lastDayOfMonth1 = theDay2.GetLastDayOfMonth();
Console.WriteLine("{0}年{1}月の最後の日は {2}"
                  theDay2.Year, theDay2.Month, lastDayOfMonth1.ToString());
// 出力結果:
// 2015年5月の最初の日は 2015/05/01 0:00:00
// 2016年2月の最後の日は 2016/02/29 0:00:00

// DateTimeOffset構造体の場合
var theDay3 = new DateTimeOffset(2015, 5, 27, 
                                  0, 0, 0, TimeSpan.FromHours(9.0));
var firstDayOfMonth2 = theDay3.GetFirstDayOfMonth();
Console.WriteLine("{0}年{1}月の最初の日は {2}"
                  theDay3.Year, theDay3.Month, firstDayOfMonth2.ToString());
var theDay4 = new DateTimeOffset(2016, 2, 14, 
                                  0, 0, 0, TimeSpan.FromHours(9.0));  // うるう年
var lastDayOfMonth2 = theDay4.GetLastDayOfMonth();
Console.WriteLine("{0}年{1}月の最後の日は {2}"
                  theDay4.Year, theDay4.Month, lastDayOfMonth2.ToString());
// 出力結果:
// 2015年5月の最初の日は 2015/05/01 0:00:00 +09:00
// 2016年2月の最後の日は 2016/02/29 0:00:00 +09:00

Imports dotNetTips1108VS2012VB.MyExtensions ' 拡張メソッドのある名前空間

……省略……

' DateTime構造体の場合
Dim theDay1 = New DateTime(2015, 5, 27)
Dim firstDayOfMonth1 = theDay1.GetFirstDayOfMonth()
Console.WriteLine("{0}年{1}月の最初の日は {2}",
                  theDay1.Year, theDay1.Month, firstDayOfMonth1.ToString())
Dim theDay2 = New DateTime(2016, 2, 14) ' うるう年
Dim lastDayOfMonth1 = theDay2.GetLastDayOfMonth()
Console.WriteLine("{0}年{1}月の最後の日は {2}",
                  theDay2.Year, theDay2.Month, lastDayOfMonth1.ToString())
' 出力結果:
' 2015年5月の最初の日は 2015/05/01 0:00:00
' 2016年2月の最後の日は 2016/02/29 0:00:00

' DateTimeOffset構造体の場合
Dim theDay3 = New DateTimeOffset(2015, 5, 27,
                                 0, 0, 0, TimeSpan.FromHours(9.0))
Dim firstDayOfMonth2 = theDay3.GetFirstDayOfMonth()
Console.WriteLine("{0}年{1}月の最初の日は {2}",
                  theDay3.Year, theDay3.Month, firstDayOfMonth2.ToString())
Dim theDay4 = New DateTimeOffset(2016, 2, 14,
                                 0, 0, 0, TimeSpan.FromHours(9.0)) ' うるう年
Dim lastDayOfMonth2 = theDay4.GetLastDayOfMonth()
Console.WriteLine("{0}年{1}月の最後の日は {2}",
                  theDay4.Year, theDay4.Month, lastDayOfMonth2.ToString())
' 出力結果:
' 2015年5月の最初の日は 2015/05/01 0:00:00 +09:00
' 2016年2月の最後の日は 2016/02/29 0:00:00 +09:00

月初/月末の日付を求める拡張メソッドを利用する例(上:C#、下:VB)
上で作成した拡張メソッドを利用して月初/月末のコードを求める例。太字の部分が、拡張メソッドの呼び出しである。DateTime構造体/DateTimeOffset構造体に最初から備わっているメソッドであるかのように使えるのだ。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。C#も同様に、C# 3.0(=VS 2008)から利用できるローカル型推論を使用している。また、拡張メソッドは、Visual Basic 2008/C# 3.0(=VS 2008)で導入された機能である。それ以前の環境では利用できない。

*1 拡張メソッドについて詳しくは、次のMSDNのドキュメントを参照していただきたい。


利用可能バージョン:.NET Framework 2.0以降
カテゴリ:クラスライブラリ 処理対象:日付と時刻
使用ライブラリ:DateTime構造体(System名前空間)
使用ライブラリ:DateTimeOffset構造体(System名前空間)
使用ライブラリ:TimeSpan構造体(System名前空間)
関連TIPS:日時や時間間隔の加減算を行うには?
関連TIPS:週の始まりの日付を求めるには?
関連TIPS:
西暦年が閏(うるう)年かどうかを判別するには?[C#、VB]
関連TIPS:n日後、nカ月後、n年後の日付を求めるには?[C#、VB]
関連TIPS:UTC(世界協定時)を取得するには?[C#、VB]
関連TIPS:指定した月から特定の曜日の日付を取得するには?[C#、VB]


「.NET TIPS」のインデックス

.NET TIPS

Copyright© 1999-2017 Digital Advantage Corp. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。