Spinning Moon

Here’s the code for the moon phase calculations included on my Web pages. This is based on calculations given in the book
Duffett-Smith, Peter. 1988. Practical Astronomy with Your Calculator, 3rd Ed. Cambridge Univ. Press.

Here’s a QBASIC program I wrote that asks the user to supply a date, time, and time zone, then calculates the moon phase for that time. This is simplified from my original code which performed calculations for a three-month period. Since it is based on user input, I tried to include as much error checking as occurred to me. Also, the calculations are cumbersome in places because QBASIC does not do date arithematic.

DIM Months$(12)

SCREEN 13: WIDTH 80
'Get the date from the user.
Retry:
COLOR 1
INPUT "Enter a date in the format mm-dd-yyyy>  ", ThisDate$
year = VAL(RIGHT$(ThisDate$, 4))
month = VAL(LEFT$(ThisDate$, 2))
day = VAL(MID$(ThisDate$, 4, 2))

IF year = 0 OR month = 0 OR day = 0 THEN
        PRINT "You did not enter the date in the proper format."
        IF year = 0 THEN PRINT "You specified a year of 0."
        IF month = 0 THEN PRINT "You specified a month of 0."
        IF day = 0 THEN PRINT "You specified a day of 0."
        PRINT "Please try again using the format mm-dd-yyyy."
        GOTO Retry
END IF

IF year < 1000 OR year > 2500 THEN
        PRINT "The year you entered was"; year
        PRINT "That doesn't make sense to me."
        INPUT "Do you want to continue anyway?", Reply$
        Reply$ = LEFT$(Reply$, 1)
        Reply$ = LCASE$(Reply$)
        IF Reply$ = "n" THEN
                CLS
                GOTO Retry
        END IF
END IF

IF day < 1 THEN
        PRINT "The day you entered was"; day
        PRINT "That doesn't make sense to me."
        PRINT "Try again using mm-dd-yyyy format."
        GOTO Retry
END IF

LeapCheck = (year - 1980) MOD 4
        IF LeapCheck = 0 THEN
                leap$ = "yes"
        ELSEIF LeapCheck <> 0 THEN
                leap$ = "no"
        END IF

SELECT CASE month
        CASE 1
                month$ = "January"
                IF day > 31 GOTO TooMany
        CASE 2
                month$ = "February"
                IF leap$ = "yes" AND day > 29 GOTO TooMany
                IF leap$ = "no" AND day > 28 GOTO TooMany
        CASE 3
                month$ = "March"
                IF day > 31 GOTO TooMany
        CASE 4
                month$ = "April"
                IF day > 30 GOTO TooMany
        CASE 5
                month$ = "May"
                IF day > 31 GOTO TooMany
        CASE 6
                month$ = "June"
                IF day > 30 GOTO TooMany
        CASE 7
                month$ = "July"
                IF day > 31 GOTO TooMany
        CASE 8
                month$ = "August"
                IF day > 31 GOTO TooMany
        CASE 9
                month$ = "September"
                IF day > 30 GOTO TooMany
        CASE 10
                month$ = "October"
                IF day > 31 GOTO TooMany
        CASE 11
                month$ = "November"
                IF day > 30 GOTO TooMany
        CASE 12
                month$ = "December"
                IF day > 31 GOTO TooMany
        CASE ELSE
                month$ = STR$(month)
                PRINT month$; " is not a valid month."
                PRINT "Please try again."
                GOTO Retry
END SELECT
GOTO Verify

TooMany:
PRINT "There aren't"; day; "days in "; month$
PRINT "Please try again."
GOTO Retry

Verify:
COLOR 2
PRINT "The day you chose was "; day; month$; year
COLOR 3
INPUT "Is this correct? ", Reply$
Reply$ = LEFT$(Reply$, 1)
Reply$ = LCASE$(Reply$)
IF Reply$ = "n" THEN
        CLS
        GOTO Retry
END IF

'Get the time from the user.
Timetry:
COLOR 4
INPUT "Enter a time in the format hh:mm:ss>  ", ThisTime$
second = VAL(RIGHT$(ThisTime$, 2))
hour = VAL(LEFT$(ThisTime$, 2))
minute = VAL(MID$(ThisTime$, 4, 2))

IF hour < 0 THEN
        PRINT "You specified less than 0 hours."
        PRINT "Try again."
        GOTO Timetry
ELSEIF minute < 0 THEN
        PRINT "You specified less than 0 minutes."
        PRINT "Try again."
        GOTO Timetry
ELSEIF second < 0 THEN
        PRINT "You specified less than 0 seconds."
        PRINT "Try again."
        GOTO Timetry
ELSEIF second > 60 THEN
        PRINT "You specified more than 60 seconds."
        PRINT "Try again."
        GOTO Timetry
ELSEIF minute > 60 THEN
        PRINT "You specified more than 60 minutes."
        PRINT "Try again."
        GOTO Timetry
ELSEIF hour > 24 THEN
        PRINT "You specified more than 24 hours."
        PRINT "Try again."
        GOTO Timetry
END IF

TimeOfDay:
COLOR 5
INPUT "Is this AM or PM?", ampm$
ampm$ = LEFT$(ampm$, 1)
ampm$ = LCASE$(ampm$)
IF ampm$ = "a" OR ampm$ = "p" THEN GOTO FixIt
GOTO TimeOfDay

FixIt:
IF ampm$ = "p" AND hour < 12 THEN hour = hour + 12
IF ampm$ = "a" AND hour > 12 THEN hour = hour - 12

COLOR 6
PRINT "The time you chose is"; hour; ":"; minute; ":"; second; ampm$
COLOR 7
INPUT "Is this correct? ", Reply$
Reply$ = LEFT$(Reply$, 1)
Reply$ = LCASE$(Reply$)
IF Reply$ = "n" THEN
        CLS
        GOTO Timetry
END IF

' correct to GMT
TimeZone:
COLOR 9
PRINT "Choose a Time Zone:"
COLOR 10
PRINT "5.  Eastern Standard Time"
COLOR 11
PRINT "4.  Eastern Daylight Time"
COLOR 12
PRINT "6.  Central Standard Time"
COLOR 13
PRINT "5.  Central Daylight Time"
COLOR 14
PRINT "0.  Greenwich Mean Time"
COLOR 15
INPUT "Pick a number corresponding to a time zone. ", choice

uthour = hour + choice

IF uthour >= 24 THEN
        uthour = uthour - 24
        IF uthour < 12 THEN ampm$ = "a"
        day = day + 1
END IF

IF day > 31 AND month = 1 THEN
        month = 2
        month$ = "February"
        day = 1
ELSEIF day > 29 AND month = 2 AND leap$ = "yes" THEN
        month = 3
        month$ = "March"
        day = 1
ELSEIF day > 28 AND month = 2 AND leap$ = "no" THEN
        month = 3
        month$ = "March"
        day = 1
ELSEIF day > 31 AND month = 3 THEN
        month = 4
        month$ = "April"
        day = 1
ELSEIF day > 30 AND month = 4 THEN
        month = 5
        month$ = "May"
        day = 1
ELSEIF day > 31 AND month = 5 THEN
        month = 6
        month$ = "June"
        day = 1
ELSEIF day > 30 AND month = 6 THEN
        month = 7
        month$ = "July"
        day = 1
ELSEIF day > 31 AND month = 7 THEN
        month = 8
        month$ = "August"
        day = 1
ELSEIF day > 31 AND month = 8 THEN
        month = 9
        month$ = "September"
        day = 1
ELSEIF day > 30 AND month = 9 THEN
        month = 10
        month$ = "October"
        day = 1
ELSEIF day > 31 AND month = 10 THEN
        month = 11
        month$ = "November"
        day = 1
ELSEIF day > 30 AND month = 11 THEN
        month = 12
        month$ = "December"
        day = 1
ELSEIF day > 31 AND month = 12 THEN
        month = 1
        month$ = "January"
        day = 1
        year = year + 1
END IF

COLOR 2
PRINT "The date and time you chose was "; day; month$; year; uthour; ":"; minute; ":"; second; ampm$; " UT"

NumHrs = uthour + (minute + (second / 60)) / 60

'Calculate number of days since beginning of the specified year.
IF month > 2 GOTO No8                   'no 3-2
DayNo = month - 1                       'no 3-3
        IF leap$ = "no" THEN            'no 3-4
                DayNo = DayNo * 63
        ELSEIF leap$ = "yes" THEN
                DayNo = DayNo * 62
        END IF
DayNo = DayNo \ 2                       'no 3-5&6
GOTO No12                               'no 3-7
No8:
DayNo = (month + 1) * 30.6              'no 3-8&9
DayNo = INT(DayNo)                      'no 3-10
        IF leap$ = "no" THEN            'no 3-11
                DayNo = DayNo - 63
        ELSEIF leap$ = "yes" THEN
                DayNo = DayNo - 62
        END IF
No12:
DayNo = DayNo + day                     'no 3-12
DayNo = DayNo + NumHrs / 24
'PRINT "This is"; DayNo; "days into the year."

OtherDays = (year - 1990) * 365
leapdays = INT((year - 1989) / 4)
D = OtherDays + leapdays                      'no 46-1
'PRINT "Days from Jan. 1 of current year to 0-0-90 is "; D
D = D + DayNo                                 'no 46-2
'PRINT "Total days from then is "; D


NoOfDays = D + nCounter / 2
N = NoOfDays * (360 / 365.242191#)                   'no 46-3
N = ABS(N) - INT(ABS(N / 360)) * 360          'no 46-3
Mo = N + 279.403303# - 282.768422#            'no 46-4
IF Mo < 0 THEN Mo = Mo + 360                  'no 46-4
Ec = 360 * .016713 * SIN(Mo * 3.141592654# / 180) / 3.141592654#   'no 46-5
lamda = N + Ec + 279.403303#                  'no 46-6
IF lamda > 360 THEN lamda = lamda - 360       'no 46-6
'PRINT "N", "Mo", "Ec", "Lamda"
'PRINT N, Mo, Ec, lamda
l = 13.1763966# * NoOfDays + 318.351648#             'no 65-4
l = ABS(l) - INT(ABS(l / 360)) * 360          'no 65-4
Mm = l - .1114041 * D - 36.34041#             'no 65-5
Mm = ABS(Mm) - INT(ABS(Mm / 360)) * 360       'no 65-5
N65 = 318.510107# - .0529539 * NoOfDays              'no 65-6
N65 = ABS(N65) - INT(ABS(N65 / 360)) * 360    'no 65-6
Ev = 1.2739 * SIN((2 * (1 - lamda) - Mm) * 3.141592654# / 180)     'no 65-7
'PRINT "l", "Mm", "N65", "Ev"
'PRINT l, Mm, N65, Ev
Ae = .1858 * SIN(Mo * 3.141592654# / 180)     'no 65-8
A3 = .37 * SIN(Mo * 3.141592654# / 180)       'no 65-8
Mmp = Mm + Ev - Ae - A3                       'no 65-9
Ec = 6.2886 * SIN(Mmp * 3.141592654# / 180)   'no 65-10
'PRINT "Ae", "A3", "Mmp", "Ec"
'PRINT Ae, A3, Mmp, Ec
A4 = .214 * SIN((2 * Mmp) * 3.141592654# / 180)                    'no 65-11
lp = l + Ev + Ec - Ae + A4                    'no 65-12
V = .6583 * SIN((2 * (lp - lamda)) * 3.141592654# / 180)           'no 65-13
lpp = lp + V                                  'no 65-14
'PRINT "A4", "lp", "V", "lpp"
'PRINT A4, lp, V, lpp
D67 = lpp - lamda                             'no 67-2
F = .5 * (1 - COS(D67 * 3.141592654# / 180))  'no 67-3
'PRINT "D67", "F"
'PRINT D67, F
x = (SIN(D67 * 3.141592654# / 180))

'variables created include:  NoOfDays, F, x

        IF F < .01 OR (F > .45 AND F < .55) OR F > .98 THEN
                SELECT CASE F
                        CASE IS < .003
                                Phase$ = "New"
                                MoonDate = NoOfDays
                                GOTO GotIt
                        CASE .47 TO .53
                                IF SGN(x) > 0 THEN Phase$ = "First Quarter"
                                IF SGN(x) < 0 THEN Phase$ = "Last Quarter"
                                MoonDate = NoOfDays
                                GOTO GotIt
                        CASE IS > .997
                                Phase$ = "Full"
                                MoonDate = NoOfDays
                                GOTO GotIt
                END SELECT
        END IF
'variables created include Phase$, MoonDate=NoOfDays

GotIt:
        Months$(1) = "Jan"
        Months$(2) = "Feb"
        Months$(3) = "Mar"
        Months$(4) = "Apr"
        Months$(5) = "May"
        Months$(6) = "Jun"
        Months$(7) = "Jul"
        Months$(8) = "Aug"
        Months$(9) = "Sep"
        Months$(10) = "Oct"
        Months$(11) = "Nov"
        Months$(12) = "Dec"
       
        MoonDate = MoonDate - D - (choice / 24)   'recalc date from start of loop - ut correction
        MoonDate = MoonDate + day
        MoonDate = ((MoonDate * 100) \ 10) / 10
        TxtMonth$ = Months$(month)

        IF MoonDate > 31 AND (month = 1 OR month = 3 OR month = 5 OR month = 7 OR month = 8 OR month = 10 OR month = 12) THEN
                MoonDate = MoonDate - 31
                TxtMonth$ = Months$(month)
        ELSEIF MoonDate > 28 AND month = 2 AND leap$ = "no" THEN
                MoonDate = MoonDate - 28
                TxtMonth$ = Months$(month)
        ELSEIF MoonDate > 29 AND month = 2 AND leap$ = "yes" THEN
                MoonDate = MoonDate - 29
                TxtMonth$ = Months$(month)
        ELSEIF MoonDate > 30 AND (month = 4 OR month = 6 OR month = 9 OR month = 11) THEN
                MoonDate = MoonDate - 30
                TxtMonth$ = Months$(month)
        END IF
      
        IF MoonDate > INT(MoonDate + .5) THEN
                MoonDate = INT(MoonDate)
        ELSE
                MoonDate = INT(MoonDate + .5)
        END IF

COLOR 3
PRINT "Phase", "Date"
COLOR 4
PRINT Phase$, MoonDate; " "; TxtMonth$; " "; year
COLOR 5

This code was eventually “translated” into JavaScript, again performing calculations for a three-month period. Since JavaScript has some built-in date functions, the date arithematic was easier. Again, here is a simplified version of the code that grabs the user’s system time and calculates the moon phase from that. In my original version, I included the option for user input, thus considerable error checking (similar to the above) was included. Since this version does not allow for user input, but works from the system time, all that error checking (is it really a valid date?) has not been included. These calculations have subsequently been updated to compensate for browser Y2K bugs and a slight error in the former calculations.

today = new Date();         // initialize to current date
hh = today.getHours();          // goes from 0 to 23
var ampm = "am"
        var NoLoops = hh        // this variable is also used for the cuckoo clock
                if(hh == 0) { NoLoops = 12};
                if(hh > 12) { NoLoops = hh - 12; ampm = "pm" }
                if(hh == 12) { ampm = "pm" }
var dd = today.getDate();
var mm = today.getMonth() + 1;  // Jan is 0, Feb is 1, etc., hence the +1
var dow = today.getDay();
MonNames = new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
var ThisMonth = MonNames[mm];

// var yy = today.getYear();
// browser Y2K bug fix -- convert from msec to years because getYear() doesn't work right
    var millisec = today.getTime();            // this gives msecs
    var yy = ((((millisec / 1000) / 3600) / 24) / 365.25);
    yy = Math.floor(yy);
    yy +=1970;

var txtDate = "" + ((dd < 10) ? "0" : "") + dd;   // add 0 if less than 10 so displays right
txtDate += "&nbsp;" + ThisMonth;
txtDate += "&nbsp;" + yy;
var mn = today.getMinutes();        // goes from 0 to 59
var txtTime = "" + ((NoLoops < 10) ? "0" : "") + NoLoops;
txtTime += ((mn<10) ? ":0" : ":") + mn;

// here's where the calculations from the book start
var moondate = today;
tzone = moondate.getTimezoneOffset() / 60               // in min so convert to hours
var moonmsec = moondate.getTime();                      // this gives msecs
GMtime = moonmsec + (tzone * 60 * 60 * 1000)            // GMT in msec
// adapted from my VB code
var startDate = new Date(89, 11, 31, 00, 00, 00);       // equivalent of the book's 0 Jan 90
var startMsec = startDate.getTime();
var dmsec = GMtime - startMsec;
D = ((((dmsec /1000) /60) /60) /24);
var n = D * (360 / 365.242191);                         //no 46-3
if (n > 0) {
        n = n - Math.floor(Math.abs(n / 360)) * 360;    //no 46-3
} else {
        n = n + (360 + Math.floor(Math.abs(n / 360)) * 360);  //no 46-3
}
var Mo = n + 279.403303 - 282.768422;                   //no 46-4;
if(Mo < 0) { Mo = Mo + 360 }                         //no 46-4
var Ec = 360 * .016713 * Math.sin(Mo * 3.141592654 / 180) / 3.141592654;        //no 46-5
var lamda = n + Ec + 279.403303;                        //no 46-6
if(lamda > 360) { lamda = lamda - 360 }              //no 46-6
var l = 13.1763966 * D + 318.351648;                    //no 65-4
if (l > 0) {
        l = l - Math.floor(Math.abs(l / 360)) * 360;    //no 65-4
} else {
        l = l + (360 + Math.floor(Math.abs(l / 360)) * 360);  //no 65-4
}
var Mm = l - .1114041 * D - 36.34041;                   //no 65-5
if (Mm > 0) {
        Mm = Mm - Math.floor(Math.abs(Mm / 360)) * 360; //no 65-5
} else {
        Mm = Mm + (360 + Math.floor(Math.abs(Mm / 360)) * 360); //no 65-5
}
var N65 = 318.510107 - .0529539 * D;                    //no 65-6
if (N65 > 0) {
        N65 = N65 - Math.floor(Math.abs(N65 / 360)) * 360;      //no 65-6
} else {
        N65 = N65 + (360 + Math.floor(Math.abs(N65 / 360)) * 360);      //no 65-6
}
var Ev = 1.2739 * Math.sin((2 * (l - lamda) - Mm) * 3.141592654 / 180); //no 65-7
var Ae = .1858 * Math.sin(Mo * 3.141592654 / 180);      //no 65-8
var A3 = .37 * Math.sin(Mo * 3.141592654 / 180);        //no 65-8
var Mmp = Mm + Ev - Ae - A3;                            //no 65-9
var Ec = 6.2886 * Math.sin(Mmp * 3.141592654 / 180);    //no 65-10
var A4 = .214 * Math.sin((2 * Mmp) * 3.141592654 / 180);        //no 65-11
var lp = l + Ev + Ec - Ae + A4;                         //no 65-12
var V = .6583 * Math.sin((2 * (lp - lamda)) * 3.141592654 / 180);       //no 65-13
var lpp = lp + V;                                       //no 65-14
var D67 = lpp - lamda;                                  //no 67-2
Ff = .5 * (1 - Math.cos(D67 * 3.141592654 / 180));      //no 67-3
Xx = (Math.sin(D67 * 3.141592654 / 180));

// figure out what phase the moon is in and what icon to use to go with it
var MoonPhaseMsg = "Just for fun, the moon is ";
if(Ff < .02) {
        document.write ('<IMG name="moonpic" SRC="graphics/steincarter/moon/moon01.gif" HEIGHT="32" WIDTH="32" ALIGN="right" alt="">');
        MoonPhaseMsg += "new."
}
if((Ff > .45) && (Ff < .55) && (Xx > 0)) {
        document.write ('<IMG name="moonpic" SRC="graphics/steincarter/moon/moon03.gif" HEIGHT="32" WIDTH="32" ALIGN="right" alt="">');
        MoonPhaseMsg += "first quarter."
}
if((Ff > .45) && (Ff < .55) && (Xx < 0)) {
        document.write ('<IMG name="moonpic" SRC="graphics/steincarter/moon/moon07.gif" HEIGHT="32" WIDTH="32" ALIGN="right" alt="">');
        MoonPhaseMsg += "last quarter."
}                
if(Ff > .98) {
        document.write ('<IMG name="moonpic" SRC="graphics/steincarter/moon/moon05.gif" HEIGHT="32" WIDTH="32" ALIGN="right" alt="">');
        MoonPhaseMsg += "full."
}                                                                             
if((Ff > .02) && (Ff < .45) && (Xx > 0)) {
        document.write ('<IMG name="moonpic" SRC="graphics/steincarter/moon/moon02.gif" HEIGHT="32" WIDTH="32" ALIGN="right" alt="">');
        MoonPhaseMsg += "waxing."
}
if((Ff > .02) && (Ff < .45) && (Xx < 0)) {
        document.write ('<IMG name="moonpic" SRC="graphics/steincarter/moon/moon08.gif" HEIGHT="32" WIDTH="32" ALIGN="right" alt="">');
        MoonPhaseMsg += "waning."
}
if((Ff > .55) && (Ff < .98) && (Xx > 0)) {
        document.write ('<IMG name="moonpic" SRC="graphics/steincarter/moon/moon04.gif" HEIGHT="32" WIDTH="32" ALIGN="right" alt="">');
        MoonPhaseMsg += "waxing gibbous."
}
if((Ff > .55) && (Ff < .98) && (Xx < 0)) {
        document.write ('<IMG name="moonpic" SRC="graphics/steincarter/moon/moon06.gif" HEIGHT="32" WIDTH="32" ALIGN="right" alt="">');
        MoonPhaseMsg += "waning gibbous."
}

document.write (MoonPhaseMsg);
document.write (' The moon phase calculations are based on your computer reporting that is is');
document.write (" " + txtTime + "&nbsp;" + ampm + " on " + txtDate + ",\n");
document.write (' and that you are ' + tzone + ' hours from GMT.');

Here is the JavaScript code in action:

If you’re using Netscape, the time zone offset is properly reported, but if you're using IE, the time zone offset is reported in negative numbers (however, the calculations still appear to work OK). For comparison, EST is +5 hours and EDT is +4 hours. From some information I found on the Web a couple years ago, if your computer is incorrectly reporting your time zone (they tend to think they’re in Silicon Valley unless you tell them otherwise) and you want to fix it, you need to add a line similar to set TZ=EST+05EDT to your autoexec.bat file (goodness only knows how to fix Bill Gates’ new non-DOS wonders...), but if you are not in the Eastern Time Zone (eastern United States), you will need to use the correct time zone offset for your area. If you are using Windows 95, make sure you have supplied the correct settings in the Date/Time portion of the Control Panel.


The saga continues. . .
In December of 2000, I had need of this code in a Perl script. All year I had been “fixing” JavaScript and Perl scripts because neither language was/is Y2K-compliant and scripts variously reported year 2000 as either “0” or “100.” Both JavaScript and Perl (and perhaps other computer languages) were written such that 1 January 1970 is time 0. JavaScript keeps track of times before then as negative milliseconds from that time. For example, if JavaScript is given a date of EarlyDate = new Date(1963,10,22,14,0,0);, this is interpreted as milliseconds from 1-1-1970. Perl’s date functions are more limited than JavaScript’s, and it totally refuses to deal with negative time values. Thus, while SomeDate = new Date(-123); will work in JavaScript, $SomeDate = time(-123); does not work in Perl. However, JavaScript does not easily deal with dates before the year 1000. For example, EarlyDate = new Date(63,10,22,14,0,0); is interpreted as milliseconds from 1-1-1970, but that’s not what I wanted. If I go to all the trouble to “manually” calculate the necessary number of milliseconds, I can force it to use the correct date.
theMsec = (-1906 * 365.25 * 24 * 3600 * 1000); returns
theMsec = (-1906 * 365.25 * 24 * 3600 * 1000) + (-25 * 24 * 3600 * 1000); returns
and finally,
theMsec = (-1906 * 365.25 * 24 * 3600 * 1000) + (-25 * 24 * 3600 * 1000) + (7 * 3600 * 1000); returns

However, that date was not a Thursday! According to the astronomy book cited above, back in 1582, Pope Gregory decreed that the days 5 through 14 October of that year just did not/do not exist. JavaScript, however, reports the day before 15 October 1582 as being 14 October, not 4 October, and thus, any day-of-week calculations it does before then are ten days off. Also, Pope Gregory adjusted our time calculations such that, since then, years that end in “00” are only leap years if they are divisible by 400. That means that 1600 and 2000 were leap years, but 1700, 1800, and 1900 were not. JavaScript dutifully takes that into account, so as far as it is concerned, 29 February 1500 did not exist. That means that day-of-week calculations for any dates in the 1400s are 11 days off. While I haven’t checked, I would suppose the same is true for 1400, 1300, etc.

So... back to the handy astronomy book, which includes calculations for what astronomers call “Julian dates” in honor of Julius Caesar. Now, the JavaScript code looks like this (isLegal is another function that tests to see if user input is really a valid number):

var today = new Date();
        var tzone = today.getTimezoneOffset() / 60;      // in min so convert to hours
        tzhr = tzone;
        tzone = tzone / 24;                             // convert to fraction of a day
var FractDay = 0;
// EpochDate = new Date(89, 11, 31, 00, 00, 00);       // equivalent of the book's 0 Jan 90
// is equiv to JD 2447891.5
var Epoch = 2447891.5;
function phasecalc() {                               //button click handler
        yyy = document.form1.theYear.value;
                isNameOK = 1;
                NotASpace = 0;
                isLegal(yyy, 1);
                while (isNameOK == 0 || NotASpace == 0 || newNameStr > 9999 || newNameStr < -4713) {
                        testNo = prompt ('Your year number can&#146;t be \"' + newNameStr + '\"!  Put a valid year here:', newNameStr);
                        isLegal(testNo, 1);
                }
                document.form1.theYear.value = yyy = parseFloat(newNameStr);
                newNameStr = "";
                isLeap = 0;
                testLeap = yyy % 4;     // will be 0 in leap year
                testCent = yyy % 100;    // is it a century year
                testSkip = yyy % 400;   // for 1700, 1800, 1900, don't do leap year, (but yes for 1500, 1600, 2000)
                if ((yyy <=1582 && testLeap == 0) || (yyy > 1582 && testLeap == 0 && testCent != 0) || (yyy > 1582 && testLeap == 0 && testCent == 0 && testSkip == 0)) {
                        isLeap == 1;
                }
        mmm = document.form1.theMnth.value;
                isNameOK = 1;
                NotASpace = 0;
                isLegal(mmm, 1);
                while (isNameOK == 0 || NotASpace == 0 || newNameStr > 12 || newNameStr < 1) {
                        testNo = prompt ('Your month number can&#146;t be \"' + newNameStr + '\"!  Put a valid month here:', newNameStr);
                        isLegal(testNo, 1);
                }
                document.form1.theMnth.value = mmm = parseFloat(newNameStr);
                newNameStr = "";
        ddd = document.form1.theDate.value;
                isNameOK = 1;
                NotASpace = 0;
                isLegal(ddd, 1);
                while (isNameOK == 0 || NotASpace == 0 || newNameStr < 1 || ((mmm == 1 || mmm == 3 || mmm == 5 || mmm == 7 || mmm == 8 || mmm == 10 || mmm == 12) && newNameStr > 31) || ((mmm == 4 || mmm == 6 || mmm == 9 || mmm == 11) && newNameStr > 30) || (mmm == 2 && isLeap == 0 && newNameStr > 28) || (mmm == 2 && isLeap == 1 && newNameStr > 29)) {
                        testNo = prompt ('Your date number can&#146;t be \"' + newNameStr + '\"!  Put a valid date here:', newNameStr);
                        isLegal(testNo, 1);
                }
                document.form1.theDate.value = ddd = parseFloat(newNameStr);
                newNameStr = "";
        hhh = document.form1.theHour.value;
                isNameOK = 1;
                NotASpace = 0;
                isLegal(hhh, 1);
                while (isNameOK == 0 || NotASpace == 0 || newNameStr < 0 || newNameStr > 23 || (yyy == -4713 && mmm == 1 && ddd == 1 && hhh < 12)) {
                        testNo = prompt ('Your hour number can&#146;t be \"' + newNameStr + '\"!  Put a valid hour here:', newNameStr);
                        isLegal(testNo, 1);
                }
                document.form1.theHour.value = hhh = parseFloat(newNameStr);
                newNameStr = "";
        FractDay = hhh / 24;
        mmn = document.form1.theMins.value;
                isNameOK = 1;
                NotASpace = 0;
                isLegal(mmn, 1);
                while (isNameOK == 0 || NotASpace == 0 || newNameStr > 59 || newNameStr < 0) {
                        testNo = prompt ('Your minutes number can&#146;t be \"' + newNameStr + '\"!  Put a valid minutes here:', newNameStr);
                        isLegal(testNo, 1);
                }
                document.form1.theMins.value = mmn = parseFloat(newNameStr);
                newNameStr = "";
        FractDay += mmn / 60 / 24;

        // this calculates the Julian Date
        if (mmm < 3) {  // if 1 or 2 (this has been changed to 1-base)
                yyy--;
                mmm += 12;
        }
        if ((yyy >= 1583) || (yyy == 1582 && mmm > 10) || (yyy == 1582 && mmm == 10 && ddd >= 15)) {
        // if it's after Gregory changed the calendar
                AtoJD = Math.floor(yyy / 100);
                BtoJD = 2 - AtoJD + Math.floor(AtoJD / 4);
        } else {
                BtoJD = 0;
        }
        if (yyy < 0) {
                CtoJD = Math.ceil((365.25 * yyy) - 0.75);
        } else {
                CtoJD = Math.floor(365.25 * yyy);
        }
        DtoJD = Math.floor(30.6001 * (mmm + 1)); // gotta use 1 bec mmm is 1-based
        JD = parseFloat(BtoJD) + parseFloat(CtoJD) + parseFloat(DtoJD) + parseFloat(ddd) + parseFloat(1720994.5);

        // add on hr & min fract of a day
        JDnow = JD + FractDay;
        // because I was also using this for something else,
        // correct for time zone after convert to JD
        // if >= 1990, use local time zone, otherwise assume GMT
        // note, it doesn't matter if it calculates the date correctly if all we want is the timezone
        // just remember that the time zone correction must first be converted to a fraction of a day
        if (yyy >= 1990) {
                JDnow += tzone;
        }

        // here is where the previous calculations start
        // note the new way of calculating D -- the answer is the same
        D = JDnow - Epoch;                      // find diff from 31 Dec 1989
        var n = D * (360 / 365.242191);                         //no 46-3
        if (n > 0) {
                n = n - Math.floor(Math.abs(n / 360)) * 360;    //no 46-3
        } else {
                n = n + (360 + Math.floor(Math.abs(n / 360)) * 360);  //no 46-3
        }
        var Mo = n + 279.403303 - 282.768422;                   //no 46-4;
        if(Mo < 0) { Mo = Mo + 360 }                            //no 46-4
        var Ec = 360 * .016713 * Math.sin(Mo * 3.141592654 / 180) / 3.141592654;        //no 46-5
        var lamda = n + Ec + 279.403303;                        //no 46-6
        if(lamda > 360) { lamda = lamda - 360 }                 //no 46-6
        var l = 13.1763966 * D + 318.351648;                    //no 65-4
        if (l > 0) {
                l = l - Math.floor(Math.abs(l / 360)) * 360;  //no 65-4
        } else {
                l = l + (360 + Math.floor(Math.abs(l / 360)) * 360);  //no 65-4
        }
        var Mm = l - .1114041 * D - 36.34041;                   //no 65-5
        if (Mm > 0) {
                Mm = Mm - Math.floor(Math.abs(Mm / 360)) * 360;                       //no 65-5
        } else {
                Mm = Mm + (360 + Math.floor(Math.abs(Mm / 360)) * 360);                       //no 65-5
        }
        var N65 = 318.510107 - .0529539 * D;                    //no 65-6
        if (N65 > 0) {
                N65 = N65 - Math.floor(Math.abs(N65 / 360)) * 360;                    //no 65-6
        } else {
                N65 = N65 + (360 + Math.floor(Math.abs(N65 / 360)) * 360);                    //no 65-6
        }
        var Ev = 1.2739 * Math.sin((2 * (l - lamda) - Mm) * 3.141592654 / 180);         //no 65-7
        var Ae = .1858 * Math.sin(Mo * 3.141592654 / 180);      //no 65-8
        var A3 = .37 * Math.sin(Mo * 3.141592654 / 180);        //no 65-8
        var Mmp = Mm + Ev - Ae - A3;                            //no 65-9
        var Ec = 6.2886 * Math.sin(Mmp * 3.141592654 / 180);    //no 65-10
        var A4 = .214 * Math.sin((2 * Mmp) * 3.141592654 / 180);                        //no 65-11
        var lp = l + Ev + Ec - Ae + A4;                         //no 65-12
        var V = .6583 * Math.sin((2 * (lp - lamda)) * 3.141592654 / 180);               //no 65-13
        var lpp = lp + V;                                       //no 65-14
        var D67 = lpp - lamda;                                  //no 67-2
        Ff = .5 * (1 - Math.cos(D67 * 3.141592654 / 180));      //no 67-3
        Xx = (Math.sin(D67 * 3.141592654 / 180));               // I added this to distinguish first from last quarters
        // figure out what phase the moon is in and what icon to use to go with it
        if(Ff < .02) {                                  //new
                document.form1.Phase.src = "../../graphics/steincarter/moon/phase01.gif"
                document.form1.moonpic.src = "../../graphics/steincarter/moon/moon01.gif"
        }
        if((Ff > .45) && (Ff < .55) && (Xx > 0)) {      //first
                document.form1.Phase.src = "../../graphics/steincarter/moon/phase03.gif"
                document.form1.moonpic.src = "../../graphics/steincarter/moon/moon03.gif"
        }
        if((Ff > .45) && (Ff < .55) && (Xx < 0)) {      //last
                document.form1.Phase.src = "../../graphics/steincarter/moon/phase07.gif"
                document.form1.moonpic.src = "../../graphics/steincarter/moon/moon07.gif"
        }                
        if(Ff > .98) {                                  //full
                document.form1.Phase.src = "../../graphics/steincarter/moon/phase05.gif"
                document.form1.moonpic.src = "../../graphics/steincarter/moon/moon05.gif"
        }                                                                             
        if((Ff > .02) && (Ff < .45) && (Xx > 0)) {      //waxing
                document.form1.Phase.src = "../../graphics/steincarter/moon/phase02.gif"
                document.form1.moonpic.src = "../../graphics/steincarter/moon/moon02.gif"
        }
        if((Ff > .02) && (Ff < .45) && (Xx < 0)) {      //waning
                document.form1.Phase.src = "../../graphics/steincarter/moon/phase08.gif"
                document.form1.moonpic.src = "../../graphics/steincarter/moon/moon08.gif"
        }
        if((Ff > .55) && (Ff < .98) && (Xx > 0)) {      //waxing gibbous
                document.form1.Phase.src = "../../graphics/steincarter/moon/phase04.gif"
                document.form1.moonpic.src = "../../graphics/steincarter/moon/moon04.gif"
        }
        if((Ff > .55) && (Ff < .98) && (Xx < 0)) {      //waning gibbous
                document.form1.Phase.src = "../../graphics/steincarter/moon/phase06.gif"
                document.form1.moonpic.src = "../../graphics/steincarter/moon/moon06.gif"
        }
}

Thus, for the date 22 Nov 63 (not 1963) at 00:00 GMT, the Julian date would be

I would ask that if you choose to make use of this code, that you please give credit where credit is due.


Copyright © 1998 by J. Stein Carter. All rights reserved.
This page has been accessed Counter times since 22 Jan 2011.