10
\$\begingroup\$

Introduction

tl;dr

In this challenge you have to calculate the moon's phase for a given date.


This challenge is inspired by the game psycho social audiovisual experiment "Superbrothers: Sword & Sworcery EP". In S:S&S EP the moon's phases are important to the outcome of the adventure as some events occur only at a specific point in time.

Screenshot from Superbrothers: Sword & Sworcery EP

The question is: Which lunar phase is present on a specific date. Each main phase – from new moon to first quarter to full moon to third quarter – is about 7.38 days long. The whole lunar cycle is roughly 29.52 days. Based on these values various methods of calculation exist.1

Input

  • A date based on the Gregorian calendar, between 1 January 1970 and 31 December 2116.
  • You can choose one of the following formats: yyyy-mm-dd, dd.mm.yyyy, dd/mm/yyyy, yyyymmdd or ddmmyyyy.

Output

Output the index [0-7] of the lunar phase based on this zero-indexed array:

['New moon', 'Waxing crescent', 'First quarter', 'Waxing gibbous', 'Full moon', 'Waning gibbous', 'Third quarter', 'Waning crescent`]

Requirements

  • You can write a program or a function. If you go with an anonymous function, please include an example of how to invoke it.
  • Input is accepted from STDIN, command line arguments, as function parameters or from the closest equivalent.
  • This is so shortest answer in bytes wins.
  • Built-ins or external libraries that calculate the moon phase are not allowed.2
  • Standard loopholes are disallowed.

Tests

The values are: date | index of the phase | illumination | name

A full lunar cycle:

08.02.2016 | 0 |   0% | New moon
07.02.2016 | 7 |   2% | Waning crescent
07.02.2016 | 7 |   2% | Waning crescent
06.02.2016 | 7 |   6% | Waning crescent
05.02.2016 | 7 |  12% | Waning crescent
04.02.2016 | 7 |  19% | Waning crescent
03.02.2016 | 7 |  28% | Waning crescent
02.02.2016 | 7 |  37% | Waning crescent
01.02.2016 | 6 |  47% | Third quarter
31.01.2016 | 5 |  56% | Waning gibbous
30.01.2016 | 5 |  65% | Waning gibbous
29.01.2016 | 5 |  74% | Waning gibbous
28.01.2016 | 5 |  82% | Waning gibbous
27.01.2016 | 5 |  89% | Waning gibbous
26.01.2016 | 5 |  94% | Waning gibbous
25.01.2016 | 5 |  98% | Waning gibbous
24.01.2016 | 4 | 100% | Full moon
23.01.2016 | 3 | 100% | Waxing gibbous
22.01.2016 | 3 |  97% | Waxing gibbous
21.01.2016 | 3 |  93% | Waxing gibbous
20.01.2016 | 3 |  86% | Waxing gibbous
19.01.2016 | 3 |  77% | Waxing gibbous
18.01.2016 | 3 |  67% | Waxing gibbous
17.01.2016 | 3 |  56% | Waxing gibbous
16.01.2016 | 2 |  45% | First quarter
15.01.2016 | 1 |  33% | Waxing crescent
14.01.2016 | 1 |  23% | Waxing crescent
13.01.2016 | 1 |  14% | Waxing crescent
12.01.2016 | 1 |   7% | Waxing crescent
11.01.2016 | 1 |   2% | Waxing crescent
10.01.2016 | 0 |   0% | New moon

Random test cases:

14.12.2016 | 4 | 100% | Full moon
16.10.1983 | 3 |  75% | Waxing gibbous
04.07.1976 | 2 |  47% | First quarter
28.11.1970 | 0 |   0% | New moon

As most methods aren't accurate to a scientific level and you also get mixed results on different websites for a couple of these days, it is acceptable if your results are within a ± 1 day range.

Bonus

Reduce your byte count and withdraw:

  • 15% – Print the actual name of the phase as listed in section Output instead of its index.
  • 25% – Print the dates of the upcoming new and full moon separated by a whitespace or newline on empty input.

1 For example: Calculating phase on Wikipedia.
2 Sorry Mathematica.

\$\endgroup\$
  • \$\begingroup\$ My money's on Japt. \$\endgroup\$ – lirtosiast Jan 19 '16 at 16:51
  • \$\begingroup\$ How long does each phase last? You refer to four main phases lasting approximately 7 days, but there are 8 phases to deal with. \$\endgroup\$ – Sherlock9 Jan 19 '16 at 18:16
  • 1
    \$\begingroup\$ I think to help me understand how long each phase should be, can you post a test case of about five consecutive days, or however long it takes to change from, say, "waxing gibbous" to "waning gibbous" by your reckoning? I am having trouble with the definitions because, for example, quarter moons are the instant of 50% illumination, so "first quarter" should only be on the day itself, with "waxing crescent" and "waning crescent" on the days before and after. But I'm not sure. \$\endgroup\$ – Sherlock9 Jan 19 '16 at 19:40
  • \$\begingroup\$ Alright then, I'll get started on my solution. Thanks for clearing some of this up. \$\endgroup\$ – Sherlock9 Jan 19 '16 at 19:59
  • \$\begingroup\$ @Sherlock9 I've updated the test cases with a full lunar cycle and some random values, including illumination of each day. Hopefully this is helpful. \$\endgroup\$ – insertusernamehere Jan 20 '16 at 14:03
3
\$\begingroup\$

Python 2 3, 255 204 180 178 bytes

This is answer is inaccurate by a day or two in several places, including for some of the test cases, though I was told that some inaccuracy was acceptable. At any rate, the motion of the moon is never very exact and this function remains generally correct (or at least, it doesn't vary too far).

Edit: In the course of correcting my code and making it more accurate, I have golfed it down considerably.

Edit: This code is now a one-line Python 3 program. (Credit to TimmyD for the name "magic numbers")

p,q,r=(int(i)for i in input().split("-"));t=q<3;y=p-2000-t;i,j=divmod(((r+(153*(q+12*t-3)+2)//5+365*y+y//4-y//100+y//400+11010)*86400-74100)%2551443,637861);print((86400<=j)+2*i)

Ungolfed:

def jul(p,q,r):
    '''
    The Julian Day Number (JDN) of the input minus the JDN of January 7, 1970,
    the first new moon after January 1, 1970.
    '''
    t=q<3
    y=p-2000-t  # -2000 years to push day 0 to January 1, 2000
    return r+(153*(q+12*t-3)+2)//5+365*y+y//4-y//100+y//400+11010
    # +11010 days to push day 0 to January 7, 1970

def moon(s):
    '''
    Input format: yyyy-mm-dd

    An attempt at explaining the "magic numbers"
    - 29.53059 days is close to 2551443 seconds, so I used that
    - The offset of +12300 seconds because the new moon of 1970-01-07 was at 2035 UTC 
      or 12300 seconds before midnight. For those of you saying that this pushes 
      the beginning of my calendar to 2035, *6* January 1970, yes it does.
      But if I need to the calendar to recognize 1970-01-07 as the day of the new moon 
      which means that midnight needed to be a positive number of seconds, 0 <= x < 86400.
      Basically, I hacked it together, and +12300 worked.        
    '''
    d = 86400
    p,q,r = map(int, s.split("-"))
    z=(jul(p,q,r)*d+12300)%2551443  # 2551443 is about the number of seconds in a lunar month
    div, mod = divmod(z, 637861)    # 637861 seconds is about a quarter of a lunar month
                                    # div is what part of the lunar month this is (0 - 3)
                                    # mod is seconds since the start of the main phase
    return 2*div + (86400 <= mod)   # 2*div for the main phase, and 
                                    # is mod >= the number seconds in a day?
                                    # (+0 if within a day of the main phase, +1 otherwise)
\$\endgroup\$
  • \$\begingroup\$ @TimmyD You have no idea how many magic numbers I tried and threw out to get this to work XD \$\endgroup\$ – Sherlock9 Jan 22 '16 at 15:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.