calamine/
formats.rs

1use crate::datatype::{Data, DataRef, ExcelDateTime, ExcelDateTimeType};
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub enum CellFormat {
5    Other,
6    DateTime,
7    TimeDelta,
8}
9
10/// Check excel number format is datetime
11pub fn detect_custom_number_format(format: &str) -> CellFormat {
12    let mut escaped = false;
13    let mut is_quote = false;
14    let mut brackets = 0u8;
15    let mut prev = ' ';
16    let mut hms = false;
17    let mut ap = false;
18    for s in format.chars() {
19        match (s, escaped, is_quote, ap, brackets) {
20            (_, true, ..) => escaped = false, // if escaped, ignore
21            ('_' | '\\', ..) => escaped = true,
22            ('"', _, true, _, _) => is_quote = false,
23            (_, _, true, _, _) => (),
24            ('"', _, _, _, _) => is_quote = true,
25            (';', ..) => return CellFormat::Other, // first format only
26            ('[', ..) => brackets += 1,
27            (']', .., 1) if hms => return CellFormat::TimeDelta, // if closing
28            (']', ..) => brackets = brackets.saturating_sub(1),
29            ('a' | 'A', _, _, false, 0) => ap = true,
30            ('p' | 'm' | '/' | 'P' | 'M', _, _, true, 0) => return CellFormat::DateTime,
31            ('d' | 'm' | 'h' | 'y' | 's' | 'D' | 'M' | 'H' | 'Y' | 'S', _, _, false, 0) => {
32                return CellFormat::DateTime
33            }
34            _ => {
35                if hms && s.eq_ignore_ascii_case(&prev) {
36                    // ok ...
37                } else {
38                    hms = prev == '[' && matches!(s, 'm' | 'h' | 's' | 'M' | 'H' | 'S');
39                }
40            }
41        }
42        prev = s;
43    }
44    CellFormat::Other
45}
46
47pub fn builtin_format_by_id(id: &[u8]) -> CellFormat {
48    match id {
49        // mm-dd-yy
50        b"14" |
51        // d-mmm-yy
52        b"15" |
53        // d-mmm
54        b"16" |
55        // mmm-yy
56        b"17" |
57        // h:mm AM/PM
58        b"18" |
59        // h:mm:ss AM/PM
60        b"19" |
61        // h:mm
62        b"20" |
63        // h:mm:ss
64        b"21" |
65        // m/d/yy h:mm
66        b"22" |
67        // mm:ss
68        b"45" |
69        // mmss.0
70        b"47" => CellFormat::DateTime,
71        // [h]:mm:ss
72        b"46" => CellFormat::TimeDelta,
73        _ => CellFormat::Other
74}
75}
76
77/// Check if code corresponds to builtin date format
78///
79/// See `is_builtin_date_format_id`
80pub fn builtin_format_by_code(code: u16) -> CellFormat {
81    match code {
82        14..=22 | 45 | 47 => CellFormat::DateTime,
83        46 => CellFormat::TimeDelta,
84        _ => CellFormat::Other,
85    }
86}
87
88// convert i64 to date, if format == Date
89pub fn format_excel_i64(value: i64, format: Option<&CellFormat>, is_1904: bool) -> Data {
90    match format {
91        Some(CellFormat::DateTime) => Data::DateTime(ExcelDateTime::new(
92            value as f64,
93            ExcelDateTimeType::DateTime,
94            is_1904,
95        )),
96        Some(CellFormat::TimeDelta) => Data::DateTime(ExcelDateTime::new(
97            value as f64,
98            ExcelDateTimeType::TimeDelta,
99            is_1904,
100        )),
101        _ => Data::Int(value),
102    }
103}
104
105// convert f64 to date, if format == Date
106#[inline]
107pub fn format_excel_f64_ref(
108    value: f64,
109    format: Option<&CellFormat>,
110    is_1904: bool,
111) -> DataRef<'static> {
112    match format {
113        Some(CellFormat::DateTime) => DataRef::DateTime(ExcelDateTime::new(
114            value,
115            ExcelDateTimeType::DateTime,
116            is_1904,
117        )),
118        Some(CellFormat::TimeDelta) => DataRef::DateTime(ExcelDateTime::new(
119            value,
120            ExcelDateTimeType::TimeDelta,
121            is_1904,
122        )),
123        _ => DataRef::Float(value),
124    }
125}
126
127// convert f64 to date, if format == Date
128pub fn format_excel_f64(value: f64, format: Option<&CellFormat>, is_1904: bool) -> Data {
129    format_excel_f64_ref(value, format, is_1904).into()
130}
131
132/// Ported from openpyxl, MIT License
133/// https://foss.heptapod.net/openpyxl/openpyxl/-/blob/a5e197c530aaa49814fd1d993dd776edcec35105/openpyxl/styles/tests/test_number_style.py
134#[test]
135fn test_is_date_format() {
136    assert_eq!(
137        detect_custom_number_format("DD/MM/YY"),
138        CellFormat::DateTime
139    );
140    assert_eq!(
141        detect_custom_number_format("H:MM:SS;@"),
142        CellFormat::DateTime
143    );
144    assert_eq!(
145        detect_custom_number_format("#,##0\\ [$\\u20bd-46D]"),
146        CellFormat::Other
147    );
148    assert_eq!(
149        detect_custom_number_format("m\"M\"d\"D\";@"),
150        CellFormat::DateTime
151    );
152    assert_eq!(
153        detect_custom_number_format("[h]:mm:ss"),
154        CellFormat::TimeDelta
155    );
156    assert_eq!(
157        detect_custom_number_format("\"Y: \"0.00\"m\";\"Y: \"-0.00\"m\";\"Y: <num>m\";@"),
158        CellFormat::Other
159    );
160    assert_eq!(
161        detect_custom_number_format("#,##0\\ [$''u20bd-46D]"),
162        CellFormat::Other
163    );
164    assert_eq!(
165        detect_custom_number_format("\"$\"#,##0_);[Red](\"$\"#,##0)"),
166        CellFormat::Other
167    );
168    assert_eq!(
169        detect_custom_number_format("[$-404]e\"\\xfc\"m\"\\xfc\"d\"\\xfc\""),
170        CellFormat::DateTime
171    );
172    assert_eq!(
173        detect_custom_number_format("0_ ;[Red]\\-0\\ "),
174        CellFormat::Other
175    );
176    assert_eq!(detect_custom_number_format("\\Y000000"), CellFormat::Other);
177    assert_eq!(
178        detect_custom_number_format("#,##0.0####\" YMD\""),
179        CellFormat::Other
180    );
181    assert_eq!(detect_custom_number_format("[h]"), CellFormat::TimeDelta);
182    assert_eq!(detect_custom_number_format("[ss]"), CellFormat::TimeDelta);
183    assert_eq!(
184        detect_custom_number_format("[s].000"),
185        CellFormat::TimeDelta
186    );
187    assert_eq!(detect_custom_number_format("[m]"), CellFormat::TimeDelta);
188    assert_eq!(detect_custom_number_format("[mm]"), CellFormat::TimeDelta);
189    assert_eq!(
190        detect_custom_number_format("[Blue]\\+[h]:mm;[Red]\\-[h]:mm;[Green][h]:mm"),
191        CellFormat::TimeDelta
192    );
193    assert_eq!(
194        detect_custom_number_format("[>=100][Magenta][s].00"),
195        CellFormat::TimeDelta
196    );
197    assert_eq!(
198        detect_custom_number_format("[h]:mm;[=0]\\-"),
199        CellFormat::TimeDelta
200    );
201    assert_eq!(
202        detect_custom_number_format("[>=100][Magenta].00"),
203        CellFormat::Other
204    );
205    assert_eq!(
206        detect_custom_number_format("[>=100][Magenta]General"),
207        CellFormat::Other
208    );
209    assert_eq!(
210        detect_custom_number_format("ha/p\\\\m"),
211        CellFormat::DateTime
212    );
213    assert_eq!(
214        detect_custom_number_format("#,##0.00\\ _M\"H\"_);[Red]#,##0.00\\ _M\"S\"_)"),
215        CellFormat::Other
216    );
217}