Compare commits

..

No commits in common. "add82dee40e7e0a4443f3aa3d2ec0a62893f2e8d" and "35c9831ba86f0240e80df38f53f655062183ed7b" have entirely different histories.

20 changed files with 97 additions and 3338 deletions

View file

@ -343,10 +343,6 @@ The application makes network calls to the following services:
**External (other):**
- Airport code -> location mapping: [Openflights](https://github.com/jpatokal/openflights)
- Ip address -> location mapping: [GeoLite2 City Database](https://github.com/maxmind/libmaxminddb)
- 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

View file

@ -11,23 +11,24 @@ Features not yet implemented in the Zig version:
- Support transparency and custom styling
- Requires image rendering library integration
## 3. Language/Localization
## 3. Multiple Locations Support
- Handle colon-separated locations (e.g., `London:Paris:Berlin`)
- Process and display weather for multiple cities in one request
## 4. Language/Localization
- Accept-Language header parsing
- lang query parameter support
- Translation of weather conditions and text (54 languages)
## 4. Json output
## 5. Moon Phase Calculation
- Real moon phase computation based on date
- Moon phase emoji display
- Moonday calculation
## 6. Astronomical Times
- Calculate dawn, sunrise, zenith, sunset, dusk times
- Based on location coordinates and timezone
- Display in custom format output
## 7. Json output
- Does not match wttr.in format
## 5. Moon endpoint
- `/Moon` and `/Moon@YYYY-MM-DD` endpoints not yet implemented
- Moon phase calculation is implemented and available in custom format (%m, %M)
## ~~Multiple Locations Support~~
- Handle colon-separated locations (e.g., `London:Paris:Berlin`)
- Process and display weather for multiple cities in one request
This is advertised as a feature in wttr.in, but it does not work on the live
site. Given that this would be an unbounded computational risk and a DOS vector,
this is not going to be implemented. That can be done easily through a script,
and it will allow the rate limiting to work better that way

View file

@ -92,8 +92,9 @@ Available preconfigured formats: 1, 2, 3, 4 and custom format using percent nota
* `format=3`: `Nuremberg: 🌦 +11⁰C`
* `format=4`: `Nuremberg: 🌦 🌡️+11°C 🌬↓4km/h`
**Note:** Wttr.in claims that you can do multiple locations at one with a ':'
separator, but that does not appear to work
You can specify multiple locations separated with `:`:
**Note:** Not yet fully implemented - see [MISSING_FEATURES.md](MISSING_FEATURES.md)
```bash
$ curl localhost:8002/Nuremberg:Hamburg:Berlin?format=3
@ -224,20 +225,18 @@ All configuration is via environment variables:
| `WTTR_CACHE_DIR` | `~/.cache/wttr` | Cache directory |
| `WTTR_CACHE_SIZE` | `10000` | Max cached responses |
| `WTTR_GEOLITE_PATH` | `~/.cache/wttr/GeoLite2-City.mmdb` | GeoIP database path |
| `WTTR_GEOCACHE_FILE` | `~/.cache/wttr/geocache.json` | Location name cache |
| `IP2LOCATION_API_KEY` | (none) | Optional IP geolocation fallback |
| `IP2LOCATION_CACHE_FILE` | `~/.cache/wttr/ip2location.cache` | IP2Location cache file |
| `METNO_TOS_IDENTIFYING_EMAIL` | (none) | Email for Met.no API identification |
## External Services
### Required
### Required (No API Key)
- **Met.no Weather API** - Weather data provider (free, no registration)
- Set METNO_TOS_IDENTIFYING_EMAIL to identify yourself to Met.No
- **IP2Location.io** - IP geolocation service
- **IP2Location.io** - Fallback IP geolocation
- Sign up at https://www.ip2location.io/
- Free tier: 30,000 requests/month
- Set `IP2LOCATION_API_KEY` environment variable for higher limits
- Set via `IP2LOCATION_API_KEY` environment variable
### Auto-Downloaded
- **MaxMind GeoLite2 City** - IP geolocation database

View file

@ -19,51 +19,6 @@ pub fn build(b: *std.Build) void {
const maxminddb_upstream = b.dependency("maxminddb", .{});
// Build sunriset as a static library
const sunriset = b.addLibrary(.{
.name = "sunriset",
.linkage = .static,
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
sunriset.addIncludePath(b.path("libs/sunriset"));
sunriset.addCSourceFiles(.{
.root = b.path("libs/sunriset"),
.files = &.{
"sunriset.c",
},
.flags = &.{ "-D_DEFAULT_SOURCE", "-DSUNRISET_NO_MAIN" },
});
sunriset.linkLibC();
sunriset.linkSystemLibrary("m");
// Build phoon as a static library
const phoon = b.addLibrary(.{
.name = "phoon",
.linkage = .static,
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
}),
});
phoon.addIncludePath(b.path("libs/phoon_14Aug2014"));
phoon.addCSourceFiles(.{
.root = b.path("libs/phoon_14Aug2014"),
.files = &.{
"astro.c",
"date_parse.c",
},
.flags = &.{ "-std=c99", "-D_DEFAULT_SOURCE" },
});
phoon.linkLibC();
phoon.linkSystemLibrary("m");
// Build libmaxminddb as a static library
const maxminddb = b.addLibrary(.{
.name = "maxminddb",
@ -119,12 +74,8 @@ pub fn build(b: *std.Build) void {
});
exe.root_module.addOptions("build_options", build_options);
exe.root_module.addIncludePath(maxminddb_upstream.path("include"));
exe.root_module.addIncludePath(b.path("libs/phoon_14Aug2014"));
exe.root_module.addIncludePath(b.path("libs/sunriset"));
exe.root_module.addConfigHeader(maxminddb_config);
exe.linkLibrary(maxminddb);
exe.linkLibrary(phoon);
exe.linkLibrary(sunriset);
exe.linkLibC();
b.installArtifact(exe);
@ -158,12 +109,8 @@ pub fn build(b: *std.Build) void {
});
tests.root_module.addOptions("build_options", test_options);
tests.root_module.addIncludePath(maxminddb_upstream.path("include"));
tests.root_module.addIncludePath(b.path("libs/phoon_14Aug2014"));
tests.root_module.addIncludePath(b.path("libs/sunriset"));
tests.root_module.addConfigHeader(maxminddb_config);
tests.linkLibrary(maxminddb);
tests.linkLibrary(phoon);
tests.linkLibrary(sunriset);
tests.linkLibC();
const run_tests = b.addRunArtifact(tests);

View file

@ -18,8 +18,6 @@
.url = "git+https://github.com/rockorager/zeit?ref=zig-0.15#7ac64d72dbfb1a4ad549102e7d4e232a687d32d8",
.hash = "zeit-0.6.0-5I6bk36tAgATpSl9wjFmRPMqYN2Mn0JQHgIcRNcqDpJA",
},
.phoon = .{ .path = "libs/phoon_14Aug2014" },
.sunriset = .{ .path = "libs/sunriset" },
},
.fingerprint = 0x710c2b57e81aa678,
.minimum_zig_version = "0.15.2",

View file

@ -1,33 +0,0 @@
# Makefile for phoon
BINDIR = /usr/local/bin
MANDIR = /usr/local/man/man1
DEFINES = -DOS_BSD
#DEFINES = -DOS_SYSV
CC = cc
CFLAGS = -O $(DEFINES) -ansi -pedantic -U__STRICT_ANSI__ -Wall -Wpointer-arith -Wshadow -Wcast-qual -Wcast-align -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -Wredundant-decls -Wno-long-long
LDFLAGS = -s
all: phoon
phoon: phoon.o date_parse.o astro.o
$(CC) $(LDFLAGS) -o phoon phoon.o date_parse.o astro.o -lm
.c.o:
$(CC) $(CFLAGS) -c $<
date_parse.o: date_parse.h
astro.o: astro.h
install: all
rm -f $(BINDIR)/phoon
cp phoon $(BINDIR)
rm -f $(MANDIR)/phoon.1
cp phoon.1 $(MANDIR)
clean:
rm -f phoon *.o a.out core

View file

@ -1,50 +0,0 @@
phoon - display current moon phase
phoon - program to display the PHase of the mOON. Unlike other
such programs, which just tell you how long since first quarter
or something like that, phoon *shows* you the phase with a little
picture. I've put an example at the end of this file. I first
wrote this program in Pascal / TOPS-20 at CMU in 1979; I translated
it to Ratfor / Software Tools in 1981; and now it's in C / Unix.
Files in this distribution:
README this
Makefile guess
phoon.c phase of moon display
astro.c phase of moon calculations
astro.h header file
phoon.1 manual for phase of moon program
date_parse.c date-parsing routine
date_parse.h header file
Unpack the files, edit Makefile and change the options to suit, make,
and enjoy! Feedback is welcome - send bug reports, enhancements,
checks, money orders, etc. to the addresses below.
Jef Poskanzer jef@mail.acme.com http://www.acme.com/jef/
.--
.--
.-'
.-'@
/@@@
./
/@@ o
/@@@@
|@@@@@
/@@@@@ Last Quarter +
| @@@@ 4 1:36:10
|@ @@@ New Moon -
| 3 7:34:53
\ . @
|
\ @
\ o
`\
\
`-.
`-.
`--
`--

View file

@ -1,467 +0,0 @@
/* Adapted from "moontool.c" by John Walker, Release 2.5
**
** Quoting from the original:
**
** The algorithms used in this program to calculate the positions Sun and
** Moon as seen from the Earth are given in the book "Practical Astronomy
** With Your Calculator" by Peter Duffett-Smith, Second Edition,
** Cambridge University Press, 1981. Ignore the word "Calculator" in the
** title; this is an essential reference if you're interested in
** developing software which calculates planetary positions, orbits,
** eclipses, and the like. If you're interested in pursuing such
** programming, you should also obtain:
**
** "Astronomical Formulae for Calculators" by Jean Meeus, Third Edition,
** Willmann-Bell, 1985. A must-have.
**
** "Planetary Programs and Tables from -4000 to +2800" by Pierre
** Bretagnon and Jean-Louis Simon, Willmann-Bell, 1986. If you want the
** utmost (outside of JPL) accuracy for the planets, it's here.
**
** "Celestial BASIC" by Eric Burgess, Revised Edition, Sybex, 1985. Very
** cookbook oriented, and many of the algorithms are hard to dig out of
** the turgid BASIC code, but you'll probably want it anyway.
**
** See http://www.fourmilab.ch/moontool/
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include "astro.h"
#define FALSE 0
#define TRUE 1
/* Astronomical constants */
#define epoch 2444238.5 /* 1980 January 0.0 */
/* Constants defining the Sun's apparent orbit */
#define elonge 278.833540 /* Ecliptic longitude of the Sun
at epoch 1980.0 */
#define elongp 282.596403 /* Ecliptic longitude of the Sun at
perigee */
#define eccent 0.016718 /* Eccentricity of Earth's orbit */
#define sunsmax 1.495985e8 /* Semi-major axis of Earth's orbit, km */
#define sunangsiz 0.533128 /* Sun's angular size, degrees, at
semi-major axis distance */
/* Elements of the Moon's orbit, epoch 1980.0 */
#define mmlong 64.975464 /* Moon's mean lonigitude at the epoch */
#define mmlongp 349.383063 /* Mean longitude of the perigee at the
epoch */
#define mlnode 151.950429 /* Mean longitude of the node at the
epoch */
#define minc 5.145396 /* Inclination of the Moon's orbit */
#define mecc 0.054900 /* Eccentricity of the Moon's orbit */
#define mangsiz 0.5181 /* Moon's angular size at distance a
from Earth */
#define msmax 384401.0 /* Semi-major axis of Moon's orbit in km */
#define mparallax 0.9507 /* Parallax at distance a from Earth */
#define synmonth 29.53058868 /* Synodic month (new Moon to new Moon) */
#define lunatbase 2423436.0 /* Base date for E. W. Brown's numbered
series of lunations (1923 January 16) */
/* Properties of the Earth */
#define earthrad 6378.16 /* Radius of Earth in kilometres */
#define PI 3.14159265358979323846 /* Assume not near black hole nor in
Tennessee */
/* Handy mathematical functions */
#define sgn(x) (((x) < 0) ? -1 : ((x) > 0 ? 1 : 0)) /* Extract sign */
#define abs(x) ((x) < 0 ? (-(x)) : (x)) /* Absolute val */
#define fixangle(a) ((a) - 360.0 * (floor((a) / 360.0))) /* Fix angle */
#define torad(d) ((d) * (PI / 180.0)) /* Deg->Rad */
#define todeg(d) ((d) * (180.0 / PI)) /* Rad->Deg */
#define dsin(x) (sin(torad((x)))) /* Sin from deg */
#define dcos(x) (cos(torad((x)))) /* Cos from deg */
/*
* UNIX_TO_JULIAN -- Convert internal Unix date/time to astronomical
* Julian time (i.e. Julian date plus day fraction, expressed as
* a double).
*/
double
unix_to_julian( time_t t )
{
return (double) t / 86400.0 + 2440587.4999996666666666666;
}
/*
* JYEAR -- Convert Julian date to year, month, day, which are
* returned via integer pointers to integers.
*/
static void
jyear( double td, int* yy, int* mm, int* dd )
{
double j, d, y, m;
td += 0.5; /* Astronomical to civil */
j = floor(td);
j = j - 1721119.0;
y = floor(((4 * j) - 1) / 146097.0);
j = (j * 4.0) - (1.0 + (146097.0 * y));
d = floor(j / 4.0);
j = floor(((4.0 * d) + 3.0) / 1461.0);
d = ((4.0 * d) + 3.0) - (1461.0 * j);
d = floor((d + 4.0) / 4.0);
m = floor(((5.0 * d) - 3) / 153.0);
d = (5.0 * d) - (3.0 + (153.0 * m));
d = floor((d + 5.0) / 5.0);
y = (100.0 * y) + j;
if (m < 10.0)
m = m + 3;
else {
m = m - 9;
y = y + 1;
}
*yy = y;
*mm = m;
*dd = d;
}
/*
* MEANPHASE -- Calculates time of the mean new Moon for a given
* base date. This argument K to this function is
* the precomputed synodic month index, given by:
*
* K = (year - 1900) * 12.3685
*
* where year is expressed as a year and fractional year.
*/
static double
meanphase( double sdate, double k )
{
double t, t2, t3, nt1;
/* Time in Julian centuries from 1900 January 0.5 */
t = (sdate - 2415020.0) / 36525;
t2 = t * t; /* Square for frequent use */
t3 = t2 * t; /* Cube for frequent use */
nt1 = 2415020.75933 + synmonth * k
+ 0.0001178 * t2
- 0.000000155 * t3
+ 0.00033 * dsin(166.56 + 132.87 * t - 0.009173 * t2);
return nt1;
}
/*
* TRUEPHASE -- Given a K value used to determine the
* mean phase of the new moon, and a phase
* selector (0.0, 0.25, 0.5, 0.75), obtain
* the true, corrected phase time.
*/
static double
truephase( double k, double pha )
{
double t, t2, t3, pt, m, mprime, f;
int apcor = FALSE;
k += pha; /* Add phase to new moon time */
t = k / 1236.85; /* Time in Julian centuries from
1900 January 0.5 */
t2 = t * t; /* Square for frequent use */
t3 = t2 * t; /* Cube for frequent use */
pt = 2415020.75933 /* Mean time of phase */
+ synmonth * k
+ 0.0001178 * t2
- 0.000000155 * t3
+ 0.00033 * dsin(166.56 + 132.87 * t - 0.009173 * t2);
m = 359.2242 /* Sun's mean anomaly */
+ 29.10535608 * k
- 0.0000333 * t2
- 0.00000347 * t3;
mprime = 306.0253 /* Moon's mean anomaly */
+ 385.81691806 * k
+ 0.0107306 * t2
+ 0.00001236 * t3;
f = 21.2964 /* Moon's argument of latitude */
+ 390.67050646 * k
- 0.0016528 * t2
- 0.00000239 * t3;
if ((pha < 0.01) || (abs(pha - 0.5) < 0.01)) {
/* Corrections for New and Full Moon */
pt += (0.1734 - 0.000393 * t) * dsin(m)
+ 0.0021 * dsin(2 * m)
- 0.4068 * dsin(mprime)
+ 0.0161 * dsin(2 * mprime)
- 0.0004 * dsin(3 * mprime)
+ 0.0104 * dsin(2 * f)
- 0.0051 * dsin(m + mprime)
- 0.0074 * dsin(m - mprime)
+ 0.0004 * dsin(2 * f + m)
- 0.0004 * dsin(2 * f - m)
- 0.0006 * dsin(2 * f + mprime)
+ 0.0010 * dsin(2 * f - mprime)
+ 0.0005 * dsin(m + 2 * mprime);
apcor = TRUE;
} else if ((abs(pha - 0.25) < 0.01 || (abs(pha - 0.75) < 0.01))) {
pt += (0.1721 - 0.0004 * t) * dsin(m)
+ 0.0021 * dsin(2 * m)
- 0.6280 * dsin(mprime)
+ 0.0089 * dsin(2 * mprime)
- 0.0004 * dsin(3 * mprime)
+ 0.0079 * dsin(2 * f)
- 0.0119 * dsin(m + mprime)
- 0.0047 * dsin(m - mprime)
+ 0.0003 * dsin(2 * f + m)
- 0.0004 * dsin(2 * f - m)
- 0.0006 * dsin(2 * f + mprime)
+ 0.0021 * dsin(2 * f - mprime)
+ 0.0003 * dsin(m + 2 * mprime)
+ 0.0004 * dsin(m - 2 * mprime)
- 0.0003 * dsin(2 * m + mprime);
if (pha < 0.5)
/* First quarter correction */
pt += 0.0028 - 0.0004 * dcos(m) + 0.0003 * dcos(mprime);
else
/* Last quarter correction */
pt += -0.0028 + 0.0004 * dcos(m) - 0.0003 * dcos(mprime);
apcor = TRUE;
}
if (!apcor) {
(void)fprintf (stderr,
"TRUEPHASE called with invalid phase selector.\n");
abort();
}
return pt;
}
/*
* PHASEHUNT5 -- Find time of phases of the moon which surround
* the current date. Five phases are found, starting
* and ending with the new moons which bound the
* current lunation.
*/
void
phasehunt5( double sdate, double phases[5] )
{
double adate, k1, k2, nt1, nt2;
int yy, mm, dd;
adate = sdate - 45;
jyear(adate, &yy, &mm, &dd);
k1 = floor((yy + ((mm - 1) * (1.0 / 12.0)) - 1900) * 12.3685);
adate = nt1 = meanphase(adate, k1);
while (TRUE) {
adate += synmonth;
k2 = k1 + 1;
nt2 = meanphase(adate, k2);
if (nt1 <= sdate && nt2 > sdate)
break;
nt1 = nt2;
k1 = k2;
}
phases [0] = truephase (k1, 0.0);
phases [1] = truephase (k1, 0.25);
phases [2] = truephase (k1, 0.5);
phases [3] = truephase (k1, 0.75);
phases [4] = truephase (k2, 0.0);
}
/*
* PHASEHUNT2 -- Find time of phases of the moon which surround
* the current date. Two phases are found.
*/
void
phasehunt2( double sdate, double phases[2], double which[2] )
{
double phases5[5];
phasehunt5( sdate, phases5 );
phases[0] = phases5[0];
which[0] = 0.0;
phases[1] = phases5[1];
which[1] = 0.25;
if ( phases[1] <= sdate ) {
phases[0] = phases[1];
which[0] = which[1];
phases[1] = phases5[2];
which[1] = 0.5;
if ( phases[1] <= sdate ) {
phases[0] = phases[1];
which[0] = which[1];
phases[1] = phases5[3];
which[1] = 0.75;
if ( phases[1] <= sdate ) {
phases[0] = phases[1];
which[0] = which[1];
phases[1] = phases5[4];
which[1] = 0.0;
}
}
}
}
/*
* KEPLER -- Solve the equation of Kepler.
*/
static double
kepler( double m, double ecc )
{
double e, delta;
#define EPSILON 1E-6
e = m = torad(m);
do {
delta = e - ecc * sin(e) - m;
e -= delta / (1 - ecc * cos(e));
} while (abs (delta) > EPSILON);
return e;
}
/*
* PHASE -- Calculate phase of moon as a fraction:
*
* The argument is the time for which the phase is requested,
* expressed as a Julian date and fraction. Returns the terminator
* phase angle as a percentage of a full circle (i.e., 0 to 1),
* and stores into pointer arguments the illuminated fraction of
* the Moon's disc, the Moon's age in days and fraction, the
* distance of the Moon from the centre of the Earth, and the
* angular diameter subtended by the Moon as seen by an observer
* at the centre of the Earth.
*
* pphase: Illuminated fraction
* mage: Age of moon in days
* dist: Distance in kilometres
* angdia: Angular diameter in degrees
* sudist: Distance to Sun
* suangdia: Sun's angular diameter
*/
double
phase( double pdate, double* pphase, double* mage, double* dist, double* angdia, double* sudist, double* suangdia )
{
double Day, N, M, Ec, Lambdasun, ml, MM, MN, Ev, Ae, A3, MmP,
mEc, A4, lP, V, lPP, NP, y, x, Lambdamoon, BetaM,
MoonAge, MoonPhase,
MoonDist, MoonDFrac, MoonAng, MoonPar,
F, SunDist, SunAng;
/* Calculation of the Sun's position */
Day = pdate - epoch; /* Date within epoch */
N = fixangle((360 / 365.2422) * Day); /* Mean anomaly of the Sun */
M = fixangle(N + elonge - elongp); /* Convert from perigee
co-ordinates to epoch 1980.0 */
Ec = kepler(M, eccent); /* Solve equation of Kepler */
Ec = sqrt((1 + eccent) / (1 - eccent)) * tan(Ec / 2);
Ec = 2 * todeg(atan(Ec)); /* True anomaly */
Lambdasun = fixangle(Ec + elongp); /* Sun's geocentric ecliptic
longitude */
/* Orbital distance factor */
F = ((1 + eccent * cos(torad(Ec))) / (1 - eccent * eccent));
SunDist = sunsmax / F; /* Distance to Sun in km */
SunAng = F * sunangsiz; /* Sun's angular size in degrees */
/* Calculation of the Moon's position */
/* Moon's mean longitude */
ml = fixangle(13.1763966 * Day + mmlong);
/* Moon's mean anomaly */
MM = fixangle(ml - 0.1114041 * Day - mmlongp);
/* Moon's ascending node mean longitude */
MN = fixangle(mlnode - 0.0529539 * Day);
/* Evection */
Ev = 1.2739 * sin(torad(2 * (ml - Lambdasun) - MM));
/* Annual equation */
Ae = 0.1858 * sin(torad(M));
/* Correction term */
A3 = 0.37 * sin(torad(M));
/* Corrected anomaly */
MmP = MM + Ev - Ae - A3;
/* Correction for the equation of the centre */
mEc = 6.2886 * sin(torad(MmP));
/* Another correction term */
A4 = 0.214 * sin(torad(2 * MmP));
/* Corrected longitude */
lP = ml + Ev + mEc - Ae + A4;
/* Variation */
V = 0.6583 * sin(torad(2 * (lP - Lambdasun)));
/* True longitude */
lPP = lP + V;
/* Corrected longitude of the node */
NP = MN - 0.16 * sin(torad(M));
/* Y inclination coordinate */
y = sin(torad(lPP - NP)) * cos(torad(minc));
/* X inclination coordinate */
x = cos(torad(lPP - NP));
/* Ecliptic longitude */
Lambdamoon = todeg(atan2(y, x));
Lambdamoon += NP;
/* Ecliptic latitude */
BetaM = todeg(asin(sin(torad(lPP - NP)) * sin(torad(minc))));
/* Calculation of the phase of the Moon */
/* Age of the Moon in degrees */
MoonAge = lPP - Lambdasun;
/* Phase of the Moon */
MoonPhase = (1 - cos(torad(MoonAge))) / 2;
/* Calculate distance of moon from the centre of the Earth */
MoonDist = (msmax * (1 - mecc * mecc)) /
(1 + mecc * cos(torad(MmP + mEc)));
/* Calculate Moon's angular diameter */
MoonDFrac = MoonDist / msmax;
MoonAng = mangsiz / MoonDFrac;
/* Calculate Moon's parallax */
MoonPar = mparallax / MoonDFrac;
*pphase = MoonPhase;
*mage = synmonth * (fixangle(MoonAge) / 360.0);
*dist = MoonDist;
*angdia = MoonAng;
*sudist = SunDist;
*suangdia = SunAng;
return fixangle(MoonAge) / 360.0;
}

View file

@ -1,46 +0,0 @@
#ifndef _ASTRO_H_
#define _ASTRO_H_
/*
* UNIX_TO_JULIAN -- Convert internal Unix date/time to astronomical
* Julian time (i.e. Julian date plus day fraction, expressed as
* a double).
*/
double unix_to_julian( time_t t );
/*
* PHASEHUNT5 -- Find time of phases of the moon which surround
* the current date. Five phases are found, starting
* and ending with the new moons which bound the
* current lunation.
*/
void phasehunt5( double sdate, double phases[5] );
/*
* PHASEHUNT2 -- Find time of phases of the moon which surround
* the current date. Two phases are found.
*/
void phasehunt2( double sdate, double phases[2], double which[2] );
/*
* PHASE -- Calculate phase of moon as a fraction:
*
* The argument is the time for which the phase is requested,
* expressed as a Julian date and fraction. Returns the terminator
* phase angle as a percentage of a full circle (i.e., 0 to 1),
* and stores into pointer arguments the illuminated fraction of
* the Moon's disc, the Moon's age in days and fraction, the
* distance of the Moon from the centre of the Earth, and the
* angular diameter subtended by the Moon as seen by an observer
* at the centre of the Earth.
*
* pphase: Illuminated fraction
* mage: Age of moon in days
* dist: Distance in kilometres
* angdia: Angular diameter in degrees
* sudist: Distance to Sun
* suangdia: Sun's angular diameter
*/
double phase( double pdate, double* pphase, double* mage, double* dist, double* angdia, double* sudist, double* suangdia );
#endif /* _ASTRO_H_ */

File diff suppressed because it is too large Load diff

View file

@ -1,33 +0,0 @@
/* date_parse.h - parse string dates into internal form
**
** Copyright © 1995 by Jef Poskanzer <jef@mail.acme.com>.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
*/
#ifndef _DATE_PARSE_H_
#define _DATE_PARSE_H_
time_t date_parse( char* str );
#endif /* _DATE_PARSE_H_ */

View file

@ -1,23 +0,0 @@
.TH phoon 1 "07 June 1988"
.SH NAME
phoon - show the PHase of the mOON
.SH SYNOPSIS
phoon
.RB -l
.IR lines ]
.RI [ date ]
.SH DESCRIPTION
.I Phoon
displays the phase of the moon, either currently
or at a specified date / time.
Unlike other such programs, which just tell you how long since first quarter
or something like that, phoon
.I shows
you the phase with a cute little picture.
You can vary the size of the picture with the -l flag, but only some
sizes have pictures defined - other sizes use @'s.
.SH "SEE ALSO"
date_parse(3)
.SH AUTHOR
Jef Poskanzer.
The moon-phase computation is from "moontool.c", by John Walker.

View file

@ -1,575 +0,0 @@
/* phoon - show the phase of the moon
**
** ver date who remarks
** --- ------- --- -------------------------------------------------------------
** 03A 01apr95 JP Updated to use date_parse.
** 02A 07jun88 JP Changed the phase calculation to use code from John Walker's
** "moontool", increasing accuracy tremendously.
** Got rid of SINFORCOS, since Apple has probably fixed A/UX
** by now.
** 01I 03feb88 JP Added 32 lines.
** 01H 14jan88 JP Added 22 lines.
** 01G 05dec87 JP Added random sabotage to generate Hubert.
** Defeated text stuff for moons 28 or larger.
** 01F 13oct87 JP Added pumpkin19 in October. Added hubert29.
** 01E 14may87 JP Added #ifdef SINFORCOS to handle broken A/UX library.
** 01D 02apr87 JP Added 21 lines.
** 01C 26jan87 JP Added backgrounds for 29 and 18 lines.
** 01B 28dec86 JP Added -l flag, and backgrounds for 19 and 24 lines.
** 01A 08nov86 JP Translated from the ratfor version of 12nov85, which itself
** was translated from the Pascal version of 05apr79.
**
** Copyright (C) 1986,1987,1988,1995 by Jef Poskanzer <jef@mail.acme.com>.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <string.h>
#include "date_parse.h"
#include "astro.h"
/* Global defines and declarations. */
#define SECSPERMINUTE 60
#define SECSPERHOUR (60 * SECSPERMINUTE)
#define SECSPERDAY (24 * SECSPERHOUR)
#define PI 3.1415926535897932384626433
#define DEFAULTNUMLINES 23
#define QUARTERLITLEN 16
#define QUARTERLITLENPLUSONE 17
/* If you change the aspect ratio, the canned backgrounds won't work. */
#define ASPECTRATIO 0.5
static void
putseconds( long secs )
{
long days, hours, minutes;
days = secs / SECSPERDAY;
secs = secs - days * SECSPERDAY;
hours = secs / SECSPERHOUR;
secs = secs - hours * SECSPERHOUR;
minutes = secs / SECSPERMINUTE;
secs = secs - minutes * SECSPERMINUTE;
printf( "%ld %2ld:%02ld:%02ld", days, hours, minutes, secs );
}
static void
putmoon( time_t t, int numlines, char* atfiller )
{
static char background18[18][37] = {
" .----------. ",
" .--' o . `--. ",
" .'@ @@@@@@ O . . `. ",
" .'@@ @@@@@@@@ @@@@ . `. ",
" .' . @@@@@@@@ @@@@@@ . `. ",
" / @@ o @@@@@@. @@@@ O @\\ ",
" |@@@@ @@@@@@ @@| ",
" / @@@@@ `.-. . @@@@@@@@ . @@\\",
" | @@@@ --`-' . o @@@@@@@ |",
" |@ @@ @@@@@@ @@@ |",
" \\ @@ @ . () @@ @@@@@ /",
" | @ @@@ @@@ @@@ | ",
" \\ . @@ @\\ . . @@ o / ",
" `. @@@@ _\\ / . o .' ",
" `. @@ ()--- .' ",
" `. / | . o .' ",
" `--./ . .--' ",
" `----------' "};
static char background19[19][39] = {
" .----------. ",
" .--' o . `--. ",
" .-'@ @@@@@@ O . . `-. ",
" .' @@ @@@@@@@@ @@@@ . `. ",
" / . @@@@@@@@ @@@@@@ . \\ ",
" /@@ o @@@@@@. @@@@ O @\\ ",
" /@@@@ @@@@@@ @@@\\ ",
" . @@@@@ `.-./ . @@@@@@@@ . @@ .",
" | @@@@ --`-' . @@@@@@@ |",
" |@ @@ ` o @@@@@@ @@@@ |",
" | @@ o @@ @@@@@@ |",
" ` . @ @@ () @@@ @@@@ '",
" \\ @@ @@@@ . @@ . o / ",
" \\ @@@@ @@\\ . o / ",
" \\ . @@ _\\ / . .-. / ",
" `. . ()--- `-' .' ",
" `-. ./ | . o .-' ",
" `--./ . .--' ",
" `----------' "};
static char pumpkin19[19][39] = {
" @@@@@@@@@@@@ ",
" @@@@@@@@@@@@@@@@@@@@ ",
" @@@@@@@@@@@@@@@@@@@@@@@@@@ ",
" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ",
" @@@@ @@@@@@@@ @@@@ ",
" @@@@@@ @@@@@@@@@@ @@@@@@ ",
" @@@@@@@@ @@@@@@@@@@@@ @@@@@@@@ ",
" @@@@@@@@@@ @@@@@@ @@@@@@ @@@@@@@@@@",
" @@@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@@",
" @@@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@@",
" @@@@@@@@@@@@@@@ @@@@@@@@@@@@@@@",
" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ ",
" @@@@@ @@ @@@@@ ",
" @@@@@@ @@@@@@ ",
" @@@@@@@@ @@@@@@@@ ",
" @@@@@@@@@ @@@@@@@@@ ",
" @@@@@@@@@@@@@@@@@@@@ ",
" @@@@@@@@@@@@ "};
static char background21[21][43] = {
" .----------. ",
" .---' O . . `---. ",
" .-'@ @@@@@@ . @@@@@ `-. ",
" .'@@ @@@@@@@@@ @@@@@@@ . `. ",
" / o @@@@@@@@@ @@@@@@@ . \\ ",
" /@ o @@@@@@@@@. @@@@@@@ O \\ ",
" /@@@ . @@@@@@o @@@@@@@@@@ @@\\ ",
" /@@@@@ . @@@@@@@@@@@@@ o @@@\\ ",
" .@@@@@ O `.-./ . @@@@@@@@@@@@ @@ .",
" | @@@@ --`-' o @@@@@@@@ @@@@ |",
" |@ @@@ ` o . @@ . @@@@@@@ |",
" | @@ @ .-. @@@ @@@@@@@ |",
" ` . @ @@@ `-' . @@@@ @@@@ o '",
" \\ @@ @@@@@ . @@ . / ",
" \\ @@@@ @\\@@ / . O . o . / ",
" \\o @@ \\ \\ / . . / ",
" \\ . .\\.-.___ . . .-. / ",
" `. `-' `-'.' ",
" `-. o / | o O . .-' ",
" `---. . . .---' ",
" `----------' "};
static char background22[22][45] = {
" .------------. ",
" .--' o . . `--. ",
" .-' . O . . `-. ",
" .'@ @@@@@@@ . @@@@@ `. ",
" .'@@@ @@@@@@@@@@@ @@@@@@@ . `. ",
" / o @@@@@@@@@@@ @@@@@@@ . \\ ",
" /@@ o @@@@@@@@@@@. @@@@@@@ O \\ ",
" /@@@@ . @@@@@@@o @@@@@@@@@@ @@@\\ ",
" |@@@@@ . @@@@@@@@@@@@ @@@@| ",
" /@@@@@ O `.-./ . @@@@@@@@@@@ @@ \\",
" | @@@@ --`-' o . @@@@@@@ @@@@ |",
" |@ @@@ @@ @ ` o .-. @@ . @@@@@@ |",
" \\ @@@ `-' . @@@ @@@@@@ /",
" | . @ @@ @@@@@ . @@@@ @@@ o | ",
" \\ @@@@ @\\@@ / . O @@ . . / ",
" \\ o @@ \\ \\ / . . o / ",
" \\ . .\\.-.___ . . . .-. / ",
" `. `-' `-' .' ",
" `. o / | o O . .' ",
" `-. / . . .-' ",
" `--. . .--' ",
" `------------' "};
static char background23[23][47] = {
" .------------. ",
" .--' o . . `--. ",
" .-' . O . . `-. ",
" .-'@ @@@@@@@ . @@@@@ `-. ",
" /@@@ @@@@@@@@@@@ @@@@@@@ . \\ ",
" ./ o @@@@@@@@@@@ @@@@@@@ . \\. ",
" /@@ o @@@@@@@@@@@. @@@@@@@ O \\ ",
" /@@@@ . @@@@@@@o @@@@@@@@@@ @@@ \\ ",
" |@@@@@ . @@@@@@@@@@@@@ o @@@@| ",
" /@@@@@ O `.-./ . @@@@@@@@@@@@ @@ \\",
" | @@@@ --`-' o @@@@@@@@ @@@@ |",
" |@ @@@ ` o . @@ . @@@@@@@ |",
" | @@ @ .-. @@@ @@@@@@@ |",
" \\ . @ @@@ `-' . @@@@ @@@@ o /",
" | @@ @@@@@ . @@ . | ",
" \\ @@@@ @\\@@ / . O . o . / ",
" \\ o @@ \\ \\ / . . / ",
" `\\ . .\\.-.___ . . .-. /' ",
" \\ `-' `-' / ",
" `-. o / | o O . .-' ",
" `-. / . . .-' ",
" `--. . .--' ",
" `------------' "};
static char background24[24][49] = {
" .------------. ",
" .---' o . . `---. ",
" .-' . O . . `-. ",
" .'@ @@@@@@@ . @@@@@ `. ",
" .'@@ @@@@@@@@@@@ @@@@@@@ . `. ",
" / o @@@@@@@@@@@ @@@@@@@ . \\ ",
" /@ o @@@@@@@@@@@. @@@@@@@ O \\ ",
" /@@@ . @@@@@@@o @@@@@@@@@@ @@@ \\ ",
" /@@@@@ . @@@@@@@@@@@@@ o @@@@ \\ ",
" |@@@@ O `.-./ . @@@@@@@@@@@@ @@ | ",
" / @@@@ --`-' o @@@@@@@@ @@@@ \\",
" |@ @@@ @ ` . @@ @@@@@@@ |",
" | @ o @ @@@@@@@ |",
" \\ @@ .-. @@@ @@@@ o /",
" | . @ @@@ `-' . @@@@ | ",
" \\ @@ @@@@@ . @@ . / ",
" \\ @@@@ @\\@@ / . O . o . / ",
" \\ o @@ \\ \\ / . . / ",
" \\ . .\\.-.___ . . .-. / ",
" `. `-' `-' .' ",
" `. o / | o O . .' ",
" `-. / . . .-' ",
" `---. . .---' ",
" `------------' "};
static char background29[29][59] = {
" .--------------. ",
" .---' o . `---. ",
" .-' . O . . `-. ",
" .-' @@@@@@ . `-. ",
" .'@@ @@@@@@@@@@@ @@@@@@@ . `. ",
" .'@@@ @@@@@@@@@@@@@@ @@@@@@@@@ `. ",
" /@@@ o @@@@@@@@@@@@@@ @@@@@@@@@ O \\ ",
" / @@@@@@@@@@@@@@ @ @@@@@@@@@ @@ . \\ ",
" /@ o @@@@@@@@@@@ . @@ @@@@@@@@@@@ @@ \\ ",
" /@@@ . @@@@@@ o @ @@@@@@@@@@@@@ o @@@@ \\ ",
" /@@@@@ @ . @@@@@@@@@@@@@@ @@@@@ \\ ",
" |@@@@@ O `.-./ . . @@@@@@@@@@@@@ @@@ | ",
" / @@@@@ --`-' o @@@@@@@@@@@ @@@ . \\",
" |@ @@@@ . @ @ ` @ @@ . @@@@@@ |",
" | @@ o @@ . @@@@@@ |",
" | . @ @ @ o @@ o @@@@@@. |",
" \\ @ @ @ .-. @@@@ @@@ /",
" | @ @ @ `-' . @@@@ . . | ",
" \\ . o @ @@@@ . @@ . . / ",
" \\ @@@ @@@@@@ . o / ",
" \\ @@@@@ @@\\@@ / O . / ",
" \\ o @@@ \\ \\ / __ . . .--. / ",
" \\ . . \\.-.--- `--' / ",
" `. `-' . .' ",
" `. o / | ` O . .' ",
" `-. / | o .-' ",
" `-. . . .-' ",
" `---. . .---' ",
" `--------------' "};
static char hubert29[29][59] = {
" .--------------. ",
" .---' o . `---. ",
" .-' . O . . `-. ",
" .-' @@@@@@ . `-. ",
" .'@@ @@@@@@@@@@@ @@@@@@@ . `. ",
" .'@@@ @@@@@ ___====-_ _-====___ @ `. ",
" /@@@ o _--~~~#####// \\\\#####~~~--_ O \\ ",
" / _-~##########// ( ) \\\\##########~-_ . \\ ",
" /@ o -############// :\\^^/: \\\\############- @@ \\ ",
" /@@@ _~############// (@::@) \\\\############~_ @@ \\ ",
" /@@@ ~#############(( \\\\// ))#############~ @@ \\ ",
" |@@ -###############\\\\ (oo) //###############- @ | ",
" / @ -#################\\\\ / \"\" \\ //#################- . \\",
" |@ -###################\\\\/ \\//###################- |",
" | _#/:##########/\\######( /\\ )######/\\##########:\\#_ |",
" | :/ :#/\\#/\\#/\\/ \\#/\\##\\ : : /##/\\#/ \\/\\#/\\#/\\#: \\: |",
" \\ \" :/ V V \" V \\#\\: : : :/#/ V \" V V \\: \" /",
" | @ \" \" \" \" / : : : : \\ \" \" \" \" | ",
" \\ . o @ @@@@ ( : : : : ) @@ . . / ",
" \\ @@@ @@@@ __\\ : : : : /__ o / ",
" \\ @@@@@ @@\\@(vvv(VVV)(VVV)vvv) . / ",
" \\ o @@@ \\ \\ / __ . . .--. / ",
" \\ . . \\.-.--- `--' / ",
" `. `-' . .' ",
" `. o / | ` O . .' ",
" `-. / | o .-' ",
" `-. . . .-' ",
" `---. . .---' ",
" `--------------' "};
static char background32[32][65] = {
" .--------------. ",
" .----' o . `----. ",
" .-' . O . . `-. ",
" .-' @@@@@@ . `-. ",
" .'@ @@@@@@@@@@@ @@@@@@@@ . `. ",
" .'@@ @@@@@@@@@@@@@@ @@@@@@@@@@ `. ",
" .'@@@ o @@@@@@@@@@@@@@ @@@@@@@@@@ o `. ",
" /@@@ @@@@@@@@@@@@@@ @ @@@@@@@@@@ @@ . \\ ",
" / @@@@@@@@@@@ . @@ @@@@@@@@@@@@ @@ \\ ",
" /@ o . @@@@@@ o @ @@@@@@@@@@@@@@ o @@@@ \\ ",
" /@@@ . @@@@@@@@@@@@@@@ @@@@@ \\ ",
" /@@@@@ @ . @@@@@@@@@@@@@@ @@@ \\ ",
" |@@@@@ o `.-./ . @@@@@@@@@@@@ @@@ . | ",
" / @@@@@ __`-' o @@ . @@@@@@ \\",
" |@ @@@@ . @ ` @ @@ . @@@@@@ |",
" | @@ @ o @@@ o @@@@@@. |",
" | @ @@@@@ @@@ |",
" | . . @ @ @ o @@@@@ . . |",
" \\ @ .-. . @@@ . . /",
" | @ @ @ @ `-' . / ",
" \\ . @ @ . o / ",
" \\ o @@@@ . . / ",
" \\ @@@ @@@@@@ . o / ",
" \\ @@@@@ @@\\@@ / o . / ",
" \\ o @@@ \\ \\ / ___ . . .--. / ",
" `. . \\.-.--- `--' .' ",
" `. `-' . .' ",
" `. o / | O . .' ",
" `-. / | o .-' ",
" `-. . . .-' ",
" `----. . .----' ",
" `--------------' "};
static char qlits[4][16] = {
"New Moon + ",
"First Quarter +",
"Full Moon + ",
"Last Quarter + ",
};
static char nqlits[4][16] = {
"New Moon - ",
"First Quarter -",
"Full Moon - ",
"Last Quarter - ",
};
struct tm* tmP;
double jd, pctphase, angphase, cphase, aom, cdist, cangdia, csund, csuang;
double phases[2], which[2];
long clocknow;
int atflrlen, atflridx, numcols, lin, col, midlin;
double mcap, yrad, xrad, y, xright, xleft;
int colright, colleft;
char c;
/* Find the length of the atfiller string. */
atflrlen = strlen( atfiller );
/* Figure out the phase. */
jd = unix_to_julian( t );
pctphase = phase( jd, &cphase, &aom, &cdist, &cangdia, &csund, &csuang );
angphase = pctphase * 2.0 * PI;
mcap = -cos( angphase );
/* Get now for use as a random number. */
(void) time( &clocknow );
/* Randomly cheat and generate Hubert. */
if ( clocknow % 13 == 3 && cphase > 0.8 )
{
numlines = 29;
clocknow = 3;
}
/* Figure out how big the moon is. */
yrad = numlines / 2.0;
xrad = yrad / ASPECTRATIO;
numcols = xrad * 2;
/* Figure out some other random stuff. */
midlin = numlines / 2;
phasehunt2( jd, phases, which );
/* Now output the moon, a slice at a time. */
atflridx = 0;
for ( lin = 0; lin < numlines; lin = lin + 1 )
{
/* Compute the edges of this slice. */
y = lin + 0.5 - yrad;
xright = xrad * sqrt( 1.0 - ( y * y ) / ( yrad * yrad ) );
xleft = -xright;
if ( angphase >= 0.0 && angphase < PI )
xleft = mcap * xleft;
else
xright = mcap * xright;
colleft = (int) (xrad + 0.5) + (int) (xleft + 0.5);
colright = (int) (xrad + 0.5) + (int) (xright + 0.5);
/* Now output the slice. */
for ( col = 0; col < colleft; ++col )
putchar( ' ' );
for ( ; col <= colright; ++col )
{
switch ( numlines )
{
case 18:
c = background18[lin][col];
break;
case 19:
tmP = localtime( &t );
if ( tmP->tm_mon == 9 &&
clocknow % ( 33 - tmP->tm_mday ) == 1 )
c = pumpkin19[lin][col];
else
c = background19[lin][col];
break;
case 21:
c = background21[lin][col];
break;
case 22:
c = background22[lin][col];
break;
case 23:
c = background23[lin][col];
break;
case 24:
c = background24[lin][col];
break;
case 29:
if ( clocknow % 23 == 3 )
c = hubert29[lin][col];
else
c = background29[lin][col];
break;
case 32:
c = background32[lin][col];
break;
default:
c = '@';
}
if ( c != '@' )
putchar( c );
else
{
putchar( atfiller[atflridx] );
atflridx = ( atflridx + 1 ) % atflrlen;
}
}
#ifdef notdef
for ( ; col <= numcols; ++col )
putchar( ' ' );
#endif /* notdef */
if ( numlines <= 27 )
{
/* Output the end-of-line information, if any. */
if ( lin == midlin - 2 )
{
fputs( "\t ", stdout );
fputs( qlits[(int) (which[0] * 4.0 + 0.001)], stdout );
}
else if ( lin == midlin - 1)
{
fputs( "\t ", stdout );
putseconds( (int) ( ( jd - phases[0] ) * SECSPERDAY ) );
}
else if ( lin == midlin )
{
fputs( "\t ", stdout );
fputs( nqlits[(int) (which[1] * 4.0 + 0.001)], stdout );
}
else if ( lin == midlin + 1 )
{
fputs( "\t ", stdout );
putseconds( (int) ( ( phases[1] - jd ) * SECSPERDAY ) );
}
}
putchar( '\n' );
}
}
/* Main program. */
int
main( int argc, char** argv )
{
time_t t;
char buf[100];
int numlines, argn;
char* usage = "usage: %s [-l <lines>] [<date/time>]\n";
/* Parge args. */
argn = 1;
/* Check for -l flag. */
numlines = DEFAULTNUMLINES;
if ( argc - argn >= 1 )
{
if ( argv[argn][0] == '-' )
{
if ( argv[argn][1] != 'l' || argv[argn][2] != '\0' )
{
fprintf( stderr, usage, argv[0] );
exit( 1 );
}
else
{
if ( argc - argn < 2 )
{
fprintf( stderr, usage, argv[0] );
exit( 1 );
}
if ( sscanf( argv[argn + 1], "%d", &numlines ) != 1 )
{
fprintf( stderr, usage, argv[0] );
exit( 1 );
}
argn += 2;
}
}
}
/* Figure out what date and time to use. */
if ( argc - argn == 0 )
{
/* No arguments present - use the current date and time. */
t = time( (time_t) 0 );
}
else if ( argc - argn == 1 || argc - argn == 2 || argc - argn == 3 )
{
/* One, two, or three args - use them. */
strcpy( buf, argv[argn] );
if ( argc - argn > 1 )
{
strcat( buf, " " );
strcat( buf, argv[argn + 1] );
if ( argc - argn > 2 )
{
strcat( buf, " " );
strcat( buf, argv[argn + 2] );
}
}
t = date_parse( buf );
if ( t <= 0 )
{
fprintf( stderr, "illegal date/time: %s\n", buf );
exit( 1 );
}
}
else
{
/* Too many args! */
fprintf( stderr, usage, argv[0] );
exit( 1 );
}
/* Pseudo-randomly decide what the moon is made of, and print it. */
if ( t % 17 == 3 )
putmoon( t, numlines, "GREENCHEESE" );
else
putmoon( t, numlines, "@" );
/* All done. */
exit( 0 );
}

View file

@ -1,522 +0,0 @@
/* +++Date last modified: 05-Jul-1997 */
/* Updated comments, 05-Aug-2013 */
/*
SUNRISET.C - computes Sun rise/set times, start/end of twilight, and
the length of the day at any date and latitude
Written as DAYLEN.C, 1989-08-16
Modified to SUNRISET.C, 1992-12-01
(c) Paul Schlyter, 1989, 1992
Released to the public domain by Paul Schlyter, December 1992
*/
#include <stdio.h>
#include <math.h>
/* A macro to compute the number of days elapsed since 2000 Jan 0.0 */
/* (which is equal to 1999 Dec 31, 0h UT) */
#define days_since_2000_Jan_0(y,m,d) \
(367L*(y)-((7*((y)+(((m)+9)/12)))/4)+((275*(m))/9)+(d)-730530L)
/* Some conversion factors between radians and degrees */
#ifndef PI
#define PI 3.1415926535897932384
#endif
#define RADEG ( 180.0 / PI )
#define DEGRAD ( PI / 180.0 )
/* The trigonometric functions in degrees */
#define sind(x) sin((x)*DEGRAD)
#define cosd(x) cos((x)*DEGRAD)
#define tand(x) tan((x)*DEGRAD)
#define atand(x) (RADEG*atan(x))
#define asind(x) (RADEG*asin(x))
#define acosd(x) (RADEG*acos(x))
#define atan2d(y,x) (RADEG*atan2(y,x))
/* Following are some macros around the "workhorse" function __daylen__ */
/* They mainly fill in the desired values for the reference altitude */
/* below the horizon, and also selects whether this altitude should */
/* refer to the Sun's center or its upper limb. */
/* 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). */
#define day_length(year,month,day,lon,lat) \
__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. */
#define day_civil_twilight_length(year,month,day,lon,lat) \
__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. */
#define day_nautical_twilight_length(year,month,day,lon,lat) \
__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. */
#define day_astronomical_twilight_length(year,month,day,lon,lat) \
__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). */
#define sun_rise_set(year,month,day,lon,lat,rise,set) \
__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. */
#define civil_twilight(year,month,day,lon,lat,start,end) \
__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. */
#define nautical_twilight(year,month,day,lon,lat,start,end) \
__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. */
#define astronomical_twilight(year,month,day,lon,lat,start,end) \
__sunriset__( year, month, day, lon, lat, -18.0, 0, start, end )
/* Function prototypes */
double __daylen__( int year, int month, int day, double lon, double lat,
double altit, int upper_limb );
int __sunriset__( int year, int month, int day, double lon, double lat,
double altit, int upper_limb, double *rise, double *set );
void sunpos( double d, double *lon, double *r );
void sun_RA_dec( double d, double *RA, double *dec, double *r );
double revolution( double x );
double rev180( double x );
double GMST0( double d );
/* A small test program */
#ifdef SUNRISET_NO_MAIN
int sunriset_test_main(void) { return 0; }
#else
int main(void)
{
int year,month,day;
double lon, lat;
double daylen, civlen, nautlen, astrlen;
double rise, set, civ_start, civ_end, naut_start, naut_end,
astr_start, astr_end;
int rs, civ, naut, astr;
char buf[80];
printf( "Longitude (+ is east) and latitude (+ is north) : " );
fgets(buf, 80, stdin);
sscanf(buf, "%lf %lf", &lon, &lat );
for(;;)
{
printf( "Input date ( yyyy mm dd ) (ctrl-C exits): " );
fgets(buf, 80, stdin);
sscanf(buf, "%d %d %d", &year, &month, &day );
daylen = day_length(year,month,day,lon,lat);
civlen = day_civil_twilight_length(year,month,day,lon,lat);
nautlen = day_nautical_twilight_length(year,month,day,lon,lat);
astrlen = day_astronomical_twilight_length(year,month,day,
lon,lat);
printf( "Day length: %5.2f hours\n", daylen );
printf( "With civil twilight %5.2f hours\n", civlen );
printf( "With nautical twilight %5.2f hours\n", nautlen );
printf( "With astronomical twilight %5.2f hours\n", astrlen );
printf( "Length of twilight: civil %5.2f hours\n",
(civlen-daylen)/2.0);
printf( " nautical %5.2f hours\n",
(nautlen-daylen)/2.0);
printf( " astronomical %5.2f hours\n",
(astrlen-daylen)/2.0);
rs = sun_rise_set ( year, month, day, lon, lat,
&rise, &set );
civ = civil_twilight ( year, month, day, lon, lat,
&civ_start, &civ_end );
naut = nautical_twilight ( year, month, day, lon, lat,
&naut_start, &naut_end );
astr = astronomical_twilight( year, month, day, lon, lat,
&astr_start, &astr_end );
printf( "Sun at south %5.2fh UT\n", (rise+set)/2.0 );
switch( rs )
{
case 0:
printf( "Sun rises %5.2fh UT, sets %5.2fh UT\n",
rise, set );
break;
case +1:
printf( "Sun above horizon\n" );
break;
case -1:
printf( "Sun below horizon\n" );
break;
}
switch( civ )
{
case 0:
printf( "Civil twilight starts %5.2fh, "
"ends %5.2fh UT\n", civ_start, civ_end );
break;
case +1:
printf( "Never darker than civil twilight\n" );
break;
case -1:
printf( "Never as bright as civil twilight\n" );
break;
}
switch( naut )
{
case 0:
printf( "Nautical twilight starts %5.2fh, "
"ends %5.2fh UT\n", naut_start, naut_end );
break;
case +1:
printf( "Never darker than nautical twilight\n" );
break;
case -1:
printf( "Never as bright as nautical twilight\n" );
break;
}
switch( astr )
{
case 0:
printf( "Astronomical twilight starts %5.2fh, "
"ends %5.2fh UT\n", astr_start, astr_end );
break;
case +1:
printf( "Never darker than astronomical twilight\n" );
break;
case -1:
printf( "Never as bright as astronomical twilight\n" );
break;
}
return 0;
}
}
#endif
/* The "workhorse" function for sun rise/set times */
int __sunriset__( int year, int month, int day, double lon, double lat,
double altit, int upper_limb, double *trise, double *tset )
/***************************************************************************/
/* Note: year,month,date = calendar date, 1801-2099 only. */
/* 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. */
/* */
/**********************************************************************/
{
double d, /* Days since 2000 Jan 0.0 (negative before) */
sr, /* Solar distance, astronomical units */
sRA, /* Sun's Right Ascension */
sdec, /* Sun's declination */
sradius, /* Sun's apparent radius */
t, /* Diurnal arc */
tsouth, /* Time when Sun is at south */
sidtime; /* Local sidereal time */
int rc = 0; /* Return cde from function - usually 0 */
/* Compute d of 12h local mean solar time */
d = days_since_2000_Jan_0(year,month,day) + 0.5 - lon/360.0;
/* Compute the local sidereal time of this moment */
sidtime = revolution( GMST0(d) + 180.0 + lon );
/* Compute Sun's RA, Decl and distance at this moment */
sun_RA_dec( d, &sRA, &sdec, &sr );
/* Compute time when Sun is at south - in hours UT */
tsouth = 12.0 - rev180(sidtime - sRA)/15.0;
/* Compute the Sun's apparent radius in degrees */
sradius = 0.2666 / sr;
/* Do correction to upper limb, if necessary */
if ( upper_limb )
altit -= sradius;
/* Compute the diurnal arc that the Sun traverses to reach */
/* the specified altitude altit: */
{
double cost;
cost = ( sind(altit) - sind(lat) * sind(sdec) ) /
( cosd(lat) * cosd(sdec) );
if ( cost >= 1.0 )
rc = -1, t = 0.0; /* Sun always below altit */
else if ( cost <= -1.0 )
rc = +1, t = 12.0; /* Sun always above altit */
else
t = acosd(cost)/15.0; /* The diurnal arc, hours */
}
/* Store rise and set times - in hours UT */
*trise = tsouth - t;
*tset = tsouth + t;
return rc;
} /* __sunriset__ */
/* The "workhorse" function */
double __daylen__( int year, int month, int day, double lon, double lat,
double altit, int upper_limb )
/**********************************************************************/
/* Note: year,month,date = calendar date, 1801-2099 only. */
/* Eastern longitude positive, Western longitude negative */
/* Northern latitude positive, Southern latitude negative */
/* The longitude value is not critical. Set it to the correct */
/* longitude if you're picky, otherwise set to to, say, 0.0 */
/* The latitude however IS critical - be sure to get it correct */
/* 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 day length */
/* and to zero when computing day+twilight length. */
/**********************************************************************/
{
double d, /* Days since 2000 Jan 0.0 (negative before) */
obl_ecl, /* Obliquity (inclination) of Earth's axis */
sr, /* Solar distance, astronomical units */
slon, /* True solar longitude */
sin_sdecl, /* Sine of Sun's declination */
cos_sdecl, /* Cosine of Sun's declination */
sradius, /* Sun's apparent radius */
t; /* Diurnal arc */
/* Compute d of 12h local mean solar time */
d = days_since_2000_Jan_0(year,month,day) + 0.5 - lon/360.0;
/* Compute obliquity of ecliptic (inclination of Earth's axis) */
obl_ecl = 23.4393 - 3.563E-7 * d;
/* Compute Sun's ecliptic longitude and distance */
sunpos( d, &slon, &sr );
/* Compute sine and cosine of Sun's declination */
sin_sdecl = sind(obl_ecl) * sind(slon);
cos_sdecl = sqrt( 1.0 - sin_sdecl * sin_sdecl );
/* Compute the Sun's apparent radius, degrees */
sradius = 0.2666 / sr;
/* Do correction to upper limb, if necessary */
if ( upper_limb )
altit -= sradius;
/* Compute the diurnal arc that the Sun traverses to reach */
/* the specified altitude altit: */
{
double cost;
cost = ( sind(altit) - sind(lat) * sin_sdecl ) /
( cosd(lat) * cos_sdecl );
if ( cost >= 1.0 )
t = 0.0; /* Sun always below altit */
else if ( cost <= -1.0 )
t = 24.0; /* Sun always above altit */
else t = (2.0/15.0) * acosd(cost); /* The diurnal arc, hours */
}
return t;
} /* __daylen__ */
/* This function computes the Sun's position at any instant */
void sunpos( double d, double *lon, double *r )
/******************************************************/
/* Computes the Sun's ecliptic longitude and distance */
/* at an instant given in d, number of days since */
/* 2000 Jan 0.0. The Sun's ecliptic latitude is not */
/* computed, since it's always very near 0. */
/******************************************************/
{
double M, /* Mean anomaly of the Sun */
w, /* Mean longitude of perihelion */
/* Note: Sun's mean longitude = M + w */
e, /* Eccentricity of Earth's orbit */
E, /* Eccentric anomaly */
x, y, /* x, y coordinates in orbit */
v; /* True anomaly */
/* Compute mean elements */
M = revolution( 356.0470 + 0.9856002585 * d );
w = 282.9404 + 4.70935E-5 * d;
e = 0.016709 - 1.151E-9 * d;
/* Compute true longitude and radius vector */
E = M + e * RADEG * sind(M) * ( 1.0 + e * cosd(M) );
x = cosd(E) - e;
y = sqrt( 1.0 - e*e ) * sind(E);
*r = sqrt( x*x + y*y ); /* Solar distance */
v = atan2d( y, x ); /* True anomaly */
*lon = v + w; /* True solar longitude */
if ( *lon >= 360.0 )
*lon -= 360.0; /* Make it 0..360 degrees */
}
void sun_RA_dec( double d, double *RA, double *dec, double *r )
/******************************************************/
/* Computes the Sun's equatorial coordinates RA, Decl */
/* and also its distance, at an instant given in d, */
/* the number of days since 2000 Jan 0.0. */
/******************************************************/
{
double lon, obl_ecl, x, y, z;
/* Compute Sun's ecliptical coordinates */
sunpos( d, &lon, r );
/* Compute ecliptic rectangular coordinates (z=0) */
x = *r * cosd(lon);
y = *r * sind(lon);
/* Compute obliquity of ecliptic (inclination of Earth's axis) */
obl_ecl = 23.4393 - 3.563E-7 * d;
/* Convert to equatorial rectangular coordinates - x is unchanged */
z = y * sind(obl_ecl);
y = y * cosd(obl_ecl);
/* Convert to spherical coordinates */
*RA = atan2d( y, x );
*dec = atan2d( z, sqrt(x*x + y*y) );
} /* sun_RA_dec */
/******************************************************************/
/* This function reduces any angle to within the first revolution */
/* by subtracting or adding even multiples of 360.0 until the */
/* result is >= 0.0 and < 360.0 */
/******************************************************************/
#define INV360 ( 1.0 / 360.0 )
double revolution( double x )
/*****************************************/
/* Reduce angle to within 0..360 degrees */
/*****************************************/
{
return( x - 360.0 * floor( x * INV360 ) );
} /* revolution */
double rev180( double x )
/*********************************************/
/* Reduce angle to within +180..+180 degrees */
/*********************************************/
{
return( x - 360.0 * floor( x * INV360 + 0.5 ) );
} /* revolution */
/*******************************************************************/
/* This function computes GMST0, the Greenwich Mean Sidereal Time */
/* at 0h UT (i.e. the sidereal time at the Greenwhich meridian at */
/* 0h UT). GMST is then the sidereal time at Greenwich at any */
/* time of the day. I've generalized GMST0 as well, and define it */
/* as: GMST0 = GMST - UT -- this allows GMST0 to be computed at */
/* other times than 0h UT as well. While this sounds somewhat */
/* contradictory, it is very practical: instead of computing */
/* GMST like: */
/* */
/* GMST = (GMST0) + UT * (366.2422/365.2422) */
/* */
/* where (GMST0) is the GMST last time UT was 0 hours, one simply */
/* computes: */
/* */
/* GMST = GMST0 + UT */
/* */
/* where GMST0 is the GMST "at 0h UT" but at the current moment! */
/* Defined in this way, GMST0 will increase with about 4 min a */
/* day. It also happens that GMST0 (in degrees, 1 hr = 15 degr) */
/* is equal to the Sun's mean longitude plus/minus 180 degrees! */
/* (if we neglect aberration, which amounts to 20 seconds of arc */
/* or 1.33 seconds of time) */
/* */
/*******************************************************************/
double GMST0( double d )
{
double sidtim0;
/* Sidtime at 0h UT = L (Sun's mean longitude) + 180.0 degr */
/* L = M + w, as defined in sunpos(). Since I'm too lazy to */
/* add these numbers, I'll let the C compiler do it for me. */
/* Any decent C compiler will add the constants at compile */
/* time, imposing no runtime or code overhead. */
sidtim0 = revolution( ( 180.0 + 356.0470 + 282.9404 ) +
( 0.9856002585 + 4.70935E-5 ) * d );
return sidtim0;
} /* GMST0 */

View file

@ -1,280 +0,0 @@
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);
}

View file

@ -1,71 +0,0 @@
const std = @import("std");
const c = @cImport({
@cInclude("time.h");
@cInclude("astro.h");
});
pub const Phase = struct {
phase: f64, // 0.0 to 1.0 (0=new, 0.25=first quarter, 0.5=full, 0.75=last quarter)
illuminated: f64, // 0.0 to 1.0
age_days: f64, // Age in days
distance_km: f64,
angular_diameter: f64,
pub fn emoji(self: Phase) []const u8 {
const p = self.phase;
if (p < 0.0625) return "🌑"; // New moon
if (p < 0.1875) return "🌒"; // Waxing crescent
if (p < 0.3125) return "🌓"; // First quarter
if (p < 0.4375) return "🌔"; // Waxing gibbous
if (p < 0.5625) return "🌕"; // Full moon
if (p < 0.6875) return "🌖"; // Waning gibbous
if (p < 0.8125) return "🌗"; // Last quarter
if (p < 0.9375) return "🌘"; // Waning crescent
return "🌑"; // New moon
}
pub fn day(self: Phase) u8 {
return @intFromFloat(@round(self.age_days));
}
};
pub fn getPhase(timestamp: i64) Phase {
const julian = c.unix_to_julian(@intCast(timestamp));
var illuminated: f64 = 0;
var age_days: f64 = 0;
var distance: f64 = 0;
var angular_diameter: f64 = 0;
var sun_distance: f64 = 0;
var sun_angular_diameter: f64 = 0;
const phase_angle = c.phase(
julian,
&illuminated,
&age_days,
&distance,
&angular_diameter,
&sun_distance,
&sun_angular_diameter,
);
return .{
.phase = phase_angle,
.illuminated = illuminated,
.age_days = age_days,
.distance_km = distance,
.angular_diameter = angular_diameter,
};
}
test "moon phase calculation" {
// Test a known date: 2024-01-01 00:00:00 UTC
const timestamp: i64 = 1704067200;
const moon = getPhase(timestamp);
try std.testing.expect(moon.phase >= 0.0 and moon.phase <= 1.0);
try std.testing.expect(moon.illuminated >= 0.0 and moon.illuminated <= 1.0);
try std.testing.expect(moon.age_days >= 0.0 and moon.age_days <= 29.53);
try std.testing.expect(moon.distance_km > 0);
}

View file

@ -69,37 +69,16 @@ pub fn init(
}
fn handleWeather(ctx: *Context, req: *httpz.Request, res: *httpz.Response) !void {
var client_ip_buf: [47]u8 = undefined;
const client_ip = try getClientIp(req, &client_ip_buf);
try rateLimitMiddleware(ctx.rate_limiter, client_ip, res);
try rateLimitMiddleware(ctx.rate_limiter, req, res);
if (res.status == 429) return;
try handler.handleWeather(&ctx.options, req, res, client_ip);
try handler.handleWeather(&ctx.options, req, res);
}
fn getClientIp(req: *httpz.Request, buf: []u8) ![]const u8 {
// Check X-Forwarded-For header first (for proxies)
if (req.header("x-forwarded-for")) |xff| {
return parseXForwardedFor(xff);
}
fn rateLimitMiddleware(limiter: *RateLimiter, req: *httpz.Request, res: *httpz.Response) !void {
var ip_buf: [45]u8 = undefined;
const ip_str = try std.fmt.bufPrint(&ip_buf, "{f}", .{req.address});
// Check X-Real-IP header
if (req.header("x-real-ip")) |real_ip| {
return real_ip;
}
// Fall back to connection address
const req_addr = try std.fmt.bufPrint(buf, "{f}", .{req.address});
return req_addr[0..std.mem.lastIndexOfScalar(u8, req_addr, ':').?];
}
fn parseXForwardedFor(xff: []const u8) []const u8 {
// Take first IP from comma-separated list
var iter = std.mem.splitScalar(u8, xff, ',');
return std.mem.trim(u8, iter.first(), " \t");
}
fn rateLimitMiddleware(limiter: *RateLimiter, client_ip: []const u8, res: *httpz.Response) !void {
if (!limiter.shouldAcceptRequest(client_ip)) {
if (!limiter.shouldAcceptRequest(ip_str)) {
res.status = 429;
res.body = "Too Many Requests";
}
@ -236,9 +215,7 @@ test "handleWeather: default endpoint uses IP address" {
ht.url("/");
ht.header("x-forwarded-for", "73.158.64.1");
var client_ip_buf: [47]u8 = undefined;
const client_ip = try getClientIp(ht.req, &client_ip_buf);
try handler.handleWeather(&harness.opts, ht.req, ht.res, client_ip);
try handler.handleWeather(&harness.opts, ht.req, ht.res);
try ht.expectStatus(200);
}
@ -254,10 +231,7 @@ test "handleWeather: x-forwarded-for with multiple IPs" {
ht.url("/");
ht.header("x-forwarded-for", "73.158.64.1, 8.8.8.8, 192.168.1.1");
var client_ip_buf: [47]u8 = undefined;
const client_ip = try getClientIp(ht.req, &client_ip_buf);
try std.testing.expectEqualStrings("73.158.64.1", client_ip);
try handler.handleWeather(&harness.opts, ht.req, ht.res, client_ip);
try handler.handleWeather(&harness.opts, ht.req, ht.res);
try ht.expectStatus(200);
}
@ -271,29 +245,11 @@ test "handleWeather: client IP only" {
defer ht.deinit();
// Set connection address to a valid IP that will be in GeoIP database
ht.req.address = try std.net.Address.parseIp("73.158.64.1", 0);
ht.conn.address = try std.net.Address.parseIp("73.158.64.1", 0);
ht.url("/");
var client_ip_buf: [47]u8 = undefined;
const client_ip = try getClientIp(ht.req, &client_ip_buf);
try std.testing.expectEqualStrings("73.158.64.1", client_ip);
try handler.handleWeather(&harness.opts, ht.req, ht.res, client_ip);
try handler.handleWeather(&harness.opts, ht.req, ht.res);
try ht.expectStatus(200);
}
test "parseXForwardedFor extracts first IP" {
try std.testing.expectEqualStrings("192.168.1.1", parseXForwardedFor("192.168.1.1"));
try std.testing.expectEqualStrings("10.0.0.1", parseXForwardedFor("10.0.0.1, 172.16.0.1"));
try std.testing.expectEqualStrings("203.0.113.1", parseXForwardedFor("203.0.113.1, 198.51.100.1, 192.0.2.1"));
}
test "parseXForwardedFor trims whitespace" {
try std.testing.expectEqualStrings("192.168.1.1", parseXForwardedFor(" 192.168.1.1 "));
try std.testing.expectEqualStrings("10.0.0.1", parseXForwardedFor(" 10.0.0.1 , 172.16.0.1"));
}
test "parseXForwardedFor handles empty string" {
try std.testing.expectEqualStrings("", parseXForwardedFor(""));
}

View file

@ -22,8 +22,18 @@ pub fn handleWeather(
opts: *HandleWeatherOptions,
req: *httpz.Request,
res: *httpz.Response,
client_ip: []const u8,
) !void {
var client_ip_buf: [47]u8 = undefined;
// We need IP possibly for location, and possibly to determine implicit Imperial/Metric
// Let's get it once here
const client_ip = blk: {
const client_ip = getClientIpFromHeaders(req);
if (client_ip.len == 0) {
const full_location = try std.fmt.bufPrint(&client_ip_buf, "{f}", .{req.conn.address});
break :blk full_location[0..std.mem.lastIndexOf(u8, full_location, ":").?];
}
break :blk client_ip;
};
// Get location from path parameter or query string
const location = req.param("location") orelse blk: {
// Check query string for location parameter
@ -65,6 +75,30 @@ pub fn handleWeather(
try handleWeatherInternal(opts, req, res, location, client_ip);
}
fn getClientIpFromHeaders(req: *httpz.Request) []const u8 {
// Check X-Forwarded-For header first (for proxies)
if (req.header("x-forwarded-for")) |xff| {
return parseXForwardedFor(xff);
}
// Check X-Real-IP header
if (req.header("x-real-ip")) |real_ip| {
return real_ip;
}
// Fall back to client connection
return "";
}
fn parseXForwardedFor(xff: []const u8) []const u8 {
// Take first IP from comma-separated list
var iter = std.mem.splitScalar(u8, xff, ',');
if (iter.next()) |first_ip| {
return std.mem.trim(u8, first_ip, " \t");
}
return "";
}
fn handleWeatherInternal(
opts: *HandleWeatherOptions,
req: *httpz.Request,
@ -195,6 +229,21 @@ fn determineFormat(params: QueryParams, user_agent: ?[]const u8) Formatted.Forma
return .html;
}
test "parseXForwardedFor extracts first IP" {
try std.testing.expectEqualStrings("192.168.1.1", parseXForwardedFor("192.168.1.1"));
try std.testing.expectEqualStrings("10.0.0.1", parseXForwardedFor("10.0.0.1, 172.16.0.1"));
try std.testing.expectEqualStrings("203.0.113.1", parseXForwardedFor("203.0.113.1, 198.51.100.1, 192.0.2.1"));
}
test "parseXForwardedFor trims whitespace" {
try std.testing.expectEqualStrings("192.168.1.1", parseXForwardedFor(" 192.168.1.1 "));
try std.testing.expectEqualStrings("10.0.0.1", parseXForwardedFor(" 10.0.0.1 , 172.16.0.1"));
}
test "parseXForwardedFor handles empty string" {
try std.testing.expectEqualStrings("", parseXForwardedFor(""));
}
test "imperial units selection logic" {
// This test documents the priority order for unit selection:
// 1. Explicit ?u or ?m parameter (highest priority)

View file

@ -64,16 +64,16 @@ pub const help_page =
\\ %f # temperature (feels like)
\\ %w # wind
\\ %l # location
\\ %m # moon phase emoji
\\ %M # moon day
\\ %m # * moon phase emoji
\\ %M # * moon day
\\ %p # precipitation (mm)
\\ %o # probability of precipitation
\\ %P # pressure (hPa)
\\ %D # dawn time
\\ %S # sunrise time
\\ %z # zenith time
\\ %s # sunset time
\\ %d # dusk time
\\ %D # * dawn time
\\ %S # * sunrise time
\\ %z # * zenith time
\\ %s # * sunset time
\\ %d # * dusk time
\\
\\PNG options:
\\

View file

@ -1,12 +1,7 @@
const std = @import("std");
const zeit = @import("zeit");
const types = @import("../weather/types.zig");
const emoji = @import("emoji.zig");
const utils = @import("utils.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 {
var output: std.ArrayList(u8) = .empty;
@ -57,38 +52,14 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format:
const unit = if (use_imperial) "inHg" else "hPa";
try writer.print("{d:.2} {s}", .{ pressure, unit });
},
'm' => {
const now = try nowAt(weather.coords);
const moon = Moon.getPhase(now);
try writer.print("{s}", .{moon.emoji()});
},
'M' => {
const now = try nowAt(weather.coords);
const moon = Moon.getPhase(now);
try writer.print("{d}", .{moon.day()});
},
'm' => try writer.print("🌕", .{}), // Moon phase placeholder
'M' => try writer.print("15", .{}), // Moon day placeholder
'o' => try writer.print("0%", .{}), // Probability of precipitation placeholder
'D', 'S', 'z', 's', 'd' => {
// Again...we only need approximate, because we simply need
// to make sure the day is correct for this. Even a day off
// should actually be ok. Unix timestamp is always UTC,
// 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)});
},
'D' => try writer.print("06:00", .{}), // Dawn placeholder
'S' => try writer.print("07:30", .{}), // Sunrise placeholder
'z' => try writer.print("12:00", .{}), // Zenith placeholder
's' => try writer.print("18:30", .{}), // Sunset placeholder
'd' => try writer.print("20:00", .{}), // Dusk placeholder
'%' => try writer.print("%", .{}),
'n' => try writer.print("\n", .{}),
else => {
@ -105,16 +76,6 @@ pub fn render(allocator: std.mem.Allocator, weather: types.WeatherData, format:
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" {
const allocator = std.testing.allocator;