8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Delphi】週番号を得る

Last updated at Posted at 2026-02-04

はじめに

よく工業製品に YYYY 年の ww 週って表記があったりしますがこれを Delphi で扱ってみます。

週番号を得る

Delphi の System.DateUtils で定義されている週番号関連の関数は ISO 8601 に準拠しています。つまり、木曜日を最初に含む週 がその年の第 1 週と定義されています。言い換えれば その年の 01/04 を含む週 (週の始まりは月曜) が第 1 週です。

See also:

ある日が第何週にあたるのか?

Delphi で、ある日が第何週にあたるのかは次の関数で取得できます。

どちらも同じ値を返しますが、WeekOfTheYear() にはオーバーロードされた 2 つのパラメータを持つ関数があります。

See also:

ある年が何週あるのか?

Delphi で、ある日の年、またはある年が何週あるのかは次の関数で取得できます。

どちらも同じ値を返しますが、WeeksInYear() のパラメータは日付、WeeksInAYear() のパラメータは年です。

週番号と開始日の対応 (ISO 8601)

週番号が何日から始まるのかの一覧が欲しくなりました。

WeekNumber.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, '):');
  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:

週番号と開始日の対応 (アメリカ式)

でも、現在日本で使われているカレンダーは多くが日曜始まりですし、普通に考えれば 01/01 を含む週が第 1 週ですよねぇ?🤔

という事で、その年の 01/01 を含む週 (週の始まりは日曜) が第 1 週なコードも書いてみました。これは Excel の WEEKNUM() 関数を使った場合と同じ結果を返します。

WeekNumber2.dpr
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:

Excel 互換 WeekNum() の実装

ここまできたら Excel の WEEKNUM() 互換の関数を作りたくなりますよね。

WeekNumber3.dpr
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.

コンソールアプリケーションです。そのまま実行すると当年分を表示しますが、コマンドラインパラメータで対象年を指定する事ができます。

実行結果

合っているようです。

image.png

image.png

See also:

Excel 互換 WeekNum() の実装 (その2)

先の WeekNum() 関数では ReturnType = 21 (ISO 8601) を判定しています。

どうせ ReturnType = 21 を判定するのなら、その時に Delphi の System.DateUtils.WeekOf() を呼んでリターンした方が条件分岐も少なくてスッキリしそうです。

WeekNum改
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 型のヘルパーやインライン変数宣言を排除したものも作ってみました。

WeekNum改2
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.dprWeekNumber2.dpr です。

WeekNumber.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.
WeekNumber2.dpr
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.

image.png

  1. System.DateUtils.TDateTimeHelperDelphi 11 Alexandria 以降で利用可能です。

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?