RTC weekday meaning incosistences.
We have just moved one step closer to make the RTC.datetime() more consistent in fd4eec5555f93 but the reports in #6928, quickly revealed that we still have a problem - the weekday has different range on different ports.
The RP2 port will accept any value betweeb 0-6 there, quite interestingly, the rp2040 datasheet mentions that dayw = 0 means Sunday but it also says:
"There is no built-in calendar function. The RTC will not compute the correct day of the week; it will only increment the existing value.". So, basically, the meaning of the dayw = 0 can be whatever you want, it just depends on your interpretation.
mimxrt port uses 0 = Monday. esp32, esp8266 uses the same interpretation as utime, so 0 = Monday. STM32 also considers Monday to be the first day of week but it uses 1-7, not 0-6 :/
Neither nrf nor cc3200 support RTC.datetime().
mimxrt, esp32, esp8266 will ignore the weekday value given when initializing the datetime and will compute its own value when returning it. STM32 and PR2 will put the given value to the hardware, but only if it is a valid weekday, otherwise the whole RTC setting will silently fail. See PR #7392 that fixes this for RP2.
To sum up:
- mimxrt, esp32, esp8266 will accept any value as weekday, it will use 0-6 (0=Monday) when returning the value
- rp2 will only accept 0-6, will return whatever was set before; if value is out of range, the whole datetime will not be set to hardware
- stm32 will only accept 1-7, will return whatever was set before; it uses assert_param to validate the value is in range but this macro is disabled by default, I don't know what will happen if the value of our range will be programmed to the hardware - it seems like in case of values >7, it will overwrite part of the year field, not sure what will happen if 0 is used - the datasheet says this value is forbidden.
@dpgeorge Should we fix that? If so, how?
- I think that we should, at least, create an equivalent to #7392 but for stm32. By default the HAL will not return an error if value out of range is used, but it seems that bad things may happen.
- We have to decide if we want to change stm32 to use 0-6 (i.e. map the values so that they are consistent) or leave it at 1-7,
- We should decide if we really want to use the user-specified value for stm32/rp2 or maybe we should ignore it just like mimxrt, esp32, esp8266, and compute it internally based on the given Y/M/D, instead of forcing the user to do it.
- In general, I think it doesn't make sense to use weekday when setting the datetime as this information is actually redundant as it can always be computed from Y/M/D and only brings confusion.
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.