ISOWeekは.NET Standard 2.1/.NET Core 3.0より導入されたクラスで、ISO 8601によって定められる週番号週暦を扱うためのクラスです。 このクラスには、DateTimeで表される日付と、ISO週暦での年(週年)・週番号との相互変換を行うメソッドが用意されています。

以下この文書では、年月日のみ(date)を表すために日付、時分秒のみ(time)を表すために時刻、日付と時刻の組み合わせ(date and time)を表すために日時の各用語を用います。

ISO 8601 週番号・週暦・週年

ここではISO 8601における週番号および関連する用語の整理と簡単な解説をします。 一部非公式な用語を用いている箇所もあるため、正式な用語・厳密な定義についてはISO 8601を参照してください。

ISO 8601における週番号(ISO週番号, week number)は、ある年の日付が、その年の第何週であるかを表すものです。 慣用的には日付を年・月・日の3要素から構成される値(ymd日)として表す一方、ISO週番号に基づく暦(ISO週暦, week calendar)では日付を年・週・曜日の3要素(y年第wd曜日、あるいはy年第wd日目)で表します。

ISO 8601における週(ISO週, week)では、月曜日が週の第一日、つまり週は月曜日から始まるとされます。 慣用的に用いられる曜日では日曜日や月曜日が週における第一日とされるなど、分野や実装によって週の始まりの定義が異なり、また場合によっては任意に選択可能なこともある中、ISO週・ISO週暦では月曜日が週の始まりであり、これに従って週番号と週年(後述)が定められます。 一方、週が常に7日(7つの曜日)で構成される点は慣用的な週と同じです。

.NETにおいては、曜日を表す列挙体DayOfWeekは日曜日(DayOfWeek.Sunday)が0となっています。 また、DateTimeFormatInfo.FirstDayOfWeekで暦(DateTimeFormatInfo.Calendar)における最初の曜日を任意に変更することができます。

ISO週暦では、週を基準にした年=週年(ISO週年, week-year)が用いられます。 これにより、ある日付における年月日のと、それと同一日付の週年は異なる場合があります。

例えば日付2020年1月1日(水曜日)はISO週暦では2020年第1週水曜日(同週3日目)となる一方で、その前日の日付2019年12月31日(火曜日)は2020年第1週火曜日(同週2日目)となります。 逆の例で言えば日付2020年第1週月曜日(同週1日目)は2019年12月30日となります。 このように、ISO週暦における最初週・最終週の週年は、年月日の年とは一致しない場合があります。

ISO週暦では、年(週年)によって週の数が異なり、52または53のいずれかになります。 また、第1週はその年における最初の木曜日が含まれる週とされます。

ISO週年・週番号と年月日の例
ISO週年・週 月曜日 火曜日 水曜日 木曜日 金曜日 土曜日 日曜日 年・月
週第1日目 週第2日目 週第3日目 週第4日目 週第5日目 週第6日目 週第7日目
2019年第52週 12月23日 12月24日 12月25日 12月26日 12月27日 12月28日 12月29日 2019年12月
2020年第1週 12月30日 12月31日
1月1日 1月2日 1月3日 1月4日 1月5日 2020年1月
2020年第2週 1月6日 1月7日 1月8日 1月9日 1月10日 1月11日 1月12日



2020年第53週 12月28日 12月29日 12月30日 12月31日 2020年12月
1月1日 1月2日 1月3日 2021年1月
2021年第1週 1月4日 1月5日 1月6日 1月7日 1月8日 1月9日 1月10日
ISO週年・週 月曜日 火曜日 水曜日 木曜日 金曜日 土曜日 日曜日 年・月

年・週・曜日の表記

ISO 8601では年・月・日の表記(YYYY-MM-DD)と同様に、年・週・曜日の表記としてYYYY-Www-DDが定められています。 年月日の表記とは異なり、YYYY部分には週年Wを前置してww部分には週番号DD部分には曜日番号(weekday number)を代入します。 曜日番号は曜日を1〜7の数字で表す番号で、週の月曜日(第1日)が1、日曜日(第7日)が7となります。

ISO 8601での日付の表記(拡張形式)の例
日付 年・月・日 年・週・曜日
2019年12月31日
2020年第1週火曜日(2日目)
2019-12-31 2020-W01-02
2020年1月1日
2020年第1週水曜日(3日目)
2020-01-01 2020-W01-03

ISOWeekクラス (System.Globalization.ISOWeek)

ISOWeekクラスはISO週・ISO週暦を扱うクラスで、DateTimeで表される日付とISO週番号・週年の変換などを行うことができます。

ISOWeekクラスではDateTimeからの/への変換を行うことができますが、単純に日付のみが扱われます。 そのため、時刻の種類(DateTime.Kind)やオフセット値(DateTimeOffset.Offset)は考慮されません。

DateTimeOffsetで表される日付を変換したい場合は、DateTimeOffset.DateプロパティまたはDateTimeOffset.DateTimeプロパティを参照して日付・日時のみを表すDateTimeを取得して使用します。

ISO週番号・週年の取得 (GetWeekOfYear/GetYear)

GetWeekOfYearメソッドを使用すると、DateTimeの表す日付に対応するISO週番号を取得することができます。 同様に、GetYearメソッドを使用するとISO週年(ISO週暦に基づく年)を取得することができます。

この2つのメソッドを用いることにより、ある日付がISO週暦での何年第何週であるかを求めることができます。

曜日(DayOfWeek列挙体)あるいはDateTimeからISO曜日番号を取得するメソッドは存在しないため、DateTime.DayOfWeekプロパティから得られる値を元に計算する必要があります。 (.NET 5の時点)

ISOWeek.GetWeekOfYear/GetYearメソッドで日付に対応するISO週番号・ISO週年を取得する
using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    var d = new DateTime(2020, 12, 26);
    var ja = new CultureInfo("ja"); // 日本語のカルチャ

    // 15日分の日付を変換する
    for (var day = 0; day < 15; day++) {
      Console.Write("{0} = ", d.ToString("D", ja)); // DateTimeを日付のみ・長い形式(ja/日本語)で文字列化

      // ISO週年・週番号・曜日番号に変換して表示する
      Console.WriteLine(
        "ISO週暦 {0}年 第{1}週 第{2}日",
        ISOWeek.GetYear(d),       // DateTimeの日付を対応するISO週年に変換
        ISOWeek.GetWeekOfYear(d), // DateTimeの日付を対応するISO週番号に変換
        GetISOWeekdayNumber(d)    // DateTimeの日付を対応するISO曜日番号に変換
      );

      d = d.AddDays(1); // 翌日のDateTimeを取得
    }
  }

  static int GetISOWeekdayNumber(DateTime date) => date.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)date.DayOfWeek;
}
ISOWeek.GetWeekOfYear/GetYearメソッドで日付に対応するISO週番号・ISO週年を取得する
Imports System
Imports System.Globalization

Class Sample
  Shared Sub Main()
    Dim d As New DateTime(2020, 12, 26)
    Dim ja As New CultureInfo("ja") ' 日本語のカルチャ

    ' 15日分の日付を変換する
    For day As Integer = 1 To 15
      Console.Write("{0} = ", d.ToString("D", ja)) ' DateTimeを日付のみ・長い形式(ja/日本語)で文字列化

      ' ISO週年・週番号・曜日番号に変換して表示する
      Console.WriteLine(
        "ISO週暦 {0}年 第{1}週 第{2}日",
        ISOWeek.GetYear(d),       ' DateTimeの日付を対応するISO週年に変換
        ISOWeek.GetWeekOfYear(d), ' DateTimeの日付を対応するISO週番号に変換
        GetISOWeekdayNumber(d)    ' DateTimeの日付を対応するISO曜日番号に変換
      )

      d = d.AddDays(1) ' 翌日のDateTimeを取得
    Next
  End Sub

  Shared Function GetISOWeekdayNumber(ByVal d As DateTime) As Integer
    Return If(d.DayOfWeek = DayOfWeek.Sunday, 7, CInt(d.DayOfWeek))
  End Function
End Class
実行結果
2020年12月26日土曜日 = ISO週暦 2020年 第52週 第6日
2020年12月27日日曜日 = ISO週暦 2020年 第52週 第7日
2020年12月28日月曜日 = ISO週暦 2020年 第53週 第1日
2020年12月29日火曜日 = ISO週暦 2020年 第53週 第2日
2020年12月30日水曜日 = ISO週暦 2020年 第53週 第3日
2020年12月31日木曜日 = ISO週暦 2020年 第53週 第4日
2021年1月1日金曜日 = ISO週暦 2020年 第53週 第5日
2021年1月2日土曜日 = ISO週暦 2020年 第53週 第6日
2021年1月3日日曜日 = ISO週暦 2020年 第53週 第7日
2021年1月4日月曜日 = ISO週暦 2021年 第1週 第1日
2021年1月5日火曜日 = ISO週暦 2021年 第1週 第2日
2021年1月6日水曜日 = ISO週暦 2021年 第1週 第3日
2021年1月7日木曜日 = ISO週暦 2021年 第1週 第4日
2021年1月8日金曜日 = ISO週暦 2021年 第1週 第5日
2021年1月9日土曜日 = ISO週暦 2021年 第1週 第6日

この例で使用している、書式と書式プロバイダを指定した書式化については日時・文字列の変換と書式 §.書式とローカライズ、カルチャについてはカルチャの基本・種類・カルチャ情報の取得、日付と時刻の書式についてはカルチャと書式・テキスト処理・暦 §.日付と時間の書式 (DateTimeFormatInfo)を参照してください。

ISO週暦での日付からDateTimeへの変換 (ToDateTime)

ToDateTimeメソッドを使うことにより、ISO週暦でのyear年・第week週・dayOfWeek曜日に対応する日付をDateTimeで取得することができます。

このメソッドでは曜日番号(week number)ではなく曜日(DayOfWeek列挙体)を引数にとるため、曜日番号から日付を求める場合は事前にDayOfWeekに変換する必要があります。

指定された値に該当する日付がDateTimeの最小値・最大値を超える場合や、週番号・曜日が範囲外の場合は、例外ArgumentOutOfRangeExceptionがスローされます。

ISOWeek.ToDateTimeメソッドでISO週暦でのy年第w週第d日に対応する日付を取得する
using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    var isoWeekDates = new[] {
      new {year = 2020, weekNumber = 1, weekdayNumber = 1},                             // 2020年・最初週・最初曜日
      new {year = 2020, weekNumber = 42, weekdayNumber = 5},                            // 2020年・第42週・第5日
      new {year = 2020, weekNumber = ISOWeek.GetWeeksInYear(2020), weekdayNumber = 7},  // 2020年・最終週・最終曜日
      new {year = 2021, weekNumber = 1, weekdayNumber = 1},                             // 2021年・最初週・最初曜日
      new {year = 2021, weekNumber = ISOWeek.GetWeeksInYear(2021), weekdayNumber = 7},  // 2021年・最終週・最終曜日
    };
    var ja = new CultureInfo("ja"); // 日本語のカルチャ

    foreach (var d in isoWeekDates) {
      var dayOfWeek = GetDayOfWeekFromISOWeekdayNumber(d.weekdayNumber); // ISO曜日番号からDayOfWeek(曜日)に変換

      Console.Write(
        "ISO週暦 {0}年 第{1}週 第{2}日 ({3}) = ",
        d.year,
        d.weekNumber,
        d.weekdayNumber,
        ja.DateTimeFormat.DayNames[(int)dayOfWeek] // 曜日名(ja/日本語)を取得
      );

      // ISO週暦での日付(週年・週番号・曜日)をDateTimeに変換して表示する
      var dt = ISOWeek.ToDateTime(d.year, d.weekNumber, dayOfWeek);

      Console.WriteLine(dt.ToString("D", ja)); // DateTimeを日付のみ・長い形式(ja/日本語)で文字列化
    }
  }

  static DayOfWeek GetDayOfWeekFromISOWeekdayNumber(int weekdayNumber)
    => weekdayNumber == 7 ? DayOfWeek.Sunday : (DayOfWeek)weekdayNumber; // 引数チェックは省略
}
ISOWeek.ToDateTimeメソッドでISO週暦でのy年第w週第d日に対応する日付を取得する
Imports System
Imports System.Globalization

Class Sample
  Shared Sub Main()
    Dim isoWeekDates = {
      New With {.year = 2020, .weekNumber = 1, .weekdayNumber = 1},                             ' 2020年・最初週・最初曜日
      New With {.year = 2020, .weekNumber = 42, .weekdayNumber = 5},                            ' 2020年・第42週・第5日
      New With {.year = 2020, .weekNumber = ISOWeek.GetWeeksInYear(2020), .weekdayNumber = 7},  ' 2020年・最終週・最終曜日
      New With {.year = 2021, .weekNumber = 1, .weekdayNumber = 1},                             ' 2021年・最初週・最初曜日
      New With {.year = 2021, .weekNumber = ISOWeek.GetWeeksInYear(2021), .weekdayNumber = 7}   ' 2021年・最終週・最終曜日
    }
    Dim ja As New CultureInfo("ja") ' 日本語のカルチャ

    For Each d In isoWeekDates
      Dim dayOfWeek As DayOfWeek = GetDayOfWeekFromISOWeekdayNumber(d.weekdayNumber) ' ISO曜日番号からDayOfWeek(曜日)に変換

      Console.Write(
        "ISO週暦 {0}年 第{1}週 第{2}日 ({3}) = ",
        d.year,
        d.weekNumber,
        d.weekdayNumber,
        ja.DateTimeFormat.DayNames(CInt(dayOfWeek)) ' 曜日名(ja/日本語)を取得
      )

      ' ISO週暦での日付(週年・週番号・曜日)をDateTimeに変換して表示する
      Dim dt As DateTime = ISOWeek.ToDateTime(d.year, d.weekNumber, dayOfWeek)

      Console.WriteLine(dt.ToString("D", ja)) ' DateTimeを日付のみ・長い形式(ja/日本語)で文字列化
    Next
  End Sub

  Shared Function GetDayOfWeekFromISOWeekdayNumber(ByVal weekdayNumber As Integer) As DayOfWeek
    Return If(weekdayNumber = 7, DayOfWeek.Sunday, CType(weekdayNumber, DayOfWeek)) ' 引数チェックは省略
  End Function
End Class
実行結果
ISO週暦 2020年 第1週 第1日 (月曜日) = 2019年12月30日月曜日
ISO週暦 2020年 第42週 第5日 (金曜日) = 2020年10月16日金曜日
ISO週暦 2020年 第53週 第7日 (日曜日) = 2021年1月3日日曜日
ISO週暦 2021年 第1週 第1日 (月曜日) = 2021年1月4日月曜日
ISO週暦 2021年 第52週 第7日 (日曜日) = 2022年1月2日日曜日

この例で使用しているGetWeeksInYearメソッドについては§.ISO週暦での年の開始日/終了日/週数の取得 (GetYearStart/GetYearEnd/GetWeeksInYear)を参照してください。

この例で使用している、書式と書式プロバイダを指定した書式化については日時・文字列の変換と書式 §.書式とローカライズ、カルチャについてはカルチャの基本・種類・カルチャ情報の取得、日付と時刻の書式についてはカルチャと書式・テキスト処理・暦 §.日付と時間の書式 (DateTimeFormatInfo)を参照してください。

ISO週暦での年の開始日/終了日/週数の取得 (GetYearStart/GetYearEnd/GetWeeksInYear)

GetYearStartメソッド/GetYearEndメソッドを使用することで、ある年(ISO週年)の開始日・終了日をDateTimeで取得することができます。 また、GetWeeksInYearメソッドを使用することで、ある週年における週の数を求めることができます。

GetYearStartメソッドが返す日付の曜日・DateTime.DayOfWeekプロパティは常に月曜日(DayOfWeek.Monday)、GetYearEndメソッドは常に日曜日(DayOfWeek.Sunday)となります。

ISOWeek.GetYearStart/GetYearEnd/GetWeeksInYearメソッドでISO週年の開始日/終了日/週の数を取得する
using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    // 2015年〜2025年について
    for (var year = 2015; year <= 2025; year++) {
      Console.WriteLine(
        "ISO週暦 {0}年: {1:d} 〜 {2:d} (全{3}週)",
        year,
        ISOWeek.GetYearStart(year),   // ISO週年yearの開始日をDateTimeで取得
        ISOWeek.GetYearEnd(year),     // ISO週年yearの終了日をDateTimeで取得
        ISOWeek.GetWeeksInYear(year)  // ISO週年yearにおける週の数を取得
      );
    }
  }
}
ISOWeek.GetYearStart/GetYearEnd/GetWeeksInYearメソッドでISO週年の開始日/終了日/週の数を取得する
Imports System
Imports System.Globalization

Class Sample
  Shared Sub Main()
    ' 2015年〜2025年について
    For year As Integer = 2015 To 2025
      Console.WriteLine(
        "ISO週暦 {0}年: {1:d} 〜 {2:d} (全{3}週)",
        year,
        ISOWeek.GetYearStart(year),   ' ISO週年yearの開始日をDateTimeで取得
        ISOWeek.GetYearEnd(year),     ' ISO週年yearの終了日をDateTimeで取得
        ISOWeek.GetWeeksInYear(year)  ' ISO週年yearにおける週の数を取得
      )
    Next
  End Sub
End Class
実行結果
ISO週暦 2015年: 2014/12/29 〜 2016/01/03 (全53週)
ISO週暦 2016年: 2016/01/04 〜 2017/01/01 (全52週)
ISO週暦 2017年: 2017/01/02 〜 2017/12/31 (全52週)
ISO週暦 2018年: 2018/01/01 〜 2018/12/30 (全52週)
ISO週暦 2019年: 2018/12/31 〜 2019/12/29 (全52週)
ISO週暦 2020年: 2019/12/30 〜 2021/01/03 (全53週)
ISO週暦 2021年: 2021/01/04 〜 2022/01/02 (全52週)
ISO週暦 2022年: 2022/01/03 〜 2023/01/01 (全52週)
ISO週暦 2023年: 2023/01/02 〜 2023/12/31 (全52週)
ISO週暦 2024年: 2024/01/01 〜 2024/12/29 (全52週)
ISO週暦 2025年: 2024/12/30 〜 2025/12/28 (全52週)

ISO週暦・ISO形式での文字列化

DateTime/DateTimeOffsetでは、標準の書式指定子O/o(書式指定子 §.O, o (round-trip date/time))またはs(書式指定子 §.s (sortable date/time))によってISO 8601の拡張形式で文字列化することができます。 ただし、いずれの書式でも日付部分には(グレゴリオ暦での)年月日が用いられます。

ISOWeekクラスには日付をISO週暦での年・週・曜日を用いた形式で文字列化するメソッドは用意されておらず、また(少なくとも.NET 5時点では)標準の書式指定子にもそういった書式は用意されていません。 そのため、必要な場合はISOWeekクラスのメソッドを組み合わせて独自に実装する必要があります。

ISO週暦での日付をISO 8601拡張形式で文字列化する
using System;
using System.Globalization;

class Sample {
  static int GetISOWeekdayNumber(DateTime date) => date.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)date.DayOfWeek;

  // DateTimeで与えられる日付をISO 8601拡張形式(年・週・曜日)で文字列化する
  static string ToISO8601WeekDateFormatString(DateTime date)
    => string.Format(
      "{0:D4}-W{1:D2}-{2:D2}",
      ISOWeek.GetYear(date),        // DateTimeに対応するISO週年を取得
      ISOWeek.GetWeekOfYear(date),  // DateTimeに対応するISO週番号を取得
      GetISOWeekdayNumber(date)     // DateTimeに対応するISO曜日番号を取得
    );

  static void Main()
  {
    var dates = new[] {
      new DateTime(2019, 12, 29),
      new DateTime(2019, 12, 30),
      new DateTime(2020, 1, 1),
      new DateTime(2020, 6, 30),
      new DateTime(2020, 12, 31),
      new DateTime(2021, 1, 3),
      new DateTime(2021, 12, 31),
    };

    foreach (var d in dates) {
      // ISO 8601拡張形式の年・月・日(秒の端数部を除く)および年・週・曜日の書式で文字列化する
      Console.WriteLine(
        "{0} => {1}",
        d.ToString("s"),
        ToISO8601WeekDateFormatString(d)
      );
    }
  }
}
ISO週暦での日付をISO 8601拡張形式で文字列化する
Imports System
Imports System.Globalization

Class Sample
  Shared Function GetISOWeekdayNumber(ByVal d As DateTime) As Integer
    Return If(d.DayOfWeek = DayOfWeek.Sunday, 7, CInt(d.DayOfWeek))
  End Function

  ' DateTimeで与えられる日付をISO 8601拡張形式(年・週・曜日)で文字列化する
  Shared Function ToISO8601WeekDateFormatString(ByVal d As DateTime) As String
    Return String.Format(
      "{0:D4}-W{1:D2}-{2:D2}",
      ISOWeek.GetYear(d),       ' DateTimeに対応するISO週年を取得
      ISOWeek.GetWeekOfYear(d), ' DateTimeに対応するISO週番号を取得
      GetISOWeekdayNumber(d)    ' DateTimeに対応するISO曜日番号を取得
    )
  End Function

  Shared Sub Main()
    Dim dates As DateTime() = {
      New DateTime(2019, 12, 29),
      New DateTime(2019, 12, 30),
      New DateTime(2020, 1, 1),
      New DateTime(2020, 6, 30),
      New DateTime(2020, 12, 31),
      New DateTime(2021, 1, 3),
      New DateTime(2021, 12, 31)
    }

    For Each d As DateTime In dates
      ' ISO 8601拡張形式の年・月・日(秒の端数部を除く)および年・週・曜日の書式で文字列化する
      Console.WriteLine(
        "{0} => {1}",
        d.ToString("s"),
        ToISO8601WeekDateFormatString(d)
      )
    Next
  End Sub
End Class
実行結果
2019-12-29T00:00:00 => 2019-W52-07
2019-12-30T00:00:00 => 2020-W01-01
2020-01-01T00:00:00 => 2020-W01-03
2020-06-30T00:00:00 => 2020-W27-02
2020-12-31T00:00:00 => 2020-W53-04
2021-01-03T00:00:00 => 2020-W53-07
2021-12-31T00:00:00 => 2021-W52-05