Changes
11 changed files (+606/-137)
-
-
@@ -26,13 +26,28 @@ sunwait-poll - Prints whether it's day or night== Synposis *sunwait* *poll* *sunwait* *poll* [*--at* _<YYYY-MM-DDThh:mm:ssZ>_] == Description This command prints *DAY* or *NIGHT* depends on the current time and location, then exits with corresponding status. Unlike normal program, this program never exits with status *0*. == Options *--at* _<YYYY-MM-DDThh:mm:ssZ>_:: Datetime to check day or night. Defaults to the current time (system time). + You can omit parts after _DD_ entirely, as in *YYYY-MM-DD*. In that case, time will be the start of the day. For example, *--at 2020-01-01* is equivalent of *--at 2020-01-01T00:00:00*. + _Z_ is timezone offset notation in *hh*, *hh:mm*, or *Z* (UTC, means 00:00). When timezone offset is not set, sunwait uses local timezone offset unless *--utc* option is set. == Exit status See *sunwait*(1) for error statuses.
-
@@ -40,6 +55,13 @@*2* It's day or twilight. *3* It' night (after twilight). == Environment *TZ*:: Value of *--at* option without timezone part will use the current timezone get by *timezone*(3) function. If this variable is set, its value takes precedence over the system timezone. See *tzset*(3) for timezone choosing algorithm. == Examples
-
-
-
@@ -17,7 +17,7 @@ // SPDX-License-Identifier: GPL-3.0-onlyconst std = @import("std"); const CalendarDate = @import("./RunOptions/date.zig").CalendarDate; const datetime = @import("./RunOptions/datetime.zig"); const EventType = @import("./RunOptions/event.zig").EventType; const ParseArgsError = @import("./RunOptions/parser.zig").ParseArgsError; const TwilightAngle = @import("./RunOptions/twilight.zig").TwilightAngle;
-
@@ -33,7 +33,30 @@ offset_mins: i32 = 0,twilight_angle: ?TwilightAngle = null, utc: bool = false, debug: bool = false, command: CommandOptions = .poll, command: CommandOptions = .{ .poll = .{} }, const PollOptions = struct { at: ?datetime.Datetime = null, pub fn parseArg(self: *@This(), arg: []const u8, args: *std.process.ArgIterator) ParseArgsError!void { if (std.mem.eql(u8, "--at", arg)) { const next = args.next() orelse { std.log.err("{s} option requires a value", .{arg}); return ParseArgsError.MissingValue; }; const at = datetime.Datetime.fromString(next) catch |err| { std.log.err("\"{s}\" is not a valid datetime string: {s}", .{ next, @errorName(err) }); return ParseArgsError.InvalidDatetimeFormat; }; self.at = at; return; } return ParseArgsError.UnknownArg; } }; pub const ReportOptions = struct { day_of_month: ?u5 = null,
-
@@ -47,7 +70,7 @@ std.log.err("{s} option requires a value", .{arg});return ParseArgsError.MissingValue; }; const date = CalendarDate.fromString(next) catch |err| { const date = datetime.CalendarDate.fromString(next) catch |err| { std.log.err("\"{s}\" is not a valid date string: {s}", .{ next, @errorName(err) }); return ParseArgsError.InvalidDateFormat; };
-
@@ -58,7 +81,7 @@ self.day_of_month = date.day;return; } if (CalendarDate.fromString(arg)) |date| { if (datetime.CalendarDate.fromString(arg)) |date| { self.year_since_2000 = @as(c_int, date.year) - 2000; self.month = date.month; self.day_of_month = date.day;
-
@@ -142,7 +165,7 @@pub const ListOptions = struct { event_type: ?EventType = null, days: c_uint = c.DEFAULT_LIST, from: ?CalendarDate = null, from: ?datetime.CalendarDate = null, pub fn parseArg(self: *@This(), arg: []const u8, args: *std.process.ArgIterator) ParseArgsError!void { if (EventType.parseArg(self.event_type, arg, args)) |e| {
-
@@ -159,7 +182,7 @@ std.log.err("{s} option requires a value", .{arg});return ParseArgsError.MissingValue; }; self.from = CalendarDate.fromString(next) catch |err| { self.from = datetime.CalendarDate.fromString(next) catch |err| { std.log.err("\"{s}\" is not a valid date string: {s}", .{ next, @errorName(err) }); return ParseArgsError.InvalidDateFormat; };
-
@@ -201,7 +224,7 @@pub const CommandOptions = union(Command) { help: void, version: void, poll: void, poll: PollOptions, report: ReportOptions, wait: WaitOptions, list: ListOptions,
-
@@ -210,7 +233,7 @@ pub fn parseArg(self: *@This(), arg: []const u8, args: *std.process.ArgIterator) ParseArgsError!void {switch (self.*) { .help => return ParseArgsError.UnknownArg, .version => return ParseArgsError.UnknownArg, .poll => return ParseArgsError.UnknownArg, .poll => try self.poll.parseArg(arg, args), .report => try self.report.parseArg(arg, args), .wait => try self.wait.parseArg(arg, args), .list => try self.list.parseArg(arg, args),
-
@@ -238,7 +261,7 @@ self.command = .help;return; }, .poll => { self.command = .poll; self.command = .{ .poll = .{} }; }, .report => { self.command = .{ .report = .{} };
-
@@ -542,8 +565,34 @@ return c.timegm(&tm);} pub fn toC(self: *const @This()) c.runStruct { var now: c.time_t = undefined; switch (self.command) { const now: c.time_t = now: switch (self.command) { .poll => |opts| { if (opts.at) |at| { var tm: c.tm = .{ .tm_year = at.date.year - 1900, .tm_mon = at.date.month - 1, .tm_mday = at.date.day, .tm_hour = at.time.hour, .tm_min = at.time.minute, .tm_sec = at.time.second, }; var now = c.mktime(&tm); if (at.offset) |offset| { now -= tm.tm_gmtoff; now += @as(c_long, if (offset.positive) 1 else -1) * (@as(c_long, offset.hour) * 60 + offset.minute) * std.time.s_per_min; } else if (self.utc) { now -= tm.tm_gmtoff; } break :now now; } break :now c.time(null); }, .list => |opts| { if (opts.from) |from| { var tm: c.tm = .{
-
@@ -552,15 +601,15 @@ .tm_mon = from.month - 1,.tm_mday = from.day, }; now = if (self.utc) c.timegm(&tm) else c.timelocal(&tm); } else { _ = c.time(&now); break :now if (self.utc) c.timegm(&tm) else c.timelocal(&tm); } break :now c.time(null); }, else => { _ = c.time(&now); break :now c.time(null); }, } }; var target_time: c.time_t = switch (self.command) { .report => |opts| getTargetDay(now, .{
-
@@ -611,5 +660,5 @@ };} test { _ = @import("RunOptions/date.zig"); _ = @import("RunOptions/datetime.zig"); }
-
-
src/RunOptions/date.zig (deleted)
-
@@ -1,119 +0,0 @@// Copyright (C) 2025 Shota FUJI // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. // // SPDX-License-Identifier: GPL-3.0-only const std = @import("std"); const ParseArgsError = @import("./parser.zig").ParseArgsError; const format = "YYYY-MM-DD"; /// Timezone-free date. Each fields' size is same to ones of `std.time.epoch`, /// in case integration will be added. pub const CalendarDate = packed struct { year: u16, month: u4, day: u5, pub const ParseError = error{ InvalidFormat, MonthOutOfRange, DayOfMonthOutOfRange, }; pub fn fromString(str: []const u8) ParseError!@This() { if (str.len != format.len) { return ParseError.InvalidFormat; } if (str[4] != '-' or str[7] != '-') { return ParseError.InvalidFormat; } const month = std.fmt.parseUnsigned(u4, str[5..7], 10) catch |err| { return switch (err) { error.InvalidCharacter => ParseError.InvalidFormat, error.Overflow => ParseError.MonthOutOfRange, }; }; if (month == 0 or month > 12) { return ParseError.MonthOutOfRange; } const day = std.fmt.parseUnsigned(u5, str[8..], 10) catch |err| { return switch (err) { error.InvalidCharacter => ParseError.InvalidFormat, error.Overflow => ParseError.DayOfMonthOutOfRange, }; }; if (day == 0 or day > 31) { return ParseError.DayOfMonthOutOfRange; } return .{ .year = std.fmt.parseUnsigned(u16, str[0..4], 10) catch { return ParseError.InvalidFormat; }, .month = month, .day = day, }; } test fromString { { const d = try fromString("2000-01-01"); try std.testing.expectEqual(2000, d.year); try std.testing.expectEqual(1, d.month); try std.testing.expectEqual(1, d.day); } { const d = try fromString("1901-12-31"); try std.testing.expectEqual(1901, d.year); try std.testing.expectEqual(12, d.month); try std.testing.expectEqual(31, d.day); } { const d = try fromString("2025-07-22"); try std.testing.expectEqual(2025, d.year); try std.testing.expectEqual(7, d.month); try std.testing.expectEqual(22, d.day); } { const d = try fromString("9999-12-31"); try std.testing.expectEqual(9999, d.year); try std.testing.expectEqual(12, d.month); try std.testing.expectEqual(31, d.day); } try std.testing.expectError(ParseError.InvalidFormat, fromString("1970-1-1")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970-01-1")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970-1-01")); try std.testing.expectError(ParseError.InvalidFormat, fromString("70-01-01")); try std.testing.expectError(ParseError.MonthOutOfRange, fromString("1970-00-01")); try std.testing.expectError(ParseError.MonthOutOfRange, fromString("1970-13-01")); try std.testing.expectError(ParseError.DayOfMonthOutOfRange, fromString("1970-08-00")); try std.testing.expectError(ParseError.DayOfMonthOutOfRange, fromString("1970-08-32")); } }; test { _ = CalendarDate; }
-
-
-
@@ -0,0 +1,396 @@// Copyright (C) 2025 Shota FUJI // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. // // SPDX-License-Identifier: GPL-3.0-only const std = @import("std"); /// Timezone-free date. Each fields' size is same to ones of `std.time.epoch`, /// in case integration will be added. pub const CalendarDate = packed struct { const format = "YYYY-MM-DD"; year: u16, month: u4, day: u5, pub const ParseError = error{ InvalidFormat, MonthOutOfRange, DayOfMonthOutOfRange, }; pub fn fromString(str: []const u8) ParseError!@This() { if (str.len != format.len) { return ParseError.InvalidFormat; } if (str[4] != format[4] or str[7] != format[7]) { return ParseError.InvalidFormat; } const month = std.fmt.parseUnsigned(u4, str[5..7], 10) catch |err| { return switch (err) { error.InvalidCharacter => ParseError.InvalidFormat, error.Overflow => ParseError.MonthOutOfRange, }; }; if (month == 0 or month > 12) { return ParseError.MonthOutOfRange; } const day = std.fmt.parseUnsigned(u5, str[8..], 10) catch |err| { return switch (err) { error.InvalidCharacter => ParseError.InvalidFormat, error.Overflow => ParseError.DayOfMonthOutOfRange, }; }; if (day == 0 or day > 31) { return ParseError.DayOfMonthOutOfRange; } // "std.fmt.parseInt/parseUnsigned" ignores underscore between digits. // hours, minutes, seconds, month and days won't be affected by this because // all of those are 2 characters, so there is no possibility "parseUnsigned" // skips underscore. However, year is 4 character thus it's possible to // insert underscore. For example, "1__0", "20_4", "2_25". if (std.mem.indexOfScalar(u8, str[0..4], '_')) |_| { return ParseError.InvalidFormat; } return .{ .year = std.fmt.parseUnsigned(u16, str[0..4], 10) catch { return ParseError.InvalidFormat; }, .month = month, .day = day, }; } test fromString { { const d = try fromString("2000-01-01"); try std.testing.expectEqual(2000, d.year); try std.testing.expectEqual(1, d.month); try std.testing.expectEqual(1, d.day); } { const d = try fromString("1901-12-31"); try std.testing.expectEqual(1901, d.year); try std.testing.expectEqual(12, d.month); try std.testing.expectEqual(31, d.day); } { const d = try fromString("2025-07-22"); try std.testing.expectEqual(2025, d.year); try std.testing.expectEqual(7, d.month); try std.testing.expectEqual(22, d.day); } { const d = try fromString("9999-12-31"); try std.testing.expectEqual(9999, d.year); try std.testing.expectEqual(12, d.month); try std.testing.expectEqual(31, d.day); } try std.testing.expectError(ParseError.InvalidFormat, fromString("1970-1-1")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970-01-1")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970-1-01")); try std.testing.expectError(ParseError.InvalidFormat, fromString("70-01-01")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970-_1-_1")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970-1_-1_")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970001001")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970 01 01")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970.01.01")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970/01/01")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1970- 1- 1")); try std.testing.expectError(ParseError.InvalidFormat, fromString("1__0-01-01")); try std.testing.expectError(ParseError.InvalidFormat, fromString("19_0-01-01")); try std.testing.expectError(ParseError.MonthOutOfRange, fromString("1970-00-01")); try std.testing.expectError(ParseError.MonthOutOfRange, fromString("1970-13-01")); try std.testing.expectError(ParseError.DayOfMonthOutOfRange, fromString("1970-08-00")); try std.testing.expectError(ParseError.DayOfMonthOutOfRange, fromString("1970-08-32")); } }; pub const ClockTime = packed struct { const format = "hh:mm:ss"; hour: u5 = 0, minute: u6 = 0, second: u6 = 0, pub const ParseError = error{ InvalidFormat, HourOutOfRange, MinuteOutOfRange, SecondOutOfRange, }; pub fn fromString(str: []const u8) ParseError!@This() { if (str.len != format.len) { return ParseError.InvalidFormat; } if (str[2] != format[2] or str[5] != format[5]) { return ParseError.InvalidFormat; } const hour = std.fmt.parseUnsigned(u5, str[0..2], 10) catch |err| { return switch (err) { error.InvalidCharacter => ParseError.InvalidFormat, error.Overflow => ParseError.HourOutOfRange, }; }; if (hour > 23) { return ParseError.HourOutOfRange; } const minute = std.fmt.parseUnsigned(u6, str[3..5], 10) catch |err| { return switch (err) { error.InvalidCharacter => ParseError.InvalidFormat, error.Overflow => ParseError.MinuteOutOfRange, }; }; if (minute > 59) { return ParseError.MinuteOutOfRange; } const second = std.fmt.parseUnsigned(u6, str[6..], 10) catch |err| { return switch (err) { error.InvalidCharacter => ParseError.InvalidFormat, error.Overflow => ParseError.SecondOutOfRange, }; }; // Leap second if (second > 60) { return ParseError.SecondOutOfRange; } return .{ .hour = hour, .minute = minute, .second = second, }; } test fromString { { const t = try fromString("00:00:00"); try std.testing.expectEqual(0, t.hour); try std.testing.expectEqual(0, t.minute); try std.testing.expectEqual(0, t.second); } { const t = try fromString("23:59:60"); try std.testing.expectEqual(23, t.hour); try std.testing.expectEqual(59, t.minute); try std.testing.expectEqual(60, t.second); } } }; pub const TimezoneOffset = struct { positive: bool = true, hour: u5 = 0, minute: u6 = 0, pub const ParseError = error{ InvalidFormat, HourOutOfRange, MinuteOutOfRange, }; pub fn fromString(str: []const u8) ParseError!@This() { switch (str.len) { 1 => return if (str[0] == 'Z') .{} else ParseError.InvalidFormat, 3, 6 => { const hour = std.fmt.parseUnsigned(u5, str[1..3], 10) catch |err| { return switch (err) { error.InvalidCharacter => ParseError.InvalidFormat, error.Overflow => ParseError.HourOutOfRange, }; }; if (hour > 23) { return ParseError.HourOutOfRange; } return .{ .positive = switch (str[0]) { '-' => false, '+' => true, else => return ParseError.InvalidFormat, }, .hour = hour, .minute = if (str.len == 6) minute: { if (str[3] != ':') { return ParseError.InvalidFormat; } const min = std.fmt.parseUnsigned(u6, str[4..6], 10) catch |err| { return switch (err) { error.InvalidCharacter => ParseError.InvalidFormat, error.Overflow => ParseError.MinuteOutOfRange, }; }; if (min > 59) { return ParseError.MinuteOutOfRange; } break :minute min; } else 0, }; }, else => return ParseError.InvalidFormat, } } test fromString { { const o = try fromString("Z"); try std.testing.expectEqual(0, o.hour); try std.testing.expectEqual(0, o.minute); } { const o = try fromString("+09:30"); try std.testing.expectEqual(true, o.positive); try std.testing.expectEqual(9, o.hour); try std.testing.expectEqual(30, o.minute); } { const o = try fromString("-00:15"); try std.testing.expectEqual(false, o.positive); try std.testing.expectEqual(0, o.hour); try std.testing.expectEqual(15, o.minute); } } }; pub const Datetime = struct { const format = std.fmt.comptimePrint("{s}T{s}", .{ CalendarDate.format, ClockTime.format }); date: CalendarDate, time: ClockTime = .{}, offset: ?TimezoneOffset = null, pub const ParseError = error{ InvalidFormat, } || CalendarDate.ParseError || ClockTime.ParseError || TimezoneOffset.ParseError; pub fn fromString(str: []const u8) ParseError!@This() { if (str.len == CalendarDate.format.len) { return .{ .date = try CalendarDate.fromString(str), }; } if (str.len < format.len or str[CalendarDate.format.len] != format[CalendarDate.format.len]) { return ParseError.InvalidFormat; } return .{ .date = try CalendarDate.fromString(str[0..CalendarDate.format.len]), .time = try ClockTime.fromString(str[CalendarDate.format.len + 1 .. format.len]), .offset = if (str.len > format.len) try TimezoneOffset.fromString(str[format.len..]) else null, }; } test fromString { { const x = try fromString("2020-08-09"); try std.testing.expectEqual(2020, x.date.year); try std.testing.expectEqual(8, x.date.month); try std.testing.expectEqual(9, x.date.day); try std.testing.expectEqual(0, x.time.hour); try std.testing.expectEqual(0, x.time.minute); try std.testing.expectEqual(0, x.time.second); try std.testing.expectEqual(null, x.offset); } { const x = try fromString("2020-08-09T12:03:48"); try std.testing.expectEqual(2020, x.date.year); try std.testing.expectEqual(8, x.date.month); try std.testing.expectEqual(9, x.date.day); try std.testing.expectEqual(12, x.time.hour); try std.testing.expectEqual(3, x.time.minute); try std.testing.expectEqual(48, x.time.second); try std.testing.expectEqual(null, x.offset); } { const x = try fromString("2020-08-09T12:03:48Z"); try std.testing.expectEqual(2020, x.date.year); try std.testing.expectEqual(8, x.date.month); try std.testing.expectEqual(9, x.date.day); try std.testing.expectEqual(12, x.time.hour); try std.testing.expectEqual(3, x.time.minute); try std.testing.expectEqual(48, x.time.second); try std.testing.expectEqual(true, x.offset.?.positive); try std.testing.expectEqual(0, x.offset.?.hour); try std.testing.expectEqual(0, x.offset.?.minute); } { const x = try fromString("2121-12-21T12:21:12+03:45"); try std.testing.expectEqual(2121, x.date.year); try std.testing.expectEqual(12, x.date.month); try std.testing.expectEqual(21, x.date.day); try std.testing.expectEqual(12, x.time.hour); try std.testing.expectEqual(21, x.time.minute); try std.testing.expectEqual(12, x.time.second); try std.testing.expectEqual(true, x.offset.?.positive); try std.testing.expectEqual(3, x.offset.?.hour); try std.testing.expectEqual(45, x.offset.?.minute); } { const x = try fromString("2005-11-20T00:59:30-01:15"); try std.testing.expectEqual(2005, x.date.year); try std.testing.expectEqual(11, x.date.month); try std.testing.expectEqual(20, x.date.day); try std.testing.expectEqual(0, x.time.hour); try std.testing.expectEqual(59, x.time.minute); try std.testing.expectEqual(30, x.time.second); try std.testing.expectEqual(false, x.offset.?.positive); try std.testing.expectEqual(1, x.offset.?.hour); try std.testing.expectEqual(15, x.offset.?.minute); } try std.testing.expectError(ParseError.InvalidFormat, fromString("2020-02-02 22:22:22")); try std.testing.expectError(ParseError.InvalidFormat, fromString("2020-02-02T22:22")); try std.testing.expectError(ParseError.InvalidFormat, fromString("2020-02-02T22:22Z")); } }; test { _ = CalendarDate; _ = ClockTime; _ = Datetime; _ = TimezoneOffset; }
-
-
-
@@ -27,4 +27,5 @@ InvalidEventType,InvalidAngle, InvalidOffset, InvalidDateFormat, InvalidDatetimeFormat, };
-
-
-
@@ -17,4 +17,5 @@ // SPDX-License-Identifier: GPL-3.0-onlycomptime { _ = @import("./help.zig"); _ = @import("./poll.zig"); }
-
-
tests/e2e/poll.zig (new)
-
@@ -0,0 +1,54 @@// Copyright (C) 2025 Shota FUJI // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. // // SPDX-License-Identifier: GPL-3.0-only const std = @import("std"); const config = @import("config"); test "2020-01-01T06:00:00 Asia/Tokyo is night" { const result = try std.process.Child.run(.{ .allocator = std.testing.allocator, .argv = &.{ config.bin, "poll", "--lat", "35.6764N", "--lon", "139.65E", "--at", "2020-01-01T06:00:00+09:00", }, }); defer std.testing.allocator.free(result.stderr); defer std.testing.allocator.free(result.stdout); try std.testing.expectEqual(3, result.term.Exited); try std.testing.expectEqual(0, result.stderr.len); } test "2020-08-01T06:00:00 Asia/Tokyo is day" { const result = try std.process.Child.run(.{ .allocator = std.testing.allocator, .argv = &.{ config.bin, "poll", "--lat", "35.6764N", "--lon", "139.65E", "--at", "2020-08-01T06:00:00+09:00", }, }); defer std.testing.allocator.free(result.stderr); defer std.testing.allocator.free(result.stdout); try std.testing.expectEqual(2, result.term.Exited); try std.testing.expectEqual(0, result.stderr.len); }
-
-
-
@@ -17,4 +17,5 @@ // SPDX-License-Identifier: GPL-3.0-onlytest { _ = @import("./list.zig"); _ = @import("./poll.zig"); }
-
-
tests/snapshot/poll.zig (new)
-
@@ -0,0 +1,62 @@// Copyright (C) 2025 Shota FUJI // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. // // SPDX-License-Identifier: GPL-3.0-only const std = @import("std"); const config = @import("config"); const snapshot = @import("./snapshot.zig"); test "snapshots/poll_basic.txt" { const result = try std.process.Child.run(.{ .allocator = std.testing.allocator, .argv = &.{ config.bin, "poll", "--at", "2025-07-07T12:30:00Z", }, }); defer std.testing.allocator.free(result.stderr); defer std.testing.allocator.free(result.stdout); try snapshot.expectMatchSnapshot(@src(), &result); } test "snapshots/poll_washington_twilight_10.txt" { // From original USAGE.txt // > Indicate by program exit-code if is Day or Night using a custom twilight angle of // > 10 degrees above horizon. Washington, UK. const result = try std.process.Child.run(.{ .allocator = std.testing.allocator, .argv = &.{ config.bin, "poll", "--twilight", "10", "--at", "2025-12-01T17:00:00Z", "54.897786N", // Original example uses -1.517536E but it does not make sense. "1.517536W", }, }); defer std.testing.allocator.free(result.stderr); defer std.testing.allocator.free(result.stdout); try snapshot.expectMatchSnapshot(@src(), &result); }
-
-
-
@@ -0,0 +1,1 @@DAY
-
-
-
@@ -0,0 +1,1 @@NIGHT
-