ds3231_rtc/datetime.rs
1//! # DateTime Module
2//!
3//! This module provides an implementation of the [`Rtc`] trait for the
4//! DS3231 real-time clock (RTC).
5
6use rtc_hal::{bcd, datetime::DateTimeError, rtc::Rtc};
7
8use crate::{Ds3231, registers::Register};
9
10impl<I2C> Rtc for Ds3231<I2C>
11where
12 I2C: embedded_hal::i2c::I2c,
13{
14 /// Read the current date and time from the DS3231.
15 fn get_datetime(&mut self) -> Result<rtc_hal::datetime::DateTime, Self::Error> {
16 // Since DS3231 allows Subsequent registers can be accessed sequentially until a STOP condition is executed
17 // Read all 7 registers in one burst operation
18 let mut data = [0; 7];
19 self.read_register_bytes(Register::Seconds, &mut data)?;
20
21 // Convert from BCD format and extract fields
22 let second = bcd::to_decimal(data[0]);
23 let minute = bcd::to_decimal(data[1]);
24
25 // Handle both 12-hour and 24-hour modes for hours
26 let raw_hour = data[2];
27 let hour = if (raw_hour & 0b0100_0000) != 0 {
28 // 12-hour mode
29 // Extract the Hour part (4-0 bits)
30 let hr = bcd::to_decimal(raw_hour & 0b0001_1111);
31 // Extract the AM/PM (5th bit). if it is set, then it is PM
32 let pm = (raw_hour & 0b0010_0000) != 0;
33
34 // Convert it to 24 hour format:
35 match (hr, pm) {
36 (12, false) => 0, // 12 AM = 00:xx
37 (12, true) => 12, // 12 PM = 12:xx
38 (h, false) => h, // 1-11 AM
39 (h, true) => h + 12, // 1-11 PM
40 }
41 } else {
42 // 24-hour mode
43 // Extrac the hour value from 5-0 bits
44 bcd::to_decimal(raw_hour & 0b0011_1111)
45 };
46
47 // let weekday = Weekday::from_number(bcd::to_decimal(data[3]))
48 // .map_err(crate::error::Error::DateTime)?;
49
50 let day_of_month = bcd::to_decimal(data[4]);
51 // Extract century bit
52 // If it is set, then it is next century
53 // Let's say base century is 20, then next century will be 21
54 let is_century_bit_set = (data[5] & 0b1000_0000) != 0;
55 let mut century = self.base_century;
56 if is_century_bit_set {
57 century += 1;
58 }
59
60 let month = bcd::to_decimal(data[5] & 0b0111_1111);
61
62 let year = (century as u16 * 100) + bcd::to_decimal(data[6]) as u16;
63
64 rtc_hal::datetime::DateTime::new(year, month, day_of_month, hour, minute, second)
65 .map_err(crate::error::Error::DateTime)
66 }
67
68 /// Set the current date and time in the DS3231.
69 ///
70 /// The DS3231 stores years as 2-digit values (00-99). This method interprets
71 /// the provided year based on the configured base century and its successor.
72 ///
73 /// # Year Range
74 ///
75 /// The year must be within one of these ranges:
76 /// - **Base century**: `base_century * 100` to `(base_century * 100) + 99`
77 /// - **Next century**: `(base_century + 1) * 100` to `(base_century + 1) * 100 + 99`
78 ///
79 /// For example, with `base_century = 20`:
80 /// - Allowed years: 2000-2099 (stored as 00-99, century bit = 0)
81 /// - Allowed years: 2100-2199 (stored as 00-99, century bit = 1)
82 /// - Rejected years: 1900-1999, 2200+
83 ///
84 /// # Century Bit Handling
85 ///
86 /// The method automatically sets the DS3231's century bit based on which
87 /// century range the year falls into, avoiding the ambiguity issues with
88 /// this hardware feature.
89 ///
90 /// # Time Format
91 ///
92 /// The DS3231 is configured to use 24-hour time format. The weekday is
93 /// calculated from the date and stored in the day register (1=Sunday, 7=Saturday).
94 ///
95 /// # Arguments
96 ///
97 /// * `datetime` - The date and time to set
98 ///
99 /// # Returns
100 ///
101 /// Returns `Err(Error::DateTime(DateTimeError::InvalidYear))` if the year
102 /// is outside the supported range.
103 ///
104 /// # Examples
105 ///
106 /// ```
107 /// // With base_century = 20, you can set dates from 2000-2199
108 /// let datetime = DateTime::new(2023, 12, 25, 15, 30, 0)?;
109 /// rtc.set_datetime(&datetime)?;
110 ///
111 /// // To set dates in a different century, update base_century first
112 /// let rtc = rtc.with_base_century(21)?; // Now supports 2100-2299
113 /// let datetime = DateTime::new(2150, 1, 1, 0, 0, 0)?;
114 /// rtc.set_datetime(&datetime)?;
115 /// ```
116 fn set_datetime(&mut self, datetime: &rtc_hal::datetime::DateTime) -> Result<(), Self::Error> {
117 let century_base = self.base_century as u16 * 100;
118
119 // Validate year is within the current or next century
120 if datetime.year() < century_base || datetime.year() > (century_base + 199) {
121 return Err(crate::error::Error::DateTime(DateTimeError::InvalidYear));
122 }
123
124 let is_next_century = datetime.year() >= (century_base + 100);
125 let year_2digit = if is_next_century {
126 (datetime.year() - century_base - 100) as u8
127 } else {
128 (datetime.year() - century_base) as u8
129 };
130
131 // Prepare data array for burst write (7 registers)
132 let mut data = [0u8; 8];
133 data[0] = Register::Seconds.addr();
134
135 // Seconds register (0x00)
136 data[1] = bcd::from_decimal(datetime.second());
137
138 // Minutes register (0x01)
139 data[2] = bcd::from_decimal(datetime.minute());
140
141 // Hours register (0x02) - set to 24-hour mode
142 // Clear bit 6 (12/24 hour mode bit) to enable 24-hour mode
143 data[3] = bcd::from_decimal(datetime.hour()) & 0b0011_1111;
144
145 let weekday = datetime
146 .calculate_weekday()
147 .map_err(crate::error::Error::DateTime)?;
148
149 // Day of week register (0x03) - 1=Sunday, 7=Saturday
150 data[4] = bcd::from_decimal(weekday.to_number());
151
152 // Day of month register (0x04)
153 data[5] = bcd::from_decimal(datetime.day_of_month());
154
155 // Month register(0x05) with century bit
156 let mut month_reg = bcd::from_decimal(datetime.month());
157 if is_next_century {
158 month_reg |= 0b1000_0000; // Set century bit
159 }
160 data[6] = month_reg;
161
162 data[7] = bcd::from_decimal(year_2digit);
163
164 // Write all 7 registers in one burst operation
165 self.write_raw_bytes(&data)?;
166
167 Ok(())
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTrans};
175 use rtc_hal::datetime::DateTime;
176
177 fn new_ds3231(i2c: I2cMock) -> Ds3231<I2cMock> {
178 Ds3231::new(i2c)
179 }
180
181 #[test]
182 fn test_get_datetime_24h_mode() {
183 // Simulate reading: sec=0x25(25), min=0x59(59), hour=0x23(23h 24h mode),
184 // day_of_week=0x04, day_of_month=0x15(15), month=0x08(August), year=0x23(2023)
185 let data = [0x25, 0x59, 0x23, 0x04, 0x15, 0x08, 0x23];
186 let expectations = [I2cTrans::write_read(
187 0x68,
188 vec![Register::Seconds.addr()],
189 data.to_vec(),
190 )];
191 let mut ds3231 = new_ds3231(I2cMock::new(&expectations));
192
193 let dt = ds3231.get_datetime().unwrap();
194 assert_eq!(dt.second(), 25);
195 assert_eq!(dt.minute(), 59);
196 assert_eq!(dt.hour(), 23);
197
198 assert_eq!(dt.day_of_month(), 15);
199 assert_eq!(dt.month(), 8);
200
201 assert_eq!(dt.year(), 2023);
202
203 ds3231.release_i2c().done();
204 }
205
206 #[test]
207 fn test_set_datetime_within_base_century() {
208 let datetime = DateTime::new(2025, 8, 27, 15, 30, 45).unwrap();
209 // base_century = 20, so 2000-2199 valid. 2023 fits.
210 let expectations = [I2cTrans::write(
211 0x68,
212 vec![
213 Register::Seconds.addr(),
214 0x45, // sec
215 0x30, // min
216 0x15, // hour (24h)
217 0x04, // weekday (2025-08-27 is Wednesday)
218 0x27, // day
219 0x8, // month
220 0x25, // year (25)
221 ],
222 )];
223
224 let mut ds3231 = new_ds3231(I2cMock::new(&expectations));
225
226 ds3231.set_datetime(&datetime).unwrap();
227
228 ds3231.release_i2c().done();
229 }
230
231 #[test]
232 fn test_set_datetime_next_century() {
233 let datetime = DateTime::new(2150, 1, 1, 0, 0, 0).unwrap();
234 // Expect century bit set in month register
235 let expectations = [I2cTrans::write(
236 0x68,
237 vec![
238 Register::Seconds.addr(),
239 0x00, // sec
240 0x00, // min
241 0x00, // hour
242 0x05, // weekday (Thursday 2150-01-01)
243 0x01, // day
244 0x81, // month with century bit
245 0x50, // year (50)
246 ],
247 )];
248
249 let mut ds3231 = new_ds3231(I2cMock::new(&expectations));
250
251 ds3231.set_datetime(&datetime).unwrap();
252
253 ds3231.release_i2c().done();
254 }
255
256 #[test]
257 fn test_set_datetime_invalid_year() {
258 let datetime = DateTime::new(1980, 1, 1, 0, 0, 0).unwrap();
259 let mut ds3231 = new_ds3231(I2cMock::new(&[]));
260
261 let result = ds3231.set_datetime(&datetime);
262 assert!(matches!(
263 result,
264 Err(crate::error::Error::DateTime(DateTimeError::InvalidYear))
265 ));
266
267 ds3231.release_i2c().done();
268 }
269
270 #[test]
271 fn test_get_datetime_12h_mode_am() {
272 // 01:15:30 AM, January 1, 2023 (Sunday)
273 let data = [
274 0x30, // seconds = 30
275 0x15, // minutes = 15
276 0b0100_0001, // hour register: 12h mode, hr=1, AM
277 0x01, // weekday = Sunday
278 0x01, // day of month
279 0x01, // month = January, century=0
280 0x23, // year = 23
281 ];
282 let expectations = [I2cTrans::write_read(
283 0x68,
284 vec![Register::Seconds.addr()],
285 data.to_vec(),
286 )];
287 let mut ds3231 = new_ds3231(I2cMock::new(&expectations));
288
289 let dt = ds3231.get_datetime().unwrap();
290 assert_eq!((dt.hour(), dt.minute(), dt.second()), (1, 15, 30));
291
292 ds3231.release_i2c().done();
293 }
294
295 #[test]
296 fn test_get_datetime_12h_mode_pm() {
297 // 11:45:50 PM, December 31, 2023 (Sunday)
298 let data = [
299 0x50, // seconds = 50
300 0x45, // minutes = 45
301 0b0110_1011, // hour register: 12h mode, hr=11, PM
302 0x01, // weekday = Sunday
303 0x31, // day of month
304 0x12, // month = December
305 0x23, // year = 23
306 ];
307 let expectations = [I2cTrans::write_read(
308 0x68,
309 vec![Register::Seconds.addr()],
310 data.to_vec(),
311 )];
312 let mut ds3231 = new_ds3231(I2cMock::new(&expectations));
313
314 let dt = ds3231.get_datetime().unwrap();
315 assert_eq!(dt.hour(), 23); // 11 PM -> 23h
316 assert_eq!(dt.month(), 12);
317 assert_eq!(dt.day_of_month(), 31);
318
319 ds3231.release_i2c().done();
320 }
321
322 #[test]
323 fn test_get_datetime_12h_mode_12am() {
324 // 12:10:00 AM, Feb 1, 2023 (Wednesday)
325 let data = [
326 0x00, // seconds = 0
327 0x10, // minutes = 10
328 0b0101_0010, // 12h mode (bit 6=1), hr=12 (0x12), AM (bit5=0)
329 0x03, // weekday = Tuesday
330 0x01, // day of month
331 0x02, // month = Feb
332 0x23, // year = 23
333 ];
334 let expectations = [I2cTrans::write_read(
335 0x68,
336 vec![Register::Seconds.addr()],
337 data.to_vec(),
338 )];
339 let mut ds3231 = new_ds3231(I2cMock::new(&expectations));
340
341 let dt = ds3231.get_datetime().unwrap();
342 assert_eq!(dt.hour(), 0); // 12 AM should be 0h
343 assert_eq!(dt.minute(), 10);
344
345 ds3231.release_i2c().done();
346 }
347
348 #[test]
349 fn test_get_datetime_12h_mode_12pm() {
350 // 12:45:00 PM, Mar 1, 2023 (Wednesday)
351 let data = [
352 0x00, // seconds = 0
353 0x45, // minutes = 45
354 0b0111_0010, // 12h mode, hr=12, PM bit set (bit5=1)
355 0x04, // weekday = Wednesday
356 0x01, // day of month
357 0x03, // month = Mar
358 0x23, // year = 23
359 ];
360 let expectations = [I2cTrans::write_read(
361 0x68,
362 vec![Register::Seconds.addr()],
363 data.to_vec(),
364 )];
365 let mut ds3231 = new_ds3231(I2cMock::new(&expectations));
366
367 let dt = ds3231.get_datetime().unwrap();
368 assert_eq!(dt.hour(), 12); // 12 PM should stay 12h
369 assert_eq!(dt.minute(), 45);
370
371 ds3231.release_i2c().done();
372 }
373}