从时间戳(毫秒)计算日历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
class MyDate {
private int year;
private int month;
private int day;
private int hour;
private int minute;
private int second;

private final long timePerSecond = 1000;
private final long timePerMinute = 1000*60;
private final long timePerHour = 3600*1000;
private final long timePerDay = 24*3600*1000;
private final long timePerPingNian = 365*timePerDay;
private final long timePerLeapYear = 366*timePerDay;
private final long timeOf1970And1971 = timePerPingNian+timePerPingNian;
private final long timePer4Year = (long)(365.25*4*timePerDay);
private final long timeEvery400Year = timePer4Year*100 - 3*timePerDay;
private final long timeOf1970To2000 = timePer4Year*7+timeOf1970And1971;
private final int[] daysPerMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

private boolean setMinuteAndSecond(long milliTime) {

assert milliTime<timePerDay;
if(milliTime>=timePerDay)
return false;

hour = (int)(milliTime/timePerHour);
long t = milliTime%timePerHour;
minute = (int)(t/timePerMinute);
t = t%timePerMinute;
second = (int)(t/timePerSecond);

return true;
}

private boolean setMonthAndDay(long milliTime, boolean isLeapYear) {

//this func should handle the case where milliTime == 0;
//this func get the milliseconds within 1 year;

assert milliTime<=timePerLeapYear && isLeapYear || milliTime<=timePerPingNian && !isLeapYear;
if(milliTime>timePerLeapYear && isLeapYear || milliTime>timePerPingNian && !isLeapYear) {
return false;
}

daysPerMonth[1] = isLeapYear ? 29 : 28; //set the days of February;

int cnt = -1;

do{
milliTime -= daysPerMonth[++cnt]*timePerDay;
}while (milliTime>=0);

month = cnt+1; //for that the cnt begin from 0, but month begin from 1;
long timeOfLastMonth = daysPerMonth[cnt]*timePerDay+milliTime;
day = (int)(timeOfLastMonth/timePerDay)+1;

setMinuteAndSecond(timeOfLastMonth % timePerDay);

return true;
}

private boolean setDataFile(long milliTime) {
if(milliTime<0)
return false;

boolean is1970Or1971 = milliTime<timeOf1970And1971 ? true : false;
boolean is20Century = milliTime<timeOf1970To2000 ? true: false;
//if equal is 2000 1 1 0:0:0

if(is1970Or1971) {
year = milliTime>=timePerPingNian ? 1971 : 1970;
setMonthAndDay(year==1971 ? milliTime-timePerPingNian : milliTime, false);
return true;
}

if(is20Century) {
long t = (milliTime-timeOf1970And1971)/timePer4Year;
long t1 = milliTime-timeOf1970And1971-t*timePer4Year;

long t2 = t1-timePerLeapYear;
boolean isLeapYear = t2<0;
long t3 = isLeapYear ? 0 : t2/timePerPingNian+1;

year = (int)(1972+t*4+t3);

long l = isLeapYear ? t1 : (t1-t3*timePerPingNian-timePerDay);
setMonthAndDay(l, isLeapYear);

return true;
}

long p = milliTime-timeOf1970To2000;

long t1 = p/timeEvery400Year;
long p1 = p-t1*timeEvery400Year;

long t2 = p1/timePer4Year;
long tN = t2/25;
//in every 400 years, other year such as xy00(y!=0) is not leap year;

long p2 = p1-t2*timePer4Year;
p2 += tN*timePerDay;

long t3 = p2-timePerLeapYear;

boolean isLeapYear = t3<0;
long t4 = isLeapYear ? 0 : t3/timePerPingNian+1;

year = (int)(2000+t1*400+t2*4+t4);

long lt = isLeapYear ? p2 : (p2-t4*timePerPingNian-timePerDay);
setMonthAndDay(lt, isLeapYear);
return true;
}
}

思路

设置时分秒(函数setMinuteAndSecond)

  • 函数的输入是 小于 每天毫秒数 的一个整数
  • 因为是从0:0:0开始计时,所以很好算
  • 直接除以每小时毫秒数,结果就是 要求的小时数
  • 然后把取模每小时毫秒数的结果除以每分钟毫秒数,算出来的结果就是分钟数
  • 以此类推,算出秒数

设置月和日(函数setMonthAndDay)

  • 函数的输入为 小于 每年毫秒数 一个整数
  • 依次减去每个月的毫秒数,当减去某个月后,得到负数,说明月份就是这个月。注意,如果减去后得到0,则是下个月的第一毫秒,因为毫秒数是相对1970.1.1.0:0:0计算的。举个例子,毫秒数==一个平年的毫秒数,那么加上1970.1.1.0:0:0后,就变成1971.1.1.0:0:0,而不是1970.12.31.23:59:59
  • 通过上面那个流程,得到属于一个月内的毫秒数(注意,比如说某个月的毫秒数是K,那么0~(K-1)都是属于这个月,但是K不是),然后除以每天的毫秒数,得到 P 。同样,如果整除,说明日期数是 P+1,如果不整除,那也是 P+1(注意,日期是从1开始,而不是从0开始)

设置年份(在函数setDataFile中)

  • 对1970/1971特殊处理,直接判断是否毫秒数是否 $\geq$ 一个平年的毫秒数,如果是,那么就是1971。注意,等于也是,原因如上所述。然后调用setMonthAndDay函数,设置日月

  • 对1972.1.1.0:0:0到2000.1.1.0:0:0 特殊处理。先减去1970和1971的总的毫秒数,得到的结果除以每4年的毫秒数,得到H。同样,无论是否整除,年份数是在$[4H+1972, 4H+1972+3]$,整除就是$4H+1972$这一年的第一毫秒(0时0分0秒)。因为1970.1.1.0:0:0加上1970和1971总的毫秒数已经是1972.1.1.0:0:0了,再加上整数年$4H$就是(1972+4H).1.1.0:0:0了。

    然后减去一个闰年的毫秒数,因为从1972年算起,每四年的第一年都是闰年,如果结果是负数,说明是闰年(同样的,如果是0,那么并不是闰年,而是闰年下一年的第一毫秒)。否则,把得到结果除以每个平年的毫秒数得到 P ,就是从闰年的下一年 开始的第 P 年,所以,年份就是$1972+4H+1+P$

  • 然后,因为当年份数是100的倍数时,只有当年份可以被400整除,才是闰年。而2000年正好就是每400年里的第一年,所以要先计算,毫秒数里总的有多少个400年,如果有 $t$ 个,那么年份数就是$[2000+400t, 2000+400t+399]$

    接下来就计算总的毫秒数减去1970到2000的毫秒数,再减去总共多少个400年的毫秒数,剩下的毫秒数里,算算有多少个4年,因为每4年是闰年。如果算出来的结果是$d$,那么年份数就是$[2000+400t+4d,2000+400t+4d+3]$

    接下来判断剩下的毫秒数是否小于一个闰年的毫秒数,如果是,那么就是闰年,年份数是$2000+400t+4d$。如果不是,就减去一个闰年的毫秒数,然后算一算剩下的有多少个平年的毫秒数,假设算出来的结果是$b$,那么年份数就是$2000+400t+4d+b+1$

    接下来调用setMonthAndDay函数设置月份日期