Function to return date of Easter for the given ye

2020-02-02 11:08发布

问题:

So, here's a funny little programming challenge. I was writing a quick method to determine all the market holidays for a particular year, and then I started reading about Easter and discovered just how crazy* the logic is for determining its date--the first Sunday after the Paschal Full Moon following the spring equinox! Does anybody know of an existing function to calculate the date of Easter for a given year?

Granted, it's probably not all that hard to do; I just figured I'd ask in case somebody's already done this. (And that seems very likely.)

UPDATE: Actually, I'm really looking for the date of Good Friday (the Friday before Easter)... I just figured Easter would get me there. And since I'm in the U.S., I assume I'm looking for the Catholic Easter? But perhaps someone can correct me on that if I'm wrong.

*By "crazy" I meant, like, involved. Not anything offensive...

回答1:

in SQL Server Easter Sunday would look like this, scroll down for Good Friday

CREATE FUNCTION dbo.GetEasterSunday 
( @Y INT ) 
RETURNS SMALLDATETIME 
AS 
BEGIN 
    DECLARE     @EpactCalc INT,  
        @PaschalDaysCalc INT, 
        @NumOfDaysToSunday INT, 
        @EasterMonth INT, 
        @EasterDay INT 

    SET @EpactCalc = (24 + 19 * (@Y % 19)) % 30 
    SET @PaschalDaysCalc = @EpactCalc - (@EpactCalc / 28) 
    SET @NumOfDaysToSunday = @PaschalDaysCalc - ( 
        (@Y + @Y / 4 + @PaschalDaysCalc - 13) % 7 
    ) 

    SET @EasterMonth = 3 + (@NumOfDaysToSunday + 40) / 44 

    SET @EasterDay = @NumOfDaysToSunday + 28 - ( 
        31 * (@EasterMonth / 4) 
    ) 

    RETURN 
    ( 
        SELECT CONVERT 
        (  SMALLDATETIME, 
                 RTRIM(@Y)  
            + RIGHT('0'+RTRIM(@EasterMonth), 2)  
            + RIGHT('0'+RTRIM(@EasterDay), 2)  
        ) 
    ) 

END 
GO

Good Friday is like this and it uses the Easter function above

CREATE FUNCTION dbo.GetGoodFriday 
( 
    @Y INT 
) 
RETURNS SMALLDATETIME 
AS 
BEGIN 
    RETURN (SELECT dbo.GetEasterSunday(@Y) - 2) 
END 
GO

From here: http://web.archive.org/web/20070611150639/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html



回答2:

Python: using dateutil's easter() function.

>>> from dateutil.easter import *
>>> print easter(2010)
2010-04-04
>>> print easter(2011)
2011-04-24

The functions gets, as an argument, the type of calculation you like:

EASTER_JULIAN   = 1
EASTER_ORTHODOX = 2
EASTER_WESTERN  = 3

You can pick the one relevant to the US.

Reducing two days from the result would give you Good Friday:

>>> from datetime import timedelta
>>> d = timedelta(days=-2)
>>> easter(2011)
datetime.date(2011, 4, 24)
>>> easter(2011)+d
datetime.date(2011, 4, 22)

Oddly enough, someone was iterating this, and published the results in Wikipedia's article about the algorithm:



回答3:

When it came for me to write this (traffic prediction based on day of week and holiday), I gave up on trying to write it by myself. I found it somewhere on the net. The code was public domain, but...

sigh

see for yourself.

void dateOfEaster(struct tm* p)
{
    int Y = p->tm_year;
    int a = Y % 19;
    int b = Y / 100;
    int c = Y % 100;
    int d = b / 4;
    int e = b % 4;
    int f = (b + 8) / 25;
    int g = (b - f + 1) / 3;
    int h = (19 * a + b - d - g + 15) % 30;
    int i = c / 4;
    int k = c % 4;
    int L = (32 + 2 * e + 2 * i - h - k) % 7;
    int m = (a + 11 * h + 22 * L) / 451;
    p->tm_mon = ((h + L - 7 * m + 114) / 31 ) - 1;
    p->tm_mday = ((h + L - 7 * m + 114) % 31) + 1;
    p->tm_hour = 12;
    const time_t tmp = mktime(p);
    *p = *localtime(&tmp);  //recover yday from mon+mday
}

Some questions are better left unasked.

I feel lucky that all moving holidays in my country are a fixed offset from the date of Easter.



回答4:

The SQL Server function below is more general than the accepted answer

The accepted answer is only correct for the range (inclusive) : 1900-04-15 to 2099-04-12

It uses the algorithm provided by The United States Naval Observatory (USNO)

http://aa.usno.navy.mil/faq/docs/easter.php

CREATE FUNCTION dbo.GetEasterSunday (@Y INT)
RETURNS DATETIME
AS
    BEGIN 

        -- Source of algorithm : http://aa.usno.navy.mil/faq/docs/easter.php

        DECLARE @c INT = @Y / 100
        DECLARE @n INT = @Y - 19 * (@Y / 19)
        DECLARE @k INT = (@c - 17) / 25
        DECLARE @i INT = @c - @c / 4 - (@c - @k) / 3 + 19 * @n + 15
        SET @i = @i - 30 * (@i / 30)
        SET @i = @i - (@i / 28) * (1 - (@i / 28) * (29 / (@i + 1)) * ((21 - @n) / 11))
        DECLARE @j INT = @Y + @Y / 4 + @i + 2 - @c + @c / 4
        SET @j = @j - 7 * (@j / 7)
        DECLARE @l INT = @i - @j
        DECLARE @m INT = 3 + (@l + 40) / 44
        DECLARE @d INT = @l + 28 - 31 * (@m / 4)

        RETURN 
    ( 
        SELECT CONVERT 
        (  DATETIME, 
                 RTRIM(@Y)  
            + RIGHT('0'+RTRIM(@m), 2)  
            + RIGHT('0'+RTRIM(@d), 2)  
    ) 
    )
    END 


GO


回答5:

The below code determines Easter through powershell:

function Get-DateOfEaster {
    param(
        [Parameter(ValueFromPipeline)]
        $theYear=(Get-Date).Year
        )

    if($theYear -lt 1583) {
        return $null
    } else {

        # Step 1: Divide the theYear by 19 and store the
        # remainder in variable A.  Example: If the theYear
        # is 2000, then A is initialized to 5.

        $a = $theYear % 19

        # Step 2: Divide the theYear by 100.  Store the integer
        # result in B and the remainder in C.

        $c = $theYear % 100
        $b = ($theYear -$c) / 100

        # Step 3: Divide B (calculated above).  Store the
        # integer result in D and the remainder in E.

        $e = $b % 4
        $d = ($b - $e) / 4

        # Step 4: Divide (b+8)/25 and store the integer
        # portion of the result in F.

        $f = [math]::floor(($b + 8) / 25)

        # Step 5: Divide (b-f+1)/3 and store the integer
        # portion of the result in G.

        $g = [math]::floor(($b - $f + 1) / 3)

        # Step 6: Divide (19a+b-d-g+15)/30 and store the
        # remainder of the result in H.

        $h = (19 * $a + $b - $d - $g + 15) % 30

        # Step 7: Divide C by 4.  Store the integer result
        # in I and the remainder in K.

        $k = $c % 4
        $i = ($c - $k) / 4

        # Step 8: Divide (32+2e+2i-h-k) by 7.  Store the
        # remainder of the result in L.

        $l = (32 + 2 * $e + 2 * $i - $h - $k) % 7

        # Step 9: Divide (a + 11h + 22l) by 451 and
        # store the integer portion of the result in M.

        $m = [math]::floor(($a + 11 * $h + 22 * $l) / 451)

        # Step 10: Divide (h + l - 7m + 114) by 31.  Store
        # the integer portion of the result in N and the
        # remainder in P.

        $p = ($h + $l - 7 * $m + 114) % 31
        $n = (($h + $l - 7 * $m + 114) - $p) / 31

        # At this point p+1 is the day on which Easter falls.
        # n is 3 for March and 4 for April.

        $DateTime = New-Object DateTime $theyear, $n, ($p+1), 0, 0, 0, ([DateTimeKind]::Utc)
        return $DateTime
    }
}

$eastersunday=Get-DateOfEaster 2015
Write-Host $eastersunday


回答6:

VB .NET Functions for Greek Orthodox and Catholic Easter:

Public Shared Function OrthodoxEaster(ByVal Year As Integer) As Date
    Dim a = Year Mod 19
    Dim b = Year Mod 7
    Dim c = Year Mod 4
    Dim d = (19 * a + 16) Mod 30
    Dim e = (2 * c + 4 * b + 6 * d) Mod 7
    Dim f = (19 * a + 16) Mod 30
    Dim key = f + e + 3
    Dim month = If((key > 30), 5, 4)
    Dim day = If((key > 30), key - 30, key)
    Return New DateTime(Year, month, day)
End Function

Public Shared Function CatholicEaster(ByVal Year As Integer) As DateTime
    Dim month = 3
    Dim a = Year Mod 19 + 1
    Dim b = Year / 100 + 1
    Dim c = (3 * b) / 4 - 12
    Dim d = (8 * b + 5) / 25 - 5
    Dim e = (5 * Year) / 4 - c - 10
    Dim f = (11 * a + 20 + d - c) Mod 30
    If f = 24 Then f += 1
    If (f = 25) AndAlso (a > 11) Then f += 1
    Dim g = 44 - f
    If g < 21 Then g = g + 30
    Dim day = (g + 7) - ((e + g) Mod 7)
    If day > 31 Then
        day = day - 31
        month = 4
    End If
    Return New DateTime(Year, month, day)
End Function


回答7:

Found this Excel formula somewhere
Assuming cell A1 contains year e.g. 2020

ROUND(DATE(A1;4;1)/7+MOD(19*MOD(A1;19)-7;30)*0,14;0)*7-6

Converted to T-SQL lead me to this:

DECLARE @yr INT=2020
SELECT DATEADD(dd, ROUND(DATEDIFF(dd, '1899-12-30', DATEFROMPARTS(@yr, 4, 1)) / 7.0 + ((19.0 * (@yr % 19) - 7) % 30) * 0.14, 0) * 7.0 - 6, -2)