Skip to content

Commit

Permalink
serde: add Chrono Option<> serialization
Browse files Browse the repository at this point in the history
Feature request: #62
  • Loading branch information
jmcnamara committed Dec 12, 2023
1 parent a4c5dd8 commit 4f69e9f
Show file tree
Hide file tree
Showing 15 changed files with 379 additions and 49 deletions.
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,21 @@ name = "doc_worksheet_serialize_datetime2"
path = "examples/doc_worksheet_serialize_datetime2.rs"
required-features = ["serde", "chrono"]

[[example]]
name = "doc_worksheet_serialize_datetime3"
path = "examples/doc_worksheet_serialize_datetime3.rs"
required-features = ["serde", "chrono"]

[[example]]
name = "doc_worksheet_serialize_datetime4"
path = "examples/doc_worksheet_serialize_datetime4.rs"
required-features = ["serde", "chrono"]


[[example]]
name = "doc_worksheet_serialize_datetime5"
path = "examples/doc_worksheet_serialize_datetime5.rs"
required-features = ["serde", "chrono"]


# Workaround to display feature specific docs.
Expand Down
22 changes: 22 additions & 0 deletions examples/doc_worksheet_serialize_datetime3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright 2022-2023, John McNamara, [email protected]

//! Example of a serializable struct with a Chrono Naive value with a helper
//! function.
use chrono::NaiveDate;
use rust_xlsxwriter::utility::serialize_chrono_naive_to_excel;
use serde::Serialize;

fn main() {
#[derive(Serialize)]
struct Student {
full_name: String,

#[serde(serialize_with = "serialize_chrono_naive_to_excel")]
birth_date: NaiveDate,

id_number: u32,
}
}
80 changes: 80 additions & 0 deletions examples/doc_worksheet_serialize_datetime4.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright 2022-2023, John McNamara, [email protected]

//! The following example demonstrates serializing instances of a Serde derived
//! data structure, including `Option` chrono datetimes, to a worksheet.
use chrono::NaiveDate;
use rust_xlsxwriter::{CustomSerializeHeader, Format, FormatBorder, Workbook, XlsxError};
use serde::Serialize;

use rust_xlsxwriter::utility::serialize_chrono_option_naive_to_excel;

fn main() -> Result<(), XlsxError> {
let mut workbook = Workbook::new();

// Add a worksheet to the workbook.
let worksheet = workbook.add_worksheet();

// Widen the date column for clarity.
worksheet.set_column_width(1, 12)?;

// Add some formats to use with the serialization data.
let header_format = Format::new()
.set_bold()
.set_border(FormatBorder::Thin)
.set_background_color("C6E0B4");

let date_format = Format::new().set_num_format("yyyy/mm/dd");

// Create a serializable test struct.
#[derive(Serialize)]
struct Student<'a> {
name: &'a str,

// Note, we add a `rust_xlsxwriter` function to serialize the date.
#[serde(serialize_with = "serialize_chrono_option_naive_to_excel")]
dob: Option<NaiveDate>,

id: u32,
}

let students = [
Student {
name: "Aoife",
dob: NaiveDate::from_ymd_opt(1998, 1, 12),
id: 564351,
},
Student {
name: "Caoimhe",
dob: NaiveDate::from_ymd_opt(2000, 5, 1),
id: 443287,
},
];

// Set up the start location and headers of the data to be serialized. Note,
// we need to add a cell format for the datetime data.
let custom_headers = [
CustomSerializeHeader::new("name")
.rename("Student")
.set_header_format(&header_format),
CustomSerializeHeader::new("dob")
.rename("Birthday")
.set_cell_format(&date_format)
.set_header_format(&header_format),
CustomSerializeHeader::new("id")
.rename("ID")
.set_header_format(&header_format),
];

worksheet.serialize_headers_with_options(0, 0, "Student", &custom_headers)?;

// Serialize the data.
worksheet.serialize(&students)?;

// Save the file.
workbook.save("serialize.xlsx")?;

Ok(())
}
22 changes: 22 additions & 0 deletions examples/doc_worksheet_serialize_datetime5.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Copyright 2022-2023, John McNamara, [email protected]

//! Example of a serializable struct with an Option Chrono Naive value with a
//! helper function.
//!
use chrono::NaiveDate;
use rust_xlsxwriter::utility::serialize_chrono_option_naive_to_excel;
use serde::Serialize;

fn main() {
#[derive(Serialize)]
struct Student {
full_name: String,

#[serde(serialize_with = "serialize_chrono_option_naive_to_excel")]
birth_date: Option<NaiveDate>,

id_number: u32,
}
}
10 changes: 5 additions & 5 deletions examples/doc_worksheet_write_date_chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ fn main() -> Result<(), XlsxError> {
let date = NaiveDate::from_ymd_opt(2023, 1, 25).unwrap();

// Write the date with different Excel formats.
worksheet.write_date_with_format(0, 0, &date, &format1)?;
worksheet.write_date_with_format(1, 0, &date, &format2)?;
worksheet.write_date_with_format(2, 0, &date, &format3)?;
worksheet.write_date_with_format(3, 0, &date, &format4)?;
worksheet.write_date_with_format(4, 0, &date, &format5)?;
worksheet.write_date_with_format(0, 0, date, &format1)?;
worksheet.write_date_with_format(1, 0, date, &format2)?;
worksheet.write_date_with_format(2, 0, date, &format3)?;
worksheet.write_date_with_format(3, 0, date, &format4)?;
worksheet.write_date_with_format(4, 0, date, &format5)?;

workbook.save("worksheet.xlsx")?;

Expand Down
10 changes: 5 additions & 5 deletions examples/doc_worksheet_write_datetime_chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ fn main() -> Result<(), XlsxError> {
.unwrap();

// Write the datetime with different Excel formats.
worksheet.write_datetime_with_format(0, 0, &datetime, &format1)?;
worksheet.write_datetime_with_format(1, 0, &datetime, &format2)?;
worksheet.write_datetime_with_format(2, 0, &datetime, &format3)?;
worksheet.write_datetime_with_format(3, 0, &datetime, &format4)?;
worksheet.write_datetime_with_format(4, 0, &datetime, &format5)?;
worksheet.write_datetime_with_format(0, 0, datetime, &format1)?;
worksheet.write_datetime_with_format(1, 0, datetime, &format2)?;
worksheet.write_datetime_with_format(2, 0, datetime, &format3)?;
worksheet.write_datetime_with_format(3, 0, datetime, &format4)?;
worksheet.write_datetime_with_format(4, 0, datetime, &format5)?;

workbook.save("worksheet.xlsx")?;

Expand Down
10 changes: 5 additions & 5 deletions examples/doc_worksheet_write_time_chrono.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ fn main() -> Result<(), XlsxError> {
let time = NaiveTime::from_hms_milli_opt(2, 59, 3, 456).unwrap();

// Write the time with different Excel formats.
worksheet.write_time_with_format(0, 0, &time, &format1)?;
worksheet.write_time_with_format(1, 0, &time, &format2)?;
worksheet.write_time_with_format(2, 0, &time, &format3)?;
worksheet.write_time_with_format(3, 0, &time, &format4)?;
worksheet.write_time_with_format(4, 0, &time, &format5)?;
worksheet.write_time_with_format(0, 0, time, &format1)?;
worksheet.write_time_with_format(1, 0, time, &format2)?;
worksheet.write_time_with_format(2, 0, time, &format3)?;
worksheet.write_time_with_format(3, 0, time, &format4)?;
worksheet.write_time_with_format(4, 0, time, &format5)?;

workbook.save("worksheet.xlsx")?;

Expand Down
4 changes: 2 additions & 2 deletions src/conditional_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6713,7 +6713,7 @@ impl From<&ExcelDateTime> for ConditionalFormatValue {
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl From<&NaiveDate> for ConditionalFormatValue {
fn from(value: &NaiveDate) -> ConditionalFormatValue {
let value = ExcelDateTime::chrono_date_to_excel(*value).to_string();
let value = ExcelDateTime::chrono_date_to_excel(value).to_string();
ConditionalFormatValue::new_from_string(value)
}
}
Expand All @@ -6731,7 +6731,7 @@ impl From<&NaiveDateTime> for ConditionalFormatValue {
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl From<&NaiveTime> for ConditionalFormatValue {
fn from(value: &NaiveTime) -> ConditionalFormatValue {
let value = ExcelDateTime::chrono_time_to_excel(*value).to_string();
let value = ExcelDateTime::chrono_time_to_excel(value).to_string();
ConditionalFormatValue::new_from_string(value)
}
}
Expand Down
54 changes: 40 additions & 14 deletions src/datetime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1237,8 +1237,8 @@ impl ExcelDateTime {
// Convert a chrono::NaiveTime to an Excel serial datetime.
#[cfg(feature = "chrono")]
pub(crate) fn chrono_datetime_to_excel(datetime: &NaiveDateTime) -> f64 {
let excel_date = Self::chrono_date_to_excel(datetime.date());
let excel_time = Self::chrono_time_to_excel(datetime.time());
let excel_date = Self::chrono_date_to_excel(&datetime.date());
let excel_time = Self::chrono_time_to_excel(&datetime.time());

excel_date + excel_time
}
Expand All @@ -1248,10 +1248,11 @@ impl ExcelDateTime {
// 1904-01-01.
#[cfg(feature = "chrono")]
#[allow(clippy::cast_precision_loss)]
pub(crate) fn chrono_date_to_excel(date: NaiveDate) -> f64 {
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn chrono_date_to_excel(date: &NaiveDate) -> f64 {
let epoch = NaiveDate::from_ymd_opt(1899, 12, 31).unwrap();

let duration = date - epoch;
let duration = *date - epoch;
let mut excel_date = duration.num_days() as f64;

// For legacy reasons Excel treats 1900 as a leap year. We add an additional
Expand All @@ -1268,9 +1269,10 @@ impl ExcelDateTime {
// milliseconds in the day.
#[cfg(feature = "chrono")]
#[allow(clippy::cast_precision_loss)]
pub(crate) fn chrono_time_to_excel(time: NaiveTime) -> f64 {
#[allow(clippy::trivially_copy_pass_by_ref)]
pub(crate) fn chrono_time_to_excel(time: &NaiveTime) -> f64 {
let midnight = NaiveTime::from_hms_milli_opt(0, 0, 0, 0).unwrap();
let duration = time - midnight;
let duration = *time - midnight;

duration.num_milliseconds() as f64 / (24.0 * 60.0 * 60.0 * 1000.0)
}
Expand Down Expand Up @@ -1318,42 +1320,66 @@ enum ExcelDateTimeType {
pub trait IntoExcelDateTime {
/// Trait method to convert a date or time into an Excel serial datetime.
///
fn to_excel_serial_date(self) -> f64;
fn to_excel_serial_date(&self) -> f64;
}

impl IntoExcelDateTime for &ExcelDateTime {
fn to_excel_serial_date(self) -> f64 {
fn to_excel_serial_date(&self) -> f64 {
self.to_excel()
}
}

impl IntoExcelDateTime for ExcelDateTime {
fn to_excel_serial_date(self) -> f64 {
fn to_excel_serial_date(&self) -> f64 {
self.to_excel()
}
}

#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoExcelDateTime for &NaiveDateTime {
fn to_excel_serial_date(self) -> f64 {
fn to_excel_serial_date(&self) -> f64 {
ExcelDateTime::chrono_datetime_to_excel(self)
}
}

#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoExcelDateTime for &NaiveDate {
fn to_excel_serial_date(self) -> f64 {
ExcelDateTime::chrono_date_to_excel(*self)
fn to_excel_serial_date(&self) -> f64 {
ExcelDateTime::chrono_date_to_excel(self)
}
}

#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoExcelDateTime for &NaiveTime {
fn to_excel_serial_date(self) -> f64 {
ExcelDateTime::chrono_time_to_excel(*self)
fn to_excel_serial_date(&self) -> f64 {
ExcelDateTime::chrono_time_to_excel(self)
}
}

#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoExcelDateTime for NaiveDateTime {
fn to_excel_serial_date(&self) -> f64 {
ExcelDateTime::chrono_datetime_to_excel(self)
}
}

#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoExcelDateTime for NaiveDate {
fn to_excel_serial_date(&self) -> f64 {
ExcelDateTime::chrono_date_to_excel(self)
}
}

#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
impl IntoExcelDateTime for NaiveTime {
fn to_excel_serial_date(&self) -> f64 {
ExcelDateTime::chrono_time_to_excel(self)
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/datetime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1832,7 +1832,7 @@ mod datetime_tests {
for test_data in dates {
let (year, month, day, expected) = test_data;
let datetime = NaiveDate::from_ymd_opt(year, month, day).unwrap();
assert_eq!(expected, ExcelDateTime::chrono_date_to_excel(datetime));
assert_eq!(expected, ExcelDateTime::chrono_date_to_excel(&datetime));
}
}

Expand Down Expand Up @@ -1945,7 +1945,7 @@ mod datetime_tests {
for test_data in times {
let (hour, min, seconds, millis, expected) = test_data;
let datetime = NaiveTime::from_hms_milli_opt(hour, min, seconds, millis).unwrap();
let mut diff = ExcelDateTime::chrono_time_to_excel(datetime) - expected;
let mut diff = ExcelDateTime::chrono_time_to_excel(&datetime) - expected;
diff = diff.abs();
assert!(diff < 0.00000000001);
}
Expand Down
Loading

0 comments on commit 4f69e9f

Please sign in to comment.