Class RTC on ESP32 documentation bug
RTC.datetime
Testing showed that the 8-tuple has the following format:
(year, month, dayofmonth, dayofweek (0..6, mon=0), hours, minutes, seconds, subseconds)
Not sure about the content/validity of "subseconds", cause this can be set to any value, even bigger 255.
Tested on firmware versions:
esp32spiram-idf3-20190529-v1.11.bin
esp32spiram-idf3-20191126-v1.11-607-g01e5802ee.bin
But documentation says:
Datetime is a tuple of the form:
(year, month, day[, hour[, minute[, second[, microsecond[, tzinfo]]]]])
This is definitely wrong, documentation of pyb.RTC also does not match, cause there weekday is in range 1-7. Can not test this cause only ESP32 available.
esp32: Fix RTC initialization from datetime.
Summary
I was pulling my hair out trying to initialize the RTC though its init function as follows:
rtc = RTC()
year = 2024
month = 12
day = 26
weekday = 3
hour = 6
minute = 30
second = 20
microsecond = 0
tzinfo = 0
rtc.init((year, month, day, hour, minute, second, microsecond, tzinfo))
However it kept being initialized with a wrong date / time. The documentation states:
Initialise the RTC. Datetime is a tuple of the form:
(year, month, day[, hour[, minute[, second[, microsecond[, tzinfo]]]]]
The problem lies in the fact that both the datetime setter and init use the same helper function. The 8-tuple for that one is different though according to the docs:
The 8-tuple has the following format:
(year, month, day, weekday, hours, minutes, seconds, subseconds)
It uses weekday which makes sense as the getter should deliver this information and both setter and getter shall use the same format. Now we can see that using the same helper function for both formats cannot work and the two need to be distinguished. This is what this PR does. It fixes the issue by treating the two differently.
Testing
I tested the fix on an Arduino Nano ESP32. I wasn't sure where to add a unit test, but here is what I wrote in MicroPython:
from machine import RTC
rtc = RTC()
year = 2024
month = 12
day = 26
weekday = 3
hour = 6
minute = 30
second = 20
microsecond = 0
tzinfo = 0
def test(source, should_fail=False):
print(f"\nTesting {source}")
# Read time
t = rtc.datetime()
print(t)
try:
assert t[0] == 2024, f"Year should be 2024 but was {t[0]}"
assert t[1] == 12, f"Month should be 12 but was {t[1]}"
assert t[2] == 26, f"Day should be 26 but was {t[2]}"
assert t[3] == 3, f"Weekday should be 3 but was {t[3]}"
assert t[4] == 6, f"Hour should be 6 but was {t[4]}"
assert t[5] == 30, f"Minute should be 30 but was {t[5]}"
assert t[6] == 20, f"Second should be 20 but was {t[6]}"
if should_fail:
print("❌ Test should have failed but didn't")
else:
print("✅ Test passed")
except AssertionError:
if should_fail:
print("✅ Test failed as expected")
else:
print("❌ Test passed but should have failed")
# Initialise the RTC. Datetime is a tuple of the form:
# (year, month, day[, hour[, minute[, second[, microsecond[, tzinfo]]]]]
rtc.init((year, month, day, hour, minute, second, microsecond, tzinfo))
test("init in order")
rtc.init((year, month, day, tzinfo, hour, minute, second, microsecond))
test("init out of order", should_fail=True)
# The 8-tuple has the following format:
# (year, month, day, weekday, hours, minutes, seconds, subseconds)
rtc.datetime((year, month, day, weekday, hour, minute, second, microsecond))
test("datetime in order")
The output before applying the fix:
Testing init in order
(2024, 12, 27, 4, 6, 20, 0, 373)
❌ Test passed but should have failed
Testing init out of order
(2024, 12, 26, 3, 6, 30, 20, 245)
❌ Test should have failed but didn't
Testing datetime in order
(2024, 12, 26, 3, 6, 30, 20, 256)
✅ Test passed
The output after applying the fix:
Testing init in order
(2024, 12, 26, 3, 6, 30, 20, 389)
✅ Test passed
Testing init out of order
(2024, 12, 26, 3, 0, 6, 30, 365)
✅ Test failed as expected
Testing datetime in order
(2024, 12, 26, 3, 6, 30, 20, 235)
✅ Test passed
Trade-offs and Alternatives
An alternative could be to change the documentation so that the init and the datetime function use the same format. tzinfo doesn't seem to be used anyway. At least not in this port.