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
//! Excel の日付の数
//!
//! Excel は日付を数で持つ。整数部が日数、小数部が 1 日のうちの位置である。
//!
//! # 1900 年のずれ
//!
//! 既定の方式では 1 が 1900-01-01 にあたる。ただし 1900 年をうるう年と見なす誤りが入っていて、
//! 60 は存在しない 1900-02-29 を指す。61 から先は 1899-12-30 からの日数として数えると合う。
//! この誤りは Lotus 1-2-3 から引き継いだもので、直すと既存のファイルがずれるため、いまも残っている。
//!
//! # 1904 年方式
//!
//! Mac 版の Excel から来た方式で、0 が 1904-01-01 にあたる。
//! どちらの方式かはワークブックが持っている。
/// 年月日と時分秒
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Moment {
pub year: i64,
pub month: u32,
pub day: u32,
pub hour: u32,
pub minute: u32,
pub second: u32,
}
/// Excel の数を年月日と時分秒に直す
pub fn moment(serial: f64, epoch1904: bool) -> Moment {
let mut whole = serial.floor() as i64;
let mut rest = ((serial - serial.floor()) * 86400.0).round() as i64;
if rest >= 86400 {
rest -= 86400;
whole += 1;
}
let (year, month, day) = civil(base(epoch1904, whole) + whole);
Moment {
year,
month,
day,
hour: (rest / 3600) as u32,
minute: ((rest % 3600) / 60) as u32,
second: (rest % 60) as u32,
}
}
/// 年月日と時分秒を Excel の数に直す
pub fn serial(moment: &Moment, epoch1904: bool) -> f64 {
let target = days(moment.year, moment.month, moment.day);
let mut whole = target - base(epoch1904, 0);
// 1900 年方式では、3 月からあとが 1 日ずれる
if !epoch1904 && whole >= 60 {
whole += 1;
}
let rest = moment.hour as i64 * 3600 + moment.minute as i64 * 60 + moment.second as i64;
whole as f64 + rest as f64 / 86400.0
}
/// 数え始めの日。1900 年方式は 60 より前だけ 1 日ずれる
fn base(epoch1904: bool, whole: i64) -> i64 {
if epoch1904 {
return days(1904, 1, 1);
}
if whole < 60 {
days(1899, 12, 31)
} else {
days(1899, 12, 30)
}
}
/// 1970-01-01 からの日数を年月日に直す
pub fn civil(count: i64) -> (i64, u32, u32) {
let shifted = count + 719468;
let era = if shifted >= 0 {shifted} else {shifted - 146096} / 146097;
let day_of_era = shifted - era * 146097;
let year_of_era =
(day_of_era - day_of_era / 1460 + day_of_era / 36524 - day_of_era / 146096) / 365;
let year = year_of_era + era * 400;
let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
let shifted_month = (5 * day_of_year + 2) / 153;
let day = day_of_year - (153 * shifted_month + 2) / 5 + 1;
let month = if shifted_month < 10 {shifted_month + 3} else {shifted_month - 9};
(if month <= 2 {year + 1} else {year}, month as u32, day as u32)
}
/// 年月日を 1970-01-01 からの日数に直す
pub fn days(year: i64, month: u32, day: u32) -> i64 {
let year = if month <= 2 {year - 1} else {year};
let era = if year >= 0 {year} else {year - 399} / 400;
let year_of_era = year - era * 400;
let shifted = if month > 2 {month - 3} else {month + 9} as i64;
let day_of_year = (153 * shifted + 2) / 5 + day as i64 - 1;
let day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
era * 146097 + day_of_era - 719468
}