BedroomLAN

Tilde~A: Alexios' Homepage

Moon Phase Calculator in S2

I have had a homebrew moon phase calculator for various LiveJournal (and similar) sites for a while. It's come to my attention that to this day, no-one does this. Strange, considering so many cultures base so much around the moon and a lunar calendar (unlike the solar Gregorian Calendar in prevailing use all over the world). So, without further ado, behold the moon phase calculator in S2!

Source Code

function pom (DateTime t): int
{
     var int cof;
     var int i;
     var int pom;
     var int best;
     var int[] daymon = [0,0,31,59,90,120,151,181,212,243,273,304,334];
     var int[] fullmoons = [
        10150, 10179, 10209, 10239, 10268, 10298, 10328, 10357, 10387,
        10417, 10446, 10475, 10505, 10534, 10564, 10593, 10623, 10652,
        10682, 10711, 10741, 10771, 10800, 10830, 10859, 10889, 10918,
        10948, 10977, 11007, 11036, 11066, 11095, 11125, 11154, 11184,
        11214, 11243, 11273, 11302, 11332, 11361, 11391, 11420, 11449,
        11479, 11508, 11538, 11568, 11597, 11627, 11657, 11686, 11716,
        11745, 11775, 11804, 11833, 11863, 11892, 11922, 11951, 11981,
        12011, 12041, 12070, 12100, 12129, 12159, 12188, 12217, 12247,
        12276, 12306, 12335, 12365, 12395, 12424, 12454, 12484, 12513,
        12543, 12572, 12601, 12631, 12660, 12689, 12719, 12749, 12778,
        12808, 12838, 12868, 12897, 12927, 12956, 12985, 13015, 13044,
        13073, 13103, 13132, 13162, 13192, 13222, 13251, 13281, 13311,
        13340, 13369, 13399, 13428, 13457, 13487, 13516, 13546, 13576,
        13605, 13635, 13665, 13694, 13724, 13753, 13783, 13812, 13842,
        13871, 13900, 13930, 13960, 13989, 14019, 14049, 14078, 14108,
        14137, 14167, 14196, 14226, 14255, 14285, 14314, 14343, 14373,
        14403, 14432, 14462, 14491, 14521, 14551, 14580, 14610, 14639,
        14669, 14698, 14727, 14757, 14786, 14816, 14845, 14875, 14905,
        14935, 14964, 14994, 15023, 15053, 15082, 15111, 15141, 15170,
        15200, 15229, 15259, 15289, 15318, 15348, 15378, 15407, 15437,
        15466, 15495, 15525, 15554, 15583, 15613, 15643, 15672, 15702,
        15732, 15762, 15791, 15821, 15850, 15879, 15909, 15938, 15967,
        15997, 16026, 16056, 16086, 16116, 16146, 16175, 16205, 16234,
        16263, 16293, 16322, 16351, 16381, 16410, 16440, 16470, 16500,
        16529, 16559, 16589, 16618, 16647, 16677, 16706, 16735, 16765,
        16794, 16824, 16854, 16883, 16913, 16943, 16972, 17002, 17031,
        17061, 17090, 17120, 17149, 17178, 17208, 17237, 17267, 17297,
        17326, 17356, 17386, 17415, 17445, 17474, 17504, 17533, 17562,
        17592, 17621, 17651, 17680, 17710, 17740, 17769, 17799, 17829,
        17858, 17888, 17917, 17947, 17976, 18005, 18035, 18064, 18094,
        18123, 18153, 18183, 18212, 18242, 18272, 18301, 18331, 18360,
        18389, 18419, 18448, 18477, 18507, 18537, 18566, 18596, 18626,
        18656, 18685, 18715, 18744, 18773, 18803, 18832, 18861, 18891,
        18920, 18950, 18980, 19010, 19040, 19069, 19099, 19128, 19157,
        19187, 19216, 19245, 19275, 19304, 19334, 19364, 19394, 19423,
        19453, 19483, 19512, 19541, 19571, 19600, 19629, 19659, 19688,
        19718, 19748, 19777, 19807, 19837, 19866, 19896, 19925, 19955,
        19984, 20013, 20043, 20072, 20102, 20131, 20161, 20191, 20220,
        20250, 20280, 20309, 20339, 20368, 20397, 20427, 20456, 20486,
        20515, 20545, 20574, 20604, 20634, 20663, 20693, 20723, 20752,
        20782, 20811, 20840, 20870, 20899, 20929, 20958, 20988, 21017,
        21047, 21077, 21106, 21136, 21166, 21195, 21225, 21254, 21283,
        21313, 21342, 21372, 21401, 21431, 21460, 21490, 21520, 21550,
        21579, 21609, 21638, 21667, 21697, 21726, 21755, 21785, 21814,
        21844, 21874, 21904, 21933, 21963, 21993, 22022, 22051, 22081,
        22110, 22139, 22169, 22198, 22228, 22258, 22288, 22317, 22347,
        22377, 22406, 22435, 22465, 22494, 22523, 22553, 22582, 22612,
        22641, 22671, 22701, 22731, 22760, 22790, 22819, 22849, 22878,
        22907, 22937, 22966, 22996, 23025, 23055, 23085, 23115, 23144,
        23174, 23203, 23233
        ];
     var bool isleap = false;
     var int y;
     # Is this a leap year?
     if ((($t.year % 4) == 0) and
         ((($t.year % 100) !=0) or
          (($t.year % 400) == 0))) {
          $isleap = true;
     }
     $y = $t.year - 1970;
     $cof = 365 * $y;
     # Correct by .25 for each leap year (add 1 as an adjustment
     # because 1972 is the first leap year after 1970). Sanity check:
     # (base 1970) 0=>0, 1=>0, 2=>0, 3=>1, 4=>1, 5=>1, 6=>1, 7=>2, 8=>2, etc.
     $cof = $cof + ($y + 1) / 4;
     # Correct for centuries. This should be easy, really.
     $cof = $cof - ($y + 69) / 100;
     # Correct for 400-year leap exceptions.
     $cof = $cof + ($y + 369) / 400;
     # Summary of leap year rule: a year is leap if divisible by 4,
     # and NOT divisible by 100, UNLESS also divisible by 400.
     # Now add the length of the months that have passed.
     $cof = $cof + $daymon[$t.month];
     if ($isleap and ($t.month > 2)) {
          $cof = $cof + 1;
     }
     $cof = $cof + $t.day - 1;
     # This is bad programming, but S2 only has fundamental flow control.
     foreach $i ($fullmoons) {
          if ($i <= $cof) {
               $best = $i;
          }
     }

     # How many days since the last full moon? Express it in minutes
     # for greater accuracy. The moon's period is 42,532 minutes.
     $cof = ($cof - $best) * 1440;
 
     # We return a value between -14 and 14, such as:
     #
     # The absolute value is the fullness of the moon.
     # The sign signifies waxing (positive) or waning (negative) moon.
     $pom = ($cof * 28 / 42532) - 14;
     return $pom;
}

Explanation

S2 is a simple Turing-complete templating language heavily based on Perl (and interpreted by same). It's okay for simple layouts, but lacks good flow control and mathematics.

The latter flaw mandates using a table of full moons (and calculating stupidly at times). There's one for every month of every year starting with, if I remember correctly, 19971. The number represents the full moon's day, with zero being the Unix Epoch (the first of January, 1970).

It's been a long time, but I believe the table of full moons and some of the code was adapted from the source of the BSD ‘game’ program pom(6). If you have this on your system, you may write a script to get more full-moon days.

Other than that, function pom accepts a DateTime object, and calculates the day number it represents. It then figures out the last full moon before that date, and figures out the phase of the moon at that date based on the moon's period (42,532 minutes).

It returns an integer between -14 and +14, which allows for 29 distinct values (the moon's synodic period, give or take). A value of zero represents the new moon. Negative values represent a waning moon, positive values a waxing moon. Read on for a couple of examples of using this to render a moon phase icon and a textual description.

Using It

If you read the above, using the function should be quite easy. The following two functions accept a DateTime object each, and respectively return a textual representation of the phase of the moon at that date and time (very similar to what the BSD programn pom(6) returns), and the URI of an icon representing the moon phase.

Textual Representation

Here's how to translate the output of pom() to text. It should be very straightforward.

function pomText (DateTime t): string
{
        var int n;
        $n = pom ($t);
        
        if ($n <= -13) {
                return "full";
        } elseif ($n < -7) {
                return "waning gibbous";
        } elseif ($n == -7) {
                return "in its last quarter";
        } elseif ($n < 0) {
                return "waning crescent";
        } elseif ($n == 0) {
                return "new";
        } elseif ($n < 7) {
                return "waxing crescent";
        } elseif ($n == 7) {
                return "in its first quarter";
        } elseif ($n < 13) {
                return "waxing gibbous";
        }
        return "full";
}

Generating Image URIs

Here's how to generate an image URI for each phase of the moon:

function pomIcon (DateTime t): string
{
        var int n;
        $n = pom ($t);  
        return "http://example.com/moon/pom-" + $n + ".gif";
}

You will need to make available 29 different icons for all possible pom() return values of -14 to 14. The values aren't zero-padded. In this case, an example waning gibbous moon could return a URI of http://example.com/moon/pom--10.gif, while a moon in its first quarter would request http://example.com/moon/pom-7.gif. Obviously, changing the last line of the function will change what URIs are generated and how.

You could also use a modified pomText() function to render fewer, or more descriptive URIs, if you want that.

  • 1 So this code will be able to calculate the phase of the moon for all non-backdated LiveJournal posts, which was founded a couple of years later. If you need more entries, feel free to add them.