Hacker News new | past | comments | ask | show | jobs | submit login
Show HN: Tiny Moon – Swift library to calculate the moon phase (github.com/mannylopez)
106 points by mannylopez 44 days ago | hide | past | favorite | 35 comments
Tiny Moon is a tiny Swift library to calculate the moon phase for any given date, works super fast, and works completely offline.

All of this started when I realized that we only have 12, sometimes 13, full moon's in a year. That doesn’t seem like that many.

I set out to build a MacOS app to remind me when a full moon occurs, so that I could take a moment and step outside to appreciate it.

The MacOS app I ended up creating can be found at https://apps.apple.com/us/app/tiny-moon/id6502374344 along with the source code [0], all powered by the Tiny Moon library.

I knew that I wanted the app to work offline, so working with a network request was out of the picture. Taking inspiration from SunCalc [1] and Moontool for Windows [2], I decided to create my own library and wrote Tiny Moon as a Swift Package to power my app.

The app tries to be as minimal as possible, does what it does very fast, and works completely offline.

[0] https://github.com/mannylopez/TinyMoonApp

[1] https://github.com/mourner/suncalc

[2] https://www.fourmilab.ch/moontoolw/




If granular accuracy is not an important factor, you can also use Islamic calendar to calculate the moon phase. Muslims uses lunar cycles for some religious events (e.g. the Ayyamul Bidh or 3 days fasting during full moon). So when its 15 of an Islamic calendar, it’ll be a full moon.

I used this approach because most platforms supports islamic calendar.

I really like the offline first approach, and would definitely use the library when there’s a need.

Thanks for sharing!


I wonder, how is Allah's tolerance towards technotheological errors?

What if there's a bug in the calendar for Ramadan or Salah?


In Islam, err is forgiven. If you’ve done your best and there’s a bug you missed, then its forgiven, and when you discover it, just fix it.

That’s why if a scholar based with intensive research and data, has concluded that X is true. If in the end its really true, they’ll receive two rewards; when its debunked and it was actually false, they receive one reward. The sin is to be ignorant and without any research and data concluded that X is true.

Specifically for Ramadan, the guideline is to use Moon sighting. If you see a new moon, then you begin fasting. If not, then you postpone for a day before starting Ramadan. Usually there’s a committee that does this and they will announce the result. But it doesn’t prevent anyone with the ability to observe to decide when to start Ramadan.

Salah guideline is the sun, e.g the dawn prayer is when there’s thin strike of the sun on the horizon. So to calculate the prayer time you use the sun position relative to your position on earth. If there’s a bug that somehow err the prayer time to ~5 minutes. We can always observe the sun first.

So there’s always this second factor that you can use to validate the first method. Time seems off? Look at the sun. Sun not visible? Estimate with time. Both seems off? Estimate with the variables that you can observe.

“ God does not burden any soul with more than it can bear “ (Al Baqarah 286)

——- So to answer the question, the tolerance depends on your effort on trying to reduce the err.

God knows best


Really cool, thank you!


I know a lot of people into fishing that would love this as an app to quickly check the moon phase on a given date when planning a multi-day fishing trip or deciding when to go out. Apex predators are typically less hungry around and during a full moon due to the extra light making hunting at night easy.


the ios weather app has moon info for the current, previous, and next month


I don't really have use for this, but I must compliment the author on a job well-done. The code is well-structured, well-documented, well-tested, and well-designed.


Thank you! I was a technical writer before learning how to program, so it's important for me to write well-documented and well-tested code.

Doing so also allowed me to completely remove and replace my first attempt at implementing the moon phase algorithm when it turned out to be not-accurate enough (the values were off by about 4 hours).


Is there a plan to add this for linux / windows? Either way, super cool project


The following formula will return the closest moon phase emoji in Excel:

=LET(phase,MOD(ROUND(MOD(NOW(),29.5275)/3.691,0)-2,8)+1,UNICHAR(127760+phase)))


Good news! According to Swift Package Index [0], Tiny Moon is already compatible with Linux environments.

0. https://swiftpackageindex.com/mannylopez/TinyMoon/


  $ curl wttr.in/Moon


this looks incredibly bad from the implementation. there is a much simpler algorithm that works for hundreds of years in either direction of the present...

by comparison this is an absolute mountain of code. here is a good one - but note that it is only so accurate for each quarter phase (...and not sure why it destroys the formatting):

function approximateMoonPhase(julianDay)

{

    // from Meeus p.319

    // JDE = 2451 550.09765 + 29.530 588 853 k

    // + 0.000 1337 T2

    // - 0.000 000 150 T3

    // + 0.000 000 000 73 T4

    const t = toJulianCenturiesSinceJ2000(julianDay);

    const lhs = julianDay - 2451550.09765 + (-0.0001337 + (0.00000015 - 0.00000000073 * t) * t) * t * t;

    return lhs / 29.530588853;
}

enjoy. its from the same source as your work appears to use...

which, as someone who as implemented a ton of this stuff. its kind of a damning sentiment that we still refer to such an old book instead of learning the problem space and making better solutions... i have a whole book in me about this at some point.


> but note that it is only so accurate for each quarter phase

So it's not actually comparable at all?


thats a fair cop

tbh the small error can be very easily fixed. its close to the difference between a sine wave and its linear piecewise approximation from tip to tip, hence the very high accuracy on the quarters

you could go even further and remove the error from path being nearly a precessing ellipse...

in both cases what you will get is nothing that you would detect by eye or even with a telescope.

for the stated purpose this algorithm does fine in almost every use case including astronomical ones.


i did a quick code review. this mountain of code has the same error and the same precision with identifying phases.


Yeah these things are really piles of code and feature integration and can obscure the underlying logic somewhat.

1. Moon phase (or "age") is trivial to compute using modular arithmetic, given an absolute moment in time as an offset relative to a reference full moon time.

2. Converting datetime formats to a reference time can get complicated, particularly if you want to handle historical dates where calendar systems changed.

3. Converting phase into named bins is a pretty trivial lookup table to quantize the continuous phase value.

4. Converting phase into an approximate percent illumination is pretty trivial geometry, using a simplified model of the moon as a circle with an elliptical boundary for the shaded versus lit part.

5. Converting percent illumination into apparent brightness requires much more work to actually compute the positions of the sun, moon, and earthbound observer.

6. Finding moonrise and moonset times requires even more work to repeatedly find the positions and search for horizon crossings.

Edit: and computing practical brightness is even more complex as you need to fuse together the above with meteorological data to consider what light actually reaches the observer!


> 0001337

Should I be suspicious of 'LEET code showing up in here?

https://en.wikipedia.org/wiki/Leet


nice spot


You almost earned a downvote from me for the unnecessarily negative tone. You have a great post and the idea that there's a more elegant solution is awesome, but I'm puzzled why you chose to phrase your response that way.

To your point though, is this your solution? Is the accuracy helped by using more precise numbers or is it more of a structural limitation?


downvote me. im tired of masses of bloat and excessive code


nice! I have needed this before. I think you're using the Meeus algorithm from Astronomical Algorithms? it's a classic, great choice


Yes, pretty much every good library I came across referenced Meeus' Astronomical Algorithms [0] book and formulas. Those were a bit dense for me to fully parse, but Dr. Louis Strous` page [1] on finding the position of the moon helped simplify it for me (although that implementation is less accurate, I think).

The algorithm in Moontool for Windows [2], which Tiny Moon is based off of, is based off of Meeus' algorithm.

0. https://www.agopax.it/Libri_astronomia/pdf/Astronomical%20Al...

1. https://aa.quae.nl/en/reken/hemelpositie.html#4

2. https://www.fourmilab.ch/moontoolw/


Hi,

Great library, super documentation and the code seems well written with lots of tests. Kudos, I will definitely check it out for my next ios project.

Thanks Otto


Nice work. Just FYI for anybody else - it adds an icon to status bar (if that's what it's called? - top right icons). I thought it was going to open an app window and didn't notice the icon, so thought it wasn't working. (In hindsight, it's clear from screenshots in app store).


I think it's called the menu bar, and apps that don't open windows (with an icon in the menu bar) are known as "menu bar apps". Until recently, these were a PITA to implement with Apple's SDK. Thankfully they provided API support sometime I think starting with macOS 13... before that it was pretty kludgy.


The menus themselves are called "menu bar extras": https://developer.apple.com/design/human-interface-guideline...


And then, there was an anecdote!

I bought my friends daughter a watch for her birthday. She was, like, 12 or something. I got it at Target.

And it had a moon phase dial! How about that?!

So I was determined to set it accurately for her.

To wit I started dredging through the internet. And to be completely honest, I'm not quite sure how I was doing that, having nothing but a Netcom.com shell account available to me. Perhaps I was using Lynx, or using some mail gateway, or who knows what. It wasn't no 10 seconds on Google, I'll tell you that.

In the end, I stumbled upon some code for my HP-48. Aha! This should do nicely!

Somehow, I got the code into the calculator. Did I type it in? Did I download it over Kermit? Who knows. Times were moving fast back then. Somehow, someway, however, I got that code in and calculated away.

Finally! ACCURATE results. Done with precision and math and engineering. Done Right!

To wit, I then proceeded to get the watch set properly.

And...it was a fashion watch for a 12 year old girl from Target. This may come as a shock to some, I was obviously taken aback by it, however -- the moon phase did not work. It was just a moon on a dial that moved, though some undetermined mechanism. It could not be set independently.

Oh.


I have a site with a lot of these stand alone "snippets" so that you don't have to include/port an entire astronomy library just to get the Sun rise/set times, etc. https://www.celestialprogramming.com/

Most of them are written in JavaScript only, but specifically written to be easy to port to other languages.


Here's a tiny calculator for moon phase that I kludged up ca. 2018 but (at least as of this month) is still tracking the full moon (which makes sense given that the algo came out of a dead tree source from over half a century ago?):

  _=min
  lphase = lambda y,m,d: _( lkp(ph,p)
        for lkp in [lambda t,x: _(v for (kl,v),(kh,w)
                        in zip(t,t[1:]) if kl <= x < kh)]
        for daynum  in [lambda y,m,d: daynum(y-1,m+12,d) if m<3 else
                         y*365 + y//4 - y//100 + y//400 + (153*m+3)//5 + d]
        for o   in [daynum(y,m,d)-daynum(2000,1,6)]
        for p   in [o%29.53] # mean; varies significantly!
        for ph  in [[(0,"new"),(0.5,"waxing crescent"),(7,"first quarter"),
                (8,"waxing gibbous"),(14.5,"full"),(15.5,"waning gibbous"),
                (21.7,"last quarter"),(22.7,"waning crescent"),(29,"new"),
                (30,None)]] )


So this is just saying that the phase of the moon is the day mod 29.53, starting from a new moon on 2000-01-06. That's about a minute off the actual mean length of a lunation - I guess the error from that approximation is less than the approximation in your "varies significantly"?

(For what it's worth - 29.53 * 300 = 8859 exactly, and 2000-01-06 + 8859 days = 2024-04-08, and I am quite sure there was a new moon on the latter date because of the solar eclipse.)


Yes. (any cultures that have a purely lunar calendar could do away with all the date fiddling? but then again, I guess they'd just know when the next full moon would be...)

[If that constant is only a minute off, it ought to be good for a few centuries more, and I'm not planning on being around longer than decades, so probably good enough for my purposes.]


I think the point is that the lunations vary by a couple of days within a year, so you might confuse users by stating a phase is on the wrong day when they are unaware a low accuracy algorithm is being used. But that algorithm probably would be good enough for just drawing an icon, since the differences would be imperceptable.

Honestly, I recommend people use the most accurate algorithm they practically can, even if it's overkill for the given application. This just avoids user confusion when different apps give different answers, even if it's not significant.

E.g., I have ported VSOP87 which gives the position of the moon and planets to sub-arcsecond accuracy to a couple of dozen languages. They are much, much longer than the "snippets", but are not impractical, and users won't notice much difference in application size or computation time. https://github.com/gmiller123456/vsop87-multilang


Side note, if you use Emacs you can get the phases of the Moon by pressing “M” in either calendar or Org Agenda.


With M-x lunar-phases you get the next three lunar months; something like:

  Thursday, June 6, 2024: New Moon 8:40am (EDT)
  Friday, June 14, 2024: First Quarter Moon 1:20am (EDT)
     .. I removed a few lines ...
  Monday, August 19, 2024: Full Moon 2:24pm (EDT)
  Monday, August 26, 2024: Last Quarter Moon 5:34am (EDT
Also, M-x sunrise-sunset computes sunrise and sunset after you give it lat/long.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: