前言
这是几个日历相关的方法, 来源似乎是很久以前才学习java的时候, 看到的问题, 但是 当时并没有想到很好解决问题的思路, so 后来某一天突然 便想到了这个问题, 便做了一下
.. 是不是又在自己造轮子了,,, 当然 这不过是闲着无事而已
问题描述
- 输入年, 和多少天, 输出月 和日
- 输入年, 月 和日, 输出该天是在该年的第N天
- 输入年, 和多少天, 输出该天距离公元0年的天数
- 输入年, 和月, 打印出该月的日历
思路
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) );
}
}
效果截图
总结
闲来无事瞎扯淡。。
注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!