如果让你计算某一天是一年中的星期几,你该怎么算(当然前提是不允许使用工具类的)?
这个算法的发明人是约翰·康威教授,任职于美国新泽西州普林斯顿大学数学系,他这个算法不像其他算法,这个末日算法在记忆一定数据之后甚至可以口算,这样在聚会或者游戏中露一手。
先简单说一下年月日的定义
我们知道,年是地球公转一圈的时间,月是一个满月到下一个满月的时间,日是地球自转一圈的时间,但是无论如何精确定义这三个时间,都不能与太阳、地球、月亮三者的运动百分百吻合。
例如,两个满月之间平均时间实际是29.5日,如果所有的月份都定义为29.5天,那么一年是354天。为了拟补这种缺陷,埃及天文学家最早设计了我们今天使用的一年365天,每4年增加一天。
虽然这种月历使用了相当长一段时间,但是还是有细小的误差,这种误差在1582年时,达到了6天之长,为了消除这种误差,教皇格雷戈里十三世宣布,一个新世纪开始的年份(即能被100整除的年份),若不能被400整除,则不是闰年。
下面开始说算法原理
原理非常简单,为了判断不同日期的星期,算法中首先设立一个必要的基准,然后根据星期以7为循环的原则和对闰年的考虑,计算日期对应的星期,而被选中的日期,就叫做末日。
我们选择年中比较特殊的天作为末日,即2月28(29)日,这样我们可以在每个月中找出永远与末日相同星期(相隔7天)的日期,即
4月4日 6月6日 8月8日 10月10日 12月12日
9月5日 5月9日 7月11日 11月7日 3月7日
比如,1997年2月28日是周五,那么11月7日也是周五,对应的11月6日周四,11月8日周六……..
这里我们基本就可以看出这个算法的大概实现过程。
那我们还剩下一个问题,那就是怎么知道的1997年2月28日是星期五呢?查日历呗!
开玩笑的哈哈哈,能查为什么还算,其实这里有一个规律,我们现在知道;
1997年2月28日 星期五
同时
1998年2月28日 星期六
1999年2月28日 星期日
差不多看出规律了吧,每增加一年,末日的星期数加1,但是闰年因为多一天的原因,则加2。
我们只要记住一个基准,1990年的末日是星期三,就可以计算所有年份的末日星期了。
同时,因为星期以7为周期,那么
1906 1917 1923 1928 1934 1945 1951 1956 1962 1973 1979 1984 1990
末日星期都是星期三
举个例子,计算1992年9月13日是周几:
首先1990年末日是星期三,那么1992年末日是星期五,那就错了,1992年是闰年,所以1992年的末日是星期六,接下来我们看日期,9月5日和末日相同,是星期六,那么9月13日,和9月5日差了8天,除7余1,于是6+1=7,为星期天,所以1992年9月13日为星期天。
其实这个算法说是只能对二十世纪有效,跨世纪之后就无效了,我其实是不太能理解为什么会失效,在程序测试时,21世纪前半叶的几组数据都输出了正确的星期,在后半叶有几组出现了错误,如果有dalao知道这是为什么,欢迎在评论区指点一下。
下面是代码实现:
这里因为是程序实现,就没有对年份进行打表,而是选择从1900年开始算起。
import java.time.LocalDate; import java.util.ArrayList; import java.util.Scanner; /** * Created by Sniper on 2018/8/24. */ public class ComputeDayOfTheWeek { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 获取用户输入的年月日 int year = scanner.nextInt(); int mouth = scanner.nextInt(); int day = scanner.nextInt(); String[] week = new String[]{"SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY"}; // 初始化一个LocalDate,用来输出星期几来验证算法 LocalDate localDate = LocalDate.of(year, mouth, day); // 用来保存几个和末日日期相同的日期 ArrayList<MyDate> staticDate = new ArrayList<>(); // 首先初始化和末日永远相同的几个日期 staticDate.add(new MyDate(4, 4)); staticDate.add(new MyDate(6, 6)); staticDate.add(new MyDate(8, 8)); staticDate.add(new MyDate(10, 10)); staticDate.add(new MyDate(12, 12)); staticDate.add(new MyDate(9, 5)); staticDate.add(new MyDate(5, 9)); staticDate.add(new MyDate(7, 11)); staticDate.add(new MyDate(11, 7)); staticDate.add(new MyDate(3, 7)); // 计算从1900年到用户输入年末日星期一共需要增加多少天 int countOfYear = 0; for (int i = 1901; i <= year; i++) { // 根据末日算法,如果该年是平年,末日星期+1,如果该年是闰年,末日日期+2 countOfYear++; if ((i % 4 == 0 && i % 400 != 0) || i % 400 == 0) { countOfYear++; } } // 初始化1900年末日星期为3 int dayOfWeekIn1900 = 3; // 计算当前年份末日的星期 int dayOfWeekInput = (dayOfWeekIn1900 + countOfYear) % 7; // 根据当前年的平年闰年去调用不同的方法 int lengthDis = 0; if ((year % 4 == 0 && year % 400 != 0) || year % 400 == 0) { lengthDis = computeDis(staticDate, mouth, day, true); } else { lengthDis = computeDis(staticDate, mouth, day, false); } //对返回的末日距离进行判断,负数需要进行简单处理,加回正常范围 int length = 0; length = (dayOfWeekInput + lengthDis) % 7; if (length < 0) { length = length + 7; } // 输出结果 System.out.println("末日星期:" + week[dayOfWeekInput]); System.out.println("末日距离:" + lengthDis); System.out.println("预期结果:" + localDate.getDayOfWeek()); System.out.println("实际结果:" + week[length]); } /** * 计算输入天数和最近的staticDate之间的天数差距 * * @param staticDate 静态日期表 * @param mouth 需要被计算的月份 * @param day 需要被计算的日 * @param flag 改年是否是闰年,如果是为true */ private static int computeDis(ArrayList<MyDate> staticDate, int mouth, int day, boolean flag) { int length = 0; // 首先检查一二月份,因为在静态日期表中,没有这两个月份,这两个月份也需要被单独运算 // 对一月份进行计算,对应平闰年分别是25号和24号和末日星期相同 if (mouth == 1) { if (flag) { length = day - 25; } else { length = day - 24; } } // 对于二月份进行计算,同时区分平闰年 if (mouth == 2) { if (flag) { length = day - 29; } else { length = day - 28; } } // 首先检查已经有的月份 for (MyDate d : staticDate) { if (d.getMouth() == mouth) { length = day - d.getDay(); } } return length; } } /** * 自定义日期类,不使用LocalDate,因为该类中可以直接返回星期几 */ class MyDate { private int mouth; private int day; MyDate(int mouth, int day) { this.mouth = mouth; this.day = day; } public int getMouth() { return mouth; } public void setMouth(int mouth) { this.mouth = mouth; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } }
0 条评论