3
1

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】CLI カレンダーアプリケーションを作る

Last updated at Posted at 2026-02-06

はじめに

先日、

という記事を書いたのですが、Windows には Linux の cal のような CLI カレンダーがないので、これを踏まえたカレンダーを作ってみたくなりました。

See also:

cal.exe

いきなりですが、ソースコードです。

cal.dpr
program cal;
{$APPTYPE CONSOLE}
{$RTTI EXPLICIT METHODS([]) FIELDS([]) PROPERTIES([])}
{$WEAKLINKRTTI ON}
{$DEFINE USE_HIGHLIGHT}
uses
  System.SysUtils, System.DateUtils;
const
  CAL_WIDTH:   array [Boolean] of Integer = (25, 32);
  CAL_COLS:    array [Boolean] of Integer = (3, 2);
  CAL_DIGITS:  array [Boolean] of Integer = (2, 3);
  CAL_HEADERS: array [Boolean] of array [Boolean] of string =
    (('Wk| Su Mo Tu We Th Fr Sa', 'Wk| Sun Mon Tue Wed Thu Fri Sat'),
     ('Wk| Mo Tu We Th Fr Sa Su', 'Wk| Mon Tue Wed Thu Fri Sat Sun'));
type
  TCal = array [0..8] of string;
var
  CalArr: array of TCal;
  MonthArr: array of Boolean;

function WeekOf_US(const AValue: TDateTime): Word;
begin
  var BaseDate: TDateTime := StartOfTheYear(AValue);
  var TargetDate := BaseDate - (BaseDate.DayOfTheWeek mod 7);
  Result := Succ(Trunc(AValue - TargetDate) div 7);
end; { WeekOf_US }

function GenerateCalendar(const ADate: TDateTime; const IsISO, IsSN, ShowToday: Boolean): TCal;
var
  _WeekOf: function (const AValue: TDateTime): Word;
  _DayOfWeek: function (const DateTime: TDateTime): Word;
begin
  var dCal := Default(TCal);
  var TargetDate := StartOfTheMonth(ADate);
  var DIM := DaysInMonth(TargetDate);
  var Line := 0;
  dCal[Line] := StringOfChar(' ', (CAL_WIDTH[IsSN] - 7) div 2) +
                FormatDateTime('YYYY/MM', TargetDate); Inc(Line);
  if IsISO  then
  begin
    _WeekOf    := WeekOf;
    _DayOfWeek := DayOfTheWeek;
  end
  else
  begin
    _WeekOf    := WeekOf_US;
    _DayOfWeek := DayOfWeek;
  end;
  dCal[Line] := CAL_HEADERS[IsISO][IsSN]; Inc(Line);
  dCal[Line] := StringOfChar('-', CAL_WIDTH[IsSN]); Inc(Line);
  var SN := 0;
  if IsSN then
    SN := Trunc(StartOfTheMonth(ADate) - StartOfTheYear(ADate));
  var StrFmt := '%' + CAL_DIGITS[IsSN].ToString + 'd';
  var Day := 1;
  var StartDoW := _DayOfWeek(TargetDate);
  var HasToday := ShowToday and (ADate.GetYear = Today.GetYear) and (ADate.GetMonth = Today.GetMonth);
  var BoW := TargetDate;
  repeat
    var LineStr := Format('%2d|', [_WeekOf(BoW)]);
    LineStr := LineStr + StringOfChar(' ', Pred(StartDoW) * Succ(CAL_DIGITS[IsSN]));
    var Count := 0;
    for var i:= StartDoW to 7 do
    begin
      var sDay := Format(StrFmt, [SN + Day + Count]);
      if HasToday and (Day + Count = Today.GetDay) then
      begin
        sDay := '[' + sDay + ']'; // Today marker
        HasToday := False;
      end
      else if LineStr[Length(LineStr)] <> ']'then
        sDay := ' ' + sDay;
      LineStr := LineStr + sDay;
      Inc(Count);
      if (Day + Count) > DIM then
        Break;
    end;
    Inc(Day, Count);
    BoW := BoW + Count;
    StartDoW := 1;
    dCal[Line] := LineStr; Inc(Line);
  until Day > DIM;
  Result := dCal;
end; { PrintCalendar }

begin
  // コマンドラインの処理
  if FindCmdLineSwitch('?') then
  begin
    Writeln('Usage: ',
            ChangeFileExt(ExtractFileName(ParamStr(0)), ''),
            ' [-3] [-h] [-j] [-m] [-y] [year [month]]');
    Exit;
  end;
  var PDate: TDateTime := Today;
  var dYear  := 0;
  var dMonth := 0;
  var UseISO    := FindCmdLineSwitch('m');
  var UseSN     := FindCmdLineSwitch('j');
  var ShowToday := not FindCmdLineSwitch('h');
  var Months := 1;
  if FindCmdLineSwitch('3') then
    Months := 3;
  if FindCmdLineSwitch('y') then
    Months := 12;
  for var i:=1 to ParamCount do
  begin
    var sw := ParamStr(i);
    if CharInSet(sw[1], ['0'..'9']) then
    begin
      var v := StrToIntDef(sw, 0);
      if v > 0 then
      begin
        if dYear = 0 then
          dYear := v
        else if v in [1..12] then
          dMonth := v;
      end;
      if dMonth > 0 then
        Break;
    end;
  end;
  if dYear > 0 then
  begin
    if dMonth = 0 then
    begin
      Months := 12;
      dMonth := 1;
    end;
    PDate := EncodeDate(dYear, dMonth, 1);
  end;
  // カレンダーの生成
  SetLength(CalArr, Months);
  SetLength(MonthArr, Months);
  var TargetDate: TDateTime;
  case Months of
    12: TargetDate := StartOfTheYear(PDate);
     3: TargetDate := StartOfTheMonth(PDate).IncMonth(-1);
  else
    TargetDate := StartOfTheMonth(PDate);
  end;
  for var i:=1 to Months do
  begin
    CalArr[Pred(i)] := GenerateCalendar(TargetDate, UseISO, UseSN, ShowToday);
    MonthArr[Pred(i)] := TargetDate = StartOfTheMonth(Today);
    TargetDate := IncMonth(TargetDate);
  end;
  // カレンダーの表示
  var Cols  := CAL_COLS[UseSN];
  var ll := Pred(Months) div Cols;
  var idx := 0;
  var Width := CAL_WIDTH[UseSN];
  for var l:=0 to ll do
  begin
    var vCal := Default(TCal);
    var ii := Cols-1;
    for var i:=0 to ii do
    begin
      if idx >= Months then
        Break;
      for var j:=Low(vCal) to High(vCal) do
      begin
        var s := CalArr[idx][j];
        s := Copy(s + StringOfChar(' ', Width), 1, Width);
        {$IFDEF USE_HIGHLIGHT}
        if ShowToday and MonthArr[idx] and (j > 2) then
        begin
          if Pos('[', s) > 0 then
          begin
            s := StringReplace(s, '[', ' ' + #$1B'[7m', []);
            s := StringReplace(s, ']', #$1B'[0m' + ' ', []);
          end;
        end;
        {$ENDIF}
        vCal[j] := vCal[j] + s;
        if i < ii then
          vCal[j] := vCal[j] + '  ';
      end;
      Inc(idx);
    end;
    for var i := Low(vCal) to High(vCal) do
      Writeln(vCal[i]);
    if l < ll then
      Writeln;
  end;
end.

Delphi 13 Florence で作成しましたが、Community Edition でもコンパイルできると思います。

使い方

普通に実行するとこうなります (2026/02/06 実行)。

image.png

今月のカレンダーが表示され、今日がハイライトされていますね。Linux のカレンダーと異なるのは週番号が表示される事です。

・ISO 8601 準拠

-m オプションを付けて実行すると、ISO 8601 準拠となり、週が月曜始まりとなります。週番号の付き方も異なります。

image.png

・3 ヵ月表示 (当年)

-3 を指定すると当月を中心に 3 ヵ月分表示されます。

image.png

・1 年表示 (当年)

-y を指定すると当年 12 ヵ月分が表示されます。

image.png

・1 年表示 (年指定)

表示年度を変えるには年を指定します。例えば 2028 年を指定するには

cal 2028

とします。他のスイッチも同時に使えますが、-3 スイッチは無視されます。

image.png

・年月指定

表示年月を変えるには年と月を指定します。例えば 2028 年 6 月を指定するには

cal 2028 6

とします。

image.png

Linux の cal の年月指定 (mm yyyy) とは順番が逆になっています。日本だとこちらの指定の方が自然だと思うので変更してみました。

image.png

MSX-DOS2 Tools 付属の cal.com も yyyy/mm 指定でした。

・年間通算日

-j を指定すると、日付ではなく、その年の 1/1 からの通算日表示になります。

image.png

表示幅が広くなるため -3-y では 2 列表示になります。

image.png

・今日の表示

-h を指定すると今日がハイライトされなくなります。

image.png

・スイッチ一覧

-? を指定するとスイッチ一覧が表示されます。

image.png

おわりに

当日のハイライト表示に ANSI エスケープコード を使っているので、Windows Terminal ではなく素のコマンドプロンプトを使うと表示が化けます。

image.png

ハイライト表示にコンソール API を使うよう改変するのはそんなに難しくないと思いますが、とりあえずハイライト表示をやめたいのなら {$DEFINE USE_HIGHLIGHT} をコメントアウトするか削除してください。

-  {$DEFINE USE_HIGHLIGHT}
+  {.$DEFINE USE_HIGHLIGHT}

当日がハイライトされる代わりに日付が [ ] で括られて表示されます。

image.png

usesSystem.SysUtilsSystem.DateUtils しかないので macOS や Linux 向けにもビルドできると思うのですが、これらには cal があるので、あまり意味はないですよね。

エスケープシーケンスで座標指定してカレンダーを書くようにすれば、処理が随分スッキリすると思います。むしろ Today の処理を抜くだけでかなりスッキリするのですが...。

See also:

3
1
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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?