Lasse said:
If it is possible to calculate when it happens, then it is possible in
Javascript. Javascript is Turing complete after all (anything that can
be calculated in any language can also be calculated in Javscript)
So, the answer to your question is "yes" (I bet that wasn't what you
really wanted to ask
.
Now, how do you do it.
Since lunar months are (according to a Google search):
29 days, 12 hours, 44 minutes and 2.87 seconds
unfortunately, nothing astronomical is this easy...
Synodic periods of the moon are almost *never* this length -- this is
simply the average (or the "mean") synodic period. The synodic month
varies from 29d06h35m to 29d19h55m [min and max for the last 100 years]
-- and close to these extrema every 19 years. Many factors are involved
-- mostly gravitational (mainly solar), but also simple motion [the
earth speeds up and slows down in it's orbit depending on proximity to
perigee and apogee -- as does every body in elliptical orbit (including
the moon)] and other not so obvious influences like rotation and "wobble".
The synodic month from new moon @ 23Nov to new moon 23Dec is 29d 10h 44m
-- from 23Dec to 21Jan04 is 29d 11h 22m -- that's a difference of 38
minutes from this month to the next.
I.e., 2551442870 ms, you can start with one point in time where
a lunar month started, and add this number to get the next month's
start.
So, assume you find one date and time that stats a lunar cycle (as exact
as possible, enter it on the first line):
var lunarDayMS = Date.UTC(startYear,startMonth-1,startDay,
startHour,startMin,startSec,startMS).valueOf();
var msPerLunarMonth = 2551442870;
// find first day of this lunar month
var todayMS = new Date().valueOf();
// subtract how far into this lunar cycle we are:
var thisStartMS = todayMS - (todayMS - lunarDayMS)%msPerLunarMonth;
var startDay = new Date(thisStartMS);
Add msPerLunarMonth to thisStartMS to get the start of the next lunar
month.
I've seen a site using this approach last updated in April 2000 -- it is
currently 5 days off from the actual day of the new moon (23 Nov 22:59
UT). That page is displaying the next new moon as 28Nov.
The difficult part is to find the date *and* time of the start of
the lunar month.
http://aa.usno.navy.mil/data/docs/MoonPhase.html [years from 1700 - 2035
available at this site].
Data to the minute for all phases of the moon (that's about all the
accuracy you can hope for -- there are many problems concerning the
apparent [visual] position of the moon [like parallax, refraction, etc...]).
You can get "nearer" the actual event of the new moon (and not relying
on the computer's clock) with this:
var jde = 2451550.09765 + 29.530588853 * k +
t2 * ( 0.0001337 - t * (0.000000150 - t *
0.00000000073));
where k is a whole number for the new moon, k + 0.25 for the first
quarter, k + 0.50 for the full moon and k + 0.75 for the last quarter...
NO OTHER VALUES have any meaning in this formula (meaning, you cannot
estimate the position of the moon at, say, k + 0.333333333...) k = 0
corresponds to the new moon of 6 Jan 2000. [negative k will give phases
before 2000].
k is originally approximated (e.g., picking today as a starting point) and
is calculated by:
k = (year - 2000) * 12.3685; // where year is the decimal year.
You can get close to the decimal year value with:
var today = new Date();
var year = today.getFullYear() +
(today.getMonth()*30.3634 + today.getDate())/365;
// for leap years, use:
// var year = today.getFullYear() +
// (today.getMonth()*30.4545 + today.getDate())/366;
// this is very "quick and dirty"
t is the time in julian centuries from epoch 2000.0. (decimalYear -
2000.0 divided by 100)
t is calculated from k as:
t = k/1236.85; // 12.3685 from above * 100 thus already in jcenturies
// and t2 = t * t;
All you need to do now is convert back from julian days to
a JS date object:
function
datefromjd(jd)
{
var jd_int_part;
var alpha;
var temp;
var A;
var B;
var C;
var D;
var E;
var jd_frac_part;
var m;
var d;
var y;
var retval;
jd_int_part = Math.floor(jd + 0.5);
jd_frac_part = (jd + 0.5) - jd_int_part;
if(jd_int_part < 2299161)
A = jd_int_part;
else
{
alpha = Math.floor((jd_int_part - 1867216.25)/36524.25);
A = jd_int_part + 1 + alpha - Math.floor(alpha/4);
}
B = A + 1524;
C = Math.floor((B - 122.1)/365.25);
D = Math.floor(365.25 * C);
E = Math.floor((B-D)/30.6001);
temp = Math.floor(30.6001 * E);
d = Math.floor(B - D - temp + jd_frac_part);
if(E < 14) E -= 1;
if(E >= 14) E -= 13;
m = E;
y = C - 4715 - (m > 2 ? 1 : 0);
jd_frac_part *= 86400;
var h = Math.floor(jd_frac_part/3600);
var min = Math.round((jd_frac_part/60)%60); // changed from floor
var sec = 0; //Math.floor(jd_frac_part % 60);
// seconds generally not used with phase times
// so minutes are rounded here...
// otherwise, change min and sec back for
// use with other JD calculations
return new Date(y, m-1, d, h, min, sec);
}
If more accuracy is required:
//********************** routines to calculate moon phase
Date.prototype.decimalYear = function()
{
var year = this.getFullYear();
var days = [0,31,59,90,120,151,181,212,243,273,304,334];
var leap = this.getMonth() > 1 && ((year % 4 == 0 && year % 100 != 0) ||
year % 400 == 0) ? 1 : 0;
var daypart = (this.getHours() + this.getMinutes()/60 + this.getSeconds()/3600)/24;
return this.getFullYear() + (days[this.getMonth()] + leap +
this.getDate() + daypart)/(365 + leap);
}
function
dr(n) { return n * Math.PI/180; } // convert degrees to radians
function
MoonPhase(date, phase)
{
var phases = { "new" : 0.0,
"first quarter" : 0.25,
"full" : 0.50,
"last quarter" : 0.75 };
var phaseAdj = phases[phase];
var quarterSpecific =
phase.indexOf("quarter") != -1 ? phase.indexOf("first") != -1 ? 1
: -1 : 0;
var useCoef = phase.indexOf("quarter") != -1 ? "quarter" : phase;
var year = date.decimalYear();
var k = Math.floor((year - 2000) * 12.3685) + phaseAdj;
var t = k/1236.85;
var t2 = t * t;
var jde = 2451550.09765 + 29.530588853 * k +
t2 * ( 0.0001337 - t * (0.000000150 -
t * 0.00000000073));
// calculate solar and lunar mean anomalies
// lunar argument of latitude (F)
// lunar ascending node
// earth eccentricity (E)
var solMA = 2.5534 + 29.10535669 * k - t2 * (0.0000218 + t * 0.00000011);
var lunMA = 201.5643 + 385.81693528 * k + t2 * (0.0107438 + t *
(0.00001239 - t * 0.000000058));
var lunF = 160.7108 + 390.67050274 * k - t2 * (0.0016341 + t *
(0.00000227 - t * 0.000000011));
var lunAscNode = 124.7746 - 1.56375580 * k + t2 * (0.0020691 + t * 0.00000215);
var E = 1 - t * (0.002516 - t * 0.0000074);
// effects due to planetary motion [most significant terms]
var planetaryArgs =
[
299.77 + 0.107408 * k - 0.009173 * t * t,
251.88 + 0.016321 * k ,
251.83 + 26.651886 * k ,
349.42 + 36.412478 * k ,
84.66 + 18.206239 * k ,
141.74 + 53.303771 * k ,
207.14 + 2.453732 * k ,
154.84 + 7.30686 * k ,
34.52 + 27.261239 * k ,
207.19 + 0.121824 * k ,
291.34 + 1.844379 * k ,
161.72 + 24.198154 * k ,
239.56 + 25.513099 * k ,
331.55 + 3.592518 * k
];
// lunar correction coefficients [new, full, and quarter]
var correctionCoefs ={ "new": [
-0.4072,
0.17241 * E,
0.01608,
0.01039,
0.00739 * E,
-0.00514 * E,
0.00208 * E * E,
-0.00111,
-0.00057,
0.00056 * E,
-0.00042,
0.00042 * E,
0.00038 * E,
-0.00024 * E,
-0.00017,
-0.00007,
0.00004,
0.00004,
0.00003,
0.00003,
-0.00003,
0.00003,
-0.00002,
-0.00002,
0.00002
],
"full" : [
-0.40614,
0.17302 * E,
0.01614,
0.01043,
0.00734 * E,
-0.00515 * E,
0.00209 * E * E,
-0.00111,
-0.00057,
0.00056 * E,
-0.00042,
0.00042 * E,
0.00038 * E,
-0.00024 *E,
-0.00017,
-0.00007,
0.00004,
0.00004,
0.00003,
0.00003,
-0.00003,
0.00003,
-0.00002,
-0.00002,
0.00002
],
"quarter" : [
-0.62801,
0.17172 * E,
-0.01183 * E,
0.00862,
0.00804,
0.00454 * E,
0.00204 * E * E,
-0.00180,
-0.00070,
-0.00040,
-0.00034 * E,
0.00032 * E,
0.00032 * E,
-0.00028 * E * E,
0.00027 * E,
-0.00017,
-0.00005,
0.00004,
-0.00004,
0.00004,
0.00003,
0.00003,
0.00002,
0.00002,
-0.00002
]};
// to be used with new/full coefs
var multipliers = [
lunMA,
solMA,
2 * lunMA,
2 * lunF,
lunMA - solMA,
lunMA + solMA,
2 * solMA,
lunMA - 2 * lunF,
lunMA + 2 * lunF,
2 * lunMA + solMA,
3 * lunMA,
solMA + 2 * lunF,
solMA - 2 * lunF,
2 * lunMA - solMA,
lunAscNode,
lunMA + 2 * solMA,
2 * lunMA - 2 * lunF,
3* solMA,
lunMA + solMA - 2 * lunF,
2 * lunMA + 2 * lunF,
lunMA + solMA + 2 * lunF,
lunMA - solMA + 2 * lunF,
lunMA - solMA - 2 * lunF,
3 * lunMA + solMA,
4 * lunMA
];
// to be used with first/last quarter coefs
var qmultipliers = [
lunMA,
solMA,
lunMA + solMA,
2 * lunMA,
2 * lunF,
lunMA - solMA,
2 * solMA,
lunMA - 2 * lunF,
lunMA + 2 * lunF,
3 * lunMA,
2 * lunMA - solMA,
solMA + 2 * lunF,
solMA - 2 * lunF,
lunMA + 2 * solMA,
2 * lunMA + solMA,
lunAscNode,
lunMA - solMA - 2 * lunF,
2 * lunMA + 2 * lunF,
lunMA + solMA + 2 * lunF,
lunMA - 2 * solMA,
lunMA + solMA - 2 * lunF,
3 * solMA ,
2 * lunMA - 2 * lunF,
lunMA - solMA + 2 * lunF,
3 * lunMA + solMA
];
// only required for first || last quarter
var forQuarters = 0.00306 - 0.00038 * E * Math.cos(dr(solMA)) +
0.00026 * Math.cos(dr(lunMA)) -
0.00002 * Math.cos(dr(lunMA - solMA)) +
0.00002 * Math.cos(dr(lunMA + solMA)) +
0.00002 * Math.cos(dr(2 * lunF));
// correction calcs are sums of series
var corrections = 0;
var mults = useCoef == "quarter" ? qmultipliers : multipliers;
for(var i = 0; i < multipliers.length; i++)
corrections += correctionCoefs[useCoef]
* Math.sin(dr(mults));
var allphasecoefs = [
0.000325,
0.000165,
0.000164,
0.000126,
0.000110,
0.000062,
0.000060,
0.000056,
0.000047,
0.000042,
0.000040,
0.000037,
0.000035,
0.000023
];
var addcorrections = 0;
for (var i = 0; i < allphasecoefs.length; i++)
addcorrections += allphasecoefs * Math.sin(dr(planetaryArgs));
// deltaT for 23Nov2003
// "close enough" for +/- a few years if necessary...
// but better if "accurate"
// deltaT is in seconds -- convert to "days"
var deltaT = -64.5659/86400; // deltaT is subtracted from terrestial time
var timeofphase = jde + deltaT + corrections +
addcorrections + quarterSpecific * forQuarters;
return datefromjd(timeofphase); // date is UT -- apply timezone for
local time
}
alert( "new moon: " + MoonPhase(today, "new") + "\n" +
"first quarter: " + MoonPhase(today, "first quarter") +"\n"
+
"full moon: " + MoonPhase(today, "full") +"\n" +
"last quarter: " + MoonPhase(today, "last quarter"));
//compare with usno data from moon phase url above
Notes:
whereas we generally know to take into account leap year days in
calendar calculations, we often forget about all the leap seconds that
have been added to our time over the last 45+ years [from about 1958].
The necessity for adding new leap seconds cannot be known (or
predicted!) until the "drift" of our time keeping devices from ephemeris
time is detected and adjusted [ephemeris time is defined as the measure
of time that brings the observed positions of the celestial bodies into
accord with the Newtonian dynamical theory of motion]. Currently, that
drift is about 65 seconds.
deltaT does NOT vary uniformly -- the observatory database maintains
data for each day!
here's a short "table" that can be used "in a pinch":
observedDeltaT = [63.83, 64.09, 64.30, 64.47, 65.8, 66, 67, 68, 69, 70,
70, 71, 72, 73];
values from 2000.0 to 2014.0 -- the whole number values are predictions
(as is index 4)...
using this array and interpolating to the end of november/beginning of december:
var curDeltaT = observedDeltaT[y - 2000] +
(observedDeltaT[y+1-2000] - observerdDeltaT[y-2000]) * dayNum/(365
+ leap);
for y = 2003 (e.g., this year)
and dayNum = 334 [1 december]
the result is ~65.687 seconds [and this is very close to the current
observed value]
so interpolating (for the near present) is a viable way to handle deltaT
from lookup tables for "our time" [or simply use the value from the
array index for the entire year]
Observed values for deltaT are available from the US Naval Observatory
online
(http://aa.usno.navy.mil/data -- time services dept - systems of time).
There are also formulae for estimating deltaT in the near future [see
http://maia.usno.navy.mil/ser7/ser7.dat -- under PREDICTIONS]
For formulae to estimate deltaT in the "far past", check out:
http://www.phys.uu.nl/~vgent/astro/deltatime.htm [or from Astronomical
Algorithms, Meeus]
All other moon phase formulae from Jean Meeus' Astronomical Algorithms...
If you have access to php or other serverside language, the deltaT
values can be fetched from usno and plugged into the javascript.
The accuracy of the routine above is within 0.5 minutes (more like <10
seconds) from at least 1993.0 until at least 2006.0 (even with the
"fixed" deltaT) -- there seems to be a difference in rounding at around
the 30 second mark -- otherwise, the data is consistent with USNO --
that's pretty damn good for JavaScript. Remember that only the *most*
signicant terms of the correction series are used. The accumulation of
error from the ignored terms will increase with time further away from
1Jan2000 (both in the past and in the future).