前言

这是几个日历相关的方法, 来源似乎是很久以前才学习java的时候, 看到的问题, 但是 当时并没有想到很好解决问题的思路, so 后来某一天突然 便想到了这个问题, 便做了一下
.. 是不是又在自己造轮子了,,, 当然 这不过是闲着无事而已

问题描述

  1. 输入年, 和多少天, 输出月 和日
  2. 输入年, 月 和日, 输出该天是在该年的第N天
  3. 输入年, 和多少天, 输出该天距离公元0年的天数
  4. 输入年, 和月, 打印出该月的日历

思路

theDay 表示在某一年中的第theDay天
monthIdx 表示在某一年中的第monthIdx月
dayInMonth 表示在某一个月的dayInMonth天
yearIdx 表示第yearIdx年

注意 : 下面的各月份的天数表示在给定的yearIdx 和给定的monthIdx月份的天数[平年2月为28天, 闰年2月为29天, 其余月份的天数固定]

问题1 : getMonthDayByYearDay(int yearIdx, int theDay)
theDay减去各个月份的天数, monthIdx递增, 直到theDay小于0, 得到的monthIdx即为所求的月份, dayInMonth为theDay加上第monthIdx月的天数

问题2 : getDaysByYearMonthDays(int yearIdx, int monthIdx, int dayInMonth)
从1月到monthIdx月依次加上各个月份的天数, 然后在加上dayInMonth即为所求

问题3 : getDaysFromBC(int yearIdx, int monthIdx, int dayInMonth)
首先计算出给定的monthIdx, dayInMonth在该年的第N天[计算theDay],
然后从缓存[yearToDays]中获取比给定的yearIdx大的最小的条目[ceilingEntry] 和比给定的yearIdx小的最大的条目[floorEntry], 比较谁离yearIdx更近, 然后从更近的条目来计算结果
    假设floorEntry更近 首先以400年为偏移计算, 然后在以1年为偏移进行计算, 然后在加上theDay 即为所求
    同理 假设ceilingEntry更近 [请注意这里需要处理一个特殊的逻辑], 首先以400年为偏移计算, 然后在以1年为偏移进行计算, 然后在计算yearIdx的theDay 即为所求

问题4 : 这个问题分为两个子问题, 第一, 获取给定的yearIdx, monthId的第一天对应的星期, 第二, 绘制日历, 这里 主要着重介绍第一个子问题, 第二个子问题 请详见代码

getTheWeekByYearMonth(int year, int month)
首先 从缓存[yearToWeeks]中获取比给定的yearIdx大的最小的条目[ceilingEntry] 和比给定的yearIdx小的最大的条目[floorEntry], 比较谁离yearIdx更近, 然后从非空的条目来计算结果, 优先选择更近的条目[当然, 后面这个”选择最近”没有什么必要[不太重要] ]
然后通过yearIdx, monIdx的第一天 和floorEntry 或者ceilingEntry”相隔的绝对天数” 推算yearIdx, monIdx的第一天的星期数

参考代码

/**
 * file name : Test28Canlender.java
 * created at : 10:58:50 AM Jun 12, 2015
 * created by 
 */

package com.hx.test05;

public class Test28Calendar {

    // 参数, 最 小/ 大 的年份,周, 月
    // 每一个月对应的天数, 平年的天数, 400年的数字表示, 400年的天数
    static int MIN_YEAR = 0;
    static int MAX_YEAR = 9999;
    static final int WEEK_NUM = 7;
    static int MIN_MONTH = 1;
    static int MAX_MONTH = 12;
    static int MIN_WEEK = 1;
    static int MAX_WEEK = 7;
    static int[] monthToDays = new int[] {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    static int nonLeapYearDays = 365;
    static int fourHundredYear = 400;
        // 以4年为单元, 计算[0-400)年的天数, - 三个非闰年多计算的三天[100, 200, 300]
    static int fourHundredYearDays = ((nonLeapYearDays << 2) + 1) * 100 - 3;

    // 1. 输入年, 和多少天, 输出月 和日 
    // 3. 输入年, 月 和多少天, 输出该日子在该年的第N天
    // 3. 输入年, 月 和多少天, 输出改日距离公元0年的天数
    // 4. 输入年, 和月, 打印出该月的日历
    public static void main(String []args) {

        int year = 2016, theDay = 63, month = 3, dayInMonth = 3;

        getMonthDayByYearDay(year, theDay);

        int theDay0 = getDaysByYearMonthDays(year, month, dayInMonth);
        Log.log("the day : " + theDay0 );

        theDay0 = getDaysFromBC(year, theDay);
        Log.log("the day from BC : " + theDay0 );

        int theWeek = getTheWeekByYearMonth(year, month);
        drawTheWeek(year, month, theWeek);

        // 计算四百天的天数
//      [0-399] 146097 天        <=>  ((nonLeapYearDays << 2) + 1) * 100 - 3;    
//      int days = 0;
//      for(int i=0; i<fourHundredYear; i++) {
//          days += nonLeapYearDays + (isLeapYear(i) ? 1 : 0);
//      }
//      Log.log(days);

    }

    // 校验year, theDay
    // 依次减去各个月份的天数, 直到theDay小于0
    // 得到的monthIdx即为月份, 然后theDay在加上该月的天数, 即为theDay在该月的天数
    public static void getMonthDayByYearDay(int year, int theDay) {
        boolean isLeapYear = isLeapYear(year);
        if(!checkYear(year)) {
            Log.log("year error ...");
            return ;
        }
        if(!checkTheDay(theDay, isLeapYear)) {
            Log.log("day error ...");
            return ;
        }

        int tmp = theDay;
        int monthIdx = 0;
        tmp -= monthToDays[monthIdx ++];
        if(tmp > 0) {
            tmp -= monthToDays[monthIdx ++];
            if(isLeapYear) {
                tmp --;
            }
        }
        while(tmp > 0) {
            tmp -= monthToDays[monthIdx ++];
        }

        int dayIdx = tmp + monthToDays[monthIdx - 1];

        Log.log("monthes : " + monthIdx + "  days : " + dayIdx);
    }

    // 通过, 年月日, 获取改日是改年中的那一天
    public static int getDaysByYearMonthDays(int year, int month, int dayInMonth) {
        boolean isLeapYear = isLeapYear(year);
        if(!checkYear(year)) {
            Log.log("year error ...");
            return -1;
        }
        if(!checkMonth(month)) {
            Log.log("month error ...");
            return -1;
        }
        if(!checkTheMonthDay(month, dayInMonth, isLeapYear)) {
            Log.log("day error ...");
            return -1;
        }

        int days = 0;
        int monthEnd = month - 1;
        for(int i=0; i<monthEnd; i++) {
            days += monthToDays[i];
        }
        days += dayInMonth;

        if(month > 2 && isLeapYear) {
            days ++;
        }

//      Log.log("the day : " + days);
        return days;
    }

    // 缓存
    static TreeMap<Integer, Integer> yearToDays = new TreeMap<Integer, Integer>();
    static {
        yearToDays.put(0, 0);
    }

    // 获取year年中theDay所在的天数, 距离公元0年的天数
        // 获取一个最接近的缓存记录, 来计算指定的year, theDay
    public static int getDaysFromBC(int year, int month, int dayInMonth) {
        return getDaysFromBC(year, getDaysByYearMonthDays(year, month, dayInMonth) );
    }
    public static int getDaysFromBC(int year, int theDay) {
        boolean isLeapYear = isLeapYear(year);
        if(!checkYear(year)) {
            Log.log("year error ...");
            return -1;
        }
        if(!checkTheDay(theDay, isLeapYear)) {
            Log.log("day error ...");
            return -1;
        }

        Entry<Integer, Integer> floorEntry = yearToDays.floorEntry(year);
        Entry<Integer, Integer> ceillingEntry = yearToDays.ceilingEntry(year);
        int floorDis = MAX_YEAR, ceillingDis = MAX_YEAR;
        if(floorEntry != null) {
            floorDis = year - floorEntry.getKey();
        }
        if(ceillingEntry != null) {
            ceillingDis = ceillingEntry.getKey() - year;
        }

        int days = 0;
        if(ceillingEntry == null || floorDis <= ceillingDis) {
            days = getDaysFromFloorEntry(year, theDay, floorEntry);
        } else {
            days = getDaysFromCeillingEntry(year, theDay, ceillingEntry);
        }

        return days;
    }

    // 缓存。。。 我觉得这里,都不用缓存了,因为这个算法, 依赖于上一个算法, 上一个算法存在缓存, 所以, 这个基本上可以取消了
    // 但是为了便于以后的扩展, 将其保留在这里吧
    static TreeMap<Integer, Integer> yearToWeeks = new TreeMap<Integer, Integer>();
    static {
        yearToWeeks.put(1990, 1);
    }

    // 输入年, 和月, 获取该月第一天的星期
    public static int getTheWeekByYearMonth(int year, int month) {
        if(!checkYear(year)) {
            Log.log("year error ...");
            return -1;
        }
        if(!checkMonth(month)) {
            Log.log("month error ...");
            return -1;
        }

        Entry<Integer, Integer> floorEntry = yearToWeeks.floorEntry(year);
        Entry<Integer, Integer> ceillingEntry = yearToWeeks.ceilingEntry(year);
        int floorDis = MAX_YEAR, ceillingDis = MAX_YEAR;
        if(floorEntry != null) {
            floorDis = year - floorEntry.getKey();
        }
        if(ceillingEntry != null) {
            ceillingDis = ceillingEntry.getKey() - year;
        }

        int theWeek = 0;
        if(ceillingEntry == null || floorDis <= ceillingDis) {
            theWeek = getTheWeekFromFloorEntry(year, month, floorEntry);
        } else {
            theWeek = getTheWeekFromCeillingEntry(year, month, ceillingEntry);
        }

        return theWeek;
    }

    // 绘制出日历
    static String HALF_NONE = "   ";
    static String NONE = HALF_NONE + HALF_NONE;
    public static void drawTheWeek(int year, int month, int theWeek) {
        Log.log("year : " + year + "  month : " + month);
        Log.log("  sun     mon     tues   wednes   thurs   fri    satur");
        for(int i=0; i<theWeek; i++) {
            Log.logWithoutLn(NONE + "  ");
        }

        int idx = 1;
        int allDays = getMonthMaxDays(month, isLeapYear(year));
        int weekIdx = theWeek;
        int completeWeekNum = (allDays + theWeek) / WEEK_NUM;
        for(int weekNum=0; weekNum<completeWeekNum; weekNum ++) {
            for(int i=weekIdx; i<WEEK_NUM; i++) {
                Log.logWithoutLn(HALF_NONE + String.format("%02d", (idx ++) ) + HALF_NONE); 
            }
            Log.enter();
            weekIdx = 0;
        }
        while(idx <= allDays) {
            Log.logWithoutLn(HALF_NONE + String.format("%02d", (idx ++) ) + HALF_NONE); 
        }
    }

    // 根据一个小于指定year的条目, 来计算year年, theDay 从公元0年的天数
    private static int getDaysFromFloorEntry(int year, int theDay, Entry<Integer, Integer> floorEntry) {
        int closestYear = floorEntry.getKey();
        int days = floorEntry.getValue();
        int nFourHundredYears = (year - closestYear) / 400;
        for(int i=0; i<nFourHundredYears; i++) {
            days += fourHundredYearDays;
            closestYear += fourHundredYear;
            yearToDays.put(closestYear, days);
        }

        for(int i=closestYear; i<year; i++) {
            if(i % 100 == 0) {
                yearToDays.put(i, days);
            }
            int tmp = nonLeapYearDays + (isLeapYear(i) ? 1 : 0);
            days += tmp;
        }
        days += theDay;

        return days;
    }

    // 根据一个大于指定year的条目, 来计算year年, theDay 从公元0年的天数
    // 不过 : 请注意, 这里需要对[year == ceillingEntry.getKey()] 做特殊处理[这取决于前面是否  判定ceillingEntry.getYear > year]
    private static int getDaysFromCeillingEntry(int year, int theDay, Entry<Integer, Integer> ceillingEntry) {
        int closestYear = ceillingEntry.getKey();
        int days = ceillingEntry.getValue();
        if(year == closestYear) {
            days += theDay;
            Log.log("days from BC0 : " + days);
            return days;
        }

        int nFourHundredYears = (closestYear - year) / 400;
        for(int i=0; i<nFourHundredYears; i++) {
            days -= fourHundredYearDays;
            closestYear -= fourHundredYear;
            yearToDays.put(closestYear, days);
        }

        for(int i=closestYear-1; i>year; i--) {
            if(i % 100 == 0) {
                yearToDays.put(i, days);
            }
            int tmp = nonLeapYearDays + (isLeapYear(i) ? 1 : 0);
            days -= tmp;
        }
        int yearDays = nonLeapYearDays + (isLeapYear(year) ? 1 : 0); 
        days -= (yearDays - theDay);

        return days;
    }

    // 通过较低的年份的条目获取当前year, month的第一天的星期
    private static int getTheWeekFromFloorEntry(int year, int month, Entry<Integer, Integer> floorEntry) {
        int theDay = getDaysByYearMonthDays(year, month, 1);
        int days = getDaysFromBC(year, theDay);

        int off = days - getDaysFromBC(floorEntry.getKey(), 1);
        int theWeek = (floorEntry.getValue() + off) % WEEK_NUM;

        return theWeek;
    }

        // 通过较高的年份的条目获取当前year, month的第一天的星期
    private static int getTheWeekFromCeillingEntry(int year, int month, Entry<Integer, Integer> ceillingEntry) {
            if(year == ceillingEntry.getKey() ) {
            return getTheWeekFromFloorEntry(year, month, ceillingEntry);
        }
        int theDay = getDaysByYearMonthDays(year, month, 1);
        int days = getDaysFromBC(year, theDay);

        int off = getDaysFromBC(ceillingEntry.getKey(), 1) - days;
        int theWeek = (ceillingEntry.getValue() - off) % WEEK_NUM + WEEK_NUM;

        return theWeek;
    }

    // 校验年份[1, 9999]
    private static boolean checkYear(int year) {
        if(year < MIN_YEAR || year > MAX_YEAR) {
            return false;
        }

        return true;
    }
    // 校验月份[1, 12]
    private static boolean checkMonth(int month) {
        if(month < MIN_MONTH || month > MAX_MONTH) {
            return false;
        }

        return true;
    }
    // 校验theMonthDays[表示一月中的天数]
        // 按照是否是2月进行校验
    private static boolean checkTheMonthDay(int month, int theMonthDay, boolean isLeapYear) {
        int maxDays = getMonthMaxDays(month, isLeapYear);
        if(theMonthDay < 1 || theMonthDay > maxDays) {
            return false;
        }

        return true;
    }
    // 获取给定的月数的最大天数
    private static int getMonthMaxDays(int month, boolean isLeapYear) {
        if(month != 2) {
            return monthToDays[month-1];
        } else {
            return monthToDays[month-1] + (isLeapYear ? 1 : 0);
        }
    }
    // 校验theDay, [1, 365/ 366]
    private static boolean checkTheDay(int theDay, boolean isLeapYear) {
        if(theDay < 1) {
            return false;
        }
        int allDays = nonLeapYearDays + (isLeapYear ? 1 : 0);
        if(theDay > allDays) {
            return false;
        }

        return true;
    }

    // 判断给定的年是否是闰年
        // 1. 能够被400整除的
        // 2. 能够被4整除, 但是不能被100整除的
    private static boolean isLeapYear(int year) {
        return (year % 400 == 0) || (((year & 3) == 0) && (year % 100 != 0) );
    }

}

效果截图

15 几个Calender相关的方法_算法

总结

闲来无事瞎扯淡。。

注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!