地球上的人类爱过节,男人可以趁机放假,女人可以借机购物。人类最重要的节日,在东方当属春节、中国国庆节和劳动节,在西方当属圣诞节、元旦和复活节。前五个都好说,阳历或阴历上的日期是确定的,那么,聪明的你,我提个问题:最后那个,就是复活节,是在哪一天?
你回答说:
复活节是每年春分月圆之后第一个星期日。
OK。那么,再问:那它到到到到到底是哪一天……
三个关键词:春分,这是阳历;月圆,这是阴历;星期日,这是不阴不阳的历。敢问这是哪位高人定的,是在考验人类的计算水平吗?够狠。
一查才知道,的确是在考计算。英文的 computer 是“计算机”,compute 和 computation 分别是动词和名词的“计算”,他们均来自拉丁语的Computus,而这个词的本意就是“计算复活节的方法”!
我又问了:为啥是这样规定呢?
你说:
春分,是因为此后北半球开始日长夜短,意味着光明;月圆,是因为夜晚也洒满光辉,也意味着光明;星期日,是因为这天大家都休息,不用倒休。
我脑海中立刻浮现出明教、日月神教等字眼,嘴上只好说,好吧,算你有理。我算是勉强被说服了,但是听到你的下一句话,我崩溃了,你说:
只不过啊,天主教和东正教的复活节是两种不同的计算方法……
基督教有三大主要派别,其中天主教和基督新教(华语圈常简称基督教)属于西方罗马教会,使用公历;东正教属于东方教会,使用儒略历。
我不信教,这已经超出我大脑能理解的极限了。没啥说的,请 R 出马,自己写个自定义函数,看看这个 Computus 是怎么计算的吧。计算方法可以选择高斯还是米乌斯,日历可以选择公历还是儒略历。因此,整个函数的结构是两个大分支下各有两个小分支。
computus <- function(year = 2013, method = "Meeus", calendar = "Gregorian")
{
Y <- year
# 高斯算法
if (method == "Gauss") # 适用于 1583 - 2299 年
{
# 公历(天主教)
if (calendar == "Gregorian")
{
k <- (Y - 1500) %/% 100 + 1
M <- c(22,22,23,23,24,24,24,25)[k]
N <- c(2,2,3,4,5,5,6,0)[k]
} else if (calendar == "Julian")
# 儒略历(东正教)
{
M <- 15; N <- 6
} else
{
print("sorry, the calendar does not exist. use method = 'Gregorian' or 'Julian'")
}
a <- Y %% 19
b <- Y %% 4
c <- Y %% 7
d <- (19 * a + M) %% 30
e <- (2 * b + 4 * c + 6 * d + N) %% 7
computus.date <- ifelse(d + e < 10,
paste("3-", d + e + 22, sep = ""),
paste("4-", d + e - 9, sep = ""))
if (computus.date == "4-26") computus.date <- "4-19"
if (computus.date == "4-25" & d == 28 & e == 6 & a > 10) computus.date <- "4-19" # 高斯,I 服了 U!
computus <- paste(Y, computus.date, sep = "-")
} else if (method == "Meeus")
# Meeus 算法
{
# 公历(天主教)
if (calendar == "Gregorian")
{
a <- Y %% 19
b <- Y %/% 100
c <- Y %% 100
d <- b %/%4
e <- b %%4
f <- (b + 8) %/% 25
g <- (b - f + 1) %/%3
h <- (19 * a + b - d - g + 15) %% 30
i <- c %/% 4
k <- c %% 4
L <- (32 + 2 * e + 2 * i - h - k) %% 7
m <- (a + 11 * h + 22 * L) %/% 451
month <- (h + L - 7 * m + 114) %/% 31
day <- ((h + L - 7 * m + 114) %% 31) + 1
} else if (calendar == "Julian")
# 儒略历(东正教)
{
a <- Y %% 4
b <- Y %% 7
c <- Y %% 19
d <- (19 * c + 15) %% 30
e <- (2 * a + 4 * b - d + 34) %% 7
month <- (d + e + 114) %/% 31
day <- ((d + e + 114) %% 31) + 1
} else
{
print("sorry, the calendar does not exist. use method = 'Gregorian' or 'Julian'")
}
computus <- paste(Y, month, day, sep = "-")
} else
{
print("sorry, the method does not exist. use method = 'Gauss' or 'Meeus'")
}
return(computus)
}
这个自定义函数使用的时候 method 可以选择 Gauss 或 Meeus 算法,日历可以选择 Gregorian 公历或者 Julian 儒略历。
让我们试试这个自定义函数的运算结果:
computus()
## [1] "2013-3-31"
computus(year = 2013, method = "Gauss")
## [1] "2013-3-31"
computus(year = 1961)
## [1] "1961-4-2"
computus(year = 2000)
## [1] "2000-4-23"
都是正确的。
下面让我们计算出本世纪所有年份的天主教复活节:
computus(year = 2000:2099)
## [1] "2000-4-23" "2001-4-15" "2002-3-31" "2003-4-20" "2004-4-11"
## [6] "2005-3-27" "2006-4-16" "2007-4-8" "2008-3-23" "2009-4-12"
## [11] "2010-4-4" "2011-4-24" "2012-4-8" "2013-3-31" "2014-4-20"
## [16] "2015-4-5" "2016-3-27" "2017-4-16" "2018-4-1" "2019-4-21"
## [21] "2020-4-12" "2021-4-4" "2022-4-17" "2023-4-9" "2024-3-31"
## [26] "2025-4-20" "2026-4-5" "2027-3-28" "2028-4-16" "2029-4-1"
## [31] "2030-4-21" "2031-4-13" "2032-3-28" "2033-4-17" "2034-4-9"
## [36] "2035-3-25" "2036-4-13" "2037-4-5" "2038-4-25" "2039-4-10"
## [41] "2040-4-1" "2041-4-21" "2042-4-6" "2043-3-29" "2044-4-17"
## [46] "2045-4-9" "2046-3-25" "2047-4-14" "2048-4-5" "2049-4-18"
## [51] "2050-4-10" "2051-4-2" "2052-4-21" "2053-4-6" "2054-3-29"
## [56] "2055-4-18" "2056-4-2" "2057-4-22" "2058-4-14" "2059-3-30"
## [61] "2060-4-18" "2061-4-10" "2062-3-26" "2063-4-15" "2064-4-6"
## [66] "2065-3-29" "2066-4-11" "2067-4-3" "2068-4-22" "2069-4-14"
## [71] "2070-3-30" "2071-4-19" "2072-4-10" "2073-3-26" "2074-4-15"
## [76] "2075-4-7" "2076-4-19" "2077-4-11" "2078-4-3" "2079-4-23"
## [81] "2080-4-7" "2081-3-30" "2082-4-19" "2083-4-4" "2084-3-26"
## [86] "2085-4-15" "2086-3-31" "2087-4-20" "2088-4-11" "2089-4-3"
## [91] "2090-4-16" "2091-4-8" "2092-3-30" "2093-4-12" "2094-4-4"
## [96] "2095-4-24" "2096-4-15" "2097-3-31" "2098-4-20" "2099-4-12"
当我敲完上面几十行代码后,突然有些异样的感觉。上网搜了一下,悲催了,原来上面那些自定义函数其实都白敲了,复活节计算从头到尾只需要两行代码:
require("timeDate")
Easter()
每到这个时候,我都会深入理解一次,R 的精髓在于包啊!
注:本文是受网友 yangliufr 的启发写成的。征得 yangliufr 同学的同意后,本打算在 yangliufr 同学所写的代码基础上修改,后来发现 yangliufr 使用的百度百科的计算方法出处不详,所以重新写了代码。在此对 yangliufr 致谢。
注:计算方法参考维基百科。