@@ -233,6 +233,243 @@ pub fn format_u64_with_suffix(
233
233
Ok ( total_len)
234
234
}
235
235
236
+ // Format duration in seconds to human-readable format with all units
237
+ #[ inline( never) ]
238
+ pub fn format_duration ( seconds : u64 , out : & mut [ u8 ] ) -> Result < usize , ParserError > {
239
+ const MINUTE : u64 = 60 ;
240
+ const HOUR : u64 = 60 * MINUTE ;
241
+ const DAY : u64 = 24 * HOUR ;
242
+ const YEAR : u64 = 365 * DAY ;
243
+
244
+ let mut remaining = seconds;
245
+ let mut write_pos = 0 ;
246
+ let mut first = true ;
247
+
248
+ // Helper function to add a unit to the output
249
+ fn add_unit (
250
+ value : u64 ,
251
+ singular : & [ u8 ] ,
252
+ plural : & [ u8 ] ,
253
+ out : & mut [ u8 ] ,
254
+ write_pos : & mut usize ,
255
+ first : & mut bool ,
256
+ ) -> Result < ( ) , ParserError > {
257
+ if value == 0 {
258
+ return Ok ( ( ) ) ;
259
+ }
260
+
261
+ // Add space if not first
262
+ if !* first && * write_pos < out. len ( ) {
263
+ out[ * write_pos] = b' ' ;
264
+ * write_pos += 1 ;
265
+ }
266
+
267
+ // Write the number
268
+ let len = u64_to_str ( value, & mut out[ * write_pos..] ) ?;
269
+ * write_pos += len;
270
+
271
+ // Add space before unit
272
+ if * write_pos < out. len ( ) {
273
+ out[ * write_pos] = b' ' ;
274
+ * write_pos += 1 ;
275
+ }
276
+
277
+ // Add the unit (singular or plural)
278
+ let unit_text = if value == 1 { singular } else { plural } ;
279
+ let unit_len = unit_text. len ( ) ;
280
+
281
+ if * write_pos + unit_len > out. len ( ) {
282
+ return Err ( ParserError :: UnexpectedBufferEnd ) ;
283
+ }
284
+
285
+ out[ * write_pos..* write_pos + unit_len] . copy_from_slice ( unit_text) ;
286
+ * write_pos += unit_len;
287
+
288
+ * first = false ;
289
+ Ok ( ( ) )
290
+ }
291
+
292
+ // Years
293
+ if remaining >= YEAR {
294
+ let years = remaining / YEAR ;
295
+ remaining %= YEAR ;
296
+ add_unit ( years, b"year" , b"years" , out, & mut write_pos, & mut first) ?;
297
+ }
298
+
299
+ // Days
300
+ if remaining >= DAY {
301
+ let days = remaining / DAY ;
302
+ remaining %= DAY ;
303
+ add_unit ( days, b"day" , b"days" , out, & mut write_pos, & mut first) ?;
304
+ }
305
+
306
+ // Hours
307
+ if remaining >= HOUR {
308
+ let hours = remaining / HOUR ;
309
+ remaining %= HOUR ;
310
+ add_unit ( hours, b"hour" , b"hours" , out, & mut write_pos, & mut first) ?;
311
+ }
312
+
313
+ // Minutes
314
+ if remaining >= MINUTE {
315
+ let minutes = remaining / MINUTE ;
316
+ remaining %= MINUTE ;
317
+ add_unit ( minutes, b"minute" , b"minutes" , out, & mut write_pos, & mut first) ?;
318
+ }
319
+
320
+ // Seconds (always show if there are any remaining, or if nothing else was shown)
321
+ if remaining > 0 || first {
322
+ add_unit ( remaining, b"second" , b"seconds" , out, & mut write_pos, & mut first) ?;
323
+ }
324
+
325
+ Ok ( write_pos)
326
+ }
327
+
328
+ // Format a Unix timestamp (seconds since epoch) into a human-readable date and time
329
+ #[ inline( never) ]
330
+ pub fn format_timestamp ( timestamp : u64 , out : & mut [ u8 ] ) -> Result < usize , ParserError > {
331
+ // Days per month (non-leap year)
332
+ const DAYS_PER_MONTH : [ u8 ; 12 ] = [ 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ] ;
333
+ const SECONDS_PER_DAY : u64 = 86400 ;
334
+ const SECONDS_PER_HOUR : u64 = 3600 ;
335
+ const SECONDS_PER_MINUTE : u64 = 60 ;
336
+ const DAYS_PER_YEAR : u64 = 365 ;
337
+ const DAYS_PER_LEAP_CYCLE : u64 = 365 * 4 + 1 ; // 4 years including one leap year
338
+
339
+ // Calculate total days since epoch and remaining seconds
340
+ let total_days = timestamp / SECONDS_PER_DAY ;
341
+ let remaining_seconds = timestamp % SECONDS_PER_DAY ;
342
+
343
+ // Calculate time components
344
+ let hours = remaining_seconds / SECONDS_PER_HOUR ;
345
+ let minutes = ( remaining_seconds % SECONDS_PER_HOUR ) / SECONDS_PER_MINUTE ;
346
+ let seconds = remaining_seconds % SECONDS_PER_MINUTE ;
347
+
348
+ // Start from 1970
349
+ let mut year = 1970u64 ;
350
+ let mut days = total_days;
351
+
352
+ // Calculate years using 4-year cycles (more efficient)
353
+ let leap_cycles = days / DAYS_PER_LEAP_CYCLE ;
354
+ year += leap_cycles * 4 ;
355
+ days %= DAYS_PER_LEAP_CYCLE ;
356
+
357
+ // Handle remaining years
358
+ while days >= DAYS_PER_YEAR {
359
+ if is_leap_year ( year) && days >= 366 {
360
+ days -= 366 ;
361
+ year += 1 ;
362
+ } else if !is_leap_year ( year) && days >= 365 {
363
+ days -= 365 ;
364
+ year += 1 ;
365
+ } else {
366
+ break ;
367
+ }
368
+ }
369
+
370
+ // Calculate month and day
371
+ let mut month = 1u8 ;
372
+ let mut day = days as u8 + 1 ; // days are 1-indexed
373
+
374
+ for i in 0 ..12 {
375
+ let mut days_in_month = DAYS_PER_MONTH [ i] ;
376
+ if i == 1 && is_leap_year ( year) {
377
+ days_in_month = 29 ;
378
+ }
379
+
380
+ if day <= days_in_month {
381
+ month = ( i + 1 ) as u8 ;
382
+ break ;
383
+ }
384
+ day -= days_in_month;
385
+ }
386
+
387
+ // Format as YYYY-MM-DD HH:MM:SS
388
+ let mut write_pos = 0 ;
389
+
390
+ // Year
391
+ let len = u64_to_str ( year, & mut out[ write_pos..] ) ?;
392
+ write_pos += len;
393
+
394
+ // Dash
395
+ if write_pos < out. len ( ) {
396
+ out[ write_pos] = b'-' ;
397
+ write_pos += 1 ;
398
+ }
399
+
400
+ // Month (with leading zero if needed)
401
+ if month < 10 && write_pos < out. len ( ) {
402
+ out[ write_pos] = b'0' ;
403
+ write_pos += 1 ;
404
+ }
405
+ let len = u64_to_str ( month as u64 , & mut out[ write_pos..] ) ?;
406
+ write_pos += len;
407
+
408
+ // Dash
409
+ if write_pos < out. len ( ) {
410
+ out[ write_pos] = b'-' ;
411
+ write_pos += 1 ;
412
+ }
413
+
414
+ // Day (with leading zero if needed)
415
+ if day < 10 && write_pos < out. len ( ) {
416
+ out[ write_pos] = b'0' ;
417
+ write_pos += 1 ;
418
+ }
419
+ let len = u64_to_str ( day as u64 , & mut out[ write_pos..] ) ?;
420
+ write_pos += len;
421
+
422
+ // Space
423
+ if write_pos < out. len ( ) {
424
+ out[ write_pos] = b' ' ;
425
+ write_pos += 1 ;
426
+ }
427
+
428
+ // Hours (with leading zero if needed)
429
+ if hours < 10 && write_pos < out. len ( ) {
430
+ out[ write_pos] = b'0' ;
431
+ write_pos += 1 ;
432
+ }
433
+ let len = u64_to_str ( hours, & mut out[ write_pos..] ) ?;
434
+ write_pos += len;
435
+
436
+ // Colon
437
+ if write_pos < out. len ( ) {
438
+ out[ write_pos] = b':' ;
439
+ write_pos += 1 ;
440
+ }
441
+
442
+ // Minutes (with leading zero if needed)
443
+ if minutes < 10 && write_pos < out. len ( ) {
444
+ out[ write_pos] = b'0' ;
445
+ write_pos += 1 ;
446
+ }
447
+ let len = u64_to_str ( minutes, & mut out[ write_pos..] ) ?;
448
+ write_pos += len;
449
+
450
+ // Colon
451
+ if write_pos < out. len ( ) {
452
+ out[ write_pos] = b':' ;
453
+ write_pos += 1 ;
454
+ }
455
+
456
+ // Seconds (with leading zero if needed)
457
+ if seconds < 10 && write_pos < out. len ( ) {
458
+ out[ write_pos] = b'0' ;
459
+ write_pos += 1 ;
460
+ }
461
+ let len = u64_to_str ( seconds, & mut out[ write_pos..] ) ?;
462
+ write_pos += len;
463
+
464
+ Ok ( write_pos)
465
+ }
466
+
467
+ // Helper function to determine if a year is a leap year
468
+ #[ inline( always) ]
469
+ fn is_leap_year ( year : u64 ) -> bool {
470
+ ( year % 4 == 0 && year % 100 != 0 ) || ( year % 400 == 0 )
471
+ }
472
+
236
473
// Format a u64 value with decimal places and optional symbol
237
474
#[ inline( never) ]
238
475
pub fn format_token_amount (
0 commit comments