summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs211
-rw-r--r--src/solar.rs314
2 files changed, 525 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..6401829
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,211 @@
+use maud::{html, Markup, PreEscaped, DOCTYPE};
+use regex::Regex;
+use worker::*;
+
+pub mod solar;
+use crate::solar::{dow, ganzhi, solar, SexagenaryDate, Term, TERMS};
+
+#[event(fetch)]
+async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
+ let r = Router::new();
+ let re = |r: Request, _| Response::redirect(r.url()?.join("shitpit")?);
+ Router::new()
+ .get("/journal", re)
+ .get("/diary", re)
+ .get("/index.html", |req, _| {
+ Response::redirect(req.url()?.join("/")?)
+ })
+ .get_async("/shitpit", |req, ctx| async move {
+ let u = req.url()?;
+ let a = ctx.env.assets("ASSETS")?;
+ let mut a = a.fetch(u.join("/_/diary")?, None).await?;
+ let a = a.text().await?;
+ let re = Regex::new(
+ r"(0[1-9]|[1-9]|1[0-2])\/(0[1-9]|[1-9]|1\d|2\d|3[01])\/(\d{2})$",
+ ).unwrap();
+ let mut a: Vec<String> =
+ a.split('\n').map(|s| s.trim().to_string()).collect();
+ bone("shitpit", "look in the sky! it's a bird! it's a plane! no it's superego!", html! {
+ pre {
+ @for x in a.into_iter().map(|l| {
+ if let Some(c) = re.captures(&l) {
+ let (m, d, y) = (c.get(1), c.get(2), c.get(3));
+ let f = |x: Option<regex::Match>| x.unwrap().as_str().parse().unwrap();
+ let (y, m, d): (usize, usize, usize) = (f(y) + 2000, f(m), f(d));
+ let s = solar(y, m, d);
+ let (dowc, dowl) = dow(y, m, d);
+ let (y, m, d, t) = (s.year, s.month, s.day, s.term);
+ let href = l.replace("/", "_");
+ html! {
+ (PreEscaped("</pre>"))
+ a name=(href) { }
+ a class="sec" href=(format!("#{href}")) {
+ (format!("{l} {dowl}. {y}年 {m}月 {d}日 ({dowc})"))
+ @if let Some((t, _)) = t {
+ (t)
+ }
+ }
+ (PreEscaped("<pre>"))
+ }
+ } else {
+ html! { (PreEscaped(l)) "\n" }
+ }
+ }) {
+ (x)
+ }
+ }
+ })
+ })
+ .get("/", |_, _| {
+ bone("home", "ataxia.moe: like i heard a transmission from Apollo 13", html! {
+ p style="padding: 1vw" {
+ "No. Stop." br; br; "Check out "
+ a href="/shitpit" { "the shitpit" }
+ " in the meantime."
+ }
+ })
+ })
+ .get("/about", |_, _| {
+ bone("about", "And they completely goddamn disrespected me! Little idiots! Idiots!", html! {
+ p style="padding: 1vw" {
+ "Ataxia is a nervous system dysfunction consisting of poor coördination of muscle movements such as gait abnormalities, slurring of speech, and eye movement issues."
+ br; br;
+ "Hosted on "
+ a href="https://developers.cloudflare.com/workers/" { "Cloudflare Workers" }
+ ". Source available once I sort the mess out."
+ }
+ })
+ })
+ .or_else_any_method("/*catchall", |_, _| {
+ Ok(bone("", "404 Not Found", html! {
+ p style="padding: 1vw" { "what?" }
+ })?.with_status(404))
+ })
+ .run(req, env)
+ .await
+}
+
+fn header() -> Markup {
+ let css = r#"/* i dont know css im so fucking sorry ill fix it l8r */
+pre {
+ white-space: pre-wrap;
+ white-space: -moz-pre-wrap;
+ white-space: -pre-wrap;
+ white-space: -o-pre-wrap;
+ word-wrap: break-word;
+}
+.remark {
+ box-sizing: content-box;
+ border-top: 0px solid #000;
+ border-left: 0px solid #000;
+ border-right: 0px solid #000;
+ border-bottom: 1px solid #000;
+ padding: 1vw;
+ padding-top: 0;
+}
+.nav {
+ border-bottom: 1px solid #000;
+ padding: 1vw;
+ padding-top: 0;
+}
+main {
+ border: 1px #000 solid;
+}
+body {
+ background-color: #eee;
+}
+
+@media only screen and (min-width: 800px) {
+
+ p, a.sec, pre {
+ padding: 0 4vw;
+/*align: center;
+display:flex;
+align-items: center;
+justify-content: center;*/
+ }
+}
+pre {
+ font-size: 12pt;
+}
+a, p { font-size: 14pt; }
+a.splink { color: #005953; font-size: inherit; }
+a.sec, a.menu, a.menuon {
+ color: #005953;
+ font-family: 'Iansui', serif;
+ font-size: 1.5em;
+}
+a.sec:link, a.sec:visited, a.sec:active {
+ text-decoration: none;
+}
+a.sec:hover, a.sec:focus {
+ text-decoration: underline;
+}
+a.menu:link, a.menu:visited, a.menu:active {
+ text-decoration: none;
+}
+a.menu:hover, a.menu:focus {
+ text-decoration: underline;
+}
+@media (prefers-color-scheme: dark) {
+ body {
+ background-color: #222;
+ color: #dcdccc;
+ }
+ .nav, .remark {
+ border-bottom: 1px solid #dcdccc;
+ }
+ main {
+ border: 1px #dcdccc solid;
+ }
+ a.splink, a.sec, a.menu, a.menuon { color: #51bd96; }
+}
+@font-face {
+ font-family: 'Iansui';
+ font-style: normal;
+ font-display: swap;
+ font-weight: 400;
+ src: url(/_/iansui.woff2) format('woff2');
+ unicode-range: U+3000-303F,U+3105-312F,U+4E00-9FFF;
+}
+body {
+ max-width: 960px;
+ margin-left: auto;
+ margin-right: auto;
+}"#;
+ html! {
+ (DOCTYPE)
+ style { (PreEscaped(css)) }
+ (PreEscaped(r#"<meta name="viewport" content="width=device-width, initial-scale=1.0" />"#))
+ }
+}
+
+fn menu(nav: &str) -> Markup {
+ html! {
+ div class="nav" {
+ @let navs = ["home", "shitpit", "about"];
+ @for (i, &n) in navs.iter().enumerate() {
+ a class=({if n == nav { "menuon" } else { "menu" }}) href={"/" (n)} {
+ (n)
+ }
+ @if i != navs.len() - 1 {
+ " / "
+ }
+ }
+ }
+ }
+}
+
+fn bone(wh: &str, rm: &str, b: Markup) -> Result<Response> {
+ Response::from_html(
+ (html! {
+ (header())
+ main id="content" {
+ h2 class="remark" { (rm) }
+ (menu(wh))
+ (b)
+ }
+ })
+ .into_string(),
+ )
+}
diff --git a/src/solar.rs b/src/solar.rs
new file mode 100644
index 0000000..af52afc
--- /dev/null
+++ b/src/solar.rs
@@ -0,0 +1,314 @@
+#![allow(non_upper_case_globals)]
+#![allow(invalid_value)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+
+// based on https://prospertimes.neocities.org/solarterms.js
+use lazy_static::lazy_static;
+use num::{cast, traits::AsPrimitive};
+use std::num::Wrapping as W;
+use std::{
+ f64::consts::PI,
+ mem::MaybeUninit,
+ ops::{Div, Rem},
+};
+
+// this is so stupid. i should not have to put pub every single line
+pub struct Term {
+ pub solar_term: &'static str,
+ pub ang: usize,
+ pub month: usize,
+ pub earliest_day: usize,
+ pub latest_day: usize,
+}
+
+#[cfg_attr(not(target_family = "wasm"), derive(Debug))]
+pub struct SexagenaryDate {
+ pub year: &'static str,
+ pub month: &'static str,
+ pub day: &'static str,
+ pub term: Option<(&'static str, usize)>,
+}
+
+const UNIT_YR: usize = 1984;
+
+// cant use [].map(|(...)| Term {}) on consts
+// this sucks, i should not have2 write design8ed initializers each&everytime
+macro_rules! terms {
+ ($({ $s: expr, $a: expr, $m: expr, $e: expr, $l: expr }),* $(,)?) => {
+ [
+ $(Term {
+ solar_term: $s,
+ ang: $a,
+ month: $m,
+ earliest_day: $e,
+ latest_day: $l,
+ }),*
+ ]
+ };
+}
+
+pub const TERMS: [Term; 24] = terms![
+ {"小寒", 285, 1, 3, 7},
+ {"大寒", 300, 1, 18, 22},
+ {"立春", 315, 2, 2, 6},
+ {"雨水", 330, 2, 16, 20},
+ {"驚蟄", 345, 3, 3, 7},
+ {"春分", 360, 3, 18, 22},
+ {"淸明", 15, 4, 2, 6},
+ {"穀雨", 30, 4, 18, 22},
+ {"立夏", 45, 5, 3, 7},
+ {"小滿", 60, 5, 19, 23},
+ {"芒種", 75, 6, 3, 7},
+ {"夏至", 90, 6, 19, 23},
+ {"小暑", 105, 7, 5, 9},
+ {"大暑", 120, 7, 20, 24},
+ {"立秋", 135, 8, 5, 9},
+ {"處暑", 150, 8, 21, 25},
+ {"白露", 165, 9, 5, 9},
+ {"秋分", 180, 9, 21, 25},
+ {"寒露", 195, 10, 6, 10},
+ {"霜降", 210, 10, 21, 25},
+ {"立冬", 225, 11, 5, 9},
+ {"小雪", 240, 11, 20, 24},
+ {"大雪", 255, 12, 5, 9},
+ {"冬至", 270, 12, 19, 23},
+];
+
+fn compute_JD(mut y: f64, mut m: f64, d: f64) -> f64 {
+ if m <= 2. {
+ y -= 1.;
+ m += 12.;
+ }
+ (365.25 * (y + 4716.)).floor() + (30.6001 * (m + 1.)).floor() + d
+ - 13.
+ - 1524.5
+}
+
+macro_rules! JD {
+ ($y: expr, $m: expr, $d: expr) => {
+ compute_JD($y as f64, $m as f64, $d as f64)
+ };
+}
+
+// dont ask me how this works
+fn compute_ang(jd: f64) -> f64 {
+ let T = (jd - 2451545.) / 36525.;
+ let Lmean = (280.46646 + (36000.76983 * T) + (0.0003032 * T * T)) % 360.;
+ let M = (357.52911 + (35999.05029 * T) - (0.0001537 * T * T)) % 360.;
+ let C = ((1.914602 - (0.004817 * T) - (0.000014 * T * T))
+ * (M * PI / 180.).sin())
+ + ((0.019993 - (0.000101 * T)) * (2. * M * PI / 180.).sin())
+ + (0.000289 * (3. * M * PI / 180.).sin());
+ let Ltrue = Lmean + C;
+ let Lapp = Ltrue
+ - 0.00569
+ - (0.00478 * ((125.04 - 1934.136 * T) * PI / 180.).sin());
+ Lapp
+}
+
+fn bisect<T>(
+ lo: &mut f64,
+ hi: &mut f64,
+ base: T,
+ scale: T,
+ target: f64,
+ y: usize,
+ m: f64,
+) where
+ T: Into<f64> + Copy,
+{
+ while lo <= hi {
+ let mid = ((*lo + *hi) / 2.).floor();
+ if compute_ang(JD!(y, m, base.into() + mid * scale.into())) < target {
+ *lo = mid + 1.;
+ } else {
+ *hi = mid - 1.;
+ }
+ }
+}
+
+fn compute_solarterm_day(i: usize, y: usize) -> f64 {
+ //double solarterm_day, test_day, test_hour, test_minute, test_long;
+ let mut solarterm_day;
+ let m: f64 = TERMS[i].month as f64;
+ let mut epd: f64 = TERMS[i].earliest_day as f64; // earliest possible day
+ let mut lpd: f64 = TERMS[i].latest_day as f64; // latest possible day
+ let mut eph: f64 = 0.; // earliest possible hour
+ let mut lph: f64 = 23.; // latest possible hour
+ let mut epm: f64 = 0.; // earliest possible minute
+ let mut lpm: f64 = 59.; // latest possible minute
+ let target_long: f64 = TERMS[i].ang as f64;
+ bisect(&mut epd, &mut lpd, 0, 1, target_long, y, m);
+ solarterm_day = lpd;
+ bisect(
+ &mut eph,
+ &mut lph,
+ solarterm_day,
+ 1. / 24.,
+ target_long,
+ y,
+ m,
+ );
+ solarterm_day += lph / 24.;
+ bisect(
+ &mut epm,
+ &mut lpm,
+ solarterm_day,
+ 1. / 1440.,
+ target_long,
+ y,
+ m,
+ );
+ solarterm_day + (lpm + 1.) / 1440.
+}
+
+#[inline(always)]
+fn align<T>(a: T, to: T, zero: T) -> usize
+where
+ T: cast::AsPrimitive<isize>,
+{
+ let (a, to, zero) = (a.as_(), to.as_(), zero.as_());
+ let tmp = a + (to - zero);
+ (tmp - to * (tmp >= to) as isize) as usize
+}
+
+#[inline(always)]
+fn mod2ganzhi<T>(g: T, z: T) -> usize
+where
+ T: cast::AsPrimitive<isize>,
+{
+ let (g, z) = (g.as_(), z.as_());
+ // chinese remainder theorem
+ // x ≡ g (mod 10)
+ // x ≡ z (mod 12)
+ // 10x + 12y = gcd(10,12) = 2
+ // x=-1, y=1
+ // X=(g-z)/gcd(10,12)=(g-z)/2
+ // 10xX + 12yX = 2X
+ // => -10X + 12X = 2X
+ // => -5(g-z) + 6(g-z) = g-z
+ // x = g-(-5(g-z)) (case 1) = 6(g-z)+z (case 2)
+ // x = g+5(g-z) = 6g-5z
+ // dont ask me how the math works idk
+ align(6 * g - 5 * z, 60, 0)
+}
+
+fn stday(i: usize, y: usize) -> f64 {
+ const YEARS: usize = 200;
+ // rust doesnt have (*a)[n], | @ least ill have2 use a cr8 4 it
+ // also lazy_static doesnt work with mutables (im not using mutex)
+ assert!(y < UNIT_YR + YEARS && UNIT_YR <= y);
+ static mut STDAYS: Vec<f64> = vec![];
+ static mut INIT: bool = false;
+ unsafe {
+ if !INIT {
+ STDAYS = vec![0.; YEARS * TERMS.len()];
+ INIT = true;
+ }
+ let idx = y - UNIT_YR;
+ let ret = &mut STDAYS[idx * TERMS.len() + i];
+ (if int(*ret) != 0 {
+ *ret
+ } else {
+ let d = compute_solarterm_day(i, y);
+ *ret = d;
+ d
+ })
+ .floor()
+ }
+}
+
+lazy_static! {
+ static ref GANZHIS: Vec<String> = {
+ let mut tmp: Vec<String> = vec![];
+ tmp.reserve(60);
+ (0..60).for_each(|i| {
+ // looks so much worse than using format!()
+ // cant be bothered to benchmark
+ let mut s = gan[i % 10].to_string();
+ s.push(zhi[i % 12]);
+ tmp.push(s);
+ });
+ tmp
+ };
+}
+const gan: [char; 10] =
+ ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸'];
+const zhi: [char; 12] = [
+ '子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥',
+];
+pub fn ganzhi(i: usize) -> &'static str {
+ &GANZHIS[i]
+}
+
+#[inline(always)]
+fn int(x: f64) -> usize {
+ x as usize
+}
+
+lazy_static! {
+ static ref JIAZI: usize = int(JD!(UNIT_YR, 1, 31));
+}
+pub fn solar(y: usize, m: usize, d: usize) -> SexagenaryDate {
+ let jdf = JD!(y, m, d);
+ let jd = int(jdf);
+ let a = compute_ang(jdf);
+ let ygz = (y
+ - UNIT_YR
+ - ((m <= 2 && a < 316.) && (m < 2 || d < int(stday(2, y)))) as usize)
+ % 60;
+ let rem = a % 15.;
+ let div = int(a.div(15.).floor());
+ let mut dz = align((div + 1) / 2, 12, 9);
+ let mut termb = rem > 14.;
+ let mut term: usize = unsafe { MaybeUninit::uninit().assume_init() };
+ if termb {
+ term = align(div, 24, 18);
+ let termday = stday(term, y);
+ termb = d == int(termday);
+ if (div & 1) == 0 {
+ dz += termb as usize;
+ dz = align(dz, 12, 12);
+ }
+ }
+ let mut tmp = ygz.rem(5);
+ tmp = tmp * 2 + 2;
+ tmp -= 10 * (tmp == 10) as usize;
+ let mgz = mod2ganzhi(tmp + align(dz, 12, 2).rem(10), dz);
+ let dgz = (jd - *JIAZI).rem(60);
+ SexagenaryDate {
+ year: ganzhi(ygz),
+ month: ganzhi(mgz),
+ day: ganzhi(dgz),
+ term: termb.then(|| (TERMS[term].solar_term, term)),
+ }
+}
+
+// https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week#Disparate_variation
+pub fn dow(y: usize, mut m: usize, d: usize) -> (&'static str, &'static str) {
+ let Y = y - (m <= 2) as usize;
+ let y2 = Y % 100;
+ let c = Y / 100;
+ m += 9;
+ m -= 12 * (m >= 12) as usize;
+ let a = (d + (26 * (m + 1) - 2) / 10 + y2 + y2 / 4 + c / 4 - 2 * c) % 7;
+ [
+ ("日", "Sun"),
+ ("月", "Mon"),
+ ("火", "Tue"),
+ ("水", "Wed"),
+ ("木", "Thu"),
+ ("金", "Fri"),
+ ("土", "Sat"),
+ ][a]
+}
+
+#[cfg(not(target_family = "wasm"))]
+fn main() {
+ (1..=12).for_each(|m| {
+ (1..28).for_each(|d| {
+ println!("{:?}", solar(2026, m, d));
+ });
+ });
+}