-
-
Save alexander-yakushev/88531e23a89a0f2acbf1 to your computer and use it in GitHub Desktop.
| -- Module for calculating sunrise/sunset times for a given location | |
| -- Based on algorithm by United Stated Naval Observatory, Washington | |
| -- Link: http://williams.best.vwh.net/sunrise_sunset_algorithm.htm | |
| -- @author Alexander Yakushev | |
| -- @license CC0 http://creativecommons.org/about/cc0 | |
| -- Module lustrous | |
| local lustrous = { | |
| update_interval = 600 | |
| } | |
| local current_time = nil | |
| local srs_args | |
| local rad = math.rad | |
| local deg = math.deg | |
| local floor = math.floor | |
| local frac = function(n) return n - floor(n) end | |
| local cos = function(d) return math.cos(rad(d)) end | |
| local acos = function(d) return deg(math.acos(d)) end | |
| local sin = function(d) return math.sin(rad(d)) end | |
| local asin = function(d) return deg(math.asin(d)) end | |
| local tan = function(d) return math.tan(rad(d)) end | |
| local atan = function(d) return deg(math.atan(d)) end | |
| local function fit_into_range(val, min, max) | |
| local range = max - min | |
| local count | |
| if val < min then | |
| count = floor((min - val) / range) + 1 | |
| return val + count * range | |
| elseif val >= max then | |
| count = floor((val - max) / range) + 1 | |
| return val - count * range | |
| else | |
| return val | |
| end | |
| end | |
| local function day_of_year(date) | |
| local n1 = floor(275 * date.month / 9) | |
| local n2 = floor((date.month + 9) / 12) | |
| local n3 = (1 + floor((date.year - 4 * floor(date.year / 4) + 2) / 3)) | |
| return n1 - (n2 * n3) + date.day - 30 | |
| end | |
| local function sunturn_time(date, rising, latitude, longitude, zenith, local_offset) | |
| local n = day_of_year(date) | |
| -- Convert the longitude to hour value and calculate an approximate time | |
| local lng_hour = longitude / 15 | |
| local t | |
| if rising then -- Rising time is desired | |
| t = n + ((6 - lng_hour) / 24) | |
| else -- Setting time is desired | |
| t = n + ((18 - lng_hour) / 24) | |
| end | |
| -- Calculate the Sun's mean anomaly | |
| local M = (0.9856 * t) - 3.289 | |
| -- Calculate the Sun's true longitude | |
| local L = fit_into_range(M + (1.916 * sin(M)) + (0.020 * sin(2 * M)) + 282.634, 0, 360) | |
| -- Calculate the Sun's right ascension | |
| local RA = fit_into_range(atan(0.91764 * tan(L)), 0, 360) | |
| -- Right ascension value needs to be in the same quadrant as L | |
| local Lquadrant = floor(L / 90) * 90 | |
| local RAquadrant = floor(RA / 90) * 90 | |
| RA = RA + Lquadrant - RAquadrant | |
| -- Right ascension value needs to be converted into hours | |
| RA = RA / 15 | |
| -- Calculate the Sun's declination | |
| local sinDec = 0.39782 * sin(L) | |
| local cosDec = cos(asin(sinDec)) | |
| -- Calculate the Sun's local hour angle | |
| local cosH = (cos(zenith) - (sinDec * sin(latitude))) / (cosDec * cos(latitude)) | |
| if rising and cosH > 1 then | |
| return "N/R" -- The sun never rises on this location on the specified date | |
| elseif cosH < -1 then | |
| return "N/S" -- The sun never sets on this location on the specified date | |
| end | |
| -- Finish calculating H and convert into hours | |
| local H | |
| if rising then | |
| H = 360 - acos(cosH) | |
| else | |
| H = acos(cosH) | |
| end | |
| H = H / 15 | |
| -- Calculate local mean time of rising/setting | |
| local T = H + RA - (0.06571 * t) - 6.622 | |
| -- Adjust back to UTC | |
| local UT = fit_into_range(T - lng_hour, 0, 24) | |
| -- Convert UT value to local time zone of latitude/longitude | |
| local LT = UT + local_offset | |
| return os.time({ day = date.day, month = date.month, year = date.year, | |
| hour = floor(LT), min = floor(frac(LT) * 60)}) | |
| end | |
| local function get(args) | |
| args = args or {} | |
| local date = args.date or os.date("*t") | |
| local lat = args.lat or 0 | |
| local lon = args.lon or 0 | |
| local offset = args.offset or 0 | |
| local zenith = args.zenith or 90.83 | |
| local rise_time = sunturn_time(date, true, lat, lon, zenith, offset) | |
| local set_time = sunturn_time(date, false, lat, lon, zenith, offset) | |
| local length = (set_time - rise_time) / 3600 | |
| return rise_time, set_time, floor(length), frac(length) * 60 | |
| end | |
| function lustrous.get_time(args) | |
| local now = os.time() | |
| local rise, set = get(args or srs_args) | |
| local new_time | |
| if now > rise and now < set then -- After sunrise, before sunset | |
| return "day", rise, set | |
| else | |
| return "night", rise, set | |
| end | |
| end | |
| function lustrous.update_time(args) | |
| local new_time = lustrous.get_time(args) | |
| if current_time and current_time ~= new_time then | |
| lustrous:emit_signal("lustrous::time_changed") | |
| end | |
| current_time = new_time | |
| end | |
| function lustrous.init(args) | |
| srs_args = args | |
| if scheduler then | |
| scheduler.register_recurring("lustrous_check_time", | |
| lustrous.update_interval, | |
| lustrous.update_time) | |
| end | |
| return current_time | |
| end | |
| return lustrous |
Thank you!
Hello,
I’ve been using this script a bit and it’s been helpful (in https://github.com/gagbo/circadian.nvim if you’re interested), and now I’d like to use this library in a "cleaner" way in my wezterm config. I have 2 questions that I still don’t know how to answer:
- where is the
emit_signaldefined? I would like to control how the signal is emitted actually - What kind of
schedulerare you expecting with theregister_recurringinterface? I wasn’t able to find it online
Thanks again for the lua version of the algorithm!
Hello @gagbo! Glad you find it useful. I used this file within Awesome Window Manager, and emit_signal is a function from there. scheduler is another custom module that I used within that environment. I suggest you just drop the references to those functions from this file and provide your custom callback-based implementations if you need something similar.
Okay, that make sense, I thought that the emit_signal was from awesome, and I didn’t find the scheduler in the docs so I was curious. In the end I added my own callbacks as you suggested. Thanks for the answer!
Line 107 should be something like this:
return os.time({ day = date.day, month = date.month, year = date.year,
hour = floor(LT), min = floor(frac(LT) * 60)})