D言語19行で3か月カレンダー表示

# はじめに
D言語で、３か月分のカレンダーを表示したいと思います。
D言語の標準ライブラリ([Phobos](https://dlang.org/phobos/index.html))や[UFCS](https://qiita.com/nak2yoshi/items/1bbd91fb23d7cc67109e)を駆使して、なるべく少ない行数での実装を目指してみました。
結果として、19行（空行を除く）ほどのソースコードになりました。

# ソースコード
```d:cal.d
import std;

string getCalendar(Date dt)
{
    string s = format("%04d / %02d%19s", dt.year, dt.month, "");
    s ~= format("%(%s %) ", [EnumMembers!DayOfWeek]);
    s ~= " ".cycle.take(Date(dt.year, dt.month, 1).dayOfWeek * 4).to!string;
    dt.daysInMonth.iota.each!(d => s ~= format(" %2d ", d + 1));
    return ( format("%-224s", s) );
}

void main()
{
    Date dt = Clock.currTime().to!Date;
    auto r  = roundRobin(
        getCalendar(Date(dt.year, dt.month, 1).add!"months"(-1)).chunks(28),
        getCalendar(Date(dt.year, dt.month, 1)).chunks(28),
        getCalendar(Date(dt.year, dt.month, 1).add!"months"( 1)).chunks(28),
        "\n".cycle.take(8).to!string.chunks(1));
    r.each!(s => writef(" %s", s));
}
```

# コンパイルと実行結果
```text:cal.dの実行結果
d:\Dev> dmd -m64 cal.d

d:\Dev> cal
 2021 / 12                    2022 / 01                    2022 / 02                    
 sun mon tue wed thu fri sat  sun mon tue wed thu fri sat  sun mon tue wed thu fri sat  
               1   2   3   4                            1            1   2   3   4   5  
   5   6   7   8   9  10  11    2   3   4   5   6   7   8    6   7   8   9  10  11  12  
  12  13  14  15  16  17  18    9  10  11  12  13  14  15   13  14  15  16  17  18  19  
  19  20  21  22  23  24  25   16  17  18  19  20  21  22   20  21  22  23  24  25  26  
  26  27  28  29  30  31       23  24  25  26  27  28  29   27  28                      
                               30  31                                                   
```

# ソースコード補足説明
## getCalendar
ひと月分のカレンダー情報を返す関数です。
横幅`28`文字、縦幅`8`行の計`224(=28*8)`バイトの文字列を返します。

`format("%04d / %02d%19s", dt.year, dt.month, "")`で対象年月を`YYYY / MM`形式に整形します。
[format](https://dlang.org/phobos/std_format.html#.format)　※[formatの書式](https://dlang.org/phobos/std_format.html#formattedWrite)

```d:
import std;
void main()
{
    Date dt = Clock.currTime().to!Date;
    writeln(dt); // 2020-Sep-19
    writefln(format("%04d / %02d%19s", dt.year, dt.month, "")); // 2020 / 09                   
}
```
`format("%(%s %) ", [EnumMembers!DayOfWeek])`ですべての曜日を文字列で取得します。
[EnumMembers](https://dlang.org/phobos/std_traits.html#EnumMembers)
[DayOfWeek](https://dlang.org/phobos/std_datetime_date.html#DayOfWeek)

```d:
import std;
void main()
{
    writeln(format("%(%s %) ", [EnumMembers!DayOfWeek]));
    // sun mon tue wed thu fri sat 
}
```
`Date(dt.year, dt.month, 1).dayOfWeek`で対象年月の1日（ついたち）が何曜日かを取得します。
`" ".cycle.take(Date(dt.year, dt.month, 1).dayOfWeek * 4).to!string`で1日（ついたち）の曜日まで、半角スペース`" "`で埋める文字列を取得します。`4`は、各日にちの表示幅です。

[Date](https://dlang.org/phobos/std_datetime_date.html#Date)
[dayOfWeek](https://dlang.org/phobos/std_datetime_date.html#.DateTime.dayOfWeek)
[cycle](https://dlang.org/phobos/std_range.html#cycle)
[take](https://dlang.org/phobos/std_range.html#take)
[to](https://dlang.org/phobos/std_conv.html#to)
`dt.daysInMonth`で対象年月の末日を取得します。うるう年の２月も自動的に計算してくれます。

```d:
import std;
void main()
{
    writeln(Date(2020, 1, 1).daysInMonth); // 31
    writeln(Date(2020, 2, 1).daysInMonth); // 29
    writeln(Date(2021, 2, 1).daysInMonth); // 28
}
```
`dt.daysInMonth.iota.each!(d => s ~= format(" %2d ", d + 1))`で末日までの日付を取得します。
[daysInMonth](https://dlang.org/phobos/std_datetime_date.html#.DateTime.daysInMonth)
[iota](https://dlang.org/phobos/std_range.html#iota)
[each](https://dlang.org/phobos/std_algorithm_iteration.html#.each)

```d:
import std;
void main()
{
    Date dt = Date(2020, 9, 1);
    string s;
    dt.daysInMonth.iota.each!(d => s ~= format(" %2d ", d + 1));
    writeln(s);
    //   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30 
}
```

## main
３か月のカレンダーを表示します。
`Clock.currTime().to!Date`で今日の日付を取得し、`dt.add!"months"`で前月、今月、来月の日付を計算しています。
[Clock](https://dlang.org/phobos/std_datetime_systime.html#Clock)
[currTime](https://dlang.org/phobos/std_datetime_systime.html#.Clock.currTime)
[to](https://dlang.org/phobos/std_conv.html#to)
[Date](https://dlang.org/phobos/std_datetime_date.html#Date)
[add](https://dlang.org/phobos/std_datetime_date.html#.DateTime.add)

```d:
import std;
void main()
{
    Date dt = Clock.currTime().to!Date;
    writeln(dt);                 // 2020-Sep-19
    writeln(dt.add!"months"(1)); // 2020-Oct-19
}
```
`chunks`を使って、引数の固定サイズごとに文字列を分割します。
引数`28`は、横幅の1週間を28文字(4文字*7日)で表しているもので、
縦幅を`8`行固定で取得できる前提で、`roundRobin`を使って３か月のそれぞれの行を順番に並べます。
[chunks](https://dlang.org/phobos/std_range.html#chunks)
[roundRobin](https://dlang.org/phobos/std_range.html#roundRobin)

```d:
import std;
void main()
{
    int[] arr1 = [ 1, 2, 3, 4, 5, 6, 7, 8];
    auto arr2 = arr1.chunks(4).array;
    writeln(arr2); // [[1, 2, 3, 4], [5, 6, 7, 8]]
    auto arr3 = roundRobin(arr2[0], arr2[1]);
    writeln(arr3); // [1, 5, 2, 6, 3, 7, 4, 8]
}
```
あとは`each`を使って、順番に表示します。
[each](https://dlang.org/phobos/std_algorithm_iteration.html#.each)
[writef](https://dlang.org/phobos/std_stdio.html#.File.writef)　※[formatの書式](https://dlang.org/phobos/std_format.html#formattedWrite)

# 更新履歴
2020.9.19 初回投稿
2022.1.26 dmd version 2.098で動作確認
2023.12.18 dmd version 2.106で動作確認、ソースコード修正