38

Does anyone have a good algorithm to calculate what date Good Friday falls on given the year as an input? Preferably in C#.

Ian Kemp
  • 28,293
  • 19
  • 112
  • 138
Bryan Denny
  • 27,363
  • 32
  • 109
  • 125
  • 3
    FYI, This is hard because Easter (and thereby Good Friday) is based on the moon, as was Passover before it. – C. Ross Mar 24 '10 at 18:32
  • 12
    @C.Ross: It is hard, hence why I asked :) – Bryan Denny Mar 24 '10 at 18:37
  • Passover is still based on the moon; because the Hebrew calendar is lunar. Passover will always fall on the first full moon of spring. –  Mar 24 '10 at 19:12
  • 1
    Duplicate of http://stackoverflow.com/questions/2192533/function-to-return-date-of-easter-for-the-given-year . Despite the title, he explicitly says, "Actually, I'm really looking for the date of Good Friday". – Matthew Flaschen Mar 24 '10 at 19:28
  • @MatthewFlaschen: Interesting, I didn't see that one before when I searched (but was searching against Good Friday, not Easter). – Bryan Denny Mar 24 '10 at 19:51
  • IIRC, one definition of Easter comes from a table (ending in 2022) of value computed by some monk who made an error at some point. The end result for that case is: just use the table. – BCS Mar 25 '10 at 17:35

6 Answers6

69

Here's a great article that should help you build your algorithm

http://www.codeproject.com/KB/datetime/christianholidays.aspx

Based on this example, you should be able to write:

DateTime goodFriday = EasterSunday(DateTime.Now.Year).AddDays(-2);

Full Example:

public static DateTime EasterSunday(int year)
{
    int day = 0;
    int month = 0;

    int g = year % 19;
    int c = year / 100;
    int h = (c - (int)(c / 4) - (int)((8 * c + 13) / 25) + 19 * g + 15) % 30;
    int i = h - (int)(h / 28) * (1 - (int)(h / 28) * (int)(29 / (h + 1)) * (int)((21 - g) / 11));

    day   = i - ((year + (int)(year / 4) + i + 2 - c + (int)(c / 4)) % 7) + 28;
    month = 3;

    if (day > 31)
    {
        month++;
        day -= 31;
    }

    return new DateTime(year, month, day);
}
Contango
  • 76,540
  • 58
  • 260
  • 305
hunter
  • 62,308
  • 19
  • 113
  • 113
  • 1
    I don't know what you're referring to... j/k, I fixed it – hunter Mar 24 '10 at 20:29
  • I can't believe how complex that is for something so seemingly simple... I wonder if I can refactor that... (wonders) – hunter Mar 24 '10 at 20:29
  • @Sarah And what would you suggest for year mod 19? int yearMod19? – hunter Mar 25 '10 at 00:40
  • @Sarah If you clicked the link in my post you'll see I used the example from that link – hunter Mar 25 '10 at 00:40
  • 1
    @Sarah I guess the point is that it is really meaningless anyway. I think with algorithms meaningful names can be ignored when there's no meaningful name to give. for(int incrementingInteger = 0; incrementingInteger < blah.Count(); incrementingInteger++) <= If I see you write that I will be very sad – hunter Mar 25 '10 at 13:54
  • @Hunter: for a loop like that, the `int` could easily be an index into something, so `gooseIndex` would be more appropriate. Not that I do this, I usually do `i` or `j` or whatever, but I had a prof that insisted on no meaningless variable names, even in loops... – Sarah Vessels Mar 25 '10 at 15:37
  • 1
    The last line 'return new DateTime(month, day, year);' seems to have the arguments in the wrong order. The DateTime constructor takes first year, then month, then day. – rmac Nov 18 '11 at 12:51
  • 1
    @rmac Corrected the last line that returns the DateTime. – Contango Aug 01 '12 at 15:50
  • 11
    g is the position within the 19 year lunar cycle; known as the golden number. c is the century. h is the number of days between the equinox and the next full moon. Pretty sure that i is the number of days between the full moon after the equinox and the first sunday after that full moon. – Neal Jun 20 '14 at 03:46
7

Don't Repeat Yourself

Think

Realize that calculating Easter is what you are really dependent upon.

Research

Here is the offical Naval Observatory page for calculating Easter.

https://web.archive.org/web/20120223154950/https://aa.usno.navy.mil/faq/docs/easter.php

Execute

Use the formula for calculating Easter then shift to the previous Friday (or subtract 2 days, details up to you).

TombMedia
  • 1,962
  • 2
  • 22
  • 27
Kelly S. French
  • 12,198
  • 10
  • 63
  • 93
3

Try this:

// test code:
Console.WriteLine(CalcGoodFri(2008));
Console.WriteLine(CalcGoodFri(2009));
Console.WriteLine(CalcGoodFri(2010));

private static DateTime CalcGoodFri(int yr)
{
 //int yr = 2010;  // The year for which to determine the date of Good Friday.
 int a = yr % 19;      
 int b = yr / 100;     
 int c = yr % 100;   
 int d = b / 4;
 int e = b % 4;      
 int i = c / 4;
 int k = c % 4;
 int g = (8 * b + 13) / 25;
 int h = ((19 * a) + b - d - g + 15) % 30;
 int l = ((2 * e) + (2 * i) - k + 32 - h) % 7;
 int m = (a + (11*h) + (19*l)) / 433;
 int days_to_good_friday = h + l - (7*m) - 2;  
 int mo = (days_to_good_friday + 90) / 25;
 int da = (days_to_good_friday + (33 * mo) + 19) % 32;
 return new DateTime ( yr, mo, da) ;    // Returns the date of Good Friday
}

Logic ported from here: http://www.kenhamady.com/form25.shtml

Paul Sasik
  • 79,492
  • 20
  • 149
  • 189
  • 16
    Its heartening to see that quality code like this never goes out of style. I especially like the use of meaningful variable names. Although, that said, I do wonder about the use of "days_to_good_friday" as a variable name, when "o" would have been more in keeping with the coding standards. Also, nice to see that "f" hasn't been used... which would allow the last three variables to be w, t and f. – Black Light Sep 03 '13 at 13:10
  • 2
    @BlackLight: LOL! +1... Honestly, I tried a refactor on this but quickly gave up. The math is quirky enough that I would have been working for days to make basically an entire class with many a constant and private method to make this readable... The frequency of magic numbers per line of code may actually be a world record! (about 2:1) – Paul Sasik Sep 03 '13 at 15:44
2

Wikipedia knows: http://en.wikipedia.org/wiki/Good_Friday#Calculating_the_date

Good Friday is the Friday before Easter, which is calculated differently in Eastern Christianity and Western Christianity (see Computus for details). Easter falls on the first Sunday following the Paschal Full Moon, the full moon on or after 21 March, taken to be the date of the vernal equinox. The Western calculation uses the Gregorian calendar, while the Eastern calculation uses the Julian calendar, whose 21 March now corresponds to the Gregorian calendar's 3 April. The calculations for identifying the date of the full moon also differ. See Easter Dating Method (Astronomical Society of South Australia).

In Eastern Christianity, Easter can fall between March 22 and April 25 on Julian Calendar (thus between April 4 and May 8 in terms of the Gregorian calendar, during the period 1900 and 2099), so Good Friday can fall between March 20 and April 23, inclusive (or between April 2 and May 6 in terms of the Gregorian calendar). (See Easter.)

Community
  • 1
  • 1
Matt Ball
  • 354,903
  • 100
  • 647
  • 710
  • also see http://en.wikipedia.org/wiki/Computus (which may or may not be linked from that wikipedia entry). One method that has been used historically is table lookup -- don't discount this method (unless of course your question is for homework :-) – Gordon Broom Mar 24 '10 at 18:34
0

Is it possible to the use the Hebrew or Arabic lunar calendars for the conversion? eg:

 DateTime getEasterSunday(int year)
        {
            const int fourTeen = 14;
            DateTime Paschal = new DateTime(1900, 3, 20);
            var iCal = new HebrewCalendar();
            DateTime eFullMoon;
            var pDate = new DateTime(year, Paschal.Month, Paschal.Day);
            var LunarYear = iCal.GetYear(pDate);
            var LunarMonth = iCal.GetMonth(pDate);
            var LunarDay = iCal.GetDayOfMonth(pDate);

            if (LunarDay >= fourTeen) LunarMonth++;

            eFullMoon = iCal.ToDateTime(LunarYear, LunarMonth, fourTeen, 0, 0, 0, 0);

            return Enumerable.Range(0, 6).Select(x => eFullMoon.Date.AddDays(x)).Where(x => x.DayOfWeek == DayOfWeek.Sunday).First();
        }
slyi
  • 321
  • 1
  • 2
  • 7
0

Necromancing.
Actually, it depends on whether it is Orthodox or Catholic Good Friday ;)

from https://mycodepad.wordpress.com/2013/04/28/c-calculating-orthodox-and-catholic-easter/

(Note: Easter = Easter Sunday)

/// <summary>
/// Get Orthodox easter for requested year
/// </summary>
/// <param name="year">Year of easter</param>
/// <returns>DateTime of Orthodox Easter</returns>
public static DateTime GetOrthodoxEaster( int year ) {
    int a = year % 19;
    int b = year % 7;
    int c = year % 4;

    int d = (19 * a + 16) % 30;
    int e = (2 * c + 4 * b + 6 * d) % 7;
    int f = (19 * a + 16) % 30;
    int key = f + e + 3;

    int month = (key > 30) ? 5 : 4;
    int day = (key > 30) ? key - 30 : key;

    return new DateTime( year, month, day );
}


/// <summary>
/// Get Catholic easter for requested year
/// </summary>
/// <param name="year">Year of easter</param>
/// <returns>DateTime of Catholic Easter</returns>
public static DateTime GetCatholicEaster( int year ) {
    int month = 3;
    int G = year % 19 + 1;
    int C = year / 100 + 1;
    int X = (3 * C) / 4 - 12;
    int Y = (8 * C + 5) / 25 - 5;
    int Z = (5 * year) / 4 - X - 10;
    int E = (11 * G + 20 + Y - X) % 30;
    if (E == 24) { E++; }
    if ((E == 25) && (G > 11)) { E++; }
    int N = 44 - E;
    if (N < 21) { N = N + 30; }
    int P = (N + 7) - ((Z + N) % 7);
    if (P > 31) {
        P = P - 31;
        month = 4;
    }
    return new DateTime( year, month, P );
}

Then you could still create an abstract EasterBunny:

private static void EasterBunnyTest()
{
    AbstractEasterBunny WesternEuropeanBunny = new CatholicEasterBunny();
    AbstractEasterBunny EasternEuropeanBunny = new OrthodoxEasterBunny();
    AbstractEasterBunny LocalizedEasterBunny = AbstractEasterBunny.CreateInstance();


    System.DateTime dtRomeEaster = WesternEuropeanBunny.EasterSunday(2016);
    System.DateTime dtAthensEaster = EasternEuropeanBunny.EasterSunday(2016);
    System.DateTime dtLocalEaster = LocalizedEasterBunny.EasterSunday(2016);

    System.Console.WriteLine(dtRomeEaster);
    System.Console.WriteLine(dtAthensEaster);
    System.Console.WriteLine(dtLocalEaster);
}

With this abstract bunny here:

public abstract class AbstractEasterBunny
{

    /// <summary>
    /// Gets the Orthodox easter sunday for the requested year
    /// </summary>
    /// <param name="year">The year you want to know the Orthodox Easter Sunday of</param>
    /// <returns>DateTime of Orthodox Easter Sunday</returns>
    public abstract System.DateTime EasterSunday(int year);

    public abstract System.DateTime GoodFriday(int year);


    public static AbstractEasterBunny CreateInstance()
    {
        System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.CurrentCulture;
        System.Globalization.RegionInfo ri = new System.Globalization.RegionInfo(ci.LCID);


        // https://msdn.microsoft.com/en-us/library/windows/desktop/dd374073(v=vs.85).aspx
        System.Collections.Generic.List<int> lsOrthodox = new System.Collections.Generic.List<int>{
             0x10D // Serbia and Montenegro
            ,0x10E // Montenegro
            ,0x10F // Serbia
            ,0x19 // Bosnia and Herzegovina

            // ,0x46 // Estonia
            // ,0x4B // Czech Republic
            // ,0x4D // Finland
            ,0x62 // Greece
            // ,0x6D // Hungary
            ,0x79 // Iraq
            // ,0x8C // Latvia
            // ,0x8D // Lithuania
            // ,0x8F // Slovakia
            // ,0x98 // Moldova
            // ,0xD4 // Slovenia
            ,0x4CA2 // Macedonia, Former Yugoslav Republic of
            ,0xEB // Turkey
        };

        // if(ci == WesternSlavonicOrthodox)
        if (lsOrthodox.Contains(ri.GeoId))
            return new OrthodoxEasterBunny();


        // TODO: Correct for Armenia/Georgia ? ? ? 
        // if(ri.GeoId == 0x7 || ri.GeoId == 0x58) // 0x7: Armenia, 0x58: Georgia
            // return new CatholicEasterBunny();


        // if(ci == EasternSlavonic)
        string strMonthName = ci.DateTimeFormat.GetMonthName(8);
        if (System.Text.RegularExpressions.Regex.IsMatch(strMonthName, @"\p{IsCyrillic}"))
        {
            // there is at least one cyrillic character in the string
            return new OrthodoxEasterBunny();
        }

        return new CatholicEasterBunny();
    }

}



public class OrthodoxEasterBunny : AbstractEasterBunny
{

    /// <summary>
    /// Gets the Orthodox easter sunday for the requested year
    /// </summary>
    /// <param name="year">The year you want to know the Orthodox Easter Sunday of</param>
    /// <returns>DateTime of Orthodox Easter Sunday</returns>
    public override System.DateTime EasterSunday(int year)
    {
        int a = year % 19;
        int b = year % 7;
        int c = year % 4;

        int d = (19 * a + 16) % 30;
        int e = (2 * c + 4 * b + 6 * d) % 7;
        int f = (19 * a + 16) % 30;
        int key = f + e + 3;

        int month = (key > 30) ? 5 : 4;
        int day = (key > 30) ? key - 30 : key;

        return new System.DateTime(year, month, day);
    }


    public override System.DateTime GoodFriday(int year)
    {
        return this.EasterSunday(year).AddDays(-2);
    }

}



public class CatholicEasterBunny : AbstractEasterBunny
{

    /// <summary>
    /// Gets the Catholic easter sunday for the requested year
    /// </summary>
    /// <param name="year">The year you want to know the Catholic Easter Sunday of</param>
    /// <returns>DateTime of Catholic Easter Sunday</returns>
    public override System.DateTime EasterSunday(int year)
    {
        int day = 0;
        int month = 0;

        int g = year % 19;
        int c = year / 100;
        int h = (c - (int)(c / 4) - (int)((8 * c + 13) / 25) + 19 * g + 15) % 30;
        int i = h - (int)(h / 28) * (1 - (int)(h / 28) * (int)(29 / (h + 1)) * (int)((21 - g) / 11));

        day = i - ((year + (int)(year / 4) + i + 2 - c + (int)(c / 4)) % 7) + 28;
        month = 3;

        if (day > 31)
        {
            month++;
            day -= 31;
        }

        return new System.DateTime(year, month, day);
    }


    public override System.DateTime GoodFriday(int year)
    {
        return this.EasterSunday(year).AddDays(-2);
    }

}
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
  • Any algorithm that i found in the web about Orthodox Easter has the same solution with yours. If the year is 2100, then with those algorithms i get 2100-05-01. But if i check it with some online "calculators" i get 2100-05-02. This "calculators" are https://www.wolframalpha.com/input/?i=orthodox+easter+at+2100 https://www.eortologio.net/pasxa/etos/2100 What am i missing? – pbaris Mar 08 '19 at 16:51