diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib.rs | 211 | ||||
| -rw-r--r-- | src/solar.rs | 314 |
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)); + }); + }); +} |
