12

I'm making a calendar generator in JavaScript. I need the Unix Timestamp for easter day midnight, for a given year. How can I do that (in JavaScript)?

PHP's function can be found here.

Rafael Tavares
  • 5,678
  • 4
  • 32
  • 48
  • Have a look at the PHP source code to see how they calculate theirs and replicate in JavaScript? – KTC Aug 16 '09 at 13:48
  • Omg, is PHP open source? Well, I didn't know that :p –  Aug 16 '09 at 13:50
  • For reference the mentioned source: https://github.com/php/php-src/blob/c8aa6f3a9a3d2c114d0c5e0c9fdd0a465dbb54a5/ext/calendar/easter.c – s1lence Jan 20 '17 at 09:44

6 Answers6

27

According to this:-

function Easter(Y) {
    var C = Math.floor(Y/100);
    var N = Y - 19*Math.floor(Y/19);
    var K = Math.floor((C - 17)/25);
    var I = C - Math.floor(C/4) - Math.floor((C - K)/3) + 19*N + 15;
    I = I - 30*Math.floor((I/30));
    I = I - Math.floor(I/28)*(1 - Math.floor(I/28)*Math.floor(29/(I + 1))*Math.floor((21 - N)/11));
    var J = Y + Math.floor(Y/4) + I + 2 - C + Math.floor(C/4);
    J = J - 7*Math.floor(J/7);
    var L = I - J;
    var M = 3 + Math.floor((L + 40)/44);
    var D = L + 28 - 31*Math.floor(M/4);

    return padout(M) + '.' + padout(D);
}

function padout(number) { return (number < 10) ? '0' + number : number; }

Example usage:-

document.write(Easter(1997));
Gavin Gilmour
  • 6,833
  • 4
  • 40
  • 44
  • Thanks, I let my other functions convert it into a Unix timestamp –  Aug 16 '09 at 13:54
  • For 2016, it yields "03.27", which is "Easter Sunday" for 2016. So correct for 2016... – Tompi Feb 07 '16 at 07:53
  • 1
    I answered my first 100K JS questions through irt.org :) Nice to see it is still up there – mplungjan Sep 04 '18 at 05:28
  • 4
    if you want to return a date, get rid of the `padout` function and change the return statement to: `return new Date(Y, M-1, D);` – Kip Mar 05 '20 at 20:53
  • Here is a jsfiddle of an ES6 update (arrow function, let/const instead of var, and lower-case var names): https://jsfiddle.net/9v2af1w5/ – Kip Mar 05 '20 at 21:09
7

Here's an alternative method based on an algorithm that R. Sivaraman adapted for the Gregorian Calendar from an algorithm originally developed by J. Meeus for the Julian calendar (cf. https://en.wikipedia.org/wiki/Computus).

It seems like a more elegant and intuitive solution than the Gauss algorithm which has already been mentioned. At least it shaves off a few steps (only 5 total plus the JS date methods) and uses fewer variables (only 4 total plus the month, date, and year).

function computus( y ) {

        var date, a, b, c, m, d;

        // Instantiate the date object.
        date = new Date;

        // Set the timestamp to midnight.
        date.setHours( 0, 0, 0, 0 );

        // Set the year.
        date.setFullYear( y );

        // Find the golden number.
        a = y % 19;

        // Choose which version of the algorithm to use based on the given year.
        b = ( 2200 <= y && y <= 2299 ) ?
            ( ( 11 * a ) + 4 ) % 30 :
            ( ( 11 * a ) + 5 ) % 30;

        // Determine whether or not to compensate for the previous step.
        c = ( ( b === 0 ) || ( b === 1 && a > 10 ) ) ?
            ( b + 1 ) :
            b;

        // Use c first to find the month: April or March.
        m = ( 1 <= c && c <= 19 ) ? 3 : 2;

        // Then use c to find the full moon after the northward equinox.
        d = ( 50 - c ) % 31;

        // Mark the date of that full moon—the "Paschal" full moon.
        date.setMonth( m, d );

        // Count forward the number of days until the following Sunday (Easter).
        date.setMonth( m, d + ( 7 - date.getDay() ) );

        // Gregorian Western Easter Sunday
        return date;

    }

For example:

console.log( computus( 2016 ) ); // Date 2016-03-27T05:00:00.000Z
Reuben Lillie
  • 71
  • 1
  • 2
  • Why does your result include the 05:00:00.000 part? Is that because the time is in UTC? This makes me worry whether your algorithm is really timezone independent. – josch Nov 20 '17 at 08:30
3

Using the 'Laurent Longre' Algorithm

The following is based on the Laurent Longre algorithm.

It works starting from the year 1583 (the start of the Gregorian calendar) onwards.

The output is an array in the form of [year, month, day].

//===========================================================
function easterDateLLongre(Y) {
  let M=3, G= Y % 19+1, C= ~~(Y/100)+1, L=~~((3*C)/4)-12,
      E=(11*G+20+ ~~((8*C+5)/25)-5-L)%30, D;
  E<0 && (E+=30);
  (E==25 && G>11 || E==24) && E++;
  (D=44-E)<21 && (D+=30);
  (D+=7-(~~((5*Y)/4)-L-10+D)%7)>31 && (D-=31,M=4);
  return [Y, M, D];
  }
//===========================================================


//======================= Tests =====================
console.log(easterDateLLongre(1583)); // [1583, 4, 10]
console.log(easterDateLLongre(1853)); // [1853, 3, 27]
console.log(easterDateLLongre(2021)); // [2021, 4, 4]
console.log(easterDateLLongre(2700)); // [2700, 4, 1]
Mohsen Alyafei
  • 4,765
  • 3
  • 30
  • 42
1

In case you need to know if its easter(sunday) right now, you can use this function. (This was what I searched for):

    function isEasterSunday() {
      var curdate = new Date();
      var year = curdate.getFullYear();
      var a = year % 19;
      var b = Math.floor(year / 100);
      var c = year % 100;
      var d = Math.floor(b / 4); 
      var e = b % 4;
      var f = Math.floor((b + 8) / 25);
      var g = Math.floor((b - f + 1) / 3); 
      var h = (19 * a + b - d - g + 15) % 30;
      var i = Math.floor(c / 4);
      var k = c % 4;
      var l = (32 + 2 * e + 2 * i - h - k) % 7;
      var m = Math.floor((a + 11 * h + 22 * l) / 451);
      var n0 = (h + l + 7 * m + 114)
      var n = Math.floor(n0 / 31) - 1;
      var p = n0 % 31 + 1;
      var date = new Date(year,n,p);
      return ((curdate.getMonth() == date.getMonth())&&(curdate.getDate() == date.getDate()));
    }
realmuster
  • 156
  • 1
  • 4
0

Don't know how accurate the other answers are, but this answer/package is a js port from the equations that power Moontool for Windows, which is known to be credible.

If you just want plain js, you can copy/paste this code. You'll want to use getEaster(year), ie. getEaster(2020) to return a Date of Easter in that year.

var epoch=2444238.5,elonge=278.83354,elongp=282.596403,eccent=.016718,sunsmax=149598500,sunangsiz=.533128,mmlong=64.975464,mmlongp=349.383063,mlnode=151.950429,minc=5.145396,mecc=.0549,mangsiz=.5181,msmax=384401,mparallax=.9507,synmonth=29.53058868,lunatbase=2423436,earthrad=6378.16,PI=3.141592653589793,epsilon=1e-6;function sgn(x){return x<0?-1:x>0?1:0}function abs(x){return x<0?-x:x}function fixAngle(a){return a-360*Math.floor(a/360)}function toRad(d){return d*(PI/180)}function toDeg(d){return d*(180/PI)}function dsin(x){return Math.sin(toRad(x))}function dcos(x){return Math.cos(toRad(x))}function toJulianTime(date){var year,month,day;year=date.getFullYear();var m=(month=date.getMonth()+1)>2?month:month+12,y=month>2?year:year-1,d=(day=date.getDate())+date.getHours()/24+date.getMinutes()/1440+(date.getSeconds()+date.getMilliseconds()/1e3)/86400,b=isJulianDate(year,month,day)?0:2-y/100+y/100/4;return Math.floor(365.25*(y+4716)+Math.floor(30.6001*(m+1))+d+b-1524.5)}function isJulianDate(year,month,day){if(year<1582)return!0;if(year>1582)return!1;if(month<10)return!0;if(month>10)return!1;if(day<5)return!0;if(day>14)return!1;throw"Any date in the range 10/5/1582 to 10/14/1582 is invalid!"}function jyear(td,yy,mm,dd){var z,f,alpha,b,c,d,e;return f=(td+=.5)-(z=Math.floor(td)),b=(z<2299161?z:z+1+(alpha=Math.floor((z-1867216.25)/36524.25))-Math.floor(alpha/4))+1524,c=Math.floor((b-122.1)/365.25),d=Math.floor(365.25*c),e=Math.floor((b-d)/30.6001),{day:Math.floor(b-d-Math.floor(30.6001*e)+f),month:Math.floor(e<14?e-1:e-13),year:Math.floor(mm>2?c-4716:c-4715)}}function jhms(j){var ij;return j+=.5,ij=Math.floor(86400*(j-Math.floor(j))+.5),{hour:Math.floor(ij/3600),minute:Math.floor(ij/60%60),second:Math.floor(ij%60)}}function jwday(j){return Math.floor(j+1.5)%7}function meanphase(sdate,k){var t,t2;return 2415020.75933+synmonth*k+1178e-7*(t2=(t=(sdate-2415020)/36525)*t)-155e-9*(t2*t)+33e-5*dsin(166.56+132.87*t-.009173*t2)}function truephase(k,phase){var t,t2,t3,pt,m,mprime,f,apcor=!1;if(pt=2415020.75933+synmonth*(k+=phase)+1178e-7*(t2=(t=k/1236.85)*t)-155e-9*(t3=t2*t)+33e-5*dsin(166.56+132.87*t-.009173*t2),m=359.2242+29.10535608*k-333e-7*t2-347e-8*t3,mprime=306.0253+385.81691806*k+.0107306*t2+1236e-8*t3,f=21.2964+390.67050646*k-.0016528*t2-239e-8*t3,phase<.01||abs(phase-.5)<.01?(pt+=(.1734-393e-6*t)*dsin(m)+.0021*dsin(2*m)-.4068*dsin(mprime)+.0161*dsin(2*mprime)-4e-4*dsin(3*mprime)+.0104*dsin(2*f)-.0051*dsin(m+mprime)-.0074*dsin(m-mprime)+4e-4*dsin(2*f+m)-4e-4*dsin(2*f-m)-6e-4*dsin(2*f+mprime)+.001*dsin(2*f-mprime)+5e-4*dsin(m+2*mprime),apcor=!0):(abs(phase-.25)<.01||abs(phase-.75)<.01)&&(pt+=(.1721-4e-4*t)*dsin(m)+.0021*dsin(2*m)-.628*dsin(mprime)+.0089*dsin(2*mprime)-4e-4*dsin(3*mprime)+.0079*dsin(2*f)-.0119*dsin(m+mprime)-.0047*dsin(m-mprime)+3e-4*dsin(2*f+m)-4e-4*dsin(2*f-m)-6e-4*dsin(2*f+mprime)+.0021*dsin(2*f-mprime)+3e-4*dsin(m+2*mprime)+4e-4*dsin(m-2*mprime)-3e-4*dsin(2*m+mprime),pt+=phase<.5?.0028-4e-4*dcos(m)+3e-4*dcos(mprime):4e-4*dcos(m)-.0028-3e-4*dcos(mprime),apcor=!0),!apcor)throw"Error calculating moon phase!";return pt}function phasehunt(sdate,phases){var adate,k1,k2,nt1,nt2,yy,mm,dd,jyearResult=jyear(adate=sdate-45,yy,mm,dd);for(yy=jyearResult.year,mm=jyearResult.month,dd=jyearResult.day,adate=nt1=meanphase(adate,k1=Math.floor(12.3685*(yy+1/12*(mm-1)-1900)));nt2=meanphase(adate+=synmonth,k2=k1+1),!(nt1<=sdate&&nt2>sdate);)nt1=nt2,k1=k2;return phases[0]=truephase(k1,0),phases[1]=truephase(k1,.25),phases[2]=truephase(k1,.5),phases[3]=truephase(k1,.75),phases[4]=truephase(k2,0),phases}function kepler(m,ecc){var e,delta;e=m=toRad(m);do{e-=(delta=e-ecc*Math.sin(e)-m)/(1-ecc*Math.cos(e))}while(abs(delta)>epsilon);return e}function getMoonPhase(julianDate){var Day,N,M,Ec,Lambdasun,ml,MM,MN,Ev,Ae,MmP,mEc,lP,lPP,NP,y,x,MoonAge,MoonPhase,MoonDist,MoonDFrac,MoonAng,F,SunDist,SunAng;return N=fixAngle(360/365.2422*(Day=julianDate-epoch)),Ec=kepler(M=fixAngle(N+elonge-elongp),eccent),Ec=Math.sqrt((1+eccent)/(1-eccent))*Math.tan(Ec/2),Lambdasun=fixAngle((Ec=2*toDeg(Math.atan(Ec)))+elongp),F=(1+eccent*Math.cos(toRad(Ec)))/(1-eccent*eccent),SunDist=sunsmax/F,SunAng=F*sunangsiz,ml=fixAngle(13.1763966*Day+mmlong),MM=fixAngle(ml-.1114041*Day-mmlongp),MN=fixAngle(mlnode-.0529539*Day),MmP=MM+(Ev=1.2739*Math.sin(toRad(2*(ml-Lambdasun)-MM)))-(Ae=.1858*Math.sin(toRad(M)))-.37*Math.sin(toRad(M)),lPP=(lP=ml+Ev+(mEc=6.2886*Math.sin(toRad(MmP)))-Ae+.214*Math.sin(toRad(2*MmP)))+.6583*Math.sin(toRad(2*(lP-Lambdasun))),NP=MN-.16*Math.sin(toRad(M)),y=Math.sin(toRad(lPP-NP))*Math.cos(toRad(minc)),x=Math.cos(toRad(lPP-NP)),toDeg(Math.atan2(y,x)),NP,toDeg(Math.asin(Math.sin(toRad(lPP-NP))*Math.sin(toRad(minc)))),MoonAge=lPP-Lambdasun,MoonPhase=(1-Math.cos(toRad(MoonAge)))/2,MoonDist=msmax*(1-mecc*mecc)/(1+mecc*Math.cos(toRad(MmP+mEc))),MoonAng=mangsiz/(MoonDFrac=MoonDist/msmax),mparallax/MoonDFrac,{moonIllumination:MoonPhase,moonAgeInDays:synmonth*(fixAngle(MoonAge)/360),distanceInKm:MoonDist,angularDiameterInDeg:MoonAng,distanceToSun:SunDist,sunAngularDiameter:SunAng,moonPhase:fixAngle(MoonAge)/360}}function getMoonInfo(date){return null==date?{moonPhase:0,moonIllumination:0,moonAgeInDays:0,distanceInKm:0,angularDiameterInDeg:0,distanceToSun:0,sunAngularDiameter:0}:getMoonPhase(toJulianTime(date))}function getEaster(year){var previousMoonInfo,moonInfo,fullMoon=new Date(year,2,21),gettingDarker=void 0;do{previousMoonInfo=getMoonInfo(fullMoon),fullMoon.setDate(fullMoon.getDate()+1),moonInfo=getMoonInfo(fullMoon),void 0===gettingDarker?gettingDarker=moonInfo.moonIllumination<previousMoonInfo.moonIllumination:gettingDarker&&moonInfo.moonIllumination>previousMoonInfo.moonIllumination&&(gettingDarker=!1)}while(gettingDarker&&moonInfo.moonIllumination<previousMoonInfo.moonIllumination||!gettingDarker&&moonInfo.moonIllumination>previousMoonInfo.moonIllumination);for(fullMoon.setDate(fullMoon.getDate()-1);0!==fullMoon.getDay();)fullMoon.setDate(fullMoon.getDate()+1);return fullMoon}

ie. getEaster(2020); // -> Sun Apr 12 2020

reZach
  • 8,945
  • 12
  • 51
  • 97
0

Checking if a given date is on the Easter days. If no date is given the current Date is used.

function isEaster(dateObject) {
  const date = dateObject ?? new Date();
  const year = date.getFullYear();

  // Calculate the date of good friday for the current year
  const a = year % 19;
  const b = Math.floor(year / 100);
  const c = year % 100;
  const d = Math.floor(b / 4);
  const e = b % 4;
  const f = Math.floor((b + 8) / 25);
  const g = Math.floor((b - f + 1) / 3);
  const h = (19 * a + b - d - g + 15) % 30;
  const i = Math.floor(c / 4);
  const k = c % 4;
  const l = (32 + 2 * e + 2 * i - h - k) % 7;
  const m = Math.floor((a + 11 * h + 22 * l) / 451);
  const n = Math.floor((h + l - 7 * m + 114) / 31) - 1;
  const p = (h + l - 7 * m + 114) % 31;

  // Calculate the easter dates
  const maundyThursday = new Date(year, n, p - 1);
  const goodFriday = new Date(year, n, p);
  const holySaturday = new Date(year, n, p + 1);
  const easterSunday = new Date(year, n, p + 2);
  const easterMonday = new Date(year, n, p + 3);

  return ([maundyThursday, goodFriday, holySaturday, easterSunday, easterMonday].map(elem => {
    return elem.toISOString().split('T')[0];
  }).indexOf(date.toISOString().split('T')[0]) > -1);
}
SYNAIKIDO
  • 69
  • 1
  • 4