📅

Zigで日付を取得する方法

2022/12/18に公開

std/time/epoch.zig

カレンダー日付に対応するタイムスタンプが必要な場合、std.time.timestamp関数(単位は秒)を使います。

https://ziglang.org/documentation/master/std/#root;time

実際のカレンダー日付(2022-12-17のような年月日の数値)を取得したい場合、time下にあるEpochSecondsが使えます。EpocSecondssecsプロパティとして、timestampを受け取ります。

EpocSecondsが持つ関数や、その戻り値のstructを使うと、以下のようにして、年月日と時刻を取得できます。時刻はUTCベースのようなので、JSTに変換したい場合は手動でやるしかなさそう。

const std = @import("std");

fn testEpoch(secs: u64, expected: struct {
    year: u16,
    month: u4,
    day: u5,
    hours: u5,
    minutes: u6,
    seconds: u6,
}) !void {
    const epoch = std.time.epoch.EpochSeconds{ .secs = secs };
    const epoch_day = epoch.getEpochDay();
    const year_day = epoch_day.calculateYearDay();
    const month_day = year_day.calculateMonthDay();
    const day_seconds = epoch.getDaySeconds();

    const year = year_day.year;
    const month = month_day.month.numeric();
    const day = month_day.day_index + 1;
    const hours = day_seconds.getHoursIntoDay();
    const minutes = day_seconds.getMinutesIntoHour();
    const seconds = day_seconds.getSecondsIntoMinute();

    try std.testing.expectEqual(expected.year, year);
    try std.testing.expectEqual(expected.month, month);
    try std.testing.expectEqual(expected.day, day);
    try std.testing.expectEqual(expected.hours, hours);
    try std.testing.expectEqual(expected.minutes, minutes);
    try std.testing.expectEqual(expected.seconds, seconds);
}

test "EpochSeconds" {
    // 1970-01-01
    try testEpoch(0, .{
        .year = 1970,
        .month = 1,
        .day = 1,
        .hours = 0,
        .minutes = 0,
        .seconds = 0,
    });

    // 2022-12-18
    try testEpoch(1671330128, .{
        .year = 2022,
        .month = 12,
        .day = 18,
        .hours = 2,
        .minutes = 22,
        .seconds = 8,
    });
}

文字列に変換

取得された整数値を文字列に変換するにはformatを使います(sprintf的なやつ)。今回は試しにyyyy-MM-dd形式で出力してみました。以下のサイトに記載のように、allocPrintstructに実装したformat関数を呼び出す方式で出来ました。

参考:https://ziglearn.org/chapter-2/#formatting

const std = @import("std");

const Date = struct {
    year: u16,
    month: u4,
    day: u5,
    hours: u5,
    minutes: u6,
    seconds: u6,

    pub fn format(self: Date, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
        _ = fmt;
        _ = options;

        try writer.print("{d}", .{self.year});
        try writer.print("-{d:0>2}", .{self.month});
        try writer.print("-{d:0>2}", .{self.day});
    }
};

fn getDate(secs: u64) Date {
    const epoch = std.time.epoch.EpochSeconds{ .secs = secs };
    const epoch_day = epoch.getEpochDay();
    const year_day = epoch_day.calculateYearDay();
    const month_day = year_day.calculateMonthDay();
    const day_seconds = epoch.getDaySeconds();

    const year = year_day.year;
    const month = month_day.month.numeric();
    const day = month_day.day_index + 1;
    const hours = day_seconds.getHoursIntoDay();
    const minutes = day_seconds.getMinutesIntoHour();
    const seconds = day_seconds.getSecondsIntoMinute();

    return Date{
        .year = year,
        .month = month,
        .day = day,
        .hours = hours,
        .minutes = minutes,
        .seconds = seconds,
    };
}

fn testEpoch(secs: u64, expected: struct {
    year: u16,
    month: u4,
    day: u5,
    hours: u5,
    minutes: u6,
    seconds: u6,
    date: []const u8,
}) !void {
    const date = getDate(secs);

    try std.testing.expectEqual(expected.year, date.year);
    try std.testing.expectEqual(expected.month, date.month);
    try std.testing.expectEqual(expected.day, date.day);
    try std.testing.expectEqual(expected.hours, date.hours);
    try std.testing.expectEqual(expected.minutes, date.minutes);
    try std.testing.expectEqual(expected.seconds, date.seconds);

    const date_string = try std.fmt.allocPrint(std.testing.allocator, "{s}", .{date});
    defer std.testing.allocator.free(date_string);

    try std.testing.expectEqualStrings(expected.date, date_string);
}

test "EpochSeconds" {
    // 1970-01-01
    try testEpoch(0, .{
        .year = 1970,
        .month = 1,
        .day = 1,
        .hours = 0,
        .minutes = 0,
        .seconds = 0,
        .date = "1970-01-01",
    });

    // 2022-12-18
    try testEpoch(1671330128, .{
        .year = 2022,
        .month = 12,
        .day = 18,
        .hours = 2,
        .minutes = 22,
        .seconds = 8,
        .date = "2022-12-18",
    });
}

formatの書式は、例えば以下に記載があって、
https://ziglang.org/documentation/master/std/#root;fmt.format

The format string must be comptime-known and may contain placeholders following this format: {[argument][specifier]:[fill][alignment][width].[precision]}
...

今回の月と日のように、必ず二桁表示にして必要な場合に二桁目を0 paddingする場合には以下のような指定になります。

try writer.print("-{d:0>2}", .{self.month});
  • 0: fillに対応。format時に空いた空白スペースを埋める文字列を指定
  • >: alignmentに対応。>で右寄せ。
  • 2: widthに対応。何文字表示するか。

まとめ

Zigを触り始めたばかりですが、Zigの標準ライブラリ(std)を調べるときに、testが実装ファイルに書かれているので仕様がわかりやすくて良いです。ただ、ggってもわからないことが多く、公式ドキュメントやソースを参考にすることが多いです。

参考

catallaxy tech blog

Discussion