Say the date and the time, and sort by both

The listing gave the time and not the date, which is fine for an
evening's scanning and useless for a directory that has been filling
for a fortnight: two recordings a week apart looked like neighbours.

Every moment in the browser is now written YY-mm-dd hh:mm:ss am/pm --
date first so a column of them reads down in order, twelve-hour so it
reads the way it would be said. Midnight is 12 am and noon is 12 pm,
which is the pair a twelve-hour clock gets wrong when it is done by
subtraction, so both are tested.

The sort is named "date/time" rather than "time" and orders by the
whole moment, with the filename breaking a tie so that the list does
not shuffle itself between one reload and the next. --sort time still
works; it is the sort of thing that lives in a shell alias.

The date costs twelve columns, so the frequency column is now only as
wide as the widest frequency in the list rather than always wide
enough for 1090.000001 MHz. It is fixed for the whole list, not the
screenful, because a column that resizes as the list scrolls under it
makes the whole listing appear to twitch.

--list is left in ISO and twenty-four hours: it is there to be piped
into grep and sort, and those want the format that sorts as text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PsWPTweCT6pwxKngvVxcg
This commit is contained in:
The Dust Council 2026-08-30 00:53:58 -07:00
parent 3d7f76118e
commit 0f378c4d6c
7 changed files with 178 additions and 27 deletions

View file

@ -377,7 +377,7 @@ def test_the_cursor_stays_on_screen_in_a_long_list(tmp_path):
def test_sorting_cycles_and_reorders(library):
b = browser(library)
assert b.sort == "time"
assert b.sort == "date/time"
b.handle("s")
assert b.sort == "frequency"
assert [c.frequency for c in b.view] == sorted(c.frequency
@ -387,6 +387,85 @@ def test_sorting_cycles_and_reorders(library):
assert b.view[0].duration >= b.view[-1].duration
def test_the_listing_gives_the_date_as_well_as_the_time(library):
"""Without the date, two recordings a week apart look like neighbours."""
text = frame(browser(library))
assert re.search(r"\d\d-\d\d-\d\d \d\d:\d\d:\d\d [ap]m", text)
def test_the_clock_is_twelve_hour_with_midnight_and_noon_named(tmp_path):
"""The two hours a twelve-hour clock gets wrong if it is done by
subtraction: midnight is 12 am and noon is 12 pm, not 0 am and 0 pm."""
make_capture(tmp_path, 146.52, "2026-08-30_00_00_30", "nfm")
make_capture(tmp_path, 146.52, "2026-08-30_12_00_30", "nfm")
make_capture(tmp_path, 146.52, "2026-08-30_13_05_00", "nfm")
text = frame(browser(tmp_path))
assert "26-08-30 12:00:30 am" in text
assert "26-08-30 12:00:30 pm" in text
assert "26-08-30 01:05:00 pm" in text
def test_sorting_by_date_time_is_chronological_across_every_boundary(tmp_path):
"""Year, month, day, then hour, minute, second. The order below is the
one a clock would put them in; the browser has to agree with it whichever
way the filenames happen to sort as text."""
moments = ["2025-12-31_23_59_59", "2026-01-01_00_00_01",
"2026-01-01_00_00_02", "2026-01-31_09_00_00",
"2026-02-01_08_00_00", "2026-08-30_11_59_59",
"2026-08-30_12_00_00", "2026-08-30_13_00_00"]
for i, when in enumerate(moments):
make_capture(tmp_path, 146.52 + i * 0.01, when, "nfm")
b = browser(tmp_path)
assert [c.when.strftime("%Y-%m-%d_%H_%M_%S") for c in b.view] == \
list(reversed(moments))
def test_two_signals_in_the_same_second_keep_a_settled_order(tmp_path):
"""A tie is broken by the filename so that the list does not shuffle
itself between one reload and the next."""
make_capture(tmp_path, 146.52, "2026-08-30_10_00_00", "nfm")
make_capture(tmp_path, 145.00, "2026-08-30_10_00_00", "nfm")
first = [c.path.name for c in browser(tmp_path).view]
assert first == sorted(first)
def test_the_sort_key_is_called_date_time(library):
b = browser(library, width=140)
assert b.sort == "date/time"
assert "date/time" in frame(b)
def test_the_old_name_for_the_time_sort_still_works(library, monkeypatch):
"""--sort time is the sort of thing that lives in a shell alias."""
from bandsaunter import browse
seen = {}
def fake_loop(self):
seen["sort"] = self.sort
return 0
monkeypatch.setattr(browse.Browser, "run", fake_loop, raising=False)
monkeypatch.setattr(browse.Console, "is_terminal", property(lambda s: True))
assert main(["--sort", "time", str(library)]) == 0
assert seen["sort"] == "date/time"
def test_the_frequency_column_does_not_change_width_while_scrolling(tmp_path):
"""One wide frequency in the list fixes the column for the whole list, so
that scrolling past it does not make everything else jump sideways."""
make_capture(tmp_path, 1090.000001, "2026-08-30_10_00_00", "nfm")
for i in range(40):
make_capture(tmp_path, 146.52, f"2026-08-30_11_{i // 60:02d}_{i % 60:02d}",
"nfm")
b = browser(tmp_path, height=24)
frame(b)
was = b.freq_width
b.handle("end")
frame(b)
assert b.freq_width == was
def test_quitting_stops_the_loop(library):
assert browser(library).handle("q") is False