はじめに
よく工業製品に YYYY 年の ww 週って表記があったりしますがこれを Delphi で扱ってみます。
週番号を得る
Delphi の System.DateUtils で定義されている週番号関連の関数は ISO 8601 に準拠しています。つまり、木曜日を最初に含む週 がその年の第 1 週と定義されています。言い換えれば その年の 01/04 を含む週 (週の始まりは月曜) が第 1 週です。
See also:
ある日が第何週にあたるのか?
Delphi で、ある日が第何週にあたるのかは次の関数で取得できます。
どちらも同じ値を返しますが、WeekOfTheYear() にはオーバーロードされた 2 つのパラメータを持つ関数があります。
See also:
ある年が何週あるのか?
Delphi で、ある日の年、またはある年が何週あるのかは次の関数で取得できます。
どちらも同じ値を返しますが、WeeksInYear() のパラメータは日付、WeeksInAYear() のパラメータは年です。
週番号と開始日の対応 (ISO 8601)
週番号が何日から始まるのかの一覧が欲しくなりました。
program WeekNumber;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.DateUtils;
begin
var BaseYear := Date.GetYear;
if ParamCount > 0 then
BaseYear := StrToIntDef(ParamStr(1), BaseYear);
var BaseDate := EncodeDate(BaseYear, 1, 4);
var TargetDate: TDateTime := StartOfTheWeek(BaseDate);
Writeln('Week numbers (', BaseYear, '):');
for var i:=1 to WeeksInAYear(BaseYear) do
begin
Writeln(i:2, ': ', TargetDate.ToString);
TargetDate := IncWeek(TargetDate);
end;
end.
この部分は
var TargetDate: TDateTime := StartOfTheWeek(BaseDate);
次のコードと等価です。
var TargetDate: TDateTime := BaseDate - Pred(DayOfTheWeek(BaseDate));
コンソールアプリケーションです。そのまま実行すると当年分を表示しますが、コマンドラインパラメータで対象年を指定する事ができます。
実行結果
実行結果です。
Week numbers (2026):
1: 2025/12/29
2: 2026/01/05
3: 2026/01/12
4: 2026/01/19
5: 2026/01/26
6: 2026/02/02
7: 2026/02/09
8: 2026/02/16
9: 2026/02/23
10: 2026/03/02
11: 2026/03/09
12: 2026/03/16
13: 2026/03/23
14: 2026/03/30
15: 2026/04/06
16: 2026/04/13
17: 2026/04/20
18: 2026/04/27
19: 2026/05/04
20: 2026/05/11
21: 2026/05/18
22: 2026/05/25
23: 2026/06/01
24: 2026/06/08
25: 2026/06/15
26: 2026/06/22
27: 2026/06/29
28: 2026/07/06
29: 2026/07/13
30: 2026/07/20
31: 2026/07/27
32: 2026/08/03
33: 2026/08/10
34: 2026/08/17
35: 2026/08/24
36: 2026/08/31
37: 2026/09/07
38: 2026/09/14
39: 2026/09/21
40: 2026/09/28
41: 2026/10/05
42: 2026/10/12
43: 2026/10/19
44: 2026/10/26
45: 2026/11/02
46: 2026/11/09
47: 2026/11/16
48: 2026/11/23
49: 2026/11/30
50: 2026/12/07
51: 2026/12/14
52: 2026/12/21
53: 2026/12/28
2026 年の最初の木曜日は 01/01 なので、この週の月曜日 (2025/12/29) が第 1 週の開始日となります。
See also:
- System.DateUtils.StartOfTheWeek (DocWiki)
- System.DateUtils.DayOfTheWeek (DocWiki) <-- 月曜日=1, 火曜日=2, ...日曜日=7 (ISO 8601)
- System.DateUtils.IncWeek (DocWiki)
週番号と開始日の対応 (アメリカ式)
でも、現在日本で使われているカレンダーは多くが日曜始まりですし、普通に考えれば 01/01 を含む週が第 1 週ですよねぇ?🤔
という事で、その年の 01/01 を含む週 (週の始まりは日曜) が第 1 週なコードも書いてみました。これは Excel の WEEKNUM() 関数を使った場合と同じ結果を返します。
program WeekNumber2;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.DateUtils;
begin
var BaseYear := Date.GetYear;
if ParamCount > 0 then
BaseYear := StrToIntDef(ParamStr(1), BaseYear);
var BaseDate := EncodeDate(BaseYear, 1, 1);
var TargetDate: TDateTime := BaseDate - Pred(DayOfWeek(BaseDate));
Writeln('Week numbers (', BaseYear, '):');
var WeeksInYear := 53;
if IsLeapYear(BaseYear) and (TargetDate.DayOfTheWeek = DaySaturday) then
Inc(WeeksInYear);
for var i:=1 to WeeksInYear do
begin
Writeln(i:2, ': ', TargetDate.ToString);
TargetDate := IncWeek(TargetDate);
end;
end.
コンソールアプリケーションです。そのまま実行すると当年分を表示しますが、コマンドラインパラメータで対象年を指定する事ができます。
実行結果
実行結果です。
Week numbers (2026):
1: 2025/12/28
2: 2026/01/04
3: 2026/01/11
4: 2026/01/18
5: 2026/01/25
6: 2026/02/01
7: 2026/02/08
8: 2026/02/15
9: 2026/02/22
10: 2026/03/01
11: 2026/03/08
12: 2026/03/15
13: 2026/03/22
14: 2026/03/29
15: 2026/04/05
16: 2026/04/12
17: 2026/04/19
18: 2026/04/26
19: 2026/05/03
20: 2026/05/10
21: 2026/05/17
22: 2026/05/24
23: 2026/05/31
24: 2026/06/07
25: 2026/06/14
26: 2026/06/21
27: 2026/06/28
28: 2026/07/05
29: 2026/07/12
30: 2026/07/19
31: 2026/07/26
32: 2026/08/02
33: 2026/08/09
34: 2026/08/16
35: 2026/08/23
36: 2026/08/30
37: 2026/09/06
38: 2026/09/13
39: 2026/09/20
40: 2026/09/27
41: 2026/10/04
42: 2026/10/11
43: 2026/10/18
44: 2026/10/25
45: 2026/11/01
46: 2026/11/08
47: 2026/11/15
48: 2026/11/22
49: 2026/11/29
50: 2026/12/06
51: 2026/12/13
52: 2026/12/20
53: 2026/12/27
米国の慣習で使われている週番号は週末 (金曜日、土曜日) が 01/01 だったら、最初の日曜日を含む週が第 1 週となったりするようです。
Excel の WEEKNUM() 関数 はこれを考慮しないため、1 年は 53 週または 54 週となります。01/01 が土曜日のうるう年である 2028 年は 54 週あります。
See also:
- System.SysUtils.DayOfWeek (DocWiki) <-- 日曜日=1, 月曜日=2, ...土曜日=7
Excel 互換 WeekNum() の実装
ここまできたら Excel の WEEKNUM() 互換の関数を作りたくなりますよね。
program WeekNumber3;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.DateUtils;
function WeekNum(const AValue: TDateTime; ReturnType: Integer = 1): Word;
const
BaseDay: array [Boolean] of Word = (1, 4);
var
BaseYear, StartDoW: Word;
begin
if ReturnType = 21 then
BaseYear := YearOf(AValue + (DayThursday - DayOfTheWeek(AValue)))
else
BaseYear := AValue.GetYear;
var BaseDate := EncodeDate(BaseYear, 1, BaseDay[ReturnType = 21]);
case ReturnType of
2, 21:
StartDoW := DayMonday;
11..16:
StartDoW := ReturnType - 10;
else
StartDoW := DaySunday;
end;
var TargetDate := BaseDate - ((DayOfTheWeek(BaseDate) - StartDoW + 7) mod 7);
Result := Succ(Trunc(AValue - TargetDate) div 7);
end; { WeekNum }
procedure PrintDate(const ADate: TDateTime);
begin
Write(FormatDateTime('MM/DD', ADate));
Write(WeekNum(ADate) :4);
Write(WeekNum(ADate, 1):4);
Write(WeekNum(ADate, 2):4);
Write(WeekNum(ADate, 11):4);
Write(WeekNum(ADate, 12):4);
Write(WeekNum(ADate, 13):4);
Write(WeekNum(ADate, 14):4);
Write(WeekNum(ADate, 15):4);
Write(WeekNum(ADate, 16):4);
Write(WeekNum(ADate, 17):4);
Write(WeekNum(ADate, 21):4);
Writeln;
end; { PrintDate }
begin
var BaseYear := Date.GetYear;
if ParamCount > 0 then
BaseYear := StrToIntDef(ParamStr(1), BaseYear);
// Heeaer
Writeln(BaseYear:5,' -', 1:4, 2:4, 11:4, 12:4, 13:4, 14:4, 15:4, 16:4, 17:4, 21:4);
Writeln(StringOfChar('-', 49));
// 1/1 .. 1/7
for var i:=1 to 7 do
PrintDate(EncodeDate(BaseYear, 01, i));
// 12/31
PrintDate(EncodeDate(BaseYear, 12, 31));
end.
コンソールアプリケーションです。そのまま実行すると当年分を表示しますが、コマンドラインパラメータで対象年を指定する事ができます。
実行結果
合っているようです。
See also:
Excel 互換 WeekNum() の実装 (その2)
先の WeekNum() 関数では ReturnType = 21 (ISO 8601) を判定しています。
どうせ ReturnType = 21 を判定するのなら、その時に Delphi の System.DateUtils.WeekOf() を呼んでリターンした方が条件分岐も少なくてスッキリしそうです。
function WeekNum(const AValue: TDateTime; ReturnType: Integer = 1): Word;
begin
if ReturnType = 21 then
Exit(AValue.WeekOfTheYear);
var BaseDate: TDateTime := StartOfTheYear(AValue);
var StartDoW := DaySunday;
case ReturnType of
2:
StartDoW := DayMonday;
11..16:
StartDoW := ReturnType - 10;
end;
var TargetDate := BaseDate - ((BaseDate.DayOfTheWeek - StartDoW + 7) mod 7);
Result := Succ(Trunc(AValue - TargetDate) div 7);
end; { WeekNum }
ついでに、使っていた日付時刻ルーチンを TDateTime 型のヘルパー 1 で置き替えました。
See also:
Excel 互換 WeekNum() の実装 (その3)
逆にかなり古い Delphi でも使えるよう、TDateTime 型のヘルパーやインライン変数宣言を排除したものも作ってみました。
function WeekNum(const AValue: TDateTime; ReturnType: Integer = 1): Word;
var
BaseDate, TargetDate: TDateTime;
StartDoW: Word;
begin
if ReturnType = 21 then
begin
Result := WeekOfTheYear(AValue);
Exit;
end;
BaseDate := StartOfTheYear(AValue);
case ReturnType of
2:
StartDoW := DayMonday;
11..16:
StartDoW := ReturnType - 10;
else
StartDoW := DaySunday;
end;
TargetDate := BaseDate - ((DayOfTheWeek(BaseDate) - StartDoW + 7) mod 7);
Result := Succ(Trunc(AValue - TargetDate) div 7);
end; { WeekNum }
少なくとも Delphi 7 で動作します。
おわりに
今回は週番号を取得してみました。慣習の US 週番号を得るためにはもう少し工夫が必要なようですね。
おまけ
当日が含まれていればそれを表示する WeekNumber.dpr と WeekNumber2.dpr です。
program WeekNumber;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.DateUtils;
begin
var BaseYear := Date.GetYear;
if ParamCount > 0 then
BaseYear := StrToIntDef(ParamStr(1), BaseYear);
var BaseDate := EncodeDate(BaseYear, 1, 4);
var TargetDate: TDateTime := StartOfTheWeek(BaseDate);
Writeln('Week numbers (', BaseYear, '):');
var Flg := BaseYear = Date.GetYear;
for var i:=1 to WeeksInAYear(BaseYear) do
begin
Write(i:2, ': ', TargetDate.ToString);
TargetDate := IncWeek(TargetDate);
if Flg and (TargetDate > Date) then
begin
Write(' <--');
Flg := False;
end;
Writeln;
end;
end.
program WeekNumber2;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.DateUtils;
begin
var BaseYear := Date.GetYear;
if ParamCount > 0 then
BaseYear := StrToIntDef(ParamStr(1), BaseYear);
var BaseDate := EncodeDate(BaseYear, 1, 1);
var TargetDate: TDateTime := BaseDate - Pred(DayOfWeek(BaseDate));
Writeln('Week numbers (', BaseYear, '):');
var WeeksInYear := 53;
if IsLeapYear(BaseYear) and (TargetDate.DayOfTheWeek = DaySaturday) then
Inc(WeeksInYear);
var Flg := BaseYear = Date.GetYear;
for var i:=1 to WeeksInYear do
begin
Write(i:2, ': ', TargetDate.ToString);
TargetDate := IncWeek(TargetDate);
if Flg and (TargetDate > Date) then
begin
Write(' <--');
Flg := False;
end;
Writeln;
end;
end.
-
System.DateUtils.TDateTimeHelperは Delphi 11 Alexandria 以降で利用可能です。 ↩


