Friday, April 26, 2019

You are not logged in.

**Nigel Garvey****Moderator**- From:: Warwickshire, England
- Registered: 2002-11-20
- Posts: 4853

While working on a script to tell me the date of the next fortnightly recycling collection in my area, I've had cause to research the calculation of Easter Day. (Easter Monday's a bank holiday, so any collections that week will be a day later.) It's quite a complex process, based on full moons and Vernal Equinoxes. One of the calculations I found on the Web actually works and looks like this when translated into AppleScript:

## Applescript:

on EasterDay(y) -- y is the year number.

-- Based on a calculation on the BBC's h2g2 Web site:

-- <[url]http://www.bbc.co.uk/dna/h2g2/A653267[/url]>

set a to y mod 19

set b to y div 100

set c to y mod 100

set d to b div 4

set e to b mod 4

set f to c div 4

set g to c mod 4

set h to (b + 8) div 25

set i to (b - h + 1) div 3

set j to (19 * a + b - d - i + 15) mod 30

set k to (32 + 2 * e + 2 * f - j - g) mod 7

set m to (a + 11 * j + 22 * k) div 451

set n to j + k - 7 * m + 114

set theDay to n mod 31 + 1

set theMonth to n div 31

return {theDay, theMonth}

end EasterDay

EasterDay(2006)

--> {16, 4} -- Sunday 16th April

This is apparently only guaranteed for years between 1700 and 2299! For my own purposes, I want the result as an AppleScript date.

## Applescript:

on EasterDay(y)

-- Based on a calculation on the BBC's h2g2 Web site:

-- <[url]http://www.bbc.co.uk/dna/h2g2/A653267[/url]>

set {a, b, c} to {y mod 19, y div 100, y mod 100}

set j to (19 * a + b - b div 4 - (b - (b + 8) div 25 + 1) div 3 + 15) mod 30

set k to (32 + 2 * (b mod 4 + c div 4) - j - c mod 4) mod 7

set n to j + k - (a + 11 * j + 22 * k) div 451 * 7 + 114

-- Get 1st January in the target year.

set theDate to date "Wednesday 1 January 1000 00:00:00"

set theDate's year to y

tell theDate + (n div 31 - 1) * 32 * days -- Get a date in the (n div 31)th month of the year.

return it + (n mod 31 + 1 - (its day)) * days -- Return the result + (target day - actual day) days.

end tell

end EasterDay

EasterDay(2006)

--> date "Sunday 16 April 2006 00:00:00"

NG

Offline

This is a different approach, found here: http://www.assa.org.au/edm.html

The dates are valid from 1583 to 4099 and independent of international system date settings.

For further determination of holidays Iâ€™ve added a few lines to calculate the days of february

## Applescript:

set {EasterDate, DaysOfFeb} to calc_easter_date(2006)

on calc_easter_date(yr)

set FirstD to yr div 100

set temp to (FirstD - 15) div 2 + 202 - 11 * (yr mod 19)

if FirstD is in {21, 24, 25, 27, 28, 29, 30, 31, 32, 34, 35, 38} then set temp to temp - 1

if FirstD is in {33, 36, 37, 39, 40} then set temp to temp - 2

set temp to temp mod 30

set VA to temp + 21

if temp = 29 or (temp = 28 and (yr mod 19) > 10) then set VA to VA - 1

-- find the next Sunday

set VB to (VA - 19) mod 7

set VC to (40 - FirstD) mod 4

if VC = 3 then set VC to VC + 1

if VC > 1 then set VC to VC + 1

set temp to yr mod 100

set VD to (temp + temp div 4) mod 7

set VE to ((20 - VB - VC - VD) mod 7) + 1

set easter_day to VA + VE

-- return the date

if easter_day > 31 then

set easter_day to easter_day - 31

set easter_month to 4

else

set easter_month to 3

end if

-- calculate days of february

set DayFeb to 28

if ((yr - (yr div 4) * 4) = 0) and (yr - ((yr div 100) * 100) â‰ 0) then set DayFeb to 29

if (yr - (yr div 100) * 100) = 0 and yr - (yr div 400) * 400 â‰ 0 then set DayFeb to 28

if (yr - (yr div 400) * 400) = 0 then set DayFeb to 29

-- make the date independent of international system date settings

set ED to date (short date string of (current date))

set year of ED to yr

set month of ED to easter_month

set day of ED to easter_day

return {ED, DayFeb}

end calc_easter_date

Model: G5 dual 2,5 GHz

Browser: Safari 419.3

Operating System: Mac OS X (10.4)

*Last edited by StefanK (2006-10-30 04:45:44 am)*

regards

Stefan

Offline

**Qwerty Denzel****Member**- Registered: 2005-06-11
- Posts: 337

Hi StefanK.

In your translation of their algorithm, it seems you have made an error!

You have written:

## Applescript:

if temp = 29 or (temp = 28 and (yr mod 19) > 10) then VA = VA - 1

That last bit should be:

## Applescript:

then set VA to VA - 1

Out of interest, off that website I found Modified Oudin's Algorithm, which I haven't tried simplifying (but have verified that it works):

## Applescript:

on modifiedOudinsAlgorithm(theYear)

(* Modified Oudin's Algorithm *)

(* [url]http://www.smart.net/~mmontes/oudin.html[/url] *)

set century to theYear div 100

set G to theYear mod 19

set K to (century - 17) div 25

set I to (century - century div 4 - (century - K) div 3 + 19 * G + 15) mod 30

set I to I - (I div 28) * (1 - (I div 28) * (29 div (I + 1)) * ((21 - G) div 11))

set J to (theYear + theYear div 4 + I + 2 - century + century div 4) mod 7

set L to I - J

set EasterMonth to 3 + (L + 40) div 44

set EasterDay to L + 28 - 31 * (EasterMonth div 4)

set EasterDate to current date

tell EasterDate to set {day, its month, year, time} to {EasterDay, EasterMonth, theYear, 0}

return EasterDate

end modifiedOudinsAlgorithm

I also found Butcher's Algorithm, which also gets the same result as yours:

## Applescript:

on butchersAlgorithm(theYear)

(* Butcher's Algorithm *)

(* [url]http://www.smart.net/~mmontes/nature1876.html[/url] *)

set a to theYear mod 19

set b to theYear div 100

set c to theYear mod 100

set d to b div 4

set e to b mod 4

set f to (b + 8) div 25

set G to (b - f + 1) div 3

set h to (19 * a + b - d - G + 15) mod 30

set I to c div 4

set K to c mod 4

set L to (32 + 2 * e + 2 * I - h - K) mod 7

set m to (a + 11 * h + 22 * L) div 451

set EasterMonth to (h + L - 7 * m + 114) div 31

set p to (h + L - 7 * m + 114) mod 31

set EasterDay to p + 1

set EasterDate to current date

tell EasterDate to set {day, its month, year, time} to {EasterDay, EasterMonth, theYear, 0}

return EasterDate

end butchersAlgorithm

Also, to find how many days in February for a particular year:

## Applescript:

on daysInFeb(theYear)

tell theYear mod 400 to return 28 + (((it mod 28 mod 4 is 0) and ((it mod 100 is not 0) or (it is 0))) as integer)

end daysInFeb

*Edit*

Look, there are reasons why you don't attempt to condense these into a one-liner form:

## Applescript:

on butchersAlgorithmButchered(theYear)

tell {theYear mod 19, theYear div 100} to tell {item 1, item 2, theYear mod 100, (19 * (item 1) + (item 2) - (item 2) div 4 - (((item 2) - (((item 2) + 8) div 25) + 1) div 3) + 15) mod 30} to tell {item 1, item 4, (32 + 2 * ((item 2) mod 4) + 2 * ((item 3) div 4) - (item 4) - ((item 3) mod 4)) mod 7} to tell ((item 2) + (item 3) - 7 * (((item 1) + 11 * (item 2) + 22 * (item 3)) div 451) + 114) to set {EasterMonth, EasterDay} to {it div 31, it mod 31 + 1}

set EasterDate to current date

tell EasterDate to set {day, its month, year, time} to {EasterDay, EasterMonth, theYear, 0}

return EasterDate

end butchersAlgorithmButchered

*Last edited by Qwerty Denzel (2006-10-30 05:07:28 am)*

Offline

Hi, Qwerty

thanks a lot for your hint. Iâ€™ve changed the line.

Your handler to calculate leap years doesnâ€™t work correctly for century years.

The rule is:*A year will be a leap year if it is divisible by 4 but not by 100.If a year is divisible by 4 and by 100, it is not a leap year unless it is also divisible by 400.*

therefore e.g. 1900 is not a leap year but 2000 is.

*Last edited by StefanK (2006-10-30 03:05:50 am)*

regards

Stefan

Offline

**Qwerty Denzel****Member**- Registered: 2005-06-11
- Posts: 337

You are of course correct.

Hmm... I honestly remembered as being the other way around. Makes much more sense though.

Consider it changed.

Offline

**Nigel Garvey****Moderator**- From:: Warwickshire, England
- Registered: 2002-11-20
- Posts: 4853

Qwerty Denzel wrote:

Consider it changed.

Consider it optimised.

## Applescript:

on daysInFeb(theYear)

if ((theYear mod 4 is 0) and ((theYear mod 100 > 0) or (theYear mod 400 is 0))) then

29

else

28

end if

end daysInFeb

NG

Offline

Nigel Garvey wrote:

Consider it optimised.

almost

## Applescript:

on daysInFeb(theYear)

(((theYear mod 4 is 0) and ((theYear mod 100 > 0) or (theYear mod 400 is 0))) as integer) + 28

end daysInFeb

regards

Stefan

Offline

**Nigel Garvey****Moderator**- From:: Warwickshire, England
- Registered: 2002-11-20
- Posts: 4853

Hi, Stefan.

By "optimised", I meant "made more efficient", not just "made shorter". I deliberately left out the boolean-to-integer coercion and the addition, which makes my version about 2.5 to 3.7 times as fast (on my machine, depending on the actual year number) as a one-liner.

NG

Offline

**Qwerty Denzel****Member**- Registered: 2005-06-11
- Posts: 337

Me too.

This is how I might go about measuring the speed difference:

## Applescript:

on daysInFeb1(theYear)

if ((theYear mod 4 is 0) and ((theYear mod 100 > 0) or (theYear mod 400 is 0))) then

29

else

28

end if

end daysInFeb1

on daysInFeb2(theYear)

(((theYear mod 4 is 0) and ((theYear mod 100 > 0) or (theYear mod 400 is 0))) as integer) + 28

end daysInFeb2

set repeatTimes to 1000

set startDate1 to current date

repeat repeatTimes times

repeat with i from 1 to 2099

daysInFeb1(i)

end repeat

end repeat

set time1 to (current date) - startDate1

set startDate2 to current date

repeat repeatTimes times

repeat with i from 1 to 2099

daysInFeb2(i)

end repeat

end repeat

set time2 to (current date) - startDate2

time2 / time1 -- times faster

However, I have heard that AppleScript can be quite fragile when doing timings, and results can change depending on the order and the number of times you run it. I certainly get small discrepancies in the order of about 5 to 10 percent, but in general it's usually quite clear as to which one is faster.

Offline

**Adam Bell****Administrator**- From:: Nova Scotia, Canada
- Registered: 2005-10-04
- Posts: 4665

QD's test results in 2.888888888889 on mm (just for the record)

iMac running OS X 10.13.1

Offline

**Nigel Garvey****Moderator**- From:: Warwickshire, England
- Registered: 2002-11-20
- Posts: 4853

Adam Bell wrote:

QD's test results in 2.888888888889 on mm (just for the record)

Similar average result here, timing both with **current date** and with **GetMilliSec**, which is a sharper instrument. But the tests aren't actually comparing the timings of 2099000 calls to the two handlers, they're testing 1000 executions of the inner 2099-repeat loops â€“ which include the handler calls. In other words, both test times include 1000 settings-up of the inner repeats + 2099 * 1000 iterations of the inner looping code + 1 setting-up of the outer repeat + 1000 iterations of the outer looping code. Subtracting these from the total times and assuming the repeat instructions take exactly the same time in both cases, we're left with smaller timings but the same difference, which suggests an even better ratio between the handlers themselves. Eschewing the outer repeats and having the inner repeats loop from 1 to 2099000, I see a small (about 1.74%) but definite improvement in the comparative figure.

Running the nested repeats empty (without the handler calls) takes about 1.34 seconds each on my machine. That's added to the timing of the test for each handler when I run Qwerty's script. Running non-nested 1-to-2099000 repeats by themselves takes about 1.09 seconds. Empty, non-nested '2099000 times' repeats take only about 0.6 seconds, so they distort the comparative speeds the least. But in the current case, the 1-to-2099000 repeats are possibly preferable since they give a better average over the various numbers that can be passed to the handlers. This is necessary because the handlers themselves do more or fewer boolean tests depending on the year number they're given, so these tests are a greater or lesser proportion of their total running times, which in turn affects their relative running times. Repeating '2099000 times' with a year number of 1999 (only the first boolean test is performed in each handler) gives me a relative time of 3.05, whereas with 2000 (all the tests are performed), it's reduced to 2.3.

Moral:

I should get an early night.

NG

Offline

Hello.

The ecclestical easter (which we base our hollidays on), differs from the astronomical easter.

This one returns the date of easter by ecclestical standards.

## Applescript:

to MonthAndDayOfEaster for aYear

# [url]http://aa.usno.navy.mil/faq/docs/easter.php[/url]

set c to aYear div 100

set n to aYear - 19 * (aYear div 19)

set k to (c - 17) div 25

set i to c - c div 4 - (c - k) div 3 + 19 * n + 15

set i to i - 30 * (i div 30)

set i to i - (i div 28) * (1 - (i div 28) * (29 div (i + 1)) * ((21 - n) div 11))

set j to aYear + aYear div 4 + i + 2 - c + c div 4

set j to j - 7 * (j div 7)

set l to i - j

set m to 3 + (l + 40) div 44

set d to l + 28 - 31 * (m div 4)

return {m, d}

end MonthAndDayOfEaster

**Edit**

In all fairness: in our particular time span, between 1982 and 2037, the astronomical easter and ecclestical easter do coincide, I just posted this, in case one wants to figure out earlier, and later! easter dates, for other purposes than calculating lunar phases.

*Last edited by McUsrII (2013-09-19 06:30:31 am)*

**T.B**

p a file peruser, work in progress, but is sane, and lets you copy the file you view to the clipboard. Enter just "p" for help. Slightly better terminal handling, when executing shell commands from within.

Offline

**CMYS****Member**- From:: Belgium
- Registered: 2007-07-02
- Posts: 83

Hi all! What about this kind of code?

## Applescript:

display dialog "Easter : " & (do shell script "LC_TIME=\"fr_BE.UTF-8\" ncal -e \"2014\"")

Offline

Hello.

Thanks for sharing! I read that ncal even has an option for calculating eastor for orthodox/Eastern churches, (-o) It's impressive!

*Last edited by McUsrII (2013-09-20 09:47:45 pm)*

**T.B**

p a file peruser, work in progress, but is sane, and lets you copy the file you view to the clipboard. Enter just "p" for help. Slightly better terminal handling, when executing shell commands from within.

Offline