如果让你计算某一天是一年中的星期几,你该怎么算(当然前提是不允许使用工具类的)?
这个算法的发明人是约翰·康威教授,任职于美国新泽西州普林斯顿大学数学系,他这个算法不像其他算法,这个末日算法在记忆一定数据之后甚至可以口算,这样在聚会或者游戏中露一手。


先简单说一下年月日的定义
我们知道,年是地球公转一圈的时间,月是一个满月到下一个满月的时间,日是地球自转一圈的时间,但是无论如何精确定义这三个时间,都不能与太阳、地球、月亮三者的运动百分百吻合。
例如,两个满月之间平均时间实际是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 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用 * 标注