diff --git a/core/.changelog.d/741.added b/core/.changelog.d/741.added new file mode 100644 index 000000000..20c3323c3 --- /dev/null +++ b/core/.changelog.d/741.added @@ -0,0 +1 @@ +Convert timestamps to human-readable dates and times. diff --git a/core/SConscript.firmware b/core/SConscript.firmware index c5d08fe6e..03bec4c57 100644 --- a/core/SConscript.firmware +++ b/core/SConscript.firmware @@ -197,7 +197,7 @@ if NEW_UI: # modutime SOURCE_MOD += [ - 'embed/firmware/modutime.c', + 'embed/extmod/modutime.c', ] SOURCE_MICROPYTHON = [ @@ -208,12 +208,12 @@ SOURCE_MICROPYTHON = [ 'vendor/micropython/extmod/utime_mphal.c', 'vendor/micropython/shared/libc/abort_.c', 'vendor/micropython/shared/libc/printf.c', - #'vendor/micropython/shared/readline/readline.c', 'vendor/micropython/shared/runtime/gchelper_m3.s', 'vendor/micropython/shared/runtime/gchelper_native.c', 'vendor/micropython/shared/runtime/interrupt_char.c', 'vendor/micropython/shared/runtime/pyexec.c', 'vendor/micropython/shared/runtime/stdout_helpers.c', + 'vendor/micropython/shared/timeutils/timeutils.c', 'vendor/micropython/ports/stm32/gccollect.c', 'vendor/micropython/ports/stm32/pendsv.c', 'vendor/micropython/ports/stm32/softtimer.c', diff --git a/core/SConscript.unix b/core/SConscript.unix index c7d73d09a..192c20888 100644 --- a/core/SConscript.unix +++ b/core/SConscript.unix @@ -194,7 +194,7 @@ if NEW_UI: # modutime SOURCE_MOD += [ - 'vendor/micropython/ports/unix/modtime.c', + 'embed/extmod/modutime.c', ] SOURCE_MICROPYTHON = [ @@ -204,6 +204,7 @@ SOURCE_MICROPYTHON = [ 'vendor/micropython/extmod/modutimeq.c', 'vendor/micropython/extmod/utime_mphal.c', 'vendor/micropython/shared/readline/readline.c', + 'vendor/micropython/shared/timeutils/timeutils.c', 'vendor/micropython/ports/unix/modos.c', 'vendor/micropython/py/argcheck.c', 'vendor/micropython/py/asmarm.c', diff --git a/core/embed/firmware/modutime.c b/core/embed/extmod/modutime.c similarity index 70% rename from core/embed/firmware/modutime.c rename to core/embed/extmod/modutime.c index 5d41a4c32..1aef073fc 100644 --- a/core/embed/firmware/modutime.c +++ b/core/embed/extmod/modutime.c @@ -25,10 +25,33 @@ */ #include "extmod/utime_mphal.h" +#include "shared/timeutils/timeutils.h" + +// copy of ports/stm32/modutime.c:time_localtime, without support +// for getting current clock time (i.e., timestamp must always be provided) +STATIC mp_obj_t time_gmtime2000(mp_obj_t timestamp) { + mp_int_t seconds = mp_obj_get_int(timestamp); + timeutils_struct_time_t tm; + timeutils_seconds_since_2000_to_struct_time(seconds, &tm); + mp_obj_t tuple[8] = { + tuple[0] = mp_obj_new_int(tm.tm_year), + tuple[1] = mp_obj_new_int(tm.tm_mon), + tuple[2] = mp_obj_new_int(tm.tm_mday), + tuple[3] = mp_obj_new_int(tm.tm_hour), + tuple[4] = mp_obj_new_int(tm.tm_min), + tuple[5] = mp_obj_new_int(tm.tm_sec), + tuple[6] = mp_obj_new_int(tm.tm_wday), + tuple[7] = mp_obj_new_int(tm.tm_yday), + }; + return mp_obj_new_tuple(8, tuple); +} + +MP_DEFINE_CONST_FUN_OBJ_1(time_gmtime2000_obj, time_gmtime2000); STATIC const mp_rom_map_elem_t time_module_globals_table[] = { {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_utime)}, + {MP_ROM_QSTR(MP_QSTR_gmtime2000), MP_ROM_PTR(&time_gmtime2000_obj)}, {MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&mp_utime_sleep_obj)}, {MP_ROM_QSTR(MP_QSTR_sleep_ms), MP_ROM_PTR(&mp_utime_sleep_ms_obj)}, {MP_ROM_QSTR(MP_QSTR_sleep_us), MP_ROM_PTR(&mp_utime_sleep_us_obj)}, @@ -46,4 +69,4 @@ const mp_obj_module_t mp_module_utime = { .globals = (mp_obj_dict_t*)&time_module_globals, }; -MP_REGISTER_MODULE(MP_QSTR_utime, mp_module_utime, MICROPY_PY_UTIME_MP_HAL); +MP_REGISTER_MODULE(MP_QSTR_utime, mp_module_utime, MICROPY_PY_UTIME); diff --git a/core/embed/firmware/mpconfigport.h b/core/embed/firmware/mpconfigport.h index 10cf234c7..c123c91a0 100644 --- a/core/embed/firmware/mpconfigport.h +++ b/core/embed/firmware/mpconfigport.h @@ -141,6 +141,7 @@ #define MICROPY_PY_URANDOM (0) #define MICROPY_PY_URANDOM_EXTRA_FUNCS (0) #define MICROPY_PY_USELECT (0) +#define MICROPY_PY_UTIME (1) #define MICROPY_PY_UTIMEQ (1) #define MICROPY_PY_UTIME_MP_HAL (1) #define MICROPY_PY_OS_DUPTERM (0) diff --git a/core/embed/unix/mpconfigport.h b/core/embed/unix/mpconfigport.h index 8651c83bd..0cdafc044 100644 --- a/core/embed/unix/mpconfigport.h +++ b/core/embed/unix/mpconfigport.h @@ -205,13 +205,9 @@ extern const struct _mp_print_t mp_stderr_print; // extra built in modules to add to the list of known ones extern const struct _mp_obj_module_t mp_module_os; -// on unix, we use time, not utime -extern const struct _mp_obj_module_t mp_module_time; #define MICROPY_PORT_BUILTIN_MODULES \ - { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_os) }, \ - { MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&mp_module_time) }, - + { MP_ROM_QSTR(MP_QSTR_uos), MP_ROM_PTR(&mp_module_os) }, // For size_t and ssize_t diff --git a/core/mocks/utime.pyi b/core/mocks/utime.pyi index ff7f10d29..c2ff9bb82 100644 --- a/core/mocks/utime.pyi +++ b/core/mocks/utime.pyi @@ -6,3 +6,4 @@ def ticks_us() -> int: ... def ticks_cpu() -> int: ... def ticks_add(ticks_in: int, delta_in: int) -> int: ... def ticks_diff(old: int, new: int) -> int: ... +def gmtime2000(timestamp: int) -> tuple: ... diff --git a/core/src/trezor/strings.py b/core/src/trezor/strings.py index 9b57df8b7..de83372c4 100644 --- a/core/src/trezor/strings.py +++ b/core/src/trezor/strings.py @@ -1,3 +1,9 @@ +import utime +from micropython import const + +_SECONDS_1970_TO_2000 = const(946684800) + + def format_amount(amount: int, decimals: int) -> str: if amount < 0: amount = -amount @@ -60,3 +66,20 @@ def format_duration_ms(milliseconds: int) -> str: divisor = 1 return format_plural("{count} {plural}", milliseconds // divisor, unit) + + +def format_timestamp(timestamp: int) -> str: + """ + Returns human-friendly representation of a unix timestamp (in seconds format). + Minutes and seconds are always displayed as 2 digits. + Example: + >>> format_timestamp_to_human(0) + '1970-01-01 00:00:00' + >>> format_timestamp_to_human(1616051824) + '2021-03-18 07:17:04' + """ + # By doing the conversion to 2000-based epoch in Python, we take advantage of the + # bignum implementation, and get another 30 years out of the 32-bit mp_int_t + # that is used internally. + d = utime.gmtime2000(timestamp - _SECONDS_1970_TO_2000) + return f"{d[0]}-{d[1]:02d}-{d[2]:02d} {d[3]:02d}:{d[4]:02d}:{d[5]:02d}" diff --git a/core/tests/test_trezor.strings.py b/core/tests/test_trezor.strings.py index 34ad1deb0..4ac14e158 100644 --- a/core/tests/test_trezor.strings.py +++ b/core/tests/test_trezor.strings.py @@ -53,6 +53,101 @@ class TestStrings(unittest.TestCase): for v in VECTORS: self.assertEqual(strings.format_duration_ms(v[0]), v[1]) + def test_format_timestamp(self): + VECTORS = [ + (0, "1970-01-01 00:00:00"), + (123456, "1970-01-02 10:17:36"), + (246912, "1970-01-03 20:35:12"), + (370368, "1970-01-05 06:52:48"), + (493824, "1970-01-06 17:10:24"), + (10000, "1970-01-01 02:46:40"), + (12355678, "1970-05-24 00:07:58"), + (24701356, "1970-10-13 21:29:16"), + (37047034, "1971-03-05 18:50:34"), + (49392712, "1971-07-26 16:11:52"), + (1610057224, "2021-01-07 22:07:04"), + (1610806549, "2021-01-16 14:15:49"), + (1611555874, "2021-01-25 06:24:34"), + (1612305199, "2021-02-02 22:33:19"), + (1613054524, "2021-02-11 14:42:04"), + (1613803849, "2021-02-20 06:50:49"), + (1614553174, "2021-02-28 22:59:34"), + (1615302499, "2021-03-09 15:08:19"), + (1616051824, "2021-03-18 07:17:04"), + (1616801149, "2021-03-26 23:25:49"), + (1617550474, "2021-04-04 15:34:34"), + (1618299799, "2021-04-13 07:43:19"), + (1619049124, "2021-04-21 23:52:04"), + (1619798449, "2021-04-30 16:00:49"), + (1620547774, "2021-05-09 08:09:34"), + (1621297099, "2021-05-18 00:18:19"), + (1622046424, "2021-05-26 16:27:04"), + (1622795749, "2021-06-04 08:35:49"), + (1623545074, "2021-06-13 00:44:34"), + (1624294399, "2021-06-21 16:53:19"), + (1625043724, "2021-06-30 09:02:04"), + (1625793049, "2021-07-09 01:10:49"), + (1626542374, "2021-07-17 17:19:34"), + (1627291699, "2021-07-26 09:28:19"), + (1628041024, "2021-08-04 01:37:04"), + (1628790349, "2021-08-12 17:45:49"), + (1629539674, "2021-08-21 09:54:34"), + (1630288999, "2021-08-30 02:03:19"), + (1631038324, "2021-09-07 18:12:04"), + (1631787649, "2021-09-16 10:20:49"), + (1632536974, "2021-09-25 02:29:34"), + (1633286299, "2021-10-03 18:38:19"), + (1634035624, "2021-10-12 10:47:04"), + (1634784949, "2021-10-21 02:55:49"), + (1635534274, "2021-10-29 19:04:34"), + (1636283599, "2021-11-07 11:13:19"), + (1637032924, "2021-11-16 03:22:04"), + (1637782249, "2021-11-24 19:30:49"), + (1638531574, "2021-12-03 11:39:34"), + (1639280899, "2021-12-12 03:48:19"), + (1640030224, "2021-12-20 19:57:04"), + (1640779549, "2021-12-29 12:05:49"), + (1641528874, "2022-01-07 04:14:34"), + (976838400, "2000-12-15 00:00:00"), + (976838399, "2000-12-14 23:59:59"), + (976838401, "2000-12-15 00:00:01"), + (1119398400, "2005-06-22 00:00:00"), + (1119398399, "2005-06-21 23:59:59"), + (1119398401, "2005-06-22 00:00:01"), + (1261958400, "2009-12-28 00:00:00"), + (1261958399, "2009-12-27 23:59:59"), + (1261958401, "2009-12-28 00:00:01"), + (1404518400, "2014-07-05 00:00:00"), + (1404518399, "2014-07-04 23:59:59"), + (1404518401, "2014-07-05 00:00:01"), + (1547078400, "2019-01-10 00:00:00"), + (1547078399, "2019-01-09 23:59:59"), + (1547078401, "2019-01-10 00:00:01"), + (1689638400, "2023-07-18 00:00:00"), + (1689638399, "2023-07-17 23:59:59"), + (1689638401, "2023-07-18 00:00:01"), + (1832198400, "2028-01-23 00:00:00"), + (1832198399, "2028-01-22 23:59:59"), + (1832198401, "2028-01-23 00:00:01"), + (1891987200, "2029-12-15 00:00:00"), + (1891987199, "2029-12-14 23:59:59"), + (1891987201, "2029-12-15 00:00:01"), + (2747347200, "2057-01-22 00:00:00"), + (2747347199, "2057-01-21 23:59:59"), + (2747347201, "2057-01-22 00:00:01"), + (3602707200, "2084-03-01 00:00:00"), + (3602707199, "2084-02-29 23:59:59"), + (3602707201, "2084-03-01 00:00:01"), + (7982409599, "2222-12-14 23:59:59"), + (7982409600, "2222-12-15 00:00:00"), + (7982409601, "2222-12-15 00:00:01"), + ] + + for v in VECTORS: + self.assertEqual(strings.format_timestamp(v[0]), v[1]) + + strings.format_timestamp(1616057224) + if __name__ == '__main__': unittest.main()