From 6b0c3b4f836de5dbb991640795a26ac8780e1b67 Mon Sep 17 00:00:00 2001 From: spiders Date: Fri, 24 Jan 2025 18:04:07 +0000 Subject: [PATCH] Upload files to "/" --- foxtime.lua | 620 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 620 insertions(+) create mode 100644 foxtime.lua diff --git a/foxtime.lua b/foxtime.lua new file mode 100644 index 0000000..12ca27d --- /dev/null +++ b/foxtime.lua @@ -0,0 +1,620 @@ +#!/usr/bin/lua + +--[[ foxtime version ᚢ.0 please read until the next 🐇 + +foxclock is a fuzzy clock for fuzzy aminals. it uses a heretical take on the +ancient method of unequal, or seasonal, hours which was displaced by the +unceasing tide of standardization, where the daylight and the nightdark are each +divided into 7 sections (not the traditional 12), so that the length of a day +hour and a night hour are only the same on equinoxes. summerday hours are long +and lazy, winterday hours are fleeting and glittering. + +the non-suncalc parts of this software were written by someone and their friend, +© a year of bnuuys. they are distributed under the Little Aminal Public License. +you are free to use, redistribute, modify, destroy, love, hate, pet, kiss, or +fuck the software ("the software") under the following conditia: + + - you agree ur cute soft little aminal in a big wide world + - if ur plant/mycelium/rock/ocean/ghost/star/house/superorganism or something + Else, that's cool too actually, don't worry about it. + - u'll affirm your animality(/beingness) in some way today, such as by + - sniffing at a berry + - stretching your wings + - feeling sunlight on your skin + - feeling wind in your fur + - feeling a tree looking back at you + - you will help a worm or snail in their time of need + - you will remember how sugar smells when it is poured into a bowl + - you will know how it feels for a robin to startle you awake + - you will think about what it would have been like to grow up by the sea + - you will never be normal again + - you will change + - you will change + - you will change + - YOU WILL CHANGE + - you will learn to tell the hours by looking at the light of the world + - you will know this is a map and not the territory + - you will redistribute this license along with the code +--]] + +-- it is currently hardcoded to be in seattle. (its bring your own coordinates +-- go look 'em up urself) also if you use this above the arctic circle it will +-- probably break. if you have ideas for timekeeping in the arctic, reach out to +-- me by searching tirelessly for someone who keeps changing their name, their +-- face, their body, their language, and you lose hope you will ever know who i +-- am, until one day, you see me on a train and you somehow just Know that it is +-- Me, and you work up the courage to approach me but just as you clear your +-- throat, I get off the train, and you stand asweved in the twilight. or find +-- me on horny trans discord servers. +local lat = 47.6 +local lng = -122.3 +-- set this to false in the summer +local winter_mode=true +-- set this to true if you want a table of hours!! +local print_timetable_mode=false + +-- change these to whatever you want. add as many seasons as you want. add +-- impossible seasons. add more hours, subtract more hours. add local seasons. +-- add dreaming-seasons. rearrange the hour names. you are alive in a world +-- where men no longer believe in unicorns and time is running out and all we +-- can do is love each other and make magic. + +-- foxtime will adjust fine if you make it have more or fewer hours. +local day_hour_names +local night_hour_names +if winter_mode then + -- winter + day_hour_names = { + "halo", + "steam", + "pang", + "mill", + "glean", + "gleam", + "glance" + } + + night_hour_names = { + "gloam", + "glow", + "glim", + "nill", + "dream", + "seem", + "frost" + } +else + -- summer + day_hour_names = { + "dew", + "stir", + "snack", + "mill", + "sprawl", + "brill", + "honey" + } + night_hour_names = { + "owl's light", + "still", + "snuck", + "nill", + "dream", + "seem", + "robin's light" + } +end + +local Early_Name = "early" +local Mid_Name = "mid" +local Late_Name = "late" + +-- 🐇 + +-- now begins Most Of Suncalc.Lua which we just copypasted into here -- +-- Thanks suncalc.lua! -- + +--[[ +Copyright (c) 2014, Vladimir Agafonkin +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 COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. +--]] + +-- for simple translation of javascript ternary comparison operator ?: +local function ternary(a, b, c) if a then return b end return c end + +-- shortcuts for easier to read formulas + +local PI = math.pi +local sin = math.sin +local cos = math.cos +local tan = math.tan +local asin = math.asin +local atan = math.atan +local acos = math.acos +local rad = PI / 180 + +-- sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas + + +-- date/time constants and conversions + +local dayS = 60 * 60 * 24 +local J1970 = 2440588 +local J2000 = 2451545 + +local function toJulian(date) return date / dayS - 0.5 + J1970 end +local function fromJulian(j) return (j + 0.5 - J1970) * dayS end +local function toDays(date) return toJulian(date) - J2000 end + + +-- general calculations for position + +local e = rad * 23.4397 -- obliquity of the Earth + +local function rightAscension(l, b) return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)) end +local function declination(l, b) return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)) end + +local function azimuth(H, phi, dec) return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)) end +local function altitude(H, phi, dec) return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)) end + +local function siderealTime(d, lw) return rad * (280.16 + 360.9856235 * d) - lw end + +local function astroRefraction(h) + if (h < 0) then -- the following formula works for positive altitudes only. + h = 0 end -- if h = -0.08901179 a div/0 would occur. + + -- formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. + -- 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad: + return 0.0002967 / math.tan(h + 0.00312536 / (h + 0.08901179)) +end + +-- general sun calculations + +local function solarMeanAnomaly(d) return rad * (357.5291 + 0.98560028 * d) end + +local function eclipticLongitude(M) + + local C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)) -- equation of center + local P = rad * 102.9372 -- perihelion of the Earth + + return M + C + P + PI +end + +local function sunCoords(d) + + local M = solarMeanAnomaly(d) + local L = eclipticLongitude(M) + + return { + dec = declination(L, 0), + ra = rightAscension(L, 0) + } +end + + +local SunCalc = {} + + +-- calculates sun position for a given date and latitude/longitude + +SunCalc.getPosition = function (date, lat, lng) + + local lw = rad * -lng + local phi = rad * lat + local d = toDays(date) + + local c = sunCoords(d) + local H = siderealTime(d, lw) - c.ra + + return { + azimuth = azimuth(H, phi, c.dec), + altitude = altitude(H, phi, c.dec) + } +end + + +-- sun times configuration (angle, morning name, evening name) + +SunCalc.times = { + {-0.833, 'sunrise', 'sunset' }, + { -0.3, 'sunriseEnd', 'sunsetStart' }, + { -6, 'dawn', 'dusk' }, + { -12, 'nauticalDawn', 'nauticalDusk'}, + { -18, 'nightEnd', 'night' }, + { 6, 'goldenHourEnd', 'goldenHour' } +} + +-- adds a custom time to the times config + +SunCalc.addTime = function (angle, riseName, setName) + table.insert(SunCalc.times, {angle, riseName, setName}) +end + + +-- calculations for sun times + +local J0 = 0.0009 + +local function julianCycle(d, lw) return math.floor(0.5 + (d - J0 - lw / (2 * PI))) end + +local function approxTransit(Ht, lw, n) return J0 + (Ht + lw) / (2 * PI) + n end +local function solarTransitJ(ds, M, L) return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L) end + +local function hourAngle(h, phi, d) return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))) end +local function observerAngle(height) return -2.076 * math.sqrt(height) / 60 end + +-- returns set time for the given sun altitude +local function getSetJ(h, lw, phi, dec, n, M, L) + + local w = hourAngle(h, phi, dec) + local a = approxTransit(w, lw, n) + return solarTransitJ(a, M, L) +end + + +-- calculates sun times for a given date, latitude/longitude, and, optionally, +-- the observer height (in meters) relative to the horizon + +SunCalc.getTimes = function (date, lat, lng, height, times) + times = times or SunCalc.times + height = height or 0 + + local lw = rad * -lng + local phi = rad * lat + + local dh = observerAngle(height) + + local d = toDays(date) + local n = julianCycle(d, lw) + local ds = approxTransit(0, lw, n) + + local M = solarMeanAnomaly(ds) + local L = eclipticLongitude(M) + local dec = declination(L, 0) + + local Jnoon = solarTransitJ(ds, M, L) + + local h0, Jset, Jrise + + + local result = { + solarNoon = fromJulian(Jnoon), + nadir = fromJulian(Jnoon - 0.5) + } + + for _, time in ipairs(times) do + + h0 = (time[01] + dh) * rad + + Jset = getSetJ(h0, lw, phi, dec, n, M, L) + Jrise = Jnoon - (Jset - Jnoon) + + result[time[2]] = fromJulian(Jrise) + result[time[3]] = fromJulian(Jset) + end + + return result +end + +-- 🦊 🦊 🦊 🦊 🦊 +-- okay suncalc over now we are in the foxtime zone -- + +-- this is a utility function for printing out tables +local function tprint (tbl, indent) + if not indent then indent = 0 end + local toprint = string.rep(" ", indent) .. "{\r\n" + indent = indent + 2 + for k, v in pairs(tbl) do + toprint = toprint .. string.rep(" ", indent) + if (type(k) == "number") then + toprint = toprint .. "[" .. k .. "] = " + elseif (type(k) == "string") then + toprint = toprint .. k .. "= " + end + if (type(v) == "number") then + toprint = toprint .. v .. ",\r\n" + elseif (type(v) == "string") then + toprint = toprint .. "\"" .. v .. "\",\r\n" + elseif (type(v) == "table") then + toprint = toprint .. tprint(v, indent + 2) .. ",\r\n" + else + toprint = toprint .. "\"" .. tostring(v) .. "\",\r\n" + end + end + toprint = toprint .. string.rep(" ", indent-2) .. "}" + print(toprint) + end + +local function hoursLater(date, h) + -- dayS is a constant + return date + h * dayS / 24 +end + + +--[[ +So let's say that time_list is a **list** of times, in time-order. Like this: + +{ + { -10, "sunset yesterday" }, + { -5, "sunrise today" }, + { 5, "sunset today" }, + { 10, "sunrise tomorrow" } +} + +And then say `now` is 0. We want to know the two times we are inbetween. So in +this case we would return "sunrise today", "sunset today" to show we are in the +day-part of today. That's what this function does. + +You need to make sure that there are enough times here that we are in between +two of them. If not... well that'd be weird. +]] +local function when_even_are_we(now, time_list) + for i in 1, #time_list - 1 do + -- event time and name + local etime_prev, ename_prev = table.unpack(time_list[i]) + local etime_next, ename_next = table.unpack(time_list[i + 1]) + + if now >= etime_prev and now < etime_next then + -- We are inbetween these two times + return ename_prev, ename_next + end + end +end + + + +--[[ +This little friend will convert all our fun friendly hour names and say when +they happen in the silly little world of "24 hour" that the "humans" like to +use. +]] +local function print_hour_timetable(fuzziest_time, suntimes) + local day_length = suntimes.sunset - suntimes.sunrise + local night_length = (60 * 60 * 24) + suntimes.sunrise - suntimes.sunset + + -- We want to print the night first if its night, and the day first if its + -- day. then we print the other. + local first, second + + -- We start off with these in day, night order. But then if it's night we + -- swap them. The start time just needs to be the start of *a* segment, it + -- doesn't really need to be exactly today's segment because itll be close + -- enough. + first = { + start = suntimes.sunrise, + length = day_length, + names = day_hour_names + } + second = { + start = suntimes.sunset, + length = night_length, + names = night_hour_names + } + if fuzziest_time == 'night' then + -- swap for night + local tmp = first + first = second + second = tmp + end + + -- Do the first, then the second. + for _, seg in ipairs({first, second}) do + -- How long is a hour? + local hourlen = seg.length / #seg.names + + -- Now go through the segment, one hourlength at a time. Print out what + -- 24-hour time it would be, and our hour name. + for i = 1, #seg.names do + local t = math.floor(seg.start + (i - 1) * hourlen) + -- 24hour date, without the day + + -- For some reason lua5.4 on debian doesn't support %l in strftime, + -- so we use %I which formats it with a leading 0, and then replace + -- that with a space. lol + local time_of_day_normalpeople = os.date('%I:%M %p', t):gsub('^0', ' ') + local hour_name = seg.names[i] + print(time_of_day_normalpeople .. ' - ' .. hour_name) + end + end +end + +local function print_hour_timetable_for_date(date) + -- HEY BUDDY YOU CANT BE COPY PASTING LAT/LNG LIKE THAT BUDDY + -- probably somethings in the code should rearrange... + local time = os.time(date) + --[[ + local lat = 47.6 + local lng = -122.3 --]] + local suntimes = SunCalc.getTimes(time, lat, lng) + + print_hour_timetable("day", suntimes) +end + + +--[[ +For anything that wants to use us as a library, here's how they can do it! Any +function we put in "module" is a thing they can use +]] +local foxtime = {} + +function foxtime.what_time_is_it(now) + + -- tprint(SunCalc.getPosition(now, lat, lng)) + -- tprint(SunCalc.getTimes(now, lat, lng)) + + --[[ + This gives us the times of a bunch of interesting solar events, with their + absolute timestamp in seconds since the epoch. Some of them will be before + Now, and some of them will be after Now. Specifically, we have: + - nadir + - nightEnd + - nauticalDawn + - dawn + - sunrise + - sunriseEnd + - goldenHour + - goldenHourEnd + - solarNoon + - sunsetStart + - sunset + - dusk + - nauticalDusk + - night + ]] + local suntimes = SunCalc.getTimes(now, lat, lng) + + --[[ + We want to know if it is day time or not time. If we are between sunrise and + sunset for the day, then that means it's day time! Otherwise it is night. It + is up to you, really, whether to use the start or end of the sunrise and + sunset. but for now we will use `sunrise` and `sunset` + ]] + local fuzziest_time + local seg_start + local seg_end + if now > suntimes.sunrise and now < suntimes.sunset then + -- DAY + fuzziest_time = 'day' + seg_start = suntimes.sunrise + seg_end = suntimes.sunset + else + -- NIGHT + fuzziest_time = 'night' + --[[print(now) + print(suntimes.sunrise) + print(suntimes.sunset) + --]] + --[[ + If we are on the left of sunrise, then the segment ends at sunrise and + starts at the *previous days* sunset. If we are on the right of sunset, + then sunset is when the segment started and it will end on tomorrows + sunrise. + --]] + if now < suntimes.sunrise then + --[[ + For now, do the inaccurate but maybe close enough thing of taking + 24 hours off of today's number. This will be least accurate around + the equinox and most accurate around the solstice. + --]] + seg_start = suntimes.sunset - 60 * 60 * 24 + seg_end = suntimes.sunrise + else + + seg_start = suntimes.sunset + seg_end = suntimes.sunrise + 60 * 60 * 24 + --print(seg_start) + --print(seg_end) + end + end + + --[[ + Now we want to know how far through the current time-section we are. We want + this as like a fraction out of 1.0, because we're going to re-scale it to + our own fucked up time system :3 :3 :3 + ]] + local seg_progression = (now - seg_start) / (seg_end - seg_start) + + --[[ + now we re-scale it to ""hours"", which are some number of divisions of the + segment. We will use the length of the names list to decide this, that way + we don't need to remember to update both places when we make changes. Plus + then you can have the day and night have different number of hours, and isnt + that kinda neat? + ]] + local num_hours + if fuzziest_time == 'day' then + num_hours = #day_hour_names + else + num_hours = #night_hour_names + end + local current_hour = math.floor(seg_progression * num_hours) + --[[print("current hour") + print(current_hour)--]] + + --[[ + If seg_progression is **exactly** 1.0, then current_hour can be num_hours. + Which is weird. For a very small instant you might have that, if the maths + work out that way. But that adds a secret num_hours + 1 hour which doesnt + fit. So if current_hour == num_hours we should make it be num_hours - 1 for + that instant. + ]] + if current_hour == num_hours then + current_hour = num_hours - 1 + end + + --[[ + But what is this hour named?? Let's find out! Don't forget to add 1, because + current_hour from the maths we did will be from 0 to (num_hours - 1) + ]] + local hour_name + if fuzziest_time == 'day' then + hour_name = day_hour_names[current_hour + 1] + else + hour_name = night_hour_names[current_hour + 1] + end + + --[[ let's do math to find out how far we are within the hour ]] + local hour_length = (seg_end - seg_start) / num_hours + --[[]] + local current_hour_start = seg_start + ((current_hour)*hour_length) + local current_hour_progress = (now - current_hour_start) / hour_length + --print(string.format("now %s current_hour %s seg_start %s seg_end %s hour_length %s current_hour_start %s current_hour_progress", now, current_hour, seg_start, seg_end, hour_length, current_hour_start, current_hour_progress)) + local minute_name + if current_hour_progress < 0 then + minute_name = "errneg" + elseif current_hour_progress <= 0.33 then + minute_name = Early_Name + elseif current_hour_progress <= 0.66 then + minute_name = Mid_Name + elseif current_hour_progress <= 1 then + minute_name = Late_Name + else minute_name = "errpos" + end + --[[ + And now we know what time it is! + ]] + + if print_timetable_mode then + print_hour_timetable("day", suntimes) + end + return string.format("%s %s", minute_name, hour_name) +end + +local function mane() + local now = os.time() + + print(foxtime.what_time_is_it(now)) +end + + +-- swap these comments out to use this as a library (for instance, to put this on koreader) + +-- return foxtime +mane()