add astronomical times support
This commit is contained in:
parent
5c1d6f41e6
commit
6ce156e243
5 changed files with 327 additions and 19 deletions
|
|
@ -344,6 +344,9 @@ The application makes network calls to the following services:
|
||||||
- Airport code -> location mapping: [Openflights](https://github.com/jpatokal/openflights)
|
- Airport code -> location mapping: [Openflights](https://github.com/jpatokal/openflights)
|
||||||
- Ip address -> location mapping: [GeoLite2 City Database](https://github.com/maxmind/libmaxminddb)
|
- Ip address -> location mapping: [GeoLite2 City Database](https://github.com/maxmind/libmaxminddb)
|
||||||
- Moon phase calculations (vendored): [Phoon](https://acme.com/software/phoon/)
|
- Moon phase calculations (vendored): [Phoon](https://acme.com/software/phoon/)
|
||||||
|
- Astronomical calculations (vendored): [Sunriset](http://www.stjarnhimlen.se/comp/sunriset.c)
|
||||||
|
- Note, a small change was made to the original to provide the ability to
|
||||||
|
skip putting main() into the object file
|
||||||
|
|
||||||
## Performance Targets
|
## Performance Targets
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,9 @@ Features not yet implemented in the Zig version:
|
||||||
- lang query parameter support
|
- lang query parameter support
|
||||||
- Translation of weather conditions and text (54 languages)
|
- Translation of weather conditions and text (54 languages)
|
||||||
|
|
||||||
## 5. Astronomical Times
|
## 5. Json output
|
||||||
- Calculate dawn, sunrise, zenith, sunset, dusk times
|
|
||||||
- Based on location coordinates and timezone
|
|
||||||
- Display in custom format output
|
|
||||||
|
|
||||||
## 6. Json output
|
|
||||||
- Does not match wttr.in format
|
- Does not match wttr.in format
|
||||||
|
|
||||||
## 7. Moon endpoint
|
## 6. Moon endpoint
|
||||||
- `/Moon` and `/Moon@YYYY-MM-DD` endpoints not yet implemented
|
- `/Moon` and `/Moon@YYYY-MM-DD` endpoints not yet implemented
|
||||||
- Moon phase calculation is implemented and available in custom format (%m, %M)
|
- Moon phase calculation is implemented and available in custom format (%m, %M)
|
||||||
|
|
|
||||||
280
src/Astronomical.zig
Normal file
280
src/Astronomical.zig
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const zeit = @import("zeit");
|
||||||
|
const TimeZoneOffsets = @import("location/timezone_offsets.zig");
|
||||||
|
const Coordinates = @import("Coordinates.zig");
|
||||||
|
|
||||||
|
const c_double = f64;
|
||||||
|
// We don't use @cImport here because sunriset.c has a main() function
|
||||||
|
// Instead we declare the functions we need directly
|
||||||
|
extern fn __sunriset__(year: c_int, month: c_int, day: c_int, lon: c_double, lat: c_double, altit: c_double, upper_limb: c_int, rise: *c_double, set: *c_double) c_int;
|
||||||
|
extern fn __daylen__(year: c_int, month: c_int, day: c_int, lon: c_double, lat: c_double, altit: c_double, upper_limb: c_int) c_double;
|
||||||
|
|
||||||
|
/// We will copy these macros in from the c file as proper functions
|
||||||
|
|
||||||
|
// This macro computes the length of the day, from sunrise to sunset.
|
||||||
|
// Sunrise/set is considered to occur when the Sun's upper limb is
|
||||||
|
// 35 arc minutes below the horizon (this accounts for the refraction
|
||||||
|
// of the Earth's atmosphere).
|
||||||
|
fn day_length(year: c_int, month: c_int, day: c_int, lon: c_double, lat: c_double) c_double {
|
||||||
|
return __daylen__(year, month, day, lon, lat, -35.0 / 60.0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This macro computes the length of the day, including civil twilight.
|
||||||
|
// Civil twilight starts/ends when the Sun's center is 6 degrees below
|
||||||
|
// the horizon.
|
||||||
|
fn day_civil_twilight_length(year: c_int, month: c_int, day: c_int, lon: c_double, lat: c_double) c_double {
|
||||||
|
return __daylen__(year, month, day, lon, lat, -6.0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This macro computes the length of the day, incl. nautical twilight.
|
||||||
|
// Nautical twilight starts/ends when the Sun's center is 12 degrees
|
||||||
|
// below the horizon.
|
||||||
|
fn day_nautical_twilight_length(year: c_int, month: c_int, day: c_int, lon: c_double, lat: c_double) c_double {
|
||||||
|
return __daylen__(year, month, day, lon, lat, -12.0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This macro computes the length of the day, incl. astronomical twilight.
|
||||||
|
// Astronomical twilight starts/ends when the Sun's center is 18 degrees
|
||||||
|
// below the horizon.
|
||||||
|
fn day_astronomical_twilight_length(year: c_int, month: c_int, day: c_int, lon: c_double, lat: c_double) c_double {
|
||||||
|
return __daylen__(year, month, day, lon, lat, -18.0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This macro computes times for sunrise/sunset.
|
||||||
|
// Sunrise/set is considered to occur when the Sun's upper limb is
|
||||||
|
// 35 arc minutes below the horizon (this accounts for the refraction
|
||||||
|
// of the Earth's atmosphere).
|
||||||
|
fn sun_rise_set(year: c_int, month: c_int, day: c_int, lon: c_double, lat: c_double, rise: *c_double, set: *c_double) c_int {
|
||||||
|
return __sunriset__(year, month, day, lon, lat, -35.0 / 60.0, 1, rise, set);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This macro computes the start and end times of civil twilight.
|
||||||
|
// Civil twilight starts/ends when the Sun's center is 6 degrees below
|
||||||
|
// the horizon.
|
||||||
|
fn civil_twilight(year: c_int, month: c_int, day: c_int, lon: c_double, lat: c_double, start: *c_double, end: *c_double) c_int {
|
||||||
|
return __sunriset__(year, month, day, lon, lat, -6.0, 0, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This macro computes the start and end times of nautical twilight.
|
||||||
|
// Nautical twilight starts/ends when the Sun's center is 12 degrees
|
||||||
|
// below the horizon.
|
||||||
|
fn nautical_twilight(year: c_int, month: c_int, day: c_int, lon: c_double, lat: c_double, start: *c_double, end: *c_double) c_int {
|
||||||
|
return __sunriset__(year, month, day, lon, lat, -12.0, 0, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This macro computes the start and end times of astronomical twilight.
|
||||||
|
// Astronomical twilight starts/ends when the Sun's center is 18 degrees
|
||||||
|
// below the horizon.
|
||||||
|
fn astronomical_twilight(year: c_int, month: c_int, day: c_int, lon: c_double, lat: c_double, start: *c_double, end: *c_double) c_int {
|
||||||
|
return __sunriset__(year, month, day, lon, lat, -18.0, 0, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Astronomical = @This();
|
||||||
|
|
||||||
|
dawn: Time, // Hours in UTC (civil twilight start)
|
||||||
|
sunrise: Time, // Hours in UTC
|
||||||
|
zenith: Time, // Hours in UTC (solar noon)
|
||||||
|
sunset: Time, // Hours in UTC
|
||||||
|
dusk: Time, // Hours in UTC (civil twilight end)
|
||||||
|
|
||||||
|
pub const Time = struct {
|
||||||
|
year: i32,
|
||||||
|
month: zeit.Month,
|
||||||
|
day: u5,
|
||||||
|
hour: u5,
|
||||||
|
minute: u6,
|
||||||
|
offset: i32 = 0,
|
||||||
|
|
||||||
|
pub fn init(sunriset_time: f64, year: i32, month: zeit.Month, day: u5) Time {
|
||||||
|
const h: u8 = @intFromFloat(@floor(sunriset_time));
|
||||||
|
return .{
|
||||||
|
.year = year,
|
||||||
|
.month = month,
|
||||||
|
.day = day,
|
||||||
|
.hour = @intCast(h),
|
||||||
|
.minute = @intFromFloat(@floor((sunriset_time - @as(f64, @floatFromInt(h))) * 60.0)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn adjustTimeToLocal(self: Time, coords: Coordinates) !Time {
|
||||||
|
const ztime: zeit.Time = .{
|
||||||
|
.year = self.year,
|
||||||
|
.month = self.month,
|
||||||
|
.day = self.day,
|
||||||
|
.hour = self.hour,
|
||||||
|
.minute = self.minute,
|
||||||
|
};
|
||||||
|
const original = ztime.instant();
|
||||||
|
const offset = TimeZoneOffsets.getTimezoneOffset(coords);
|
||||||
|
const new = if (offset >= 0)
|
||||||
|
try original.add(.{ .minutes = @abs(offset) })
|
||||||
|
else
|
||||||
|
try original.subtract(.{ .minutes = @abs(offset) });
|
||||||
|
const new_ztime = new.time();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.year = new_ztime.year,
|
||||||
|
.month = new_ztime.month,
|
||||||
|
.day = new_ztime.day,
|
||||||
|
.hour = new_ztime.hour,
|
||||||
|
.minute = new_ztime.minute,
|
||||||
|
.offset = offset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format(self: Time, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||||
|
try writer.print("{d:0>2}:{d:0>2}", .{ self.hour, self.minute });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/// Returns all times in UTC
|
||||||
|
///
|
||||||
|
/// calculates astronomical data for lat/long and a timestamp using
|
||||||
|
/// sunriset.c (http://www.stjarnhimlen.se/comp/sunriset.c)
|
||||||
|
///
|
||||||
|
/// Note: year,month,date = calendar date, 1801-2099 only.
|
||||||
|
pub fn init(latitude: f64, longitude: f64, timestamp: i64) Astronomical {
|
||||||
|
const instant = zeit.instant(.{ .source = .{ .unix_timestamp = timestamp } }) catch
|
||||||
|
@panic("This can't happen");
|
||||||
|
|
||||||
|
const time = instant.time();
|
||||||
|
const year: c_int = @intCast(time.year);
|
||||||
|
const month: c_int = @intFromEnum(time.month);
|
||||||
|
const day: c_int = @intCast(time.day);
|
||||||
|
|
||||||
|
std.log.debug("year: {}, month: {}, day: {}", .{ year, month, day });
|
||||||
|
var sunrise: f64 = 0;
|
||||||
|
var sunset: f64 = 0;
|
||||||
|
var dawn: f64 = 0;
|
||||||
|
var dusk: f64 = 0;
|
||||||
|
|
||||||
|
// Notes from the c file itself:
|
||||||
|
// Eastern longitude positive, Western longitude negative
|
||||||
|
// Northern latitude positive, Southern latitude negative
|
||||||
|
//
|
||||||
|
// The longitude value IS critical in this function!
|
||||||
|
//
|
||||||
|
// altit = the altitude which the Sun should cross
|
||||||
|
// Set to -35/60 degrees for rise/set, -6 degrees
|
||||||
|
// for civil, -12 degrees for nautical and -18
|
||||||
|
// degrees for astronomical twilight.
|
||||||
|
// upper_limb: non-zero -> upper limb, zero -> center
|
||||||
|
// Set to non-zero (e.g. 1) when computing rise/set
|
||||||
|
// times, and to zero when computing start/end of
|
||||||
|
// twilight.
|
||||||
|
// *rise = where to store the rise time
|
||||||
|
// *set = where to store the set time
|
||||||
|
// Both times are relative to the specified altitude,
|
||||||
|
// and thus this function can be used to compute
|
||||||
|
// various twilight times, as well as rise/set times
|
||||||
|
// Return value: 0 = sun rises/sets this day, times stored at
|
||||||
|
// *trise and *tset.
|
||||||
|
// +1 = sun above the specified "horizon" 24 hours.
|
||||||
|
// *trise set to time when the sun is at south,
|
||||||
|
// minus 12 hours while *tset is set to the south
|
||||||
|
// time plus 12 hours. "Day" length = 24 hours
|
||||||
|
// -1 = sun is below the specified "horizon" 24 hours
|
||||||
|
// "Day" length = 0 hours, *trise and *tset are
|
||||||
|
// both set to the time when the sun is at south.
|
||||||
|
|
||||||
|
// Get sunrise/sunset
|
||||||
|
_ = sun_rise_set(year, month, day, longitude, latitude, &sunrise, &sunset);
|
||||||
|
|
||||||
|
// Get civil twilight (dawn/dusk)
|
||||||
|
_ = civil_twilight(year, month, day, longitude, latitude, &dawn, &dusk);
|
||||||
|
|
||||||
|
// Calculate solar noon (zenith)
|
||||||
|
const zenith = (sunrise + sunset) / 2.0;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.dawn = Time.init(dawn, time.year, time.month, time.day),
|
||||||
|
.sunrise = Time.init(sunrise, time.year, time.month, time.day),
|
||||||
|
.zenith = Time.init(zenith, time.year, time.month, time.day),
|
||||||
|
.sunset = Time.init(sunset, time.year, time.month, time.day),
|
||||||
|
.dusk = Time.init(dusk, time.year, time.month, time.day),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "astronomical calculations" {
|
||||||
|
// Test for London on 2024-01-01
|
||||||
|
const timestamp: i64 = 1704067200;
|
||||||
|
const astro = init(51.5074, -0.1278, timestamp);
|
||||||
|
|
||||||
|
// Winter in London: sunrise around 8am, sunset around 4pm UTC
|
||||||
|
// dawn: 07:26, sunrise: 08:06, zenith: 12:03, sunset: 16:01, dusk: 16:41
|
||||||
|
try std.testing.expectEqual(@as(u5, 7), astro.dawn.hour);
|
||||||
|
try std.testing.expectEqual(@as(u6, 26), astro.dawn.minute);
|
||||||
|
try std.testing.expectEqual(@as(u5, 8), astro.sunrise.hour);
|
||||||
|
try std.testing.expectEqual(@as(u6, 6), astro.sunrise.minute);
|
||||||
|
try std.testing.expectEqual(@as(u5, 12), astro.zenith.hour);
|
||||||
|
try std.testing.expectEqual(@as(u6, 3), astro.zenith.minute);
|
||||||
|
try std.testing.expectEqual(@as(u5, 16), astro.sunset.hour);
|
||||||
|
try std.testing.expectEqual(@as(u6, 1), astro.sunset.minute);
|
||||||
|
try std.testing.expectEqual(@as(u5, 16), astro.dusk.hour);
|
||||||
|
try std.testing.expectEqual(@as(u6, 41), astro.dusk.minute);
|
||||||
|
|
||||||
|
// Sanity checks
|
||||||
|
try std.testing.expect(astro.dawn.hour < astro.sunrise.hour);
|
||||||
|
try std.testing.expect(astro.sunset.hour <= astro.dusk.hour);
|
||||||
|
try std.testing.expect(astro.zenith.hour > astro.sunrise.hour and astro.zenith.hour < astro.sunset.hour);
|
||||||
|
}
|
||||||
|
test "Oregon modern time" {
|
||||||
|
// Test for Oregon 2026-01-06
|
||||||
|
const timestamp: i64 = 1767722166;
|
||||||
|
|
||||||
|
const coords: Coordinates = .{
|
||||||
|
.latitude = 44.052071,
|
||||||
|
.longitude = -123.086754,
|
||||||
|
};
|
||||||
|
const astro = init(coords.latitude, coords.longitude, timestamp);
|
||||||
|
|
||||||
|
const sunrise = try astro.sunrise.adjustTimeToLocal(coords);
|
||||||
|
// Sunrise at 7:47, sunset 16:49, zenith 12:18
|
||||||
|
|
||||||
|
try std.testing.expectEqualDeep(Time{
|
||||||
|
.year = 2026,
|
||||||
|
.month = .jan,
|
||||||
|
.day = 6,
|
||||||
|
.hour = 7,
|
||||||
|
.minute = 46,
|
||||||
|
.offset = -480,
|
||||||
|
}, sunrise);
|
||||||
|
|
||||||
|
// UTC times: dawn: 15:14, sunrise: 15:46, zenith: 20:18, sunset: 24:49 (00:49 next day), dusk: 25:22 (01:22 next day)
|
||||||
|
// Local PST times: dawn: 07:14, sunrise: 07:46, zenith: 12:18, sunset: 16:49, dusk: 17:22
|
||||||
|
const sunset = try astro.sunset.adjustTimeToLocal(coords);
|
||||||
|
const dusk = try astro.dusk.adjustTimeToLocal(coords);
|
||||||
|
const zenith = try astro.zenith.adjustTimeToLocal(coords);
|
||||||
|
const dawn = try astro.dawn.adjustTimeToLocal(coords);
|
||||||
|
|
||||||
|
try std.testing.expectEqual(@as(u5, 7), dawn.hour);
|
||||||
|
try std.testing.expectEqual(@as(u6, 14), dawn.minute);
|
||||||
|
try std.testing.expectEqual(@as(u5, 12), zenith.hour);
|
||||||
|
try std.testing.expectEqual(@as(u6, 18), zenith.minute);
|
||||||
|
try std.testing.expectEqual(@as(u5, 16), sunset.hour);
|
||||||
|
try std.testing.expectEqual(@as(u6, 49), sunset.minute);
|
||||||
|
try std.testing.expectEqual(@as(u5, 17), dusk.hour);
|
||||||
|
try std.testing.expectEqual(@as(u6, 22), dusk.minute);
|
||||||
|
|
||||||
|
// Sanity checks
|
||||||
|
try std.testing.expect(dawn.hour <= sunrise.hour);
|
||||||
|
try std.testing.expect(dawn.minute < sunrise.minute or dawn.hour < sunrise.hour);
|
||||||
|
try std.testing.expect(sunset.hour < dusk.hour or (sunset.hour == dusk.hour and sunset.minute < dusk.minute));
|
||||||
|
try std.testing.expect(zenith.hour > sunrise.hour and zenith.hour < sunset.hour);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "format time" {
|
||||||
|
const time1 = Time{ .year = 2026, .month = .jan, .day = 6, .hour = 12, .minute = 30, .offset = 0 };
|
||||||
|
const time2 = Time{ .year = 2026, .month = .jan, .day = 6, .hour = 8, .minute = 15, .offset = 0 };
|
||||||
|
|
||||||
|
var buf1: [5]u8 = undefined;
|
||||||
|
var buf2: [5]u8 = undefined;
|
||||||
|
|
||||||
|
var writer1 = std.Io.Writer.fixed(&buf1);
|
||||||
|
var writer2 = std.Io.Writer.fixed(&buf2);
|
||||||
|
|
||||||
|
try time1.format(&writer1);
|
||||||
|
try time2.format(&writer2);
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("12:30", &buf1);
|
||||||
|
try std.testing.expectEqualStrings("08:15", &buf2);
|
||||||
|
}
|
||||||
|
|
@ -69,11 +69,11 @@ pub const help_page =
|
||||||
\\ %p # precipitation (mm)
|
\\ %p # precipitation (mm)
|
||||||
\\ %o # probability of precipitation
|
\\ %o # probability of precipitation
|
||||||
\\ %P # pressure (hPa)
|
\\ %P # pressure (hPa)
|
||||||
\\ %D # * dawn time
|
\\ %D # dawn time
|
||||||
\\ %S # * sunrise time
|
\\ %S # sunrise time
|
||||||
\\ %z # * zenith time
|
\\ %z # zenith time
|
||||||
\\ %s # * sunset time
|
\\ %s # sunset time
|
||||||
\\ %d # * dusk time
|
\\ %d # dusk time
|
||||||
\\
|
\\
|
||||||
\\PNG options:
|
\\PNG options:
|
||||||
\\
|
\\
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const zeit = @import("zeit");
|
||||||
const types = @import("../weather/types.zig");
|
const types = @import("../weather/types.zig");
|
||||||
const emoji = @import("emoji.zig");
|
const emoji = @import("emoji.zig");
|
||||||
const utils = @import("utils.zig");
|
const utils = @import("utils.zig");
|
||||||
const Moon = @import("../Moon.zig");
|
const Moon = @import("../Moon.zig");
|
||||||
|
const Astronomical = @import("../Astronomical.zig");
|
||||||
|
const TimeZoneOffsets = @import("../location/timezone_offsets.zig");
|
||||||
|
const Coordinates = @import("../Coordinates.zig");
|
||||||
|
|
||||||
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: []const u8, use_imperial: bool) ![]const u8 {
|
pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format: []const u8, use_imperial: bool) ![]const u8 {
|
||||||
var output: std.ArrayList(u8) = .empty;
|
var output: std.ArrayList(u8) = .empty;
|
||||||
|
|
@ -54,21 +58,37 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format:
|
||||||
try writer.print("{d:.2} {s}", .{ pressure, unit });
|
try writer.print("{d:.2} {s}", .{ pressure, unit });
|
||||||
},
|
},
|
||||||
'm' => {
|
'm' => {
|
||||||
const now = std.time.timestamp();
|
const now = try nowAt(weather.coords);
|
||||||
const moon = Moon.getPhase(now);
|
const moon = Moon.getPhase(now);
|
||||||
try writer.print("{s}", .{moon.emoji()});
|
try writer.print("{s}", .{moon.emoji()});
|
||||||
},
|
},
|
||||||
'M' => {
|
'M' => {
|
||||||
const now = std.time.timestamp();
|
const now = try nowAt(weather.coords);
|
||||||
const moon = Moon.getPhase(now);
|
const moon = Moon.getPhase(now);
|
||||||
try writer.print("{d}", .{moon.day()});
|
try writer.print("{d}", .{moon.day()});
|
||||||
},
|
},
|
||||||
'o' => try writer.print("0%", .{}), // Probability of precipitation placeholder
|
'o' => try writer.print("0%", .{}), // Probability of precipitation placeholder
|
||||||
'D' => try writer.print("06:00", .{}), // Dawn placeholder
|
'D', 'S', 'z', 's', 'd' => {
|
||||||
'S' => try writer.print("07:30", .{}), // Sunrise placeholder
|
// Again...we only need approximate, because we simply need
|
||||||
'z' => try writer.print("12:00", .{}), // Zenith placeholder
|
// to make sure the day is correct for this. Even a day off
|
||||||
's' => try writer.print("18:30", .{}), // Sunset placeholder
|
// should actually be ok. Unix timestamp is always UTC,
|
||||||
'd' => try writer.print("20:00", .{}), // Dusk placeholder
|
// so we convert to local
|
||||||
|
const now = try nowAt(weather.coords);
|
||||||
|
const astro = Astronomical.init(
|
||||||
|
weather.coords.latitude,
|
||||||
|
weather.coords.longitude,
|
||||||
|
now,
|
||||||
|
);
|
||||||
|
const utc_time = switch (code) {
|
||||||
|
'D' => astro.dawn,
|
||||||
|
'S' => astro.sunrise,
|
||||||
|
'z' => astro.zenith,
|
||||||
|
's' => astro.sunset,
|
||||||
|
'd' => astro.dusk,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
try writer.print("{f}", .{try utc_time.adjustTimeToLocal(weather.coords)});
|
||||||
|
},
|
||||||
'%' => try writer.print("%", .{}),
|
'%' => try writer.print("%", .{}),
|
||||||
'n' => try writer.print("\n", .{}),
|
'n' => try writer.print("\n", .{}),
|
||||||
else => {
|
else => {
|
||||||
|
|
@ -85,6 +105,16 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format:
|
||||||
return output.toOwnedSlice(allocator);
|
return output.toOwnedSlice(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn nowAt(coords: Coordinates) !i64 {
|
||||||
|
const now = try zeit.instant(.{});
|
||||||
|
const offset = TimeZoneOffsets.getTimezoneOffset(coords);
|
||||||
|
const new = if (offset >= 0)
|
||||||
|
try now.add(.{ .minutes = @abs(offset) })
|
||||||
|
else
|
||||||
|
try now.subtract(.{ .minutes = @abs(offset) });
|
||||||
|
return new.unixTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
test "render custom format with location and temp" {
|
test "render custom format with location and temp" {
|
||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue