Skip to content
Merged
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
2 changes: 2 additions & 0 deletions electrum/gui/qml/components/History.qml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ Pane {
'mempool': qsTr('Mempool'),
'today': qsTr('Today'),
'yesterday': qsTr('Yesterday'),
'thisweek': qsTr('Earlier this week'),
'lastweek': qsTr('Last week'),
'thismonth': qsTr('Earlier this month'),
'lastmonth': qsTr('Last month'),
'older': qsTr('Older')
}
Expand Down
26 changes: 19 additions & 7 deletions electrum/gui/qml/qetransactionlistmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from typing import TYPE_CHECKING, Dict, Any

from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot
from PyQt6.QtCore import Qt, QAbstractListModel, QModelIndex
from PyQt6.QtCore import Qt, QAbstractListModel, QModelIndex, QLocale

from electrum.logging import get_logger
from electrum.util import Satoshis, TxMinedInfo
Expand Down Expand Up @@ -168,14 +168,24 @@ def tx_to_model(self, tx_item):
def get_section_by_timestamp(timestamp):
txts = datetime.fromtimestamp(timestamp)
today = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)

if txts > today:
first_day_of_week = QLocale.system().firstDayOfWeek()
# Qt.DayOfWeek and isoweekday() both count Monday=1 .. Sunday=7
this_week_start = today - timedelta(days=(today.isoweekday() - first_day_of_week.value) % 7)
this_month_start = today.replace(day=1)
last_month_start = (this_month_start - timedelta(days=1)).replace(day=1)

# calendar based, the most specific section wins (e.g. on mondays sunday is 'yesterday', not 'lastweek')
if txts >= today:
return 'today'
elif txts > today - timedelta(days=1):
elif txts >= today - timedelta(days=1):
return 'yesterday'
elif txts > today - timedelta(days=7):
elif txts >= this_week_start:
return 'thisweek'
elif txts >= this_week_start - timedelta(days=7):
return 'lastweek'
elif txts > today - timedelta(days=31):
elif txts >= this_month_start:
return 'thismonth'
elif txts >= last_month_start:
return 'lastmonth'
else:
return 'older'
Expand All @@ -186,7 +196,9 @@ def format_date_by_section(section: str, date: datetime):
dfmt = {
'today': '%H:%M',
'yesterday': '%H:%M',
'lastweek': '%a, %H:%M',
'thisweek': '%a, %H:%M',
'lastweek': '%a %d, %H:%M',
'thismonth': '%a %d, %H:%M',
'lastmonth': '%a %d, %H:%M',
'older': '%Y-%m-%d %H:%M'
}
Expand Down
61 changes: 51 additions & 10 deletions tests/qml/test_qml_qetransactionlistmodel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from datetime import datetime
from unittest.mock import patch

from PyQt6.QtCore import Qt

from electrum.gui.qml.qetransactionlistmodel import QETransactionListModel

from .. import ElectrumTestCase
Expand All @@ -11,41 +13,74 @@ class TestQETransactionListModel(ElectrumTestCase):
def test_get_section_by_timestamp(self):
f = QETransactionListModel.get_section_by_timestamp

mock_today = datetime(2023, 6, 15, 0, 0, 0, 0)
with patch('electrum.gui.qml.qetransactionlistmodel.datetime') as mock_dt:
mock_today = datetime(2023, 6, 15, 0, 0, 0, 0) # thursday
with patch('electrum.gui.qml.qetransactionlistmodel.datetime') as mock_dt, \
patch('electrum.gui.qml.qetransactionlistmodel.QLocale') as mock_locale:
mock_dt.today.return_value = mock_today
mock_dt.fromtimestamp = datetime.fromtimestamp
mock_first_day_of_week = mock_locale.system.return_value.firstDayOfWeek
mock_first_day_of_week.return_value = Qt.DayOfWeek.Monday

today_ts = datetime(2023, 6, 15, 10, 30, 0).timestamp()
self.assertEqual(f(today_ts), 'today')

today_edge_ts = datetime(2023, 6, 15, 0, 0, 1).timestamp()
today_edge_ts = datetime(2023, 6, 15, 0, 0, 0).timestamp()
self.assertEqual(f(today_edge_ts), 'today')

yesterday_ts = datetime(2023, 6, 14, 15, 0, 0).timestamp()
self.assertEqual(f(yesterday_ts), 'yesterday')

yesterday_edge_ts = datetime(2023, 6, 13, 23, 59, 59).timestamp()
self.assertEqual(f(yesterday_edge_ts), 'lastweek')
self.assertEqual(f(yesterday_edge_ts), 'thisweek')

thisweek_boundary_ts = datetime(2023, 6, 12, 0, 0, 0).timestamp()
self.assertEqual(f(thisweek_boundary_ts), 'thisweek')

lastweek_ts = datetime(2023, 6, 12, 12, 0, 0).timestamp()
lastweek_ts = datetime(2023, 6, 11, 23, 59, 59).timestamp()
self.assertEqual(f(lastweek_ts), 'lastweek')

lastweek_boundary_ts = datetime(2023, 6, 8, 12, 0, 0).timestamp()
lastweek_boundary_ts = datetime(2023, 6, 5, 0, 0, 0).timestamp()
self.assertEqual(f(lastweek_boundary_ts), 'lastweek')

lastmonth_ts = datetime(2023, 6, 5, 9, 0, 0).timestamp()
thismonth_ts = datetime(2023, 6, 4, 23, 59, 59).timestamp()
self.assertEqual(f(thismonth_ts), 'thismonth')

thismonth_boundary_ts = datetime(2023, 6, 1, 0, 0, 0).timestamp()
self.assertEqual(f(thismonth_boundary_ts), 'thismonth')

lastmonth_ts = datetime(2023, 5, 31, 23, 59, 59).timestamp()
self.assertEqual(f(lastmonth_ts), 'lastmonth')

lastmonth_boundary_ts = datetime(2023, 5, 15, 8, 0, 0).timestamp()
lastmonth_boundary_ts = datetime(2023, 5, 1, 0, 0, 0).timestamp()
self.assertEqual(f(lastmonth_boundary_ts), 'lastmonth')

older_ts = datetime(2023, 5, 14, 10, 0, 0).timestamp()
older_ts = datetime(2023, 4, 30, 23, 59, 59).timestamp()
self.assertEqual(f(older_ts), 'older')

much_older_ts = datetime(2022, 1, 1, 0, 0, 0).timestamp()
self.assertEqual(f(much_older_ts), 'older')

# the week boundaries follow the locale's first day of the week
sunday_ts = datetime(2023, 6, 11, 12, 0, 0).timestamp()
prev_sunday_ts = datetime(2023, 6, 4, 12, 0, 0).timestamp()
prev_saturday_ts = datetime(2023, 6, 3, 12, 0, 0).timestamp()
self.assertEqual(f(sunday_ts), 'lastweek')
self.assertEqual(f(prev_sunday_ts), 'thismonth')
mock_first_day_of_week.return_value = Qt.DayOfWeek.Sunday
self.assertEqual(f(sunday_ts), 'thisweek')
self.assertEqual(f(prev_sunday_ts), 'lastweek')
self.assertEqual(f(prev_saturday_ts), 'thismonth')
mock_first_day_of_week.return_value = Qt.DayOfWeek.Saturday
self.assertEqual(f(sunday_ts), 'thisweek')
self.assertEqual(f(prev_saturday_ts), 'lastweek')

# the more specific section wins where weeks and months overlap
mock_first_day_of_week.return_value = Qt.DayOfWeek.Monday
mock_dt.today.return_value = datetime(2023, 5, 2, 0, 0, 0, 0) # tuesday
self.assertEqual(f(datetime(2023, 5, 1, 12, 0, 0).timestamp()), 'yesterday')
self.assertEqual(f(datetime(2023, 4, 30, 12, 0, 0).timestamp()), 'lastweek')
self.assertEqual(f(datetime(2023, 4, 23, 12, 0, 0).timestamp()), 'lastmonth')

def test_format_date_by_section(self):
f = QETransactionListModel.format_date_by_section

Expand All @@ -57,9 +92,15 @@ def test_format_date_by_section(self):
result = f('yesterday', test_date)
self.assertEqual(result, '14:30')

result = f('lastweek', test_date)
result = f('thisweek', test_date)
self.assertEqual(result, 'Thu, 14:30')

result = f('lastweek', test_date)
self.assertEqual(result, 'Thu 15, 14:30')

result = f('thismonth', test_date)
self.assertEqual(result, 'Thu 15, 14:30')

result = f('lastmonth', test_date)
self.assertEqual(result, 'Thu 15, 14:30')

Expand Down