Skip to content

Commit c668d51

Browse files
committed
Merge branch 'dhylands-localtime'
2 parents 3c658a4 + 8ba8324 commit c668d51

5 files changed

Lines changed: 311 additions & 26 deletions

File tree

stmhal/modtime.c

Lines changed: 238 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
* THE SOFTWARE.
2525
*/
2626

27+
#include <stdio.h>
28+
#include <string.h>
2729
#include "stm32f4xx_hal.h"
2830

2931
#include "mpconfig.h"
@@ -39,23 +41,32 @@
3941
/// The `time` module provides functions for getting the current time and date,
4042
/// and for sleeping.
4143

42-
STATIC const uint16_t days_since_jan1[]= { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
44+
STATIC const uint16_t days_since_jan1[]= { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
4345

4446
STATIC bool is_leap_year(mp_uint_t year) {
4547
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
4648
}
4749

50+
// Month is one based
51+
STATIC mp_uint_t mod_time_days_in_month(mp_uint_t year, mp_uint_t month) {
52+
mp_uint_t mdays = days_since_jan1[month] - days_since_jan1[month - 1];
53+
if (month == 2 && is_leap_year(year)) {
54+
mdays++;
55+
}
56+
return mdays;
57+
}
58+
4859
// compute the day of the year, between 1 and 366
4960
// month should be between 1 and 12, date should start at 1
50-
mp_uint_t mod_time_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date) {
61+
STATIC mp_uint_t mod_time_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date) {
5162
mp_uint_t yday = days_since_jan1[month - 1] + date;
5263
if (month >= 3 && is_leap_year(year)) {
5364
yday += 1;
5465
}
5566
return yday;
5667
}
5768

58-
// returns the number of seconds, as an integer, since 1/1/2000
69+
// returns the number of seconds, as an integer, since 2000-01-01
5970
mp_uint_t mod_time_seconds_since_2000(mp_uint_t year, mp_uint_t month, mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) {
6071
return
6172
second
@@ -69,29 +80,230 @@ mp_uint_t mod_time_seconds_since_2000(mp_uint_t year, mp_uint_t month, mp_uint_t
6980
+ (year - 2000) * 31536000;
7081
}
7182

72-
/// \function localtime()
73-
/// Returns time stored in RTC as: (year, month, date, hour, minute, second, weekday).
74-
/// Weekday is 0-6 for Mon-Sun.
75-
STATIC mp_obj_t time_localtime(void) {
76-
// get date and time
77-
// note: need to call get time then get date to correctly access the registers
78-
RTC_DateTypeDef date;
79-
RTC_TimeTypeDef time;
80-
HAL_RTC_GetTime(&RTCHandle, &time, FORMAT_BIN);
81-
HAL_RTC_GetDate(&RTCHandle, &date, FORMAT_BIN);
82-
mp_obj_t tuple[8] = {
83-
mp_obj_new_int(2000 + date.Year),
84-
mp_obj_new_int(date.Month),
85-
mp_obj_new_int(date.Date),
86-
mp_obj_new_int(time.Hours),
87-
mp_obj_new_int(time.Minutes),
88-
mp_obj_new_int(time.Seconds),
89-
mp_obj_new_int(date.WeekDay - 1),
90-
mp_obj_new_int(mod_time_year_day(2000 + date.Year, date.Month, date.Date)),
91-
};
92-
return mp_obj_new_tuple(8, tuple);
83+
// LEAPOCH corresponds to 2000-03-01, which is a mod-400 year, immediately
84+
// after Feb 29. We calculate seconds as a signed integer relative to that.
85+
//
86+
// Our timebase is is relative to 2000-01-01.
87+
88+
#define LEAPOCH ((31 + 29) * 86400)
89+
90+
#define DAYS_PER_400Y (365*400 + 97)
91+
#define DAYS_PER_100Y (365*100 + 24)
92+
#define DAYS_PER_4Y (365*4 + 1)
93+
94+
typedef struct {
95+
uint16_t tm_year; // i.e. 2014
96+
uint8_t tm_mon; // 1..12
97+
uint8_t tm_mday; // 1..31
98+
uint8_t tm_hour; // 0..23
99+
uint8_t tm_min; // 0..59
100+
uint8_t tm_sec; // 0..59
101+
uint8_t tm_wday; // 0..6 0 = Monday
102+
uint16_t tm_yday; // 1..366
103+
} mod_struct_time;
104+
105+
STATIC void mod_time_seconds_since_2000_to_struct_time(mp_uint_t t, mod_struct_time *tm) {
106+
// The following algorithm was adapted from musl's __secs_to_tm and adapted
107+
// for differences in Micro Python's timebase.
108+
109+
mp_int_t seconds = t - LEAPOCH;
110+
111+
mp_int_t days = seconds / 86400;
112+
seconds %= 86400;
113+
tm->tm_hour = seconds / 3600;
114+
tm->tm_min = seconds / 60 % 60;
115+
tm->tm_sec = seconds % 60;
116+
117+
mp_int_t wday = (days + 2) % 7; // Mar 1, 2000 was a Wednesday (2)
118+
if (wday < 0) {
119+
wday += 7;
120+
}
121+
tm->tm_wday = wday;
122+
123+
mp_int_t qc_cycles = days / DAYS_PER_400Y;
124+
days %= DAYS_PER_400Y;
125+
if (days < 0) {
126+
days += DAYS_PER_400Y;
127+
qc_cycles--;
128+
}
129+
mp_int_t c_cycles = days / DAYS_PER_100Y;
130+
if (c_cycles == 4) {
131+
c_cycles--;
132+
}
133+
days -= (c_cycles * DAYS_PER_100Y);
134+
135+
mp_int_t q_cycles = days / DAYS_PER_4Y;
136+
if (q_cycles == 25) {
137+
q_cycles--;
138+
}
139+
days -= q_cycles * DAYS_PER_4Y;
140+
141+
mp_int_t years = days / 365;
142+
if (years == 4) {
143+
years--;
144+
}
145+
days -= (years * 365);
146+
147+
/* We will compute tm_yday at the very end
148+
mp_int_t leap = !years && (q_cycles || !c_cycles);
149+
150+
tm->tm_yday = days + 31 + 28 + leap;
151+
if (tm->tm_yday >= 365 + leap) {
152+
tm->tm_yday -= 365 + leap;
153+
}
154+
155+
tm->tm_yday++; // Make one based
156+
*/
157+
158+
tm->tm_year = 2000 + years + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
159+
160+
// Note: days_in_month[0] corresponds to March
161+
STATIC const int8_t days_in_month[] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29};
162+
163+
mp_int_t month;
164+
for (month = 0; days_in_month[month] <= days; month++) {
165+
days -= days_in_month[month];
166+
}
167+
168+
tm->tm_mon = month + 2;
169+
if (tm->tm_mon >= 12) {
170+
tm->tm_mon -= 12;
171+
tm->tm_year++;
172+
}
173+
tm->tm_mday = days + 1; // Make one based
174+
tm->tm_mon++; // Make one based
175+
176+
tm->tm_yday = mod_time_year_day(tm->tm_year, tm->tm_mon, tm->tm_mday);
177+
}
178+
179+
/// \function localtime([secs])
180+
/// Convert a time expressed in seconds since Jan 1, 2000 into an 8-tuple which
181+
/// contains: (year, month, mday, hour, minute, second, weekday, yearday)
182+
/// If secs is not provided or None, then the current time from the RTC is used.
183+
/// year includes the century (for example 2014)
184+
/// month is 1-12
185+
/// mday is 1-31
186+
/// hour is 0-23
187+
/// minute is 0-59
188+
/// second is 0-59
189+
/// weekday is 0-6 for Mon-Sun.
190+
/// yearday is 1-366
191+
STATIC mp_obj_t time_localtime(uint n_args, const mp_obj_t *args) {
192+
if (n_args == 0 || args[0] == mp_const_none) {
193+
// get current date and time
194+
// note: need to call get time then get date to correctly access the registers
195+
RTC_DateTypeDef date;
196+
RTC_TimeTypeDef time;
197+
HAL_RTC_GetTime(&RTCHandle, &time, FORMAT_BIN);
198+
HAL_RTC_GetDate(&RTCHandle, &date, FORMAT_BIN);
199+
mp_obj_t tuple[8] = {
200+
mp_obj_new_int(2000 + date.Year),
201+
mp_obj_new_int(date.Month),
202+
mp_obj_new_int(date.Date),
203+
mp_obj_new_int(time.Hours),
204+
mp_obj_new_int(time.Minutes),
205+
mp_obj_new_int(time.Seconds),
206+
mp_obj_new_int(date.WeekDay - 1),
207+
mp_obj_new_int(mod_time_year_day(2000 + date.Year, date.Month, date.Date)),
208+
};
209+
return mp_obj_new_tuple(8, tuple);
210+
} else {
211+
mp_int_t seconds = mp_obj_get_int(args[0]);
212+
mod_struct_time tm;
213+
mod_time_seconds_since_2000_to_struct_time(seconds, &tm);
214+
mp_obj_t tuple[8] = {
215+
tuple[0] = mp_obj_new_int(tm.tm_year),
216+
tuple[1] = mp_obj_new_int(tm.tm_mon),
217+
tuple[2] = mp_obj_new_int(tm.tm_mday),
218+
tuple[3] = mp_obj_new_int(tm.tm_hour),
219+
tuple[4] = mp_obj_new_int(tm.tm_min),
220+
tuple[5] = mp_obj_new_int(tm.tm_sec),
221+
tuple[6] = mp_obj_new_int(tm.tm_wday),
222+
tuple[7] = mp_obj_new_int(tm.tm_yday),
223+
};
224+
return mp_obj_new_tuple(8, tuple);
225+
}
226+
}
227+
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(time_localtime_obj, 0, 1, time_localtime);
228+
229+
230+
/// \function mktime()
231+
/// This is inverse function of localtime. It's argument is a full 8-tuple
232+
/// which expresses a time as per localtime. It returns an integer which is
233+
/// the number of seconds since Jan 1, 2000.
234+
STATIC mp_obj_t time_mktime(mp_obj_t tuple) {
235+
236+
uint len;
237+
mp_obj_t *elem;
238+
239+
mp_obj_get_array(tuple, &len, &elem);
240+
241+
// localtime generates a tuple of len 8. CPython uses 9, so we accept both.
242+
if (len < 8 || len > 9) {
243+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError, "mktime needs a tuple of length 8 or 9 (%d given)", len));
244+
}
245+
246+
mp_int_t year = mp_obj_get_int(elem[0]);
247+
mp_int_t month = mp_obj_get_int(elem[1]);
248+
mp_int_t mday = mp_obj_get_int(elem[2]);
249+
mp_int_t hours = mp_obj_get_int(elem[3]);
250+
mp_int_t minutes = mp_obj_get_int(elem[4]);
251+
mp_int_t seconds = mp_obj_get_int(elem[5]);
252+
253+
// Normalise the tuple. This allows things like:
254+
//
255+
// tm_tomorrow = list(time.localtime())
256+
// tm_tomorrow[2] += 1 # Adds 1 to mday
257+
// tomorrow = time.mktime(tm_tommorrow)
258+
//
259+
// And not have to worry about all the weird overflows.
260+
//
261+
// You can subtract dates/times this way as well.
262+
263+
minutes += seconds / 60;
264+
if ((seconds = seconds % 60) < 0) {
265+
seconds += 60;
266+
minutes--;
267+
}
268+
269+
hours += minutes / 60;
270+
if ((minutes = minutes % 60) < 0) {
271+
minutes += 60;
272+
hours--;
273+
}
274+
275+
mday += hours / 24;
276+
if ((hours = hours % 24) < 0) {
277+
hours += 24;
278+
mday--;
279+
}
280+
281+
month--; // make month zero based
282+
year += month / 12;
283+
if ((month = month % 12) < 0) {
284+
month += 12;
285+
year--;
286+
}
287+
month++; // back to one based
288+
289+
while (mday < 1) {
290+
if (--month == 0) {
291+
month = 12;
292+
year--;
293+
}
294+
mday += mod_time_days_in_month(year, month);
295+
}
296+
while (mday > mod_time_days_in_month(year, month)) {
297+
mday -= mod_time_days_in_month(year, month);
298+
if (++month == 13) {
299+
month = 1;
300+
year++;
301+
}
302+
}
303+
return mp_obj_new_int_from_uint(mod_time_seconds_since_2000(year, month, mday, hours, minutes, seconds));
93304
}
94-
MP_DEFINE_CONST_FUN_OBJ_0(time_localtime_obj, time_localtime);
305+
MP_DEFINE_CONST_FUN_OBJ_1(time_mktime_obj, time_mktime);
306+
95307

96308
/// \function sleep(seconds)
97309
/// Sleep for the given number of seconds. Seconds can be a floating-point number to
@@ -127,6 +339,7 @@ STATIC const mp_map_elem_t time_module_globals_table[] = {
127339
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_time) },
128340

129341
{ MP_OBJ_NEW_QSTR(MP_QSTR_localtime), (mp_obj_t)&time_localtime_obj },
342+
{ MP_OBJ_NEW_QSTR(MP_QSTR_mktime), (mp_obj_t)&time_mktime_obj },
130343
{ MP_OBJ_NEW_QSTR(MP_QSTR_sleep), (mp_obj_t)&time_sleep_obj },
131344
{ MP_OBJ_NEW_QSTR(MP_QSTR_time), (mp_obj_t)&time_time_obj },
132345
};

stmhal/portmodules.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,4 @@ extern const mp_obj_module_t time_module;
3131

3232
// additional helper functions exported by the modules
3333

34-
mp_uint_t mod_time_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date);
3534
mp_uint_t mod_time_seconds_since_2000(mp_uint_t year, mp_uint_t month, mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second);

stmhal/qstrdefsport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ Q(urandom)
265265
// for time module
266266
Q(time)
267267
Q(localtime)
268+
Q(mktime)
268269
Q(sleep)
269270

270271
// for input

tests/pyb/modtime.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import time
2+
3+
DAYS_PER_MONTH = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
4+
5+
def is_leap(year):
6+
return (year % 4) == 0
7+
8+
def test():
9+
seconds = 0
10+
wday = 5 # Jan 1, 2000 was a Saturday
11+
for year in range(2000, 2034):
12+
print("Testing %d" % year)
13+
yday = 1
14+
for month in range(1, 13):
15+
if month == 2 and is_leap(year):
16+
DAYS_PER_MONTH[2] = 29
17+
else:
18+
DAYS_PER_MONTH[2] = 28
19+
for day in range(1, DAYS_PER_MONTH[month] + 1):
20+
secs = time.mktime((year, month, day, 0, 0, 0, 0, 0))
21+
if secs != seconds:
22+
print("mktime failed for %d-%02d-%02d got %d expected %d" % (year, month, day, secs, seconds))
23+
tuple = time.localtime(seconds)
24+
secs = time.mktime(tuple)
25+
if secs != seconds:
26+
print("localtime failed for %d-%02d-%02d got %d expected %d" % (year, month, day, secs, seconds))
27+
return
28+
seconds += 86400
29+
if yday != tuple[7]:
30+
print("locatime for %d-%02d-%02d got yday %d, expecting %d" % (year, month, day, tuple[7], yday))
31+
return
32+
if wday != tuple[6]:
33+
print("locatime for %d-%02d-%02d got wday %d, expecting %d" % (year, month, day, tuple[6], wday))
34+
return
35+
yday += 1
36+
wday = (wday + 1) % 7
37+
38+
test()

tests/pyb/modtime.py.exp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
Testing 2000
2+
Testing 2001
3+
Testing 2002
4+
Testing 2003
5+
Testing 2004
6+
Testing 2005
7+
Testing 2006
8+
Testing 2007
9+
Testing 2008
10+
Testing 2009
11+
Testing 2010
12+
Testing 2011
13+
Testing 2012
14+
Testing 2013
15+
Testing 2014
16+
Testing 2015
17+
Testing 2016
18+
Testing 2017
19+
Testing 2018
20+
Testing 2019
21+
Testing 2020
22+
Testing 2021
23+
Testing 2022
24+
Testing 2023
25+
Testing 2024
26+
Testing 2025
27+
Testing 2026
28+
Testing 2027
29+
Testing 2028
30+
Testing 2029
31+
Testing 2030
32+
Testing 2031
33+
Testing 2032
34+
Testing 2033

0 commit comments

Comments
 (0)