fix(news): order news by instant, not timestamp string

String order put 10:00:00Z before the newer 10:00:00.500Z and mixed
time-zone offsets; with a small --limit the newest item could drop out.
Compare parsed RFC 3339 instants; missing or unparseable dates sort last.
From a Codex review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
hermes 2026-10-16 08:31:11 +00:00
commit 8c9c93339f

View file

@ -604,11 +604,13 @@ pub(super) fn parse_news(
}
})
.collect();
// ISO 8601 UTC timestamps sort as strings; missing dates sort last.
// Compare instants, not strings (fractional seconds and offsets vary);
// missing or unparseable dates sort last.
let instant = |item: &NewsItem| DateTime::parse_from_rfc3339(&item.published_at).ok();
items.sort_by(|a, b| {
a.relevance
.cmp(&b.relevance)
.then_with(|| b.published_at.cmp(&a.published_at))
.then_with(|| instant(b).cmp(&instant(a)))
});
items.truncate(limit);
Ok(items)
@ -945,6 +947,24 @@ mod tests {
assert_eq!(limited[1].id, "tagged");
}
#[test]
fn parse_news_orders_by_instant_with_undated_items_last() {
let raw = news_feed(serde_json::json!([
{"id": "undated", "title": "No date"},
{"id": "whole", "title": "A", "publishedDateTime": "2026-10-16T10:00:00Z"},
{"id": "fraction", "title": "B", "publishedDateTime": "2026-10-16T10:00:00.500Z"},
{"id": "offset", "title": "C", "publishedDateTime": "2026-10-16T16:30:00+07:00"},
{"id": "garbage", "title": "D", "publishedDateTime": "yesterday"}
]));
let items = parse_news("BBCA.JK", "bn91jc", &[], &raw, 10).expect("news");
let order: Vec<_> = items.iter().map(|i| i.id.as_str()).collect();
// 16:30+07:00 is 09:30Z, the oldest dated item.
assert_eq!(&order[..3], ["fraction", "whole", "offset"]);
assert!(order[3..].contains(&"undated") && order[3..].contains(&"garbage"));
}
#[test]
fn ticker_match_requires_a_whole_word() {
let raw = news_feed(serde_json::json!([