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要素から構成される値(y年m月d日)として表す一方、ISO週番号に基づく暦(ISO週暦, week calendar)では日付を年・週・曜日の3要素(y年第w週d曜日、あるいはy年第w週d日目)で表します。
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週年・週 | 月曜日 | 火曜日 | 水曜日 | 木曜日 | 金曜日 | 土曜日 | 日曜日 | 年・月 |
---|---|---|---|---|---|---|---|---|
週第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
となります。
日付 | 年・月・日 | 年・週・曜日 |
---|---|---|
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の時点)
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;
}
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がスローされます。
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; // 引数チェックは省略
}
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)となります。
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における週の数を取得
);
}
}
}
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クラスのメソッドを組み合わせて独自に実装する必要があります。
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)
);
}
}
}
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