From 29084a7b9d5d6629addd168dda98b406ffbce171 Mon Sep 17 00:00:00 2001 From: Paul Bender Date: Fri, 10 Oct 2025 12:57:58 -0700 Subject: [PATCH 1/7] Limit setting year to 20th century. --- src/ds323x/datetime.rs | 50 ++++++++++++------------------------------ tests/datetime.rs | 28 +++-------------------- 2 files changed, 17 insertions(+), 61 deletions(-) diff --git a/src/ds323x/datetime.rs b/src/ds323x/datetime.rs index 41fd610..1a48e4c 100644 --- a/src/ds323x/datetime.rs +++ b/src/ds323x/datetime.rs @@ -36,10 +36,9 @@ where } fn set_datetime(&mut self, datetime: &NaiveDateTime) -> Result<(), Self::Error> { - if datetime.year() < 2000 || datetime.year() > 2100 { + if !(2000..=2099).contains(&datetime.year()) { return Err(Error::InvalidInputData); } - let (month, year) = month_year_to_registers(datetime.month() as u8, datetime.year() as u16); let mut payload = [ Register::SECONDS, decimal_to_packed_bcd(datetime.second() as u8), @@ -47,8 +46,8 @@ where hours_to_register(Hours::H24(datetime.hour() as u8))?, datetime.weekday().number_from_sunday() as u8, decimal_to_packed_bcd(datetime.day() as u8), - month, - year, + decimal_to_packed_bcd(datetime.month() as u8), + decimal_to_packed_bcd((datetime.year() - 2000) as u8), ]; self.iface.write_data(&mut payload) } @@ -174,39 +173,30 @@ where } fn set_year(&mut self, year: u16) -> Result<(), Self::Error> { - if !(2000..=2100).contains(&year) { + if !(2000..=2099).contains(&year) { return Err(Error::InvalidInputData); } let data = self.iface.read_register(Register::MONTH)?; let month_bcd = data & !BitFlags::CENTURY; - if year > 2099 { - let mut data = [ - Register::MONTH, - BitFlags::CENTURY | month_bcd, - decimal_to_packed_bcd((year - 2100) as u8), - ]; - self.iface.write_data(&mut data) - } else { - let mut data = [ - Register::MONTH, - month_bcd, - decimal_to_packed_bcd((year - 2000) as u8), - ]; - self.iface.write_data(&mut data) - } + + let mut data = [ + Register::MONTH, + month_bcd, + decimal_to_packed_bcd((year - 2000) as u8), + ]; + self.iface.write_data(&mut data) } fn set_date(&mut self, date: &rtcc::NaiveDate) -> Result<(), Self::Error> { - if date.year() < 2000 || date.year() > 2100 { + if !(2000..=2099).contains(&date.year()) { return Err(Error::InvalidInputData); } - let (month, year) = month_year_to_registers(date.month() as u8, date.year() as u16); let mut payload = [ Register::DOW, date.weekday().number_from_sunday() as u8, decimal_to_packed_bcd(date.day() as u8), - month, - year, + decimal_to_packed_bcd(date.month() as u8), + decimal_to_packed_bcd((date.year() - 2000) as u8), ]; self.iface.write_data(&mut payload) } @@ -251,18 +241,6 @@ fn year_from_registers(month: u8, year: u8) -> u16 { } } -fn month_year_to_registers(month: u8, year: u16) -> (u8, u8) { - if year > 2099 { - let month = BitFlags::CENTURY | decimal_to_packed_bcd(month); - (month, decimal_to_packed_bcd((year - 2100) as u8)) - } else { - ( - decimal_to_packed_bcd(month), - decimal_to_packed_bcd((year - 2000) as u8), - ) - } -} - fn is_24h_format(hours_data: u8) -> bool { hours_data & BitFlags::H24_H12 == 0 } diff --git a/tests/datetime.rs b/tests/datetime.rs index a10acb8..eb465c3 100644 --- a/tests/datetime.rs +++ b/tests/datetime.rs @@ -206,18 +206,7 @@ mod year { 0b1001_1001 ); - get_param_read_array_test!(century1_get, year, 2100, MONTH, [0b1000_0000, 0], [0, 0]); - read_set_param_write_two_test!( - century1_set, - set_year, - 2100, - MONTH, - 0b0001_0010, - 0b1001_0010, - 0 - ); - - set_invalid_param_range_test!(invalid, set_year, 1999, 2101); + set_invalid_param_range_test!(invalid, set_year, 1999, 2100); } macro_rules! invalid_dt_test { @@ -233,7 +222,7 @@ macro_rules! invalid_dt_test { } #[test] fn datetime_too_big() { - let dt = new_datetime(2101, 1, 2, 3, 4, 5); + let dt = new_datetime(2100, 1, 2, 3, 4, 5); let mut dev = $create_method(&[]); assert_invalid_input_data!(dev.set_datetime(&dt)); $destroy_method(dev); @@ -247,7 +236,7 @@ macro_rules! invalid_dt_test { } #[test] fn date_too_big() { - let d = new_date(2101, 1, 2); + let d = new_date(2100, 1, 2); let mut dev = $create_method(&[]); assert_invalid_input_data!(dev.set_date(&d)); $destroy_method(dev); @@ -339,17 +328,6 @@ macro_rules! dt_test { $destroy_method(dev); } - #[test] - fn set_date_century() { - let d = new_date(2100, 8, 13); - let mut dev = $create_method(&$mac_trans_write!( - DOW, - [0b0000_0110, 0b0001_0011, 0b1000_1000, 0] - )); - dev.set_date(&d).unwrap(); - $destroy_method(dev); - } - #[test] fn get_time() { let t = NaiveTime::from_hms_opt(23, 59, 58).unwrap(); From e7c41a64241d50a3a604660af56f2ddcccfee847 Mon Sep 17 00:00:00 2001 From: Paul Bender Date: Fri, 10 Oct 2025 13:17:53 -0700 Subject: [PATCH 2/7] Generate error when century bit is set in functions already reading the century bit --- src/ds323x/datetime.rs | 56 ++++++++++++++++++++++++------------------ src/lib.rs | 5 ++++ tests/datetime.rs | 6 ----- 3 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/ds323x/datetime.rs b/src/ds323x/datetime.rs index 1a48e4c..3491ea7 100644 --- a/src/ds323x/datetime.rs +++ b/src/ds323x/datetime.rs @@ -19,11 +19,13 @@ where let mut data = [0; 8]; self.iface.read_data(&mut data)?; - let year = year_from_registers( - data[Register::MONTH as usize + 1], - data[Register::YEAR as usize + 1], - ); - let month = packed_bcd_to_decimal(data[Register::MONTH as usize + 1] & !BitFlags::CENTURY); + let century = data[Register::MONTH as usize + 1] & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + + let year = 2000 + (packed_bcd_to_decimal(data[Register::YEAR as usize + 1]) as u16); + let month = packed_bcd_to_decimal(data[Register::MONTH as usize + 1]); let day = packed_bcd_to_decimal(data[Register::DOM as usize + 1]); let hour = hours_from_register(data[Register::HOURS as usize + 1]); let minute = packed_bcd_to_decimal(data[Register::MINUTES as usize + 1]); @@ -91,15 +93,28 @@ where fn month(&mut self) -> Result { let data = self.iface.read_register(Register::MONTH)?; - let value = data & !BitFlags::CENTURY; - Ok(packed_bcd_to_decimal(value)) + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + + Ok(packed_bcd_to_decimal(data)) } fn year(&mut self) -> Result { let mut data = [0; 3]; data[0] = Register::MONTH; self.iface.read_data(&mut data)?; - Ok(year_from_registers(data[1], data[2])) + + let century = data[1] & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + + let year = 2000 + (packed_bcd_to_decimal(data[2]) as u16); + + Ok(year) } fn date(&mut self) -> Result { @@ -108,12 +123,15 @@ where self.iface.read_data(&mut data)?; let offset = Register::DOM as usize; - let year = year_from_registers( - data[Register::MONTH as usize + 1 - offset], - data[Register::YEAR as usize + 1 - offset], - ); - let month = - packed_bcd_to_decimal(data[Register::MONTH as usize + 1 - offset] & !BitFlags::CENTURY); + + let century = data[Register::MONTH as usize + 1 - offset] & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + + let year = + 2000 + (packed_bcd_to_decimal(data[Register::YEAR as usize + 1 - offset]) as u16); + let month = packed_bcd_to_decimal(data[Register::MONTH as usize + 1 - offset]); let day = packed_bcd_to_decimal(data[Register::DOM as usize + 1 - offset]); let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()); some_or_invalid_error(date) @@ -231,16 +249,6 @@ fn hours_from_register(data: u8) -> Hours { } } -fn year_from_registers(month: u8, year: u8) -> u16 { - let century = month & BitFlags::CENTURY; - let year = packed_bcd_to_decimal(year); - if century != 0 { - 2100 + u16::from(year) - } else { - 2000 + u16::from(year) - } -} - fn is_24h_format(hours_data: u8) -> bool { hours_data & BitFlags::H24_H12 == 0 } diff --git a/src/lib.rs b/src/lib.rs index 5efa490..f98afda 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -388,6 +388,11 @@ pub enum Error { /// It was not possible to read a valid date and/or time. /// The device is probably missing initialization. InvalidDeviceState, + /// Device century is not the 20th century. + /// + /// The device does not produce valid dates for centuries + /// other than the 20th century. + InvalidDeviceCentury, } /// Square-wave output frequency diff --git a/tests/datetime.rs b/tests/datetime.rs index eb465c3..ec88bdf 100644 --- a/tests/datetime.rs +++ b/tests/datetime.rs @@ -185,12 +185,6 @@ mod month { get_param_test!(get, month, MONTH, 1, 1); read_set_param_test!(set, set_month, MONTH, 12, 0b0000_0010, 0b0001_0010); set_invalid_param_range_test!(invalid, set_month, 0, 13); - - mod keeps_century { - use super::*; - get_param_test!(get, month, MONTH, 12, 0b1001_0010); - read_set_param_test!(set, set_month, MONTH, 12, 0b1000_0010, 0b1001_0010); - } } mod year { From 25f695d20a8ee3462aa5be6012fbe75fba083507 Mon Sep 17 00:00:00 2001 From: Paul Bender Date: Sat, 11 Oct 2025 08:35:24 -0700 Subject: [PATCH 3/7] Replace some_or_invalid_error with idiomatic Rust. --- src/ds323x/datetime.rs | 23 ++++++++++++----------- src/ds323x/mod.rs | 24 ------------------------ 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/src/ds323x/datetime.rs b/src/ds323x/datetime.rs index 3491ea7..d2cbb90 100644 --- a/src/ds323x/datetime.rs +++ b/src/ds323x/datetime.rs @@ -1,7 +1,7 @@ //! Common implementation use super::{ - decimal_to_packed_bcd, hours_to_register, packed_bcd_to_decimal, some_or_invalid_error, + decimal_to_packed_bcd, hours_to_register, packed_bcd_to_decimal, }; use crate::{ interface::{ReadData, WriteData}, @@ -27,14 +27,15 @@ where let year = 2000 + (packed_bcd_to_decimal(data[Register::YEAR as usize + 1]) as u16); let month = packed_bcd_to_decimal(data[Register::MONTH as usize + 1]); let day = packed_bcd_to_decimal(data[Register::DOM as usize + 1]); - let hour = hours_from_register(data[Register::HOURS as usize + 1]); + let hour = get_h24(hours_from_register(data[Register::HOURS as usize + 1])); let minute = packed_bcd_to_decimal(data[Register::MINUTES as usize + 1]); let second = packed_bcd_to_decimal(data[Register::SECONDS as usize + 1]); - let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()); - let date = some_or_invalid_error(date)?; - let datetime = date.and_hms_opt(get_h24(hour).into(), minute.into(), second.into()); - some_or_invalid_error(datetime) + let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()).ok_or(Error::InvalidDeviceState)?; + let time = NaiveTime::from_hms_opt(hour.into(), minute.into(), second.into()).ok_or(Error::InvalidDeviceState)?; + let datetime = NaiveDateTime::new(date, time); + + Ok(datetime) } fn set_datetime(&mut self, datetime: &NaiveDateTime) -> Result<(), Self::Error> { @@ -75,12 +76,12 @@ where fn time(&mut self) -> Result { let mut data = [0; 4]; self.iface.read_data(&mut data)?; - let hour = hours_from_register(data[Register::HOURS as usize + 1]); + + let hour = get_h24(hours_from_register(data[Register::HOURS as usize + 1])); let minute = packed_bcd_to_decimal(data[Register::MINUTES as usize + 1]); let second = packed_bcd_to_decimal(data[Register::SECONDS as usize + 1]); - let time = NaiveTime::from_hms_opt(get_h24(hour).into(), minute.into(), second.into()); - some_or_invalid_error(time) + NaiveTime::from_hms_opt(hour.into(), minute.into(), second.into()).ok_or(Error::InvalidDeviceState) } fn weekday(&mut self) -> Result { @@ -133,8 +134,8 @@ where 2000 + (packed_bcd_to_decimal(data[Register::YEAR as usize + 1 - offset]) as u16); let month = packed_bcd_to_decimal(data[Register::MONTH as usize + 1 - offset]); let day = packed_bcd_to_decimal(data[Register::DOM as usize + 1 - offset]); - let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()); - some_or_invalid_error(date) + + NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()).ok_or(Error::InvalidDeviceState) } fn set_seconds(&mut self, seconds: u8) -> Result<(), Self::Error> { diff --git a/src/ds323x/mod.rs b/src/ds323x/mod.rs index 63c59ba..3543a9c 100644 --- a/src/ds323x/mod.rs +++ b/src/ds323x/mod.rs @@ -28,34 +28,10 @@ fn hours_to_register(hours: Hours) -> Result> { } } -fn some_or_invalid_error(data: Option) -> Result> { - if let Some(data) = data { - Ok(data) - } else { - Err(Error::InvalidDeviceState) - } -} - #[cfg(test)] mod tests { use super::*; - #[test] - fn if_some_then_get_inner() { - match some_or_invalid_error::(Some(1)) { - Ok(1) => (), - _ => panic!(), - } - } - - #[test] - fn if_none_then_error() { - match some_or_invalid_error::(None) { - Err(Error::InvalidDeviceState) => (), - _ => panic!(), - } - } - #[test] fn can_convert_packed_bcd_to_decimal() { assert_eq!(0, packed_bcd_to_decimal(0b0000_0000)); From 57409f96dc5ae651c6f0976f616fe16659330769 Mon Sep 17 00:00:00 2001 From: Paul Bender Date: Sat, 11 Oct 2025 18:02:49 -0700 Subject: [PATCH 4/7] Generate an error for all time functions when the century bit is set. --- src/ds323x/datetime.rs | 68 ++++++++++++++++++----- tests/common/mod.rs | 25 +++++++++ tests/datetime.rs | 120 +++++++++++++++++++++++++++++++++++------ 3 files changed, 183 insertions(+), 30 deletions(-) diff --git a/src/ds323x/datetime.rs b/src/ds323x/datetime.rs index d2cbb90..619a5fb 100644 --- a/src/ds323x/datetime.rs +++ b/src/ds323x/datetime.rs @@ -1,8 +1,6 @@ //! Common implementation -use super::{ - decimal_to_packed_bcd, hours_to_register, packed_bcd_to_decimal, -}; +use super::{decimal_to_packed_bcd, hours_to_register, packed_bcd_to_decimal}; use crate::{ interface::{ReadData, WriteData}, BitFlags, DateTimeAccess, Datelike, Ds323x, Error, Hours, NaiveDate, NaiveDateTime, NaiveTime, @@ -31,8 +29,10 @@ where let minute = packed_bcd_to_decimal(data[Register::MINUTES as usize + 1]); let second = packed_bcd_to_decimal(data[Register::SECONDS as usize + 1]); - let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()).ok_or(Error::InvalidDeviceState)?; - let time = NaiveTime::from_hms_opt(hour.into(), minute.into(), second.into()).ok_or(Error::InvalidDeviceState)?; + let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()) + .ok_or(Error::InvalidDeviceState)?; + let time = NaiveTime::from_hms_opt(hour.into(), minute.into(), second.into()) + .ok_or(Error::InvalidDeviceState)?; let datetime = NaiveDateTime::new(date, time); Ok(datetime) @@ -61,19 +61,47 @@ where DI: ReadData> + WriteData>, { fn seconds(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + self.read_register_decimal(Register::SECONDS) } fn minutes(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + self.read_register_decimal(Register::MINUTES) } fn hours(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + let data = self.iface.read_register(Register::HOURS)?; Ok(hours_from_register(data)) } fn time(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + let mut data = [0; 4]; self.iface.read_data(&mut data)?; @@ -81,14 +109,29 @@ where let minute = packed_bcd_to_decimal(data[Register::MINUTES as usize + 1]); let second = packed_bcd_to_decimal(data[Register::SECONDS as usize + 1]); - NaiveTime::from_hms_opt(hour.into(), minute.into(), second.into()).ok_or(Error::InvalidDeviceState) + NaiveTime::from_hms_opt(hour.into(), minute.into(), second.into()) + .ok_or(Error::InvalidDeviceState) } fn weekday(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + self.read_register_decimal(Register::DOW) } fn day(&mut self) -> Result { + let data = self.iface.read_register(Register::MONTH)?; + + let century = data & BitFlags::CENTURY; + if century != 0 { + return Err(Error::InvalidDeviceCentury); + } + self.read_register_decimal(Register::DOM) } @@ -104,18 +147,14 @@ where } fn year(&mut self) -> Result { - let mut data = [0; 3]; - data[0] = Register::MONTH; - self.iface.read_data(&mut data)?; + let data = self.iface.read_register(Register::MONTH)?; - let century = data[1] & BitFlags::CENTURY; + let century = data & BitFlags::CENTURY; if century != 0 { return Err(Error::InvalidDeviceCentury); } - let year = 2000 + (packed_bcd_to_decimal(data[2]) as u16); - - Ok(year) + Ok(2000 + (self.read_register_decimal(Register::YEAR)? as u16)) } fn date(&mut self) -> Result { @@ -135,7 +174,8 @@ where let month = packed_bcd_to_decimal(data[Register::MONTH as usize + 1 - offset]); let day = packed_bcd_to_decimal(data[Register::DOM as usize + 1 - offset]); - NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()).ok_or(Error::InvalidDeviceState) + NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()) + .ok_or(Error::InvalidDeviceState) } fn set_seconds(&mut self, seconds: u8) -> Result<(), Self::Error> { diff --git a/tests/common/mod.rs b/tests/common/mod.rs index a1ff32c..df3c5a1 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -23,6 +23,7 @@ impl Register { pub const DOW: u8 = 0x03; pub const DOM: u8 = 0x04; pub const MONTH: u8 = 0x05; + pub const YEAR: u8 = 0x06; pub const ALARM1_SECONDS: u8 = 0x07; pub const ALARM2_MINUTES: u8 = 0x0B; pub const CONTROL: u8 = 0x0E; @@ -36,6 +37,7 @@ pub struct BitFlags; #[allow(unused)] impl BitFlags { + pub const CENTURY: u8 = 0b1000_0000; pub const EOSC: u8 = 0b1000_0000; pub const BBSQW: u8 = 0b0100_0000; pub const TEMP_CONV: u8 = 0b0010_0000; @@ -123,6 +125,16 @@ macro_rules! assert_invalid_input_data { }; } +#[macro_export] +macro_rules! assert_invalid_device_century { + ($result:expr) => { + match $result { + Err(Error::InvalidDeviceCentury) => (), + _ => panic!("InvalidDeviceCentury error not returned."), + } + }; +} + #[macro_export] macro_rules! set_invalid_test { ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $value:expr) => { @@ -135,6 +147,19 @@ macro_rules! set_invalid_test { }; } +#[macro_export] +macro_rules! get_invalid_test { + ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $transactions:expr) => { + #[test] + fn $name() { + let trans = $transactions; + let mut dev = $create_method(&trans); + assert_invalid_device_century!(dev.$method()); + $destroy_method(dev); + } + }; +} + #[macro_export] macro_rules! call_test { ($name:ident, $method:ident, $create_method:ident, $destroy_method:ident, $transactions:expr) => { diff --git a/tests/datetime.rs b/tests/datetime.rs index ec88bdf..38c740b 100644 --- a/tests/datetime.rs +++ b/tests/datetime.rs @@ -2,8 +2,8 @@ use embedded_hal_mock::eh1::{i2c::Transaction as I2cTrans, spi::Transaction as S use rtcc::NaiveDateTime; mod common; use self::common::{ - destroy_ds3231, destroy_ds3232, destroy_ds3234, new_ds3231, new_ds3232, new_ds3234, Register, - DEVICE_ADDRESS as DEV_ADDR, + destroy_ds3231, destroy_ds3232, destroy_ds3234, new_ds3231, new_ds3232, new_ds3234, BitFlags, + Register, DEVICE_ADDRESS as DEV_ADDR, }; #[allow(unused)] // Rust 1.31.0 is confused due to the macros use ds323x::Rtcc; @@ -78,6 +78,31 @@ macro_rules! read_set_param_test { }; } +macro_rules! read_get_param_test { + ($name:ident, $method:ident, $register:ident, $value:expr, $binary_value:expr) => { + _get_param_test!( + $name, + $method, + $value, + [ + I2cTrans::write_read(DEV_ADDR, vec![Register::MONTH], vec![1]), + I2cTrans::write_read(DEV_ADDR, vec![Register::$register], vec![$binary_value]) + ], + [ + SpiTrans::transaction_start(), + SpiTrans::transfer_in_place(vec![Register::MONTH, 0], vec![Register::MONTH, 1]), + SpiTrans::transaction_end(), + SpiTrans::transaction_start(), + SpiTrans::transfer_in_place( + vec![Register::$register, 0], + vec![Register::$register, $binary_value] + ), + SpiTrans::transaction_end(), + ] + ); + }; +} + macro_rules! set_invalid_param_test { ($name:ident, $method:ident, $value:expr) => { mod $name { @@ -117,6 +142,50 @@ macro_rules! set_invalid_param_range_test { }; } +macro_rules! get_invalid_device_century_test { + ($name:ident, $method:ident) => { + mod $name { + use super::*; + get_invalid_test!( + cannot_get_invalid_ds3231, + $method, + new_ds3231, + destroy_ds3231, + [I2cTrans::write_read( + DEV_ADDR, + vec![Register::MONTH], + vec![BitFlags::CENTURY] + )] + ); + get_invalid_test!( + cannot_get_invalid_ds3232, + $method, + new_ds3232, + destroy_ds3232, + [I2cTrans::write_read( + DEV_ADDR, + vec![Register::MONTH], + vec![BitFlags::CENTURY] + )] + ); + get_invalid_test!( + cannot_get_invalid_ds3234, + $method, + new_ds3234, + destroy_ds3234, + [ + SpiTrans::transaction_start(), + SpiTrans::transfer_in_place( + vec![Register::MONTH, 0], + vec![Register::MONTH, BitFlags::CENTURY | 0] + ), + SpiTrans::transaction_end(), + ] + ); + } + }; +} + macro_rules! for_all { ($name:ident) => { mod $name { @@ -133,51 +202,58 @@ macro_rules! for_all { mod seconds { use super::*; - get_param_test!(get, seconds, SECONDS, 1, 1); + read_get_param_test!(get, seconds, SECONDS, 1, 1); set_param_test!(set, set_seconds, SECONDS, 1, 1); set_invalid_param_test!(invalid, set_seconds, 60); + get_invalid_device_century_test!(invalid_century, seconds); } mod minutes { use super::*; - get_param_test!(get, minutes, MINUTES, 1, 1); + read_get_param_test!(get, minutes, MINUTES, 1, 1); set_param_test!(set, set_minutes, MINUTES, 1, 1); set_invalid_param_test!(invalid, set_minutes, 60); + get_invalid_device_century_test!(invalid_century, minutes); } mod hours_24h { use super::*; - get_param_test!(get, hours, HOURS, Hours::H24(21), 0b0010_0001); + read_get_param_test!(get, hours, HOURS, Hours::H24(21), 0b0010_0001); set_param_test!(set, set_hours, HOURS, Hours::H24(21), 0b0010_0001); set_invalid_param_test!(invalid, set_hours, Hours::H24(24)); + get_invalid_device_century_test!(invalid_century, hours); } mod hours_12h_am { use super::*; - get_param_test!(get, hours, HOURS, Hours::AM(12), 0b0101_0010); + read_get_param_test!(get, hours, HOURS, Hours::AM(12), 0b0101_0010); set_param_test!(set, set_hours, HOURS, Hours::AM(12), 0b0101_0010); set_invalid_param_range_test!(invalid, set_hours, Hours::AM(0), Hours::AM(13)); + get_invalid_device_century_test!(invalid_century, hours); } mod hours_12h_pm { use super::*; - get_param_test!(get, hours, HOURS, Hours::PM(12), 0b0111_0010); + read_get_param_test!(get, hours, HOURS, Hours::PM(12), 0b0111_0010); set_param_test!(set, set_hours, HOURS, Hours::PM(12), 0b0111_0010); set_invalid_param_range_test!(invalid, set_hours, Hours::PM(0), Hours::PM(13)); + get_invalid_device_century_test!(invalid_century, hours); } mod weekday { use super::*; - get_param_test!(get, weekday, DOW, 1, 1); + read_get_param_test!(get, weekday, DOW, 1, 1); set_param_test!(set, set_weekday, DOW, 1, 1); set_invalid_param_range_test!(invalid, set_weekday, 0, 8); + get_invalid_device_century_test!(invalid_century, weekday); } mod day { use super::*; - get_param_test!(get, day, DOM, 1, 1); + read_get_param_test!(get, day, DOM, 1, 1); set_param_test!(set, set_day, DOM, 1, 1); set_invalid_param_range_test!(invalid, set_day, 0, 32); + get_invalid_device_century_test!(invalid_century, day); } mod month { @@ -185,13 +261,14 @@ mod month { get_param_test!(get, month, MONTH, 1, 1); read_set_param_test!(set, set_month, MONTH, 12, 0b0000_0010, 0b0001_0010); set_invalid_param_range_test!(invalid, set_month, 0, 13); + get_invalid_device_century_test!(invalid_century, month); } mod year { use super::*; - get_param_read_array_test!(century0_get, year, 2099, MONTH, [0, 0b1001_1001], [0, 0]); + read_get_param_test!(get, year, YEAR, 2099, 0b1001_1001); read_set_param_write_two_test!( - century0_set, + set, set_year, 2099, MONTH, @@ -201,6 +278,7 @@ mod year { ); set_invalid_param_range_test!(invalid, set_year, 1999, 2100); + get_invalid_device_century_test!(invalid_century, year); } macro_rules! invalid_dt_test { @@ -325,11 +403,21 @@ macro_rules! dt_test { #[test] fn get_time() { let t = NaiveTime::from_hms_opt(23, 59, 58).unwrap(); - let mut dev = $create_method(&$mac_trans_read!( - SECONDS, - [0b0101_1000, 0b0101_1001, 0b0010_0011], - [0, 0, 0] - )); + let month_transaction = $mac_trans_read!(MONTH, [0], [0]); + let time_transaction = + $mac_trans_read!(SECONDS, [0b0101_1000, 0b0101_1001, 0b0010_0011], [0, 0, 0]); + let mut transactions = vec![]; + let mut i: usize = 0; + while i < month_transaction.len() { + transactions.push(month_transaction[i].clone()); + i += 1; + } + let mut j: usize = 0; + while j < time_transaction.len() { + transactions.push(time_transaction[j].clone()); + j += 1; + } + let mut dev = $create_method(&transactions); assert_eq!(t, dev.time().unwrap()); $destroy_method(dev); } From b803d3b4d3e6960508f1a883baa7f21f915a2fd5 Mon Sep 17 00:00:00 2001 From: Paul Bender Date: Sun, 12 Oct 2025 14:19:17 -0700 Subject: [PATCH 5/7] Added more test coverage. --- tests/datetime.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/tests/datetime.rs b/tests/datetime.rs index 38c740b..7ccc0ba 100644 --- a/tests/datetime.rs +++ b/tests/datetime.rs @@ -154,7 +154,7 @@ macro_rules! get_invalid_device_century_test { [I2cTrans::write_read( DEV_ADDR, vec![Register::MONTH], - vec![BitFlags::CENTURY] + vec![BitFlags::CENTURY | 0] )] ); get_invalid_test!( @@ -165,7 +165,7 @@ macro_rules! get_invalid_device_century_test { [I2cTrans::write_read( DEV_ADDR, vec![Register::MONTH], - vec![BitFlags::CENTURY] + vec![BitFlags::CENTURY | 0] )] ); get_invalid_test!( @@ -358,6 +358,26 @@ macro_rules! dt_test { $destroy_method(dev); } + #[test] + fn get_datetime_invalid_century() { + let _dt = new_datetime(2018, 8, 13, 23, 59, 58); + let mut dev = $create_method(&$mac_trans_read!( + SECONDS, + [ + 0b0101_1000, + 0b0101_1001, + 0b0010_0011, + 0b0000_0010, + 0b0001_0011, + 0b0000_1000 | BitFlags::CENTURY, + 0b0001_1000 + ], + [0, 0, 0, 0, 0, 0, 0] + )); + assert_invalid_device_century!(dev.datetime()); + $destroy_method(dev); + } + #[test] fn set_datetime() { let dt = new_datetime(2018, 8, 13, 23, 59, 58); @@ -389,6 +409,18 @@ macro_rules! dt_test { $destroy_method(dev); } + #[test] + fn get_date_invalid_century() { + let _d = new_date(2018, 8, 13); + let mut dev = $create_method(&$mac_trans_read!( + DOM, + [0b0001_0011, 0b0000_1000 | BitFlags::CENTURY, 0b0001_1000], + [0, 0, 0] + )); + assert_invalid_device_century!(dev.date()); + $destroy_method(dev); + } + #[test] fn set_date() { let d = new_date(2018, 8, 13); @@ -403,7 +435,7 @@ macro_rules! dt_test { #[test] fn get_time() { let t = NaiveTime::from_hms_opt(23, 59, 58).unwrap(); - let month_transaction = $mac_trans_read!(MONTH, [0], [0]); + let month_transaction = $mac_trans_read!(MONTH, [1], [0]); let time_transaction = $mac_trans_read!(SECONDS, [0b0101_1000, 0b0101_1001, 0b0010_0011], [0, 0, 0]); let mut transactions = vec![]; @@ -422,6 +454,15 @@ macro_rules! dt_test { $destroy_method(dev); } + #[test] + fn get_time_invalid_century() { + let transactions = + $mac_trans_read!(MONTH, [0 | BitFlags::CENTURY], [0]); + let mut dev = $create_method(&transactions); + assert_invalid_device_century!(dev.time()); + $destroy_method(dev); + } + #[test] fn set_time() { let t = NaiveTime::from_hms_opt(23, 59, 58).unwrap(); From 97db2af80e4f56d1408b1d4d7df2d8e5758045cb Mon Sep 17 00:00:00 2001 From: Paul Bender Date: Sun, 12 Oct 2025 14:21:18 -0700 Subject: [PATCH 6/7] Fix formatting. --- tests/datetime.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/datetime.rs b/tests/datetime.rs index 7ccc0ba..e69e976 100644 --- a/tests/datetime.rs +++ b/tests/datetime.rs @@ -456,8 +456,7 @@ macro_rules! dt_test { #[test] fn get_time_invalid_century() { - let transactions = - $mac_trans_read!(MONTH, [0 | BitFlags::CENTURY], [0]); + let transactions = $mac_trans_read!(MONTH, [0 | BitFlags::CENTURY], [0]); let mut dev = $create_method(&transactions); assert_invalid_device_century!(dev.time()); $destroy_method(dev); From 887f4ced89c3d189ad4be59d69c794eb54731814 Mon Sep 17 00:00:00 2001 From: Paul Bender Date: Sun, 12 Oct 2025 15:04:04 -0700 Subject: [PATCH 7/7] Update changelog. --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index adaa735..68e7c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] - ReleaseDate +### Added + +- [breaking-change] Return Error::InvalidInputData when attempting to set the year to something + outside of the range 2000-2099 because all chips have a problem with the year 2100 and beyond. +- [breaking-change] Return Error::InvalidDeviceCentury when attempting to get a date and/or time value + and the the device's century bit is set because all chips have a problem withe year 2100 and beyond. + ## [0.7.0] - 2025-10-11 ### Added