---
README.org | 8 +--
man/thp.1.scd | 3 +
man/thpd.5.scd | 31 +++++++++++
mods/+sun.ha | 148 +++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 186 insertions(+), 4 deletions(-)
create mode 100644 mods/+sun.ha
diff --git a/README.org b/README.org
index 6c8e372..69a7dfc 100644
--- a/README.org
+++ b/README.org
@@ -32,16 +32,16 @@ one patche applied (as of now):
To build thp with all modules included, you need libgit2 and your platforms libgit2-dev equivalent installed, then issued
#+BEGIN_SRC sh :exports code
-make EXTRA_MODS="project git clock timer shind uptime moon"
-make EXTRA_MODS="project git clock timer shind uptime moon" test
+make EXTRA_MODS="project git clock timer shind uptime moon sun"
+make EXTRA_MODS="project git clock timer shind uptime moon sun" test
make PREFIX="${HOME}" install
#+END_SRC
To build with just the modules you use in your spec, use ~MODS~:
#+BEGIN_SRC sh :exports code
-make MODS="path host project timer uptime moon"
-make MODS="path host project timer uptime moon" test
+make MODS="path host project timer uptime moon sun"
+make MODS="path host project timer uptime moon sun" test
make PREFIX="${HOME}" install
#+END_SRC
diff --git a/man/thp.1.scd b/man/thp.1.scd
index dcdf50c..cd6a11f 100644
--- a/man/thp.1.scd
+++ b/man/thp.1.scd
@@ -107,6 +107,9 @@ All modules are optionally compiled into the server executable.
*moon* displays an icon representing the current moon phase.
+*sunrise* and *sunset* display the time of sunrise and sunset, respectively.
+ The location and time format can be configured. See thpd(5).
+
# STYLES
Available colors are: black, red, green, yellow, blue, magenta, cyan, white,
diff --git a/man/thpd.5.scd b/man/thpd.5.scd
index e4d9dfd..1673a01 100644
--- a/man/thpd.5.scd
+++ b/man/thpd.5.scd
@@ -173,6 +173,37 @@ Valid values: "S" and "N".
hemisphere="N"
```
+## sun
+
+Display the time of sunrise and sunset.
+
+- latitude[=-23.5898764]
+ The latitude in decimal format.
+
+- longitude[=-46.6589231]
+ The longitude in decimal format.
+
+- sunrise.layout[=🌅%H:%M]
+- sunset.layout[=🌇%H:%M]
+ Format string as understood by date::asformat from the hare standard library.
+
+- midnight.sun.msg[=Midnight sun]
+ Display message when the sun stays above the horizon all day.
+
+- polar.night.msg[=Polar night]
+ Display message when the sun stays below the horizon all day.
+
+*example*:
+
+```
+[sun]
+\# Buenos Aires/AR
+latitude=-34.603333
+longitude=-58.381667
+sunrise.layout=%H:%M|
+sunset.layout=%H:%M|
+```
+
# SEE ALSO
*thpd*(1) *thp*(1) *thp*(5)
diff --git a/mods/+sun.ha b/mods/+sun.ha
new file mode 100644
index 0000000..ef9626e
--- /dev/null
+++ b/mods/+sun.ha
@@ -0,0 +1,148 @@
+// Author: Carlos Une <une@fastmail.fm>
+// Maintainer: Carlos Une <une@fastmail.fm>
+//
+// SPDX-FileCopyrightText: 2023 Carlos Une <une@fastmail.fm>
+// SPDX-License-Identifier: GPL-3.0-or-later
+// Reference: https://en.wikipedia.org/wiki/Sunrise_equation
+use config;
+use env;
+use math;
+use strconv;
+use strings;
+use time;
+use time::chrono;
+use time::date;
+
+type midnightsun = !void; // Sun doesn't set
+type polarnight = !void; // Sun doesn't rise
+
+@init fn register_sun() void = {
+ register("sunrise", &mod_sunrise);
+ register("sunset", &mod_sunset);
+};
+
+fn readconfig() (f64, f64, str, str, str, str) = (
+ strconv::stof64(config::setting("sun.latitude", "-23.5898764"))!,
+ strconv::stof64(config::setting("sun.longitude", "-46.6589231"))!,
+ config::setting("sun.sunrise.layout", "🌅%H:%M"),
+ config::setting("sun.sunset.layout", "🌇%H:%M"),
+ config::setting("sun.midnight.sun.msg", "Midnight sun"),
+ config::setting("sun.polar.night.msg", "Polar night"));
+
+export fn mod_sunrise(pe: *env::env) str = {
+ const cfg = readconfig();
+ match(sunrise_sunset(local_today_utc_noon(), cfg.0, cfg.1)) {
+ case let r: (date::date, date::date) =>
+ return strings::dup(date::asformat(cfg.2, &date::in(chrono::LOCAL, r.0)!)!);
+ case midnightsun =>
+ return strings::dup(cfg.4);
+ case polarnight =>
+ return strings::dup(cfg.5);
+ };
+};
+
+export fn mod_sunset(pe: *env::env) str = {
+ const cfg = readconfig();
+ match(sunrise_sunset(local_today_utc_noon(), cfg.0, cfg.1)) {
+ case let r: (date::date, date::date) =>
+ return strings::dup(date::asformat(cfg.3, &date::in(chrono::LOCAL, r.1)!)!);
+ case midnightsun =>
+ return strings::dup(cfg.4);
+ case polarnight =>
+ return strings::dup(cfg.5);
+ };
+};
+
+fn rad(a: f64) f64 = a * math::PI/180.0;
+fn deg(a: f64) f64 = a * 180.0/math::PI;
+fn sin(a: f64) f64 = math::sinf64(rad(a));
+fn cos(a: f64) f64 = math::cosf64(rad(a));
+
+// Get today's yyyy/mm/dd in LOCAL tzone, build yyyy/mm/dd 12:00:00 in UTC tzone
+fn local_today_utc_noon() date::date = {
+ const now = date::now();
+ return date::new(chrono::UTC, 0, date::year(&now), date::month(&now), date::day(&now), 12, 0, 0, 0)!;
+};
+
+fn julian_day_n(dt: date::date) f64 = {
+ const ref = date::new(chrono::UTC, 0, 2000, 1, 1, 12, 0, 0, 0)!;
+ return chrono::diff(&ref, &dt)!: f64/(24*time::HOUR): f64;
+};
+
+// https://en.wikipedia.org/wiki/Julian_day#Julian_or_Gregorian_calendar_from_Julian_day_number
+fn julian_to_gregorian(jday: f64) date::date = {
+ const J: int = math::truncf64(jday): int;
+ const y: int = 4716;
+ const v: int = 3;
+ const j: int = 1401;
+ const u: int = 5;
+ const m: int = 2;
+ const s: int = 153;
+ const n: int = 12;
+ const w: int = 2;
+ const r: int = 4;
+ const B: int = 274277;
+ const p: int = 1461;
+ const C: int = -38;
+ const f: int = J + j + (((4 * J + B) / 146097) * 3) / 4 + C;
+ const e = r * f + v;
+ const g = (e % p) / r;
+ const h = u * g + w;
+ const D = (h % s) / u + 1;
+ const M = (h/s + m) % n + 1;
+ const Y = (e / p) - y + (n + m - M) / n;
+ const fday = jday - J: f64;
+ const hour = (fday*24.0): int;
+ const minute = (fday*24.0*60.0): int - (hour*60);
+ const second = (fday*24.0*60.0*60.0): int - (hour*3600) - (minute*60);
+ let date = date::new(chrono::UTC, 0, Y, M, D, 0, minute, second, 0)!;
+ return date::add(date, (hour+12)*time::HOUR);
+};
+
+fn mean_solar_time(n: f64, lw: f64) f64 = {
+ return n - lw/360.0;
+};
+
+fn solar_mean_anomaly(J: f64) f64 = {
+ return math::modf64(357.5291 + 0.98560028 * J, 360.0);
+};
+
+fn equation_of_the_center(M: f64) f64 = {
+ return 1.9148*sin(M) + 0.02*sin(2.0*M) + 0.0003*sin(3.0*M);
+};
+
+fn eclyptic_longitude(M: f64, C: f64) f64 = {
+ return math::modf64(M + C + 180.0 + 102.9372, 360.0);
+};
+
+fn solar_transit(J: f64, M: f64, lambda: f64) f64 = {
+ return 2451545.0 + J + 0.0053*sin(M) - 0.0069*sin(2.0*lambda);
+};
+
+fn declination_of_the_sun(lambda: f64) (f64, f64) = {
+ let sindelta = sin(lambda)*sin(23.44);
+ return (sindelta, deg(math::asinf64(sindelta)));
+};
+
+fn hour_angle(phi: f64, delta: f64) (f64, f64) = {
+ let cosomega0 = (sin(-0.83) - sin(phi)*sin(delta))/(cos(phi)*cos(delta));
+ return (cosomega0, deg(math::acosf64(cosomega0)));
+};
+
+fn sunrise_sunset(d: date::date, lat: f64, lon: f64) ((date::date, date::date) | midnightsun | polarnight) = {
+ const J = mean_solar_time(math::ceilf64(julian_day_n(d)), lon);
+ const M = solar_mean_anomaly(J);
+ const C = equation_of_the_center(M);
+ const lambda = eclyptic_longitude(M, C);
+ const J_transit = solar_transit(J, M, lambda);
+ const (sindelta, delta) = declination_of_the_sun(lambda);
+ const (cosomega0, omega0) = hour_angle(lat, delta);
+ if (cosomega0 < -1.0) {
+ return midnightsun;
+ } else if (cosomega0 > 1.0) {
+ return polarnight;
+ };
+ const J_rise = J_transit - omega0/360.0;
+ const J_set = J_transit + omega0/360.0;
+ return (julian_to_gregorian(J_rise), julian_to_gregorian(J_set));
+};
--
2.30.2