17 歳と x 日ジェネレーター

17歳とx日ジェネレータからのお題です。 gawk は時間関係の関数が増強されているため、内部関数である mktime() を用いれば簡単に実装することができますが、nawk などでは時間関係の関数が一切実装されていません。

どうやって実装すれば良いでしょうか? 実はこの mktime() 関数は元々 awk で実装できるということでメンテナーの Arnold Robbins がコードを書いています。 最新の gawk.info では削除されてしまっているかもしれませんが、Turning Dates Into Timestamps (英語) などで awk で実装された mktime() を見ることができます。 ここでは、この awk で実装された mktime() を使って解いてみます。

#! /usr/bin/gawk -f
# seventeen.awk

BEGIN {
    date = ARGV[1];

    split(date, a_date, /-/);

    srand();
    today    = srand();
    birthday = mktime_nawk(a_date[1] " " a_date[2] " " a_date[3] \
                           " 00 00 00");
    age17    = mktime_nawk(a_date[1] + 17 " " a_date[2] " " a_date[3] \
                           " 00 00 00");

    printf("17 才と %d 日\n", (today - age17) / (60 * 60 * 24));
}

# 以下 mktime.awk からの改版
# decide if a year is a leap year
function _tm_isleap(year,    ret)
{
    ret = (year % 4 == 0 && year % 100 != 0) ||
            (year % 400 == 0)

    return ret
}

# convert a date into seconds
function _tm_addup(a,    total, yearsecs, daysecs,
                         hoursecs, i, j)
{
    hoursecs = 60 * 60
    daysecs = 24 * hoursecs
    yearsecs = 365 * daysecs

    total = (a[1] - 1970) * yearsecs

    # extra day for leap years
    for (i = 1970; i < a[1]; i++)
        if (_tm_isleap(i))
            total += daysecs

    j = _tm_isleap(a[1])
    for (i = 1; i < a[2]; i++)
        total += _tm_months[j, i] * daysecs

    total += (a[3] - 1) * daysecs
    total += a[4] * hoursecs
    total += a[5] * 60
    total += a[6]

    return total
}

# mktime --- convert a date into seconds,
#            compensate for time zone
function mktime_nawk(str,    res1, res2, a, b, i, j, t, diff)
{
    {
        # Initialize table of month lengths
        _tm_months[0,1] = _tm_months[1,1] = 31
        _tm_months[0,2] = 28; _tm_months[1,2] = 29
        _tm_months[0,3] = _tm_months[1,3] = 31
        _tm_months[0,4] = _tm_months[1,4] = 30
        _tm_months[0,5] = _tm_months[1,5] = 31
        _tm_months[0,6] = _tm_months[1,6] = 30
        _tm_months[0,7] = _tm_months[1,7] = 31
        _tm_months[0,8] = _tm_months[1,8] = 31
        _tm_months[0,9] = _tm_months[1,9] = 30
        _tm_months[0,10] = _tm_months[1,10] = 31
        _tm_months[0,11] = _tm_months[1,11] = 30
        _tm_months[0,12] = _tm_months[1,12] = 31
    }

    i = split(str, a, " ")    # don't rely on FS

    if (i != 6)
        return -1

    # force numeric
    for (j in a)
        a[j] += 0

    # validate
    if (a[1] < 1970 ||
        a[2] < 1 || a[2] > 12 ||
        a[3] < 1 || a[3] > 31 ||
        a[4] < 0 || a[4] > 23 ||
        a[5] < 0 || a[5] > 59 ||
        a[6] < 0 || a[6] > 60 )
            return -1

    res1 = _tm_addup(a)

    return res1
}

コード自体は長いのですが、17歳とx日ジェネレータのコードに合わせるように記述していますので、容易に理解できると思います。 なお、今日の systime() に該当する Unix Time はawk で epoch time を YYYY/MM/DD HH:MM:SS に変換するなどでも記述したように srand() 関数の戻り値を利用しています。

さて、実行してみましょう。

$ nawk -f seventeen.awk 1971-03-08
17 才と 7706 日

Id:mzp さんのものと比較すると 2000 日近く差がありますが気にしないでください。

便利な関数が用意されていることは大きなアドバンテージになりますが、そうした関数の多くはその言語自身で実装可能なものも多く含まれています。 特に時間関数は知っていても損はありませんので、今回も載せてみました。

tag_nawk.pngtag_nawk.pngtag_nawk.pngtag_nawk.png