rust标准库没有时间戳转日期的方法,为了区区一个功能引入三方库又觉得划不来,于是准备自己实现
以前看到过转换时间戳的方法,但是这回网上找了半天都没找到想要的代码,全是各种语言调标准库或者三方库的,找来找去只有这个,但是评论区又说有bug,我看过代码好像闰年部分有点问题
思路
首先时间戳定义是1970年1月1日到指定时间的秒数,正数往后,负数往前,这里不需要考虑负数,一般采用32位存储,所以最多只能存储到2038年,又叫千年虫问题
时间戳计算的难度就在闰年,闰年导致每年的秒数不一致,从而不便于定位到年。顺便一提,除了闰年还有闰秒,每隔不确定的时间,将现在时间进行减一秒或者加一秒操作,闰秒也会导致一些大公司的系统出现bug,而且闰秒是国际协会根据地球运动情况确定的,不像闰年这样规律,所以不太好弄,不过时间戳里没有闰秒概念
为了确定过去到底有多少秒,最简单的办法就是把1970年到2038年每一年的秒数都累加出来,看时间戳小于哪一年,这个时间戳就是上一年的,再减去过去经过的秒数,剩下的就是在今年的秒数,确定日期和时间就很简单了。我看jdk里似乎就是这样干的,代码里把每一年的都硬编码了
实现
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
|
#[inline] fn is_leap(year: u64) -> bool { return year % 4 == 0 && ((year % 100) != 0 || year % 400 == 0); }
fn do_time_format(value: u64) -> String { let mut time = value; let per_year_sec = 365 * 24 * 60 * 60;
let mut day_of_year: [u64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let mut all_sec = 0; for year in 1970..2038 { let is_leap = is_leap(year);
let before_sec = all_sec; all_sec += per_year_sec; if is_leap { all_sec += 86400; } if time < all_sec { time = value - before_sec; let sec = time % 60; time /= 60; let min = time % 60; time /= 60; let hour = time % 24; time /= 24;
if is_leap { day_of_year[1] += 1; } let mut month = 0; for (index, ele) in day_of_year.iter().enumerate() { if &time < ele { month = index + 1; time += 1; break; } time -= ele; }
return format!( "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", year, month, time, hour, min, sec ); } }
String::new() }
|
改进
再思索下上面的程序,可以发现一个问题,那就是即使使用了64位存储,千年虫问题依然存在,因为只计算到了2038年
那怎么去掉这个上限呢?
因为闰年的存在,没法简单的直接确定年份,但是如果假设只有平年,那么年份就可以做个除法获得,这个粗略值只会比精确值更晚,比如粗略值可能是1988-01-01,因为中间有闰年,所以实际年份应该是1987,而且不会出现往前走两年的情况,因为这样需要中间有365个闰年,但是粗略值本身就是除以365的结果,这里我也不知道该怎样描述更清晰明了一点。
总之现在有个年份的粗略值,以及剩余的在年内的秒数,只需要循环判断一下1970到粗略值有多少个闰年,再用时间戳减去按平年计算的秒数和多出来的闰年的秒数,不直接用剩余秒数去减主要是考虑两种情况
一是中间没有闰年的情况,比如1971年;二是时间戳除以平年刚好能整除,剩余秒数就是0。
实现
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
| fn do_time_format2(value: u64) -> String {
let per_year_sec = 365 * 24 * 60 * 60;
let mut year = value / per_year_sec; let mut last_sec = value - (year) * per_year_sec; year += 1970;
let mut leap_year_sec = 0; for y in 1970..year { if is_leap(y) { leap_year_sec += 86400; } } if last_sec < leap_year_sec { year -= 1; if is_leap(year) { leap_year_sec -= 86400; } } let mut time = value - leap_year_sec - (year - 1970) * per_year_sec;
let mut day_of_year: [u64; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let sec = time % 60; time /= 60; let min = time % 60; time /= 60; let hour = time % 24; time /= 24;
if is_leap(year) { day_of_year[1] += 1; } let mut month = 0; for (index, ele) in day_of_year.iter().enumerate() { if &time < ele { month = index + 1; time += 1; break; } time -= ele; }
return format!( "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", year, month, time, hour, min, sec ); }
|
验证
我用java直接生成2038为止每一天,随机时间的时间戳数据,共计两万多天。用了assert_eq!宏来做断言,然后编译总是出错,还没有错误原因,只有一个kill 9。最后把宏换成了方法调用就可以了,怀疑就是太多宏影响了编译,毕竟两万多个宏。