Happy new year.

昨年末いまさらですが `clickhouse-client` を終了するときに変わった出力をしていることに気づきました。

```sql
> clickhouse-client
ClickHouse client version 21.12.2.17 (official build).
Connecting to localhost:9000 as user default.
Connected to ClickHouse server version 21.12.2 revision 54452.

:) exit
Happy new year.
```

`Happy new year.` 

普段は`Bye.`。年末年始仕様のメッセージが仕組まれています。クライアントのコードを追ってみます。

https://github.com/ClickHouse/ClickHouse/blob/6757afa05235feaf59b86a3baadf7c6451bb69db/src/Client/ClientBase.cpp#L1575-L1580

```c++:Client/ClientBase.cpp
    if (isNewYearMode())
        std::cout << "Happy new year." << std::endl;
    else if (isChineseNewYearMode(local_tz))
        std::cout << "Happy Chinese new year. 春节快乐!" << std::endl;
    else
        std::cout << "Bye." << std::endl;
```

この特殊演出は、 `isNewYearMode` と `isChineseNewYearMode` の2パターン用意されているようです。
isNewYearModeから見ていきます。

https://github.com/ClickHouse/ClickHouse/blob/0b3926950d235e6a4198fc2c98c0fe63fd42652e/src/Client/ClientBaseHelpers.cpp#L14-L24

```c++:Client/ClientBaseHelpers.cpp
bool isNewYearMode()
{
    time_t current_time = time(nullptr);

    /// It's bad to be intrusive.
    if (current_time % 3 != 0)
        return false;

    LocalDate now(current_time);
    return (now.month() == 12 && now.day() >= 20) || (now.month() == 1 && now.day() <= 5);
}
```

12/20 - 1/5まで出力されるようです。

続いてisChineseNewYearMode。名前からして中国の春節に対応するメソッドです。

https://github.com/ClickHouse/ClickHouse/blob/0b3926950d235e6a4198fc2c98c0fe63fd42652e/src/Client/ClientBaseHelpers.cpp#L26-L93

```c++:Client/ClientBaseHelpers.cpp
bool isChineseNewYearMode(const String & local_tz)
{
    /// Days of Dec. 20 in Chinese calendar starting from year 2019 to year 2105
    static constexpr UInt16 chineseNewYearIndicators[]
        = {18275, 18659, 19014, 19368, 19752, 20107, 20491, 20845, 21199, 21583, 21937, 22292, 22676, 23030, 23414, 23768, 24122, 24506,
            24860, 25215, 25599, 25954, 26308, 26692, 27046, 27430, 27784, 28138, 28522, 28877, 29232, 29616, 29970, 30354, 30708, 31062,
            31446, 31800, 32155, 32539, 32894, 33248, 33632, 33986, 34369, 34724, 35078, 35462, 35817, 36171, 36555, 36909, 37293, 37647,
            38002, 38386, 38740, 39095, 39479, 39833, 40187, 40571, 40925, 41309, 41664, 42018, 42402, 42757, 43111, 43495, 43849, 44233,
            44587, 44942, 45326, 45680, 46035, 46418, 46772, 47126, 47510, 47865, 48249, 48604, 48958, 49342};

    /// All time zone names are acquired from https://www.iana.org/time-zones
    static constexpr const char * chineseNewYearTimeZoneIndicators[] = {
        /// Time zones celebrating Chinese new year.
        "Asia/Shanghai",
        "Asia/Chongqing",
        "Asia/Harbin",
        "Asia/Urumqi",
        "Asia/Hong_Kong",
        "Asia/Chungking",
        "Asia/Macao",
        "Asia/Macau",
        "Asia/Taipei",
        "Asia/Singapore",

        /// Time zones celebrating Chinese new year but with different festival names. Let's not print the message for now.
        // "Asia/Brunei",
        // "Asia/Ho_Chi_Minh",
        // "Asia/Hovd",
        // "Asia/Jakarta",
        // "Asia/Jayapura",
        // "Asia/Kashgar",
        // "Asia/Kuala_Lumpur",
        // "Asia/Kuching",
        // "Asia/Makassar",
        // "Asia/Pontianak",
        // "Asia/Pyongyang",
        // "Asia/Saigon",
        // "Asia/Seoul",
        // "Asia/Ujung_Pandang",
        // "Asia/Ulaanbaatar",
        // "Asia/Ulan_Bator",
    };
    static constexpr size_t M = sizeof(chineseNewYearTimeZoneIndicators) / sizeof(chineseNewYearTimeZoneIndicators[0]);

    time_t current_time = time(nullptr);

    if (chineseNewYearTimeZoneIndicators + M
        == std::find_if(chineseNewYearTimeZoneIndicators, chineseNewYearTimeZoneIndicators + M, [&local_tz](const char * tz)
                        {
                            return tz == local_tz;
                        }))
        return false;

    /// It's bad to be intrusive.
    if (current_time % 3 != 0)
        return false;

    auto days = DateLUT::instance().toDayNum(current_time).toUnderType();
    for (auto d : chineseNewYearIndicators)
    {
        /// Let's celebrate until Lantern Festival
        if (d <= days && d + 25 >= days)
            return true;
        else if (d > days)
            return false;
    }
    return false;
}
```

しっかり書いてる。。。C++はわからないので、調べ調べ読み進めます。

```c++
static constexpr size_t M = sizeof(chineseNewYearTimeZoneIndicators) / sizeof(chineseNewYearTimeZoneIndicators[0]);
```

配列の要素数を求めています。`配列全体のサイズ / 要素1つのサイズ = 要素数` ということみたいです。要素を追加したときでも対応してくれる書き方のようです。

```c++
std::find_if(chineseNewYearTimeZoneIndicators, chineseNewYearTimeZoneIndicators + M, [&local_tz](const char * tz)
                        {
                            return tz == local_tz;
                        })
```

こメソッドの引数で渡されたタイムゾーンが、`chineseNewYearTimeZoneIndicators`内に存在するか検索し、見つかったポインタが戻ります。見つからなかった場合は、`chineseNewYearTimeZoneIndicators + M`(配列の終端のポインタ)が返ります。

```c++
auto days = DateLUT::instance().toDayNum(current_time).toUnderType();
```

ここでは現在の日数を求めています。日数は1925/01/01からの経過日数です。
起点はDateLUTのコードの以下の箇所にあります。

https://github.com/ClickHouse/ClickHouse/blob/888a5532a682d8b3950a039f611968f80bad1ab5/src/Common/DateLUTImpl.h#L13

```c++:Common/DateLUTImpl.h
#define DATE_LUT_MIN_YEAR 1925 /// 1925 since wast majority of timezones changed to 15-minute aligned offsets somewhere in 1924 or earlier.
```

求めた現在の日数と、`chineseNewYearIndicators`のリストを比較して春節当日から25日間出力されます。リストは2015年まで用意されています。

---

最後に、2022年のClickHouseのロードマップを置いておきます。

https://github.com/ClickHouse/ClickHouse/issues/32513