It is currently April 19th, 2024, 6:33 pm

Converting JavaScript to LUA: I need help!

Get help with creating, editing & fixing problems with skins
User avatar
raiguard
Posts: 660
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA

Converting JavaScript to LUA: I need help!

Post by raiguard »

So I am attempting to create a sun and moon dial for my weather skin, and I have already taken a look at this post and managed to make that script work with my skin. However, it seems needlessly complicated and doesn't include everything that I want (e.g. moon angle).

I found this JavaScript script that seems to be a very accurate and compact script to get all of the information I want. However, since it is in JavaScript, I can't use it directly with Rainmeter. So I began the long and arduous process of converting it to LUA.

I have gone through and fixed all of the syntax so it can be compiled as LUA code, but I have run into a major, MAJOR hurdle: the script uses JavaScript's Date object. LUA has no such object and searching online has yielded no ports of it to LUA for use easily. I have gone through and found the instances where it needs a raw timestamp and I will be able to just use the value of a timestamp measure for those, but I have absolutely no idea how to convert everything else.

You can find the script code below (warning: HUGE). All you need to make it work is a script measure. The script attempts to print the tables generated by SunCalc.getTimes() and SunCalc.getMoonTimes() to the Rainmeter log.

Thanks in advance for any assistance that is offered! You're the best. :17good

Code: Select all

--[[

    CHANGES SO FAR:
    (variable) date.valueOf() = SKIN:GetMeasure('MeasureLocalTime'):GetValue() -- raw timestamp value


]]--

debug = true

function Initialize() 

    local timestamp = 13182906972
    local latitude = 40.34
    local longitude = -111.92

    -- debug
    LogHelper(PrintTable(SunCalc.getTimes(timestamp, latitude, longitude)))
    LogHelper(PrintTable(SunCalc.getMoonTimes(timestamp, latitude, longitude)))

end

function Update() end

-- function GetSunMoonTimes(timestamp, latitude, longitude)

-- end

-- function to make logging messages less cluttered
function LogHelper(message, type)

    if type == nil then type = 'Debug' end
      
    if debug == true then
        SKIN:Bang("!Log", message, type)
    elseif type ~= 'Debug' then
        SKIN:Bang("!Log", message, type)
    end
      
end

printIndent = ''

-- prints the entire contents of a table to the Rainmeter log
function PrintTable(table)
    for k,v in pairs(table) do
        if type(v) == 'table' then
            local pI = printIndent
            print(printIndent .. k .. ':')
            printIndent = printIndent .. '  '
            PrintTable(v)
            printIndent = pI
        else
            print(printIndent .. k .. ': ' .. v)
        end
    end
end

function math.round(x)
    if x%2 ~= 0.5 then
        return math.floor(x+0.5)
    end
    return x-0.5
end

-- ------------------------------------------------------------------------------------------------------------------------
-- ------------------------------------------------------------------------------------------------------------------------
-- ------------------------------------------------------------------------------------------------------------------------

--[[
 (c) 2011-2015, Vladimir Agafonkin
 SunCalc is a JavaScript library for calculating sun/moon position and light phases.
 https://github.com/mourner/suncalc
]]--

-- shortcuts for easier to read formulas

PI   = math.pi
sin  = math.sin
cos  = math.cos
tan  = math.tan
asin = math.asin
atan = math.atan2
acos = math.acos
rad  = PI / 180

-- sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas


-- date/time constants and conversions

dayMs = 1000 * 60 * 60 * 24
J1970 = 2440588
J2000 = 2451545

function toJulian(date)  return date.valueOf() / dayMs - 0.5 + J1970  end
function fromJulian(j)   return Date((j + 0.5 - J1970) * dayMs)  end
function toDays(date)    return toJulian(date) - J2000  end


-- general calculations for position

e = rad * 23.4397 -- obliquity of the Earth 

function rightAscension(l, b)  return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l));  end
function declination(l, b)     return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l));  end

function azimuth(H, phi, dec)   return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi));  end
function altitude(H, phi, dec)  return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H));  end

function siderealTime(d, lw)  return rad * (280.16 + 360.9856235 * d) - lw;  end

function astroRefraction(h)
    if (h < 0) then -- the following formula works for positive altitudes only. 
        h = 0 -- if h = -0.08901179 a div/0 would occur. 
    end

    -- 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

function solarMeanAnomaly(d)  return rad * (357.5291 + 0.98560028 * d)  end

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

function sunCoords(d)

    M = solarMeanAnomaly(d)
    L = eclipticLongitude(M)

    return {
        dec = declination(L, 0),
        ra = rightAscension(L, 0)
    }

end

SunCalc = {}

-- calculates sun position for a given date and latitude/longitude

SunCalc.getPosition = function (date, lat, lng)

    lw  = rad * -lng
    phi = rad * lat
    d   = toDays(date)
    c  = sunCoords(d)
    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)

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(times, {angle, riseName, setName})
end


-- calculations for sun times

J0 = 0.0009

function julianCycle(d, lw)  return math.round(d - J0 - lw / (2 * PI))  end

function approxTransit(Ht, lw, n)  return J0 + (Ht + lw) / (2 * PI) + n  end
function solarTransitJ(ds, M, L)   return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L)  end

function hourAngle(h, phi, d)  return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)))  end

-- returns set time for the given sun altitude
function getSetJ(h, lw, phi, dec, n, M, L)

    w = hourAngle(h, phi, dec)
    a = approxTransit(w, lw, n)
    return solarTransitJ(a, M, L)

end


-- calculates sun times for a given date and latitude/longitude

SunCalc.getTimes = function (date, lat, lng)

    lw = rad * -lng
    phi = rad * lat

    d = toDays(date)
    n = julianCycle(d, lw)
    ds = approxTransit(0, lw, n)

    M = solarMeanAnomaly(ds)
    L = eclipticLongitude(M)
    dec = declination(L, 0)

    Jnoon = solarTransitJ(ds, M, L)

    i, len, time, Jset, Jrise = nil


    result = {
        solarNoon = fromJulian(Jnoon),
        nadir = fromJulian(Jnoon - 0.5)
    }

    for i = 0,times.length do
        time = times[i]

        Jset = getSetJ(time[0] * rad, lw, phi, dec, n, M, L)
        Jrise = Jnoon - (Jset - Jnoon)

        result[time[1]] = fromJulian(Jrise)
        result[time[2]] = fromJulian(Jset)
    end

    return result

end


-- moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas

function moonCoords(d) -- geocentric ecliptic coordinates of the moon 

    L = rad * (218.316 + 13.176396 * d) -- ecliptic longitude 
    M = rad * (134.963 + 13.064993 * d) -- mean anomaly 
    F = rad * (93.272 + 13.229350 * d)  -- mean distance 

    l  = L + rad * 6.289 * sin(M) -- longitude 
    b  = rad * 5.128 * sin(F)    -- latitude 
    dt = 385001 - 20905 * cos(M)  -- distance to the moon in km 

    return {
        ra = rightAscension(l, b),
        dec = declination(l, b),
        dist = dt
    }

end

SunCalc.getMoonPosition = function (date, lat, lng)

    lw  = rad * -lng
    phi = rad * lat
    d   = toDays(date)

    c = moonCoords(d)
    H = siderealTime(d, lw) - c.ra
    h = altitude(H, phi, c.dec)
    -- formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. 
    pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H))

    h = h + astroRefraction(h) -- altitude correction for refraction 

    return {
        azimuth = azimuth(H, phi, c.dec),
        altitude = h,
        distance = c.dist,
        parallacticAngle = pa
    }

end


-- calculations for illumination parameters of the moon,
-- based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
-- Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.

SunCalc.getMoonIllumination = function (date)

    d = toDays(date or Date())
    s = sunCoords(d)
    m = moonCoords(d)

    sdist = 149598000 -- distance from Earth to Sun in km 

    phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra))
    inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi))
    angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) - cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra))

    return {
        fraction = (1 + cos(inc)) / 2,
        phase = 0.5 + 0.5 * inc * (angle < 0 and -1 or 1) / math.PI,
        angle = angle
    }

end


function hoursLater(date, h)
    return Date(date.valueOf() + h * dayMs / 24)
end

-- calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article

SunCalc.getMoonTimes = function (date, lat, lng, inUTC)
    t = Date(date)
    if inUTC then t.setUTCHours(0, 0, 0, 0)
    else t.setHours(0, 0, 0, 0) end

    hc = 0.133 * rad
    h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc
    h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx = nil

    -- go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set) 
    for i=1,24,2 do
        h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc
        h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc

        a = (h0 + h2) / 2 - h1
        b = (h2 - h0) / 2
        xe = -b / (2 * a)
        ye = (a * xe + b) * xe + h1
        d = b * b - 4 * a * h1
        roots = 0

        if d >= 0 then
            dx = math.sqrt(d) / (math.abs(a) * 2)
            x1 = xe - dx
            x2 = xe + dx
            if math.abs(x1) <= 1 then roots = roots + 1 end
            if math.abs(x2) <= 1 then roots = roots + 1 end
            if x1 < -1 then x1 = x2 end
        end

        if roots == 1 then
            if h0 < 0 then rise = i + x1
            else set = i + x1 end

        elseif roots == 2 then
            rise = i + (ye < 0 and x2 or x1)
            set = i + (ye < 0 and x1 or x2)
        end

        if rise and set then break end

        h0 = h2
    end

    result = {}

    if rise then result.rise = hoursLater(t, rise) end
    if set then result.set = hoursLater(t, set) end

    if not rise and not set then result[ye > 0 and 'alwaysUp' or 'alwaysDown'] = true end

    return result

end
EDIT: I'm starting to think that it would be easier just to learn the math and write my own script...
Last edited by raiguard on October 4th, 2018, 4:06 am, edited 2 times in total.
”We are pretty sure that r2922 resolves the regression in resolution caused by a reversion to a revision.” - jsmorley, 2017
User avatar
SilverAzide
Rainmeter Sage
Posts: 2604
Joined: March 23rd, 2015, 5:26 pm

Re: Converting JavaScript to LUA: I need help!

Post by SilverAzide »

Hi Raiguard! Pardon me if I missed the point of your question... Lua dates and Javascript dates are the same: the number of milliseconds since 1 January 1970 UTC. If you are attempting to convert a Rainmeter Time measure (a Windows timestamp) to use with Lua, Windows timestamps are the number of milliseconds since 1 January 1601 UTC.

So, something like this maybe???

Code: Select all

local tDate = os.date("!*t", SKIN:GetMeasure('MeasureLocalTime'):GetValue()) -- WINDOWS timestamp value

-- convert Windows timestamp (0 = 1/1/1601) to Unix/Lua timestamp (0 = 1/1/1970)
tDate.year = tDate.year - (1970 - 1601)
Not sure if this is what you are looking for. I converted Mordasius' Sun/Moon Lua code to work with inline Lua. His script does calculate sun and moon angles, but I don't think the moon angle is exposed in an easily understandable way (it is buried in the "moon()" function). This is assuming you want the "real" azimuth angles... if you just want the relative angle between rise and set so you can plot it on the arc, that is just a simple calc of the fraction between rise and set.
Gadgets Wiki GitHub More Gadgets...
User avatar
SilverAzide
Rainmeter Sage
Posts: 2604
Joined: March 23rd, 2015, 5:26 pm

Re: Converting JavaScript to LUA: I need help!

Post by SilverAzide »

P.S.: If you REALLY want to get into some seriously gnarly sun-moon calcs, user buckb posted some mind-blowing code here:
https://forum.rainmeter.net/viewtopic.php?f=27&t=28386

This stuff will calculate everything you could need to get rise/set time and angles for the sun, moon, and even planets and stars.
Gadgets Wiki GitHub More Gadgets...
User avatar
raiguard
Posts: 660
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA

Re: Converting JavaScript to LUA: I need help!

Post by raiguard »

Ah, thanks SilverAzide, I didn't realize that the timestamp used in Rainmeter would be different. I wouldn't have ever thought of that.

However, you did misunderstand my question. I was aware of all of the timestamp stuff, my problem is the JavaScript date library itself. For example, all of the functions in the script output as date objects, which LUA doesn't have, and I don't know if simply converting it to an integer for the timestamp would work (since I don't actually know if they output the dates as timestamps, or create the date object using a string, or what).

Hopefully it's way easier than I think it is and I'm just having a brain cramp, but either way, that's what I need help with: figuring out how the script uses the date object in each function and converting it to using the Rainmeter-provided timestamp.

Edit: And I'll also take a look at the script you linked. Thanks!
”We are pretty sure that r2922 resolves the regression in resolution caused by a reversion to a revision.” - jsmorley, 2017
User avatar
kyriakos876
Posts: 919
Joined: January 30th, 2017, 2:01 am
Location: Greece

Re: Converting JavaScript to LUA: I need help!

Post by kyriakos876 »

I don't think it's worth understanding and then translating the whole thing as it is in lua.
This is a link I found in the script tho, might help you:

https://www.aa.quae.nl/en/reken/zonpositie.html

Here's the formula:

Code: Select all

M=(−3.59°+0.98560°×d)mod360°
where d is the time since 00:00 UTC at the beginning of the most recent January 1st, measured in (whole and fractional) days.
Hope this helped, if you haven't seen it already.
User avatar
raiguard
Posts: 660
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA

Re: Converting JavaScript to LUA: I need help!

Post by raiguard »

kyriakos876 wrote:I don't think it's worth understanding and then translating the whole thing as it is in lua.
This is a link I found in the script tho, might help you:

https://www.aa.quae.nl/en/reken/zonpositie.html

Here's the formula:

Code: Select all

M=(−3.59°+0.98560°×d)mod360°


Hope this helped, if you haven't seen it already.
Yeah, I have looked at that. Since I posted the plea for help I have come to learn that this script is not completely accurate, it can get off by several minutes. I have been looking at the Meeus equations to see if I want to try and do it myself, but I think that would be even more complicated than trying to convert an existing script.
”We are pretty sure that r2922 resolves the regression in resolution caused by a reversion to a revision.” - jsmorley, 2017
User avatar
raiguard
Posts: 660
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA

Re: Converting JavaScript to LUA: I need help!

Post by raiguard »

SilverAzide wrote:Hi Raiguard! Pardon me if I missed the point of your question... Lua dates and Javascript dates are the same: the number of milliseconds since 1 January 1970 UTC. If you are attempting to convert a Rainmeter Time measure (a Windows timestamp) to use with Lua, Windows timestamps are the number of milliseconds since 1 January 1601 UTC.

So, something like this maybe???

Code: Select all

local tDate = os.date("!*t", SKIN:GetMeasure('MeasureLocalTime'):GetValue()) -- WINDOWS timestamp value

-- convert Windows timestamp (0 = 1/1/1601) to Unix/Lua timestamp (0 = 1/1/1970)
tDate.year = tDate.year - (1970 - 1601)
Not sure if this is what you are looking for. I converted Mordasius' Sun/Moon Lua code to work with inline Lua. His script does calculate sun and moon angles, but I don't think the moon angle is exposed in an easily understandable way (it is buried in the "moon()" function). This is assuming you want the "real" azimuth angles... if you just want the relative angle between rise and set so you can plot it on the arc, that is just a simple calc of the fraction between rise and set.
Oh geez, I just spent the last two hours looking over the script on my work breaks, and you were actually right, this is exactly what I need. I thought that I would have to do some really convoluted stuff to fix for the javascript date object for use in LUA, but it looks like the script takes timestamp inputs, so I don't actually need to do much. Thanks!
”We are pretty sure that r2922 resolves the regression in resolution caused by a reversion to a revision.” - jsmorley, 2017
User avatar
raiguard
Posts: 660
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA

Re: Converting JavaScript to LUA: I need help!

Post by raiguard »

So SA, I took a look at the AstroCalendar thing you linked... and that is pure black magic wizardry. It is completely bonkers. I have switched to using that (because although I got the SunCalc script to work, it was returning completely incorrect values...) and I am happy as a clam. Thanks again!
”We are pretty sure that r2922 resolves the regression in resolution caused by a reversion to a revision.” - jsmorley, 2017
User avatar
SilverAzide
Rainmeter Sage
Posts: 2604
Joined: March 23rd, 2015, 5:26 pm

Re: Converting JavaScript to LUA: I need help!

Post by SilverAzide »

raiguard wrote:So SA, I took a look at the AstroCalendar thing you linked... and that is pure black magic wizardry. It is completely bonkers. I have switched to using that (because although I got the SunCalc script to work, it was returning completely incorrect values...) and I am happy as a clam. Thanks again!
You're welcome, glad you got it working. I could mostly figure out Mordasius' code, but buckb's was beyond me, and also massive overkill. But now that you have it working, your weather gadget can now show the positions of Betelgeuse and Aldebaran, which is awesome to know when I'm trying to decide if I need a coat or not. </kidding>
Gadgets Wiki GitHub More Gadgets...
User avatar
raiguard
Posts: 660
Joined: June 25th, 2015, 7:02 pm
Location: The Sky, USA

Re: Converting JavaScript to LUA: I need help!

Post by raiguard »

EDIT: ONCE AGAIN, I FIGURED IT OUT RIGHT AFTER I POSTED. PLEASE DISREGARD (AGAIN). :( It turned out to be simply that I was calling the sun position function twice, when I was supposed to call the sun and moon position functions once each... SMH.

So, perhaps someone can help me figure this one out. After much testing and experimentation, I have reverted to the original plan of translating the SunCalc script to LUA, for it has absolutely everything that I want, and not much more. I have actually succeeded at translating it, and it appears to be working correctly, giving the correct timestamp values for events when compared to other sources.

However, there is a problem: I wish to obtain the parallactic angle from the SunCalc.getMoonPosition() function. This will allow me to draw the moon shape from the observers perspective (e.g. moon lying on its back) if the user wants it to. The script has the necessary code, but for some reason, it doesn't actually end up in the output table.

Here is the function by itself (probably not very useful since it references several other functions):

Code: Select all

SunCalc.getMoonPosition = function (date, lat, lng)

    lw  = rad * -lng
    phi = rad * lat
    d   = toDays(date)

    c = moonCoords(d)
    H = siderealTime(d, lw) - c.ra
    h = altitude(H, phi, c.dec)
    -- formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. 
    pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H))

    h = h + astroRefraction(h) -- altitude correction for refraction 

    return {
        azimuth = azimuth(H, phi, c.dec),
        altitude = h,
        distance = c.dist,
        parallacticAngle = pa
    }

end
And here is the entire script (warning: HUGE):

Code: Select all

debug = true
data = {}

function Initialize() 

    latitude = SELF:GetOption('Latitude')
    longitude = SELF:GetOption('Longitude')

end

function Update()

    local mDate = tonumber(tostring(os.time()) .. '000') -- millisecond date (timestamp with three extra zeroes)
    
    -- create 'zero-date', or timestamp at 0:00 on current day
    local zDateTbl = os.date('*t', os.time()) -- table with current time values
    local zDate = tonumber(tostring(os.time{ year = zDateTbl.year, month = zDateTbl.month, day = zDateTbl.day, hour = 0, min = 0, sec = 0 }) .. '000')

    -- debug
    RmLog('getTimes():')
    PrintTable(SunCalc.getTimes(mDate, latitude, longitude))
    RmLog('getMoonTimes():')
    PrintTable(SunCalc.getMoonTimes(zDate, latitude, longitude))
    RmLog('getPosition():')
    PrintTable(SunCalc.getPosition(mDate, latitude, longitude))
    RmLog('getMoonPosition():')
    PrintTable(SunCalc.getPosition(mDate, latitude, longitude))
    RmLog('getMoonIllumination():')
    PrintTable(SunCalc.getMoonIllumination(mDate, latitude, longitude))
    -- local sunPosition = SunCalc.getPosition(mDate, latitude, longitude)
    -- RmLog(toDegrees(sunPosition['azimuth']) .. '    ' .. toDegrees(sunPosition['altitude']))

    

end

-- ----- Utilities -----

function toDegrees(rad) return rad * 180 / math.pi end

-- function to make logging messages less cluttered
function RmLog(message, type)

    if type == nil then type = 'Debug' end
      
    if debug == true then
        SKIN:Bang("!Log", message, type)
    elseif type ~= 'Debug' then
        SKIN:Bang("!Log", message, type)
    end
      
end

printIndent = '     '

-- prints the entire contents of a table to the Rainmeter log
function PrintTable(table)
    for k,v in pairs(table) do
        if type(v) == 'table' then
            local pI = printIndent
            RmLog(printIndent .. tostring(k) .. ':')
            printIndent = printIndent .. '  '
            PrintTable(v)
            printIndent = pI
        else
            RmLog(printIndent .. tostring(k) .. ': ' .. tostring(v))
        end
    end
end

-- ------------------------------------------------------------------------------------------------------------------------
-- ------------------------------------------------------------------------------------------------------------------------
-- ------------------------------------------------------------------------------------------------------------------------

--[[
 (c) 2011-2015, Vladimir Agafonkin
 SunCalc is a JavaScript library for calculating sun/moon position and light phases.
 https://github.com/mourner/suncalc
]]--

-- shortcuts for easier to read formulas

PI   = math.pi
sin  = math.sin
cos  = math.cos
tan  = math.tan
asin = math.asin
atan = math.atan2
acos = math.acos
rad  = PI / 180

-- sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas


-- date/time constants and conversions

dayMs = 1000 * 60 * 60 * 24
J1970 = 2440588
J2000 = 2451545

function toJulian(date)  return date / dayMs - 0.5 + J1970  end
function fromJulian(j)   return (j + 0.5 - J1970) * dayMs  end
function toDays(date)    return toJulian(date) - J2000  end


-- general calculations for position

e = rad * 23.4397 -- obliquity of the Earth 

function rightAscension(l, b)  return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l));  end
function declination(l, b)     return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l));  end

function azimuth(H, phi, dec)   return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi));  end
function altitude(H, phi, dec)  return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H));  end

function siderealTime(d, lw)  return rad * (280.16 + 360.9856235 * d) - lw;  end

function astroRefraction(h)
    if (h < 0) then -- the following formula works for positive altitudes only. 
        h = 0 -- if h = -0.08901179 a div/0 would occur. 
    end

    -- 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

function solarMeanAnomaly(d)  return rad * (357.5291 + 0.98560028 * d)  end

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

function sunCoords(d)

    M = solarMeanAnomaly(d)
    L = eclipticLongitude(M)

    return {
        dec = declination(L, 0),
        ra = rightAscension(L, 0)
    }

end

SunCalc = {}

-- calculates sun position for a given date and latitude/longitude

SunCalc.getPosition = function (date, lat, lng)

    lw  = rad * -lng
    phi = rad * lat
    d   = toDays(date)
    c  = sunCoords(d)
    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)

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(times, {angle, riseName, setName})
end


-- calculations for sun times

J0 = 0.0009

function julianCycle(d, lw)  return math.round(d - J0 - lw / (2 * PI))  end

function approxTransit(Ht, lw, n)  return J0 + (Ht + lw) / (2 * PI) + n  end
function solarTransitJ(ds, M, L)   return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L)  end

function hourAngle(h, phi, d)  return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d)))  end

-- returns set time for the given sun altitude
function getSetJ(h, lw, phi, dec, n, M, L)

    w = hourAngle(h, phi, dec)
    a = approxTransit(w, lw, n)
    return solarTransitJ(a, M, L)

end


-- calculates sun times for a given date and latitude/longitude

SunCalc.getTimes = function (date, lat, lng)

    lw = rad * -lng
    phi = rad * lat

    d = toDays(date)
    n = julianCycle(d, lw)
    ds = approxTransit(0, lw, n)

    M = solarMeanAnomaly(ds)
    L = eclipticLongitude(M)
    dec = declination(L, 0)

    Jnoon = solarTransitJ(ds, M, L)

    i, len, time, Jset, Jrise = nil


    result = {
        solarNoon = fromJulian(Jnoon),
        nadir = fromJulian(Jnoon - 0.5)
    }

    for i = 1,table.length(times) do
        time = times[i]

        Jset = getSetJ(time[1] * rad, lw, phi, dec, n, M, L)
        Jrise = Jnoon - (Jset - Jnoon)

        result[time[2]] = fromJulian(Jrise)
        result[time[3]] = fromJulian(Jset)
    end

    return result

end


-- moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas

function moonCoords(d) -- geocentric ecliptic coordinates of the moon 

    L = rad * (218.316 + 13.176396 * d) -- ecliptic longitude 
    M = rad * (134.963 + 13.064993 * d) -- mean anomaly 
    F = rad * (93.272 + 13.229350 * d)  -- mean distance 

    l  = L + rad * 6.289 * sin(M) -- longitude 
    b  = rad * 5.128 * sin(F)    -- latitude 
    dt = 385001 - 20905 * cos(M)  -- distance to the moon in km 

    return {
        ra = rightAscension(l, b),
        dec = declination(l, b),
        dist = dt
    }

end

SunCalc.getMoonPosition = function (date, lat, lng)

    lw  = rad * -lng
    phi = rad * lat
    d   = toDays(date)

    c = moonCoords(d)
    H = siderealTime(d, lw) - c.ra
    h = altitude(H, phi, c.dec)
    -- formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998. 
    pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H))

    h = h + astroRefraction(h) -- altitude correction for refraction 

    return {
        azimuth = azimuth(H, phi, c.dec),
        altitude = h,
        distance = c.dist,
        parallacticAngle = pa
    }

end


-- calculations for illumination parameters of the moon,
-- based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
-- Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.

SunCalc.getMoonIllumination = function (date)

    d = toDays(date)
    s = sunCoords(d)
    m = moonCoords(d)

    sdist = 149598000 -- distance from Earth to Sun in km 

    phi = acos(sin(s.dec) * sin(m.dec) + cos(s.dec) * cos(m.dec) * cos(s.ra - m.ra))
    inc = atan(sdist * sin(phi), m.dist - sdist * cos(phi))
    angle = atan(cos(s.dec) * sin(s.ra - m.ra), sin(s.dec) * cos(m.dec) - cos(s.dec) * sin(m.dec) * cos(s.ra - m.ra))

    return {
        fraction = (1 + cos(inc)) / 2,
        phase = 0.5 + 0.5 * inc * (angle < 0 and -1 or 1) / math.pi,
        angle = angle
    }

end


function hoursLater(date, h)
    return date + h * dayMs / 24
end

-- calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article

SunCalc.getMoonTimes = function (date, lat, lng)

    t = date
    hc = 0.133 * rad
    h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc
    h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx = nil

    -- go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set) 
    for i=1,24,2 do
        h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc
        h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc

        a = (h0 + h2) / 2 - h1
        b = (h2 - h0) / 2
        xe = -b / (2 * a)
        ye = (a * xe + b) * xe + h1
        d = b * b - 4 * a * h1
        roots = 0

        if d >= 0 then
            dx = math.sqrt(d) / (math.abs(a) * 2)
            x1 = xe - dx
            x2 = xe + dx
            if math.abs(x1) <= 1 then roots = roots + 1 end
            if math.abs(x2) <= 1 then roots = roots + 1 end
            if x1 < -1 then x1 = x2 end
        end

        if roots == 1 then
            if h0 < 0 then rise = i + x1
            else set = i + x1 end

        elseif roots == 2 then
            rise = i + (ye < 0 and x2 or x1)
            set = i + (ye < 0 and x1 or x2)
        end

        if rise and set then break end

        h0 = h2
    end

    result = {}

    if rise then result.rise = hoursLater(t, rise) end
    if set then result.set = hoursLater(t, set) end

    if not rise and not set then result[ye > 0 and 'alwaysUp' or 'alwaysDown'] = true end

    return result

end

-- ---------- NOT PART OF THE ORIGINAL SCRIPT - HAD TO BE ADDED FOR THE SCRIPT TO WORK IN LUA ----------

function math.round(x)
    if x%2 ~= 0.5 then
        return math.floor(x+0.5)
    end
    return x-0.5
end

function table.length(T)
    local count = 0
    for _ in pairs(T) do count = count + 1 end
    return count
  end
To make it work, simply add a script measure with your own Latitude and Longitude options. Also add UpdateDivider=-1 so it doesn't print new values to the log every second.
”We are pretty sure that r2922 resolves the regression in resolution caused by a reversion to a revision.” - jsmorley, 2017