@@ -63,6 +63,7 @@ struct HNItem {
6363 url : Option < String > ,
6464 by : Option < String > ,
6565 score : Option < i64 > ,
66+ time : Option < u64 > ,
6667 descendants : Option < u64 > ,
6768 kids : Option < Vec < u64 > > ,
6869}
@@ -190,6 +191,9 @@ fn format_hn_response(item: &HNItem, comments: &[(HNItem, Vec<HNItem>)]) -> Stri
190191 if let Some ( score) = item. score {
191192 out. push_str ( & format ! ( "- **Score:** {}\n " , score) ) ;
192193 }
194+ if let Some ( time) = item. time {
195+ out. push_str ( & format ! ( "- **Time:** {}\n " , format_unix_timestamp( time) ) ) ;
196+ }
193197 if let Some ( descendants) = item. descendants {
194198 out. push_str ( & format ! ( "- **Comments:** {}\n " , descendants) ) ;
195199 }
@@ -228,6 +232,62 @@ fn format_hn_response(item: &HNItem, comments: &[(HNItem, Vec<HNItem>)]) -> Stri
228232 out
229233}
230234
235+ /// Format a Unix timestamp as an ISO 8601 UTC date-time string
236+ fn format_unix_timestamp ( ts : u64 ) -> String {
237+ let secs = ts % 60 ;
238+ let mins = ( ts / 60 ) % 60 ;
239+ let hours = ( ts / 3600 ) % 24 ;
240+
241+ // Days since epoch
242+ let mut days = ( ts / 86400 ) as i64 ;
243+
244+ // Calculate year/month/day from days since epoch
245+ let mut year = 1970i64 ;
246+ loop {
247+ let days_in_year = if is_leap_year ( year) { 366 } else { 365 } ;
248+ if days < days_in_year {
249+ break ;
250+ }
251+ days -= days_in_year;
252+ year += 1 ;
253+ }
254+
255+ let leap = is_leap_year ( year) ;
256+ let days_in_months: [ i64 ; 12 ] = [
257+ 31 ,
258+ if leap { 29 } else { 28 } ,
259+ 31 ,
260+ 30 ,
261+ 31 ,
262+ 30 ,
263+ 31 ,
264+ 31 ,
265+ 30 ,
266+ 31 ,
267+ 30 ,
268+ 31 ,
269+ ] ;
270+
271+ let mut month = 0 ;
272+ for ( i, & dim) in days_in_months. iter ( ) . enumerate ( ) {
273+ if days < dim {
274+ month = i + 1 ;
275+ break ;
276+ }
277+ days -= dim;
278+ }
279+ let day = days + 1 ;
280+
281+ format ! (
282+ "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z" ,
283+ year, month, day, hours, mins, secs
284+ )
285+ }
286+
287+ fn is_leap_year ( year : i64 ) -> bool {
288+ ( year % 4 == 0 && year % 100 != 0 ) || year % 400 == 0
289+ }
290+
231291fn format_comment ( out : & mut String , comment : & HNItem , depth : usize ) {
232292 let indent = "> " . repeat ( depth) ;
233293 let by = comment. by . as_deref ( ) . unwrap_or ( "anonymous" ) ;
@@ -333,6 +393,7 @@ mod tests {
333393 url : Some ( "https://example.com" . to_string ( ) ) ,
334394 by : Some ( "pg" . to_string ( ) ) ,
335395 score : Some ( 100 ) ,
396+ time : Some ( 1704067200 ) , // 2024-01-01T00:00:00Z
336397 descendants : Some ( 5 ) ,
337398 kids : None ,
338399 } ;
@@ -342,6 +403,67 @@ mod tests {
342403 assert ! ( output. contains( "# Show HN: My Project" ) ) ;
343404 assert ! ( output. contains( "**By:** pg" ) ) ;
344405 assert ! ( output. contains( "**Score:** 100" ) ) ;
406+ assert ! ( output. contains( "**Time:** 2024-01-01T00:00:00Z" ) ) ;
345407 assert ! ( output. contains( "https://example.com" ) ) ;
346408 }
409+
410+ #[ test]
411+ fn test_format_hn_response_with_comments ( ) {
412+ let item = HNItem {
413+ id : 42 ,
414+ item_type : Some ( "story" . to_string ( ) ) ,
415+ title : Some ( "Test Story" . to_string ( ) ) ,
416+ text : None ,
417+ url : None ,
418+ by : Some ( "user1" . to_string ( ) ) ,
419+ score : Some ( 50 ) ,
420+ time : None ,
421+ descendants : Some ( 2 ) ,
422+ kids : None ,
423+ } ;
424+
425+ let comment = HNItem {
426+ id : 43 ,
427+ item_type : Some ( "comment" . to_string ( ) ) ,
428+ title : None ,
429+ text : Some ( "Great post!" . to_string ( ) ) ,
430+ url : None ,
431+ by : Some ( "user2" . to_string ( ) ) ,
432+ score : None ,
433+ time : None ,
434+ descendants : None ,
435+ kids : None ,
436+ } ;
437+
438+ let output = format_hn_response ( & item, & [ ( comment, vec ! [ ] ) ] ) ;
439+ assert ! ( output. contains( "## Comments" ) ) ;
440+ assert ! ( output. contains( "**user2**" ) ) ;
441+ assert ! ( output. contains( "Great post!" ) ) ;
442+ }
443+
444+ #[ test]
445+ fn test_format_hn_response_ask_hn ( ) {
446+ let item = HNItem {
447+ id : 100 ,
448+ item_type : Some ( "story" . to_string ( ) ) ,
449+ title : Some ( "Ask HN: Best Rust crates?" . to_string ( ) ) ,
450+ text : Some ( "<p>Looking for recommendations.</p>" . to_string ( ) ) ,
451+ url : None ,
452+ by : Some ( "asker" . to_string ( ) ) ,
453+ score : Some ( 25 ) ,
454+ time : None ,
455+ descendants : Some ( 0 ) ,
456+ kids : None ,
457+ } ;
458+
459+ let output = format_hn_response ( & item, & [ ] ) ;
460+ assert ! ( output. contains( "Ask HN: Best Rust crates?" ) ) ;
461+ assert ! ( output. contains( "Looking for recommendations." ) ) ;
462+ }
463+
464+ #[ test]
465+ fn test_format_unix_timestamp ( ) {
466+ assert_eq ! ( format_unix_timestamp( 0 ) , "1970-01-01T00:00:00Z" ) ;
467+ assert_eq ! ( format_unix_timestamp( 1704067200 ) , "2024-01-01T00:00:00Z" ) ;
468+ }
347469}
0 commit comments