Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Timestamp to human readable date&time string (Martin Poloch home assignment) #1535

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions core/src/apps/bitcoin/sign_tx/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from ubinascii import hexlify

from trezor.messages import AmountUnit, ButtonRequestType, OutputScriptType
from trezor.strings import format_amount
from trezor.strings import format_amount, format_timestamp_to_human
from trezor.ui import layouts
from trezor.ui.layouts import require

Expand Down Expand Up @@ -176,8 +176,8 @@ async def confirm_nondefault_locktime(
param = str(lock_time)
else:
title = "Confirm locktime"
text = "Locktime for this\ntransaction is set to\ntimestamp:\n{}"
param = str(lock_time)
text = "Locktime for this\ntransaction is set to\ndate & time:\n{}"
param = format_timestamp_to_human(lock_time)

await require(
layouts.confirm_metadata(
Expand Down
60 changes: 60 additions & 0 deletions core/src/trezor/strings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import utime

if False:
from typing import Tuple


def format_amount(amount: int, decimals: int) -> str:
if amount < 0:
amount = -amount
Expand Down Expand Up @@ -64,3 +70,57 @@ def format_duration_ms(milliseconds: int) -> str:
divisor = 1

return format_plural("{count} {plural}", milliseconds // divisor, unit)


def _calculate_timestamp_correction(timestamp: int) -> (Tuple[tuple, int]):
"""
utime module can't convert timestamp to datetime with seconds precision.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this problem occurs only in the UNIX implementation, because micropython quite pointlessly converts the timestamp to a C float which has a mantissa of 23 bits, causing the lower bits to get lost. The STM32 implementation reads it as an integer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually fixed upstream.

Copy link
Member

@prusnak prusnak Mar 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we need to merge MicroPython 1.14 in before merging in this PR - #1548

returns date in tuple format and correction in seconds
from the utime-calculated date's midnight
to the correct datetime
Example:
timestamp 1616057224
correct datetime "2021-03-18 08:47:04"
utime-calculated datetime "2021-03-18 08:46:56"
midnight datetime "2021-03-18 00:00:00"
returned date (2021, 3, 18, 0, 0, 0, 3, 77, 0)
correction 31624 (~ 8:47:04)
"""
approx_datetime = utime.localtime(timestamp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the actual device this fails with:

AttributeError: 'module' object has no attribute 'localtime'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if we got this working, I suspect that it might give an incorrect time on the device, because the STM32 implementation seems to be using 2000-01-01 as the beginning of the epoch as opposed to the UNIX implementation which uses 1970-01-01. It's hard to believe, but that's what I see in the C code. I didn't actually test.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, from my limited sources, I expect the behavior you described. Sorry for not mentioning it in the PR description.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the two implementations are really using different epochs, then I have doubts whether we should be using utime at all. We would then have to have a piece of code like "If not EMULATOR, then subtract 30 years", which just seems very wrong.

Secondly, there is #741, which indicates that there may have been some opposition to adding the utime module to the firmware build.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just added PR #1548 to update uPy to 1.14 - among other things it introduces utime.gmtime() function which is exactly what we need here I suppose.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fwiw #741 is not opposed to adding utime, but adding full python's datetime. At that time there was probably no date processing in utime, but now there is.

# filter date only, erase time
date = approx_datetime[:3] + (0, 0, 0) + approx_datetime[6:]
# get timestamp corresponding to time 0:00:00
first_daily_timestamp = utime.mktime(date)
# calculate correction of the midnight timestamp to the origiral
correction = timestamp - first_daily_timestamp
Comment on lines +93 to +95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "correction" can be calculated more simply by taking correction = timestamp % 86400.


return date, correction


def format_timestamp_to_human(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'
"""
date, correction = _calculate_timestamp_correction(timestamp)
# negative correction = substract 12 hours and calculate again
if correction < 0:
date, correction = _calculate_timestamp_correction(timestamp - 43_200)
correction += 43_200
andrewkozlik marked this conversation as resolved.
Show resolved Hide resolved
# create precise datetime by adding the correction datetime to date
correction_tuple = utime.localtime(correction)
precise_datetime = date[:3] + correction_tuple[3:6]

return "{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(
precise_datetime[0], # year
precise_datetime[1], # month
precise_datetime[2], # mday
precise_datetime[3], # hour
precise_datetime[4], # minute
precise_datetime[5], # second
)
95 changes: 95 additions & 0 deletions core/tests/test_trezor.strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,101 @@ def test_format_duration_ms(self):
for v in VECTORS:
self.assertEqual(strings.format_duration_ms(v[0]), v[1])

def test_format_timestamp_to_human(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_to_human(v[0]), v[1])

strings.format_timestamp_to_human(1616057224)


if __name__ == '__main__':
unittest.main()
26 changes: 26 additions & 0 deletions tests/device_tests/test_msg_signtx.py
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,28 @@ def test_lock_time(self, client, lock_time, sequence):
script_type=messages.OutputScriptType.PAYTOADDRESS,
)

def input_flow():
yield # show confirm sending
lines1 = client.debug.wait_layout().lines
assert lines1[0] == "Confirm sending"

client.debug.press_yes()
yield # show confirm locktime
lines2 = client.debug.wait_layout().lines
assert lines2[0] == "Confirm locktime"
assert lines2[1] == "Locktime for this"
assert lines2[2] == "transaction is set to"
assert lines2[3] == "date & time:"
assert lines2[5] == "1985-11-05 00:53:20"
assert lines2[7] == "Continue?"

client.debug.press_yes()
yield # show confirm transaction
lines3 = client.debug.wait_layout().lines
assert lines3[0] == "Confirm transaction"

client.debug.press_yes()

with client:
client.set_expected_responses(
[
Expand All @@ -1380,6 +1402,10 @@ def test_lock_time(self, client, lock_time, sequence):
request_finished(),
]
)
# assertion with input flow only for date & time lock
if lock_time == 500000000:
client.watch_layout()
client.set_input_flow(input_flow)

btc.sign_tx(
client,
Expand Down
6 changes: 3 additions & 3 deletions tests/ui_tests/fixtures.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,9 +409,9 @@
"test_msg_signtx.py-test_incorrect_input_script_type[2]": "ff8306b910f6886638e30736acd025ff7f45dde3c6648de1f6c6922bc6f590c5",
"test_msg_signtx.py-test_incorrect_output_script_type[0]": "ff8306b910f6886638e30736acd025ff7f45dde3c6648de1f6c6922bc6f590c5",
"test_msg_signtx.py-test_incorrect_output_script_type[1]": "ff8306b910f6886638e30736acd025ff7f45dde3c6648de1f6c6922bc6f590c5",
"test_msg_signtx.py-test_lock_time[1-4294967295]": "da3d6b38ec9264e38f3547427eb748d7b94e0802eb2aee411e0f72bf97620280",
"test_msg_signtx.py-test_lock_time[499999999-4294967294]": "23a154e7b40680161bb099cfc6702d75909c222056867515647123573eef1716",
"test_msg_signtx.py-test_lock_time[500000000-4294967294]": "2ac61446c20785e45223a20ae90660905fedf20d2a383a65fcbc1edd5fe87ad1",
"test_msg_signtx.py-test_lock_time[1-4294967295]": "60a8cb7f3739b1bacaaf3a22ab7170325f3ecbea447197892bc519fe8f7f69f6",
"test_msg_signtx.py-test_lock_time[499999999-4294967294]": "129b136a4933dae401d9a6bba9923d8ec0c1d921cbd094b71dde8600beb01174",
"test_msg_signtx.py-test_lock_time[500000000-4294967294]": "9f5d7ca04a29744f2b3c39754b832c4637fe51eb813240450c4cb50b768a7e80",
"test_msg_signtx.py-test_lots_of_change": "0fc26d5d47b2fd07b3423c4092cdeb27c1af2a8095eed375dd1e8db10766135a",
"test_msg_signtx.py-test_lots_of_inputs": "7d02dd952be20b46005af23c8b46a95590529d8e838459e540eca0b058fc32c1",
"test_msg_signtx.py-test_lots_of_outputs": "f9d50f30dbdaeddf1f54e8bf76dce07fa9d40dcb7ef36a908e76123f2151501c",
Expand Down