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

.NET TIPS:祝日を求めるには?[C#、VB]

国民の祝日には、日付が決まっているものもあれば、その年によって変化するものもある。本稿では法律に従って、これらを算出する方法を示す。

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

 

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

連載目次

対象:.NET 3.5以降


 カレンダーを表示するプログラムを作ろうとしたとき、厄介なのは祝日だ。日本の祝日には、特定の日に固定されていないものがあるからだ。法律に書いてある通りにロジックを組めば祝日を決定できるとはいうものの、なかなか面倒なコーディングになる。本稿では、2007年以降のある年の祝日を求める方法を紹介しよう。なお、本稿のサンプルは「Windows desktop code samples:.NET Tips #1112」からダウンロードできる。

日本の祝日とは?

 本稿で扱う祝日とは、国民の祝日/振替休日/国民の休日の三種類である。

 一つ目の国民の祝日は、さらに3種類に分類できる。

  • 固定日:元日(1月1日)/建国記念の日(2月11日)/昭和の日(4月29日)/憲法記念日(5月3日)/みどりの日(5月4日、ただし2007年から)/こどもの日(5月5日)/山の日(8月11日、ただし2016年から)/文化の日(11月3日)/勤労感謝の日(11月23日)/天皇誕生日(12月23日)
  • 曜日指定:成人の日(1月の第2月曜日)/海の日(7月の第3月曜日)/敬老の日(9月の第3月曜日)/体育の日(10月の第2月曜日)
  • 天文現象:春分の日/秋分の日

 これらのうち春分の日/秋分の日は、その前年の2月に官報で発表されるまでは確定しない*1

 祝日をプログラムに組み込む最も確実な方法は、官報での発表を見て、その翌年の祝日テーブルを作成することである。しかし実際には、翌年以降の祝日が必要になることも多いだろう。そこで、以下に説明するようなロジックで祝日を算出したり、それで得られた結果をテーブルに格納したりする。

*1 例えば2016年の祝日は、2015年2月2日付けの官報本紙6463号に、暦要項(れきようこう)として掲載された。同じ内容が国立天文台のWebページにもPDFで載っている。


祝日を求めるには?

 上で説明した法令に従ってロジックを組めばよい。

 とはいうものの、かなり面倒なコードになる。ここでは、まず祝日データを格納しておくクラスを定義し、メインメソッドを作り始めよう。それから、項目を分けて祝日データを作成するロジックを追加していく。

 1件の祝日データは、次のコードに示す「Holiday」クラスに格納しよう。

public class Holiday
{
  public DateTime Date { get; set; }  // 日付
  public HolidayKind Kind { get; set; } // 種類
  public string Name { get; set; } // 名称
  public string Definition { get; set; } // 祝日の定義
}

Public Class Holiday
  Public Property [Date] As DateTime ' 日付
  Public Property Kind As HolidayKind ' 種類
  Public Property Name As String ' 名称
  Public Property Definition As String ' 祝日の定義
End Class

祝日のデータを格納するためのクラス(上:C#、下:VB)

 上のコードで祝日の種類を表している「HolidayKind」列挙体は、次のコードのように定める。

public enum HolidayKind

  平日 = 0,
  国民の祝日 = 1,
  振替休日 = 2,
  国民の休日 = 3,
}

Public Enum HolidayKind
  平日 = 0
  国民の祝日 = 1
  振替休日 = 2
  国民の休日 = 3
End Enum

祝日の種類を表す「HolidayKind」列挙体(上:C#、下:VB)
「国民の祝日」と「国民の休日」は、日本語でも紛らわしい。これを英訳したら、さらに識別困難になってしまうだろう。そこで、このメンバー名は日本語とさせていただいた。

 本稿ではコンソールプログラムを作っていく。Mainメソッドの先頭で、「Holiday」クラスのインスタンスを格納するコレクションを用意する。Mainメソッドの末尾では、そのコレクションに入っている「Holiday」オブジェクトをコンソールに出力しよう(次のコード)。

using System;
using System.Collections.Generic;
using System.Linq;

……省略……

static void Main(string[] args)
{
  // 祝日を計算する年
  const int Year = 2015;

  // 祝日を格納するコレクション
  SortedDictionary<DateTime, Holiday> holidays 
    = new SortedDictionary<DateTime, Holiday>();

  // ここに祝日を求めるロジックを書く

  // 祝日を出力する
  Console.WriteLine("{0}年の休日", Year);
  foreach (var d in holidays.Values)
    Console.WriteLine("{0:MM/dd(ddd)} {1}({2}、{3})"
                      d.Date, d.Name, d.Definition, d.Kind);
  Console.WriteLine();

#if DEBUG
  // デバッグ実行時にコンソールがすぐに閉じてしまうのを防ぐ
  Console.ReadKey();
#endif
}

Sub Main(args As String())
  ' 祝日を計算する年
  Const Year As Integer = 2015

  ' 祝日を格納するコレクション
  Dim holidays As SortedDictionary(Of DateTime, Holiday) _
    = New SortedDictionary(Of DateTime, Holiday)()

  ' ここに祝日を求めるロジックを書く

  ' 祝日を出力する
  Console.WriteLine("{0}年の休日", Year)
  For Each d In holidays.Values
    Console.WriteLine("{0:MM/dd(ddd)} {1}({2}、{3})",
                      d.Date, d.Name, d.Definition, d.Kind)
  Next
  Console.WriteLine()

#If DEBUG Then
  ' デバッグ実行時にコンソールがすぐに閉じてしまうのを防ぐ
  Console.ReadKey()
#End If
End Sub

Mainメソッドの冒頭と末尾(上:C#、下:VB)
祝日を計算する年を、ここでは定数「Year」として与えることにした。
「ここに祝日を求めるロジックを書く」とコメントしてある部分に、以降で解説するコードを入れていく。
なお、このVBのコードは、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2010以前の環境で試すには、適宜修正していただきたい。

固定日の祝日を求めるには?

 これは簡単である。法令で指定されている日付をコレクションに加えていくだけだ(次のコード)。ただし、きちんと算出するためには、次のコードの「山の日」にあるように、法令の施行年を判別する必要がある。

// 元日:1月1日
var 元日 = new Holiday()
{
  Date = new DateTime(Year, 1, 1),
  Kind = HolidayKind.国民の祝日,
  Name = "元日", Definition = "1月1日",
};
holidays.Add(元日.Date, 元日);

……省略……

// 山の日:8月11日 (2016年より)
if (2016 <= Year)
{
  var 山の日 = new Holiday()
  {
    Date = new DateTime(Year, 8, 11),
    Kind = HolidayKind.国民の祝日,
    Name = "山の日", Definition = "8月11日",
  };
  holidays.Add(山の日.Date, 山の日);
}

……省略……

' 元日:1月1日
Dim 元日 = New Holiday() With
        {
          .Date = New DateTime(Year, 1, 1),
          .Kind = HolidayKind.国民の祝日,
          .Name = "元日", .Definition = "1月1日"
        }
holidays.Add(元日.Date, 元日)

……省略……

' 山の日:8月11日 (2016年より)
If (2016 <= Year) Then
  Dim 山の日 = New Holiday() With
          {
            .Date = New DateTime(Year, 8, 11),
            .Kind = HolidayKind.国民の祝日,
            .Name = "山の日", .Definition = "8月11日"
          }
  holidays.Add(山の日.Date, 山の日)
End If

……省略……

固定日の祝日をコレクションに加えるコードの例(上:C#、下:VB)
前述した固定日の祝日の全てを、このようにしてholidaysコレクションに追加していく。何の祝日なのか分かりやすいように、日本語の変数名とさせていただいた(以下同じ)。
また、山の日は2016年からの施行になるので、このようなif文が必要だ。過去にさかのぼって祝日を算出する場合には、全ての祝日についてこのような判定が必要になる。また、成人の日/海の日/敬老の日/体育の日(いずれも過去には固定日付だった)のように、定義が変更されることもある。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と「オブジェクト初期化子」、および、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2010以前の環境で試すには、適宜修正していただきたい。

曜日指定の祝日を求めるには?

 現在のところそのような祝日は全て月曜日になっているので、「○月の第N月曜日」が算出できればよい。そのようなロジックを「GetNthMonday」メソッドとしてまとめると、次のコードのようになる。

private static DateTime GetNthMonday(int nth, int year, int month)
{
  // 指定された月の日数
  int days = DateTime.DaysInMonth(year, month);

  // 指定された月の全ての日から、月曜日だけを取り出す
  IEnumerable<DateTime> allMondays
    = Enumerable.Range(1, days) // 1日〜月末の日付(int型のコレクション)を作り
      .Select(d => new DateTime(year, month, d)) // DateTime型のコレクションに変換して
      .Where(dt => dt.DayOfWeek == DayOfWeek.Monday); // そこから月曜日だけを取り出す

  // N番目の月曜日を求める
  return allMondays.ElementAt(nth - 1);
}

Private Function GetNthMonday(nth As Integer, year As Integer, month As Integer) As DateTime

  ' 指定された月の日数
  Dim days As Integer = DateTime.DaysInMonth(year, month)

  ' 指定された月の全ての日から、月曜日だけを取り出す
  Dim allMondays As IEnumerable(Of DateTime) _
    = Enumerable.Range(1, days) _
      .Select(Function(d) New DateTime(year, month, d)) _
      .Where(Function(dt) dt.DayOfWeek = DayOfWeek.Monday)
  ' Enumerable.Rangeで1日〜月末の日付(int型のコレクション)を作り
  ' SelectでDateTime型のコレクションに変換して
  ' Whereでそこから月曜日だけを取り出す

  ' N番目の月曜日を求める
  Return allMondays.ElementAt(nth - 1)
End Function

指定月の第N月曜日を求めるメソッドの例(上:C#、下:VB)
ここでは現代風にLINQを使ったが、「.NET TIPS:指定した月から特定の曜日の日付を取得するには?[C#、VB]」のようにループで書いてももちろん構わない。また、剰余演算を使ってうまく式を組み立てれば、1行で求めることも可能ではある(可読性に劣るのであまりお勧めはしない)。

 このメソッドを使って、法令で指定されている曜日指定の祝日をコレクションに加えるコードは、次のようになる。なお、コレクションとしてSortedDictionaryクラスを使っているので、Holidayインスタンスをコレクションに追加すると日付順に自動的にソートされる。

// 成人の日:1月の第2月曜日
var 成人の日 = new Holiday()
{
  Date = GetNthMonday(2, Year, 1),
  Kind = HolidayKind.国民の祝日,
  Name = "成人の日", Definition = "1月の第2月曜日",
};
holidays.Add(成人の日.Date, 成人の日);

……省略……

' 成人の日:1月の第2月曜日
Dim 成人の日 = New Holiday() With
        {
          .Date = GetNthMonday(2, Year, 1),
          .Kind = HolidayKind.国民の祝日,
          .Name = "成人の日", .Definition = "1月の第2月曜日"
        }
holidays.Add(成人の日.Date, 成人の日)

……省略……

曜日指定の祝日をコレクションに加えるコードの例(上:C#、下:VB)
残りの曜日指定の祝日(海の日/敬老の日/体育の日)も、同様にしてholidaysコレクションに追加する。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と「オブジェクト初期化子」、および、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2010以前の環境で試すには、適宜修正していただきたい。

春分の日/秋分の日を求めるには?

 春分日/秋分日は、実験式から求める*2。そのようなロジックを「CalcVernalEquinoxDay」メソッド(春分日を求める)/「CalcAutumnalEquinoxDay」メソッド(秋分日を求める)としてまとめると、次のコードのようになる。

// 春分日を求める(2099年まで有効な実験式)
private static DateTime CalcVernalEquinoxDay(int year)
{
  // 1. 2000年の太陽の春分点通過日
  double 基準日 = 20.69115;

  // 2. 春分点通過日の移動量=(西暦年−2000年)×0.242194
  double 移動量 = (year - 2000) * 0.242194;

  // 3. 閏年によるリセット量=INT{(西暦年−2000年)/ 4}
  int 閏年補正 = (int)((year - 2000) / 4.0);

  // 求める年の春分日=INT{(1)+(2)−(3)}
  int 春分日 = (int)(基準日 + 移動量 - 閏年補正);

  return new DateTime(year, 3, 春分日);
}

// 秋分日を求める(2099年まで有効な実験式)
private static DateTime CalcAutumnalEquinoxDay(int year)
{
  // 1. 2000年の太陽の秋分点通過日
  double 基準日 = 23.09; // 秋分点の揺らぎ補正済みの値

  // 2. 秋分点通過日の移動量=(西暦年−2000年)×0.242194
  double 移動量 = (year - 2000) * 0.242194;

  // 3. 閏年によるリセット量=INT{(西暦年−2000年)/ 4}
  int 閏年補正 = (int)((year - 2000) / 4.0);

  // 求める年の秋分日=INT{(1)+(2)−(3)}
  int 秋分日 = (int)(基準日 + 移動量 - 閏年補正);

  return new DateTime(year, 9, 秋分日);
}

' 春分日を求める(2099年まで有効な実験式)
Private Function CalcVernalEquinoxDay(year As Integer) As DateTime

  ' 1. 2000年の太陽の春分点通過日
  Dim 基準日 As Double = 20.69115

  ' 2. 春分点通過日の移動量=(西暦年−2000年)×0.242194
  Dim 移動量 As Double = (year - 2000) * 0.242194

  ' 3. 閏年によるリセット量=INT{(西暦年−2000年)/ 4}
  Dim 閏年補正 As Integer = CType(Int((year - 2000) / 4.0), Integer)

  ' 求める年の春分日=INT{(1)+(2)−(3)}
  Dim 春分日 As Integer = CType(Int(基準日 + 移動量 - 閏年補正), Integer)

  Return New DateTime(year, 3, 春分日)
End Function

' 秋分日を求める(2099年まで有効な実験式)
Private Function CalcAutumnalEquinoxDay(year As Integer) As DateTime

  ' 1. 2000年の太陽の秋分点通過日
  Dim 基準日 As Double = 23.09 ' 秋分点の揺らぎ補正済みの値

  ' 2. 秋分点通過日の移動量=(西暦年−2000年)×0.242194
  Dim 移動量 As Double = (year - 2000) * 0.242194

  ' 3. 閏年によるリセット量=INT{(西暦年−2000年)/ 4}
  Dim 閏年補正 As Integer = CType(Int((year - 2000) / 4.0), Integer)

  ' 求める年の秋分日=INT{(1)+(2)−(3)}
  Dim 秋分日 As Integer = CType(Int(基準日 + 移動量 - 閏年補正), Integer)

  Return New DateTime(year, 9, 秋分日)
End Function

春分日/秋分日を求めるメソッドの例(上:C#、下:VB)
参照したWebページの記事*2との対応を分かりやすくするため、変数名を日本語とさせていただいた。

*2 春分日/秋分日とは、太陽が春分点/秋分点(=天の赤道と黄道が交差する点)を通過する瞬間を含んだ日。実験式とは、過去の観測データから導き出された式。未来の春分点/秋分点は天体の運動を計算すれば求められるように思える。しかし、まだ見つかっていない天体が将来において影響を及ぼす可能性がないとはいえないため、未来の春分点/秋分点を正確に決定することは不可能である。ここでは、「暦と天文の雑学〜将来の春分日・秋分日の計算」に掲載されている実験式を用いた。これは2099年まで合っているとされる。なお、国立天文台のWebページには2030年までの表が掲載されているので、それを利用するのも一案だ。


* 2015/07/24追記 [参考]
 春分の日と秋分の日を求める計算式が「int 閏年補正 = (int)((year - 2000) / 4.0);」となっている(C#の場合。VBも同様)。これは以下の条件から実験式が有効な範囲では常に4年に一度閏年があるためだ。

  • 基準日は2000年の春分点/秋分点である。つまり、400で割りきれる年の閏日よりも後の日付が計算の開始点
  • 2099年まで有効な実験式である。つまり、2000年以外に100で割りきれる年は含んでいない

 なお、これは実験式であるので、その適用範囲外での正当性は保証されない。実際、1917年の秋分点は9月24日であったが、この実験式を無理に当てはめてみると9月23日という結果になった。


 これらのメソッドを使って、法令で指定されている春分の日/秋分の日をコレクションに加えるコードは、次のようになる。なお念を押しておくと、春分の日/秋分の日は公式にはその前年の2月に官報で発表されるまで確定しない。

// 春分の日
var 春分の日 = new Holiday()
{
  Date = CalcVernalEquinoxDay(Year),
  Kind = HolidayKind.国民の祝日,
  Name = "春分の日", Definition = "春分日",
};
holidays.Add(春分の日.Date, 春分の日);

// 秋分の日
var 秋分の日 = new Holiday()
{
  Date = CalcAutumnalEquinoxDay(Year),
  Kind = HolidayKind.国民の祝日,
  Name = "秋分の日", Definition = "秋分日",
};
holidays.Add(秋分の日.Date, 秋分の日);

' 春分の日
Dim 春分の日 = New Holiday() With
        {
          .Date = CalcVernalEquinoxDay(Year),
          .Kind = HolidayKind.国民の祝日,
          .Name = "春分の日", .Definition = "春分日"
        }
holidays.Add(春分の日.Date, 春分の日)

' 秋分の日
Dim 秋分の日 = New Holiday() With
        {
          .Date = CalcAutumnalEquinoxDay(Year),
          .Kind = HolidayKind.国民の祝日,
          .Name = "秋分の日", .Definition = "秋分日"
        }
holidays.Add(秋分の日.Date, 秋分の日)

春分の日/秋分の日をコレクションに加えるコードの例(上:C#、下:VB)
このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と「オブジェクト初期化子」、および、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2010以前の環境で試すには、適宜修正していただきたい。

振替休日を求めるには?

 法に定められた規則をそのままロジックに書き直せばよい。

 国民の祝日に関する法律第三条2項には、「『国民の祝日』が日曜日に当たるときは、その日後においてその日に最も近い「国民の祝日」でない日を休日とする」と定められている。このロジックを「GetSubstituteHolidays」メソッドとしてまとめると、次のコードのようになる。

// 振替休日を全て求める
private static IEnumerable<Holiday> GetSubstituteHolidays(
                                      SortedDictionary<DateTime, Holiday> holidays)
{
  // 振替休日を格納するためのコレクション
  List<Holiday> substituteHolidays = new List<Holiday>();

  // これまでに求めた祝日を全部チェックする
  foreach (var holiday in holidays.Values)
  {
    if (holiday.Date.DayOfWeek != DayOfWeek.Sunday)
      continue; // 日曜でなければ除外する

    // 翌日(=月曜日)を仮に振替休日とする
    DateTime substitute = holiday.Date.AddDays(1.0);

    // その日がすでに祝日ならば振替休日はさらにその翌日
    while (holidays.ContainsKey(substitute))
      substitute = substitute.AddDays(1.0);

    // 見つかった振替休日をコレクションに追加する
    var substituteHoliday = new Holiday()
    {
      Date = substitute,
      Kind = HolidayKind.振替休日,
      Name = "振替休日",
      Definition = string.Format("{0}の振替休日", holiday.Name),
    };
    substituteHolidays.Add(substituteHoliday);
  }
  return substituteHolidays;
}

' 振替休日を全て求める
Private Function GetSubstituteHolidays(
  holidays As SortedDictionary(Of DateTime, Holiday)) As IEnumerable(Of Holiday)

  ' 振替休日を格納するためのコレクション
  Dim substituteHolidays As List(Of Holiday) = New List(Of Holiday)()

  ' これまでに求めた祝日を全部チェックする
  For Each holiday In holidays.Values
    If (holiday.Date.DayOfWeek <> DayOfWeek.Sunday) Then
      Continue For ' 日曜でなければ除外する
    End If

    ' 翌日(=月曜日)を仮に振替休日とする
    Dim substitute As DateTime = holiday.Date.AddDays(1.0)

    ' その日がすでに祝日ならば振替休日はさらにその翌日
    While (holidays.ContainsKey(substitute))
      substitute = substitute.AddDays(1.0)
    End While

    ' 見つかった振替休日をコレクションに追加する
    Dim substituteHoliday = New Holiday() With
    {
      .Date = substitute,
      .Kind = HolidayKind.振替休日,
      .Name = "振替休日",
      .Definition = String.Format("{0}の振替休日", holiday.Name)
    }
    substituteHolidays.Add(substituteHoliday)
  Next
  Return substituteHolidays
End Function

振替休日を求めるメソッドの例(上:C#、下:VB)
このメソッドは、引数holidaysに国民の祝日が全て格納されており、かつ、振替休日と国民の休日は含まれていないことを前提にしている。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と「オブジェクト初期化子」、および、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2010以前の環境で試すには、適宜修正していただきたい。

 このメソッドを使って、法令で指定されている振替休日をコレクションに加えるコードは、次のようになる。

var substituteHolidays = GetSubstituteHolidays(holidays);
foreach (var s in substituteHolidays)
  holidays.Add(s.Date, s);

Dim substituteHolidays = GetSubstituteHolidays(holidays)
For Each s In substituteHolidays
  holidays.Add(s.Date, s)
Next

振替休日をコレクションに加えるコードの例(上:C#、下:VB)
このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」を使用している。

国民の休日を求めるには?

 法に定められた規則をそのままロジックに書き直せばよい。

 国民の祝日に関する法律第三条3項には、「その前日及び翌日が『国民の祝日』である日(『国民の祝日』でない日に限る。)は、休日とする」と定められている。このロジックを「GetSandwichedHolidays」メソッドとしてまとめると、次のコードのようになる。

// 国民の休日を全て求める
private static IEnumerable<Holiday> GetSandwichedHolidays(
                                      SortedDictionary<DateTime, Holiday> holidays)
{
  List<Holiday> sandwichedHolidays = new List<Holiday>();

  // これまでに求めた祝日を全部チェックする
  foreach (var holiday0 in holidays.Values)
  {
    if (holiday0.Kind != HolidayKind.国民の祝日)
      continue; // その休日が国民の祝日でなければ除外する

    var day0 = holiday0.Date;

    var day2 = day0.AddDays(2.0); // 2日後
    if (!holidays.ContainsKey(day2))
      continue; // 2日後が祝日でないときは除外する
    var holiday2 = holidays[day2];
    if (holiday2.Kind != HolidayKind.国民の祝日)
      continue; // 2日後が祝日であっても国民の祝日でなければ除外する

    var day1 = day0.AddDays(1.0); // 1日後=国民の祝日で挟まれた日
    if (day1.DayOfWeek == DayOfWeek.Sunday)
      continue; // その日が日曜(=もともと休日)のときは除外する
    if (holidays.ContainsKey(day1))
      continue; // その日がすでに祝日のときは除外する

    // 見つかった国民の休日をコレクションに追加する
    var sandwichedHoliday = new Holiday()
    {
      Date = day1,
      Kind = HolidayKind.国民の休日,
      Name = "国民の休日",
      Definition = string.Format("{0}と{1}の間の日", holiday0.Name, holiday2.Name),
    };
    sandwichedHolidays.Add(sandwichedHoliday);
  }
  return sandwichedHolidays;
}

' 国民の休日を全て求める
Private Function GetSandwichedHolidays(
  holidays As SortedDictionary(Of DateTime, Holiday)) As IEnumerable(Of Holiday)

  Dim sandwichedHolidays As List(Of Holiday) = New List(Of Holiday)()

  ' これまでに求めた祝日を全部チェックする
  For Each holiday0 In holidays.Values
    If (holiday0.Kind <> HolidayKind.国民の祝日) Then
      Continue For ' その休日が国民の祝日でなければ除外する
    End If

    Dim day0 = holiday0.Date

    Dim day2 = day0.AddDays(2.0) ' 2日後
    If (Not holidays.ContainsKey(day2)) Then
      Continue For ' 2日後が祝日でないときは除外する
    End If
    Dim holiday2 = holidays(day2)
    If (holiday2.Kind <> HolidayKind.国民の祝日) Then
      Continue For ' 2日後が祝日であっても国民の祝日でなければ除外する
    End If

    Dim day1 = day0.AddDays(1.0) ' 1日後=国民の祝日で挟まれた日
    If (day1.DayOfWeek = DayOfWeek.Sunday) Then
      Continue For ' その日が日曜(=もともと休日)のときは除外する
    End If
    If (holidays.ContainsKey(day1)) Then
      Continue For ' その日がすでに祝日のときは除外する
    End If

    ' 見つかった国民の休日をコレクションに追加する
    Dim sandwichedHoliday = New Holiday() With
    {
      .Date = day1,
      .Kind = HolidayKind.国民の休日,
      .Name = "国民の休日",
      .Definition = String.Format("{0}と{1}の間の日", holiday0.Name, holiday2.Name)
    }
    sandwichedHolidays.Add(sandwichedHoliday)
  Next
  Return sandwichedHolidays
End Function

国民の休日を求めるメソッドの例(上:C#、下:VB)
このメソッドは、引数holidaysに国民の祝日が全て格納されており、かつ、振替休日も含まれている可能性があることを前提にしている。
なお、このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」と「オブジェクト初期化子」、および、Visual Basic 2010から利用できるようになった「暗黙の行連結」を使用している。Visual Basic 2010以前の環境で試すには、適宜修正していただきたい。

 このメソッドを使って、法令で指定されている国民の休日をコレクションに加えるコードは、次のようになる。

var sandwichedHolidays = GetSandwichedHolidays(holidays);
foreach (var s in sandwichedHolidays)
  holidays.Add(s.Date, s);

Dim sandwichedHolidays = GetSandwichedHolidays(holidays)
For Each s In sandwichedHolidays
  holidays.Add(s.Date, s)
Next

国民の休日をコレクションに加えるコードの例(上:C#、下:VB)
このVBのコードは、Visual Basic 2008から利用できるようになった「ローカル型の推論」を使用している。

実行結果

 以上でようやく完成である。出来上がったプログラムの実行結果を次の画像に示す。

祝日を求めるプログラムの実行結果の例(上:2015年、下:2016年)
祝日を求めるプログラムの実行結果の例(上:2015年、下:2016年) 祝日を求めるプログラムの実行結果の例(上:2015年、下:2016年)
2015年は5月6日に振替休日が、9月22日に国民の休日がある。
2016年は3月21日に振替休日があり、山の日(8月11日)が増えている。

まとめ

 日本の祝日を求めるロジックは厄介ではあるが、場合分けしてロジックを個別に組み立てていけば作り上げられる。ただし、法改正があるたびにロジックの修正が必要になる。本稿で示したコードは過去の法改正を取り込んでいないため、2007年以前には適用できない(過去の法改正を調べ上げてロジックを調整するよりも、過去の祝日はテーブルを作ってしまう方が楽だと思われる*3)。また、春分の日/秋分の日は、実験式で求めた春分日/秋分日と異なることはないとは思うが、前年2月の公式発表を確認するのを忘れないでほしい。

*3 日本の祝日を定めた過去の法改正を全て調べ上げるとなると、少なくとも「明治6年10月14日太政官第344号布告」(近代デジタルライブラリー収蔵)までさかのぼることになるだろう。昭和以降に限ったとしても大正元年勅令19号まで、戦後に限っても昭和2年勅令25号までさかのぼる必要がある。


利用可能バージョン: .NET Framework 3.5以降
カテゴリ: クラスライブラリ 処理対象:日付と時刻
使用ライブラリ: DateTime構造体(System名前空間)
使用ライブラリ: SortedDictionaryクラス(System.Collections.Generic名前空間)
関連TIPS: 指定した月から特定の曜日の日付を取得するには?[C#、VB]
関連TIPS: 日時や時間間隔の加減算を行うには?


■この記事と関連性の高い別の.NET TIPS


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

.NET TIPS

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

@IT Special

- PR -

TechTargetジャパン

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

RSSについて

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

メールマガジン登録

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