proc split_amount {amount dividers {lead 0} {inter 1}} { set result {} foreach {unit divider} $dividers { if {!$lead && $amount==0} break; if {$divider eq {}} { set result "$amount$unit $result" break } if {$inter || !($amount%$divider == 0)} { set result "[expr {$amount % $divider}]$unit $result" } set amount [expr {$amount/$divider}] } return $result }Example usage:
% split_amount [clock seconds] {s 60 m 60 h 24 d 365 y} 37y 238d 13h 2m 59s % split_amount [clock seconds] {{ seconds} 60 { minutes and} 60 { hours,} 24 { days,} 365 { years,}} 37 years, 238 days, 13 hours, 4 minutes and 7 seconds % split_amount 2234141 {{ gram} 1000 { kilogram and} 1000 { tonne,}} 2 tonne, 234 kilogram and 141 gram % split_amount 1200 {s 60 m 60 h 24 d 365 y} 20m 0s % split_amount 1200 {s 60 m 60 h 24 d 365 y} 1 1 0y 0d 0h 20m 0s % split_amount 1200 {s 60 m 60 h 24 d 365 y} 1 0 0y 20m % split_amount 1200 {s 60 m 60 h 24 d 365 y} 0 0 20m
MAKR 2007-08-31: Something similar, but less flexible:
proc timemsg {seconds} { if {$seconds < 0} { return -code error "seconds should be unsigned integer" } elseif {$seconds < 60} { set num $seconds set unit second } elseif {$seconds < 3600} { set num [expr {($seconds%3600)/60}] set unit minute } elseif {$seconds < 86400} { set num [expr {($seconds%86400)/3600}] set unit hour } else { set num [expr {int($seconds/86400)}] set unit day } if {$num > 1} { append unit "s" } return "$num $unit" } % timemsg 1 1 second % timemsg 125 2 minutes % timemsg [clock seconds] 13756 days
LEG 2015-0913:- A slightly different implementation of the first algorithm which is friendly to interp alias:
proc _split_amount_ {dividers t {_s {}} {_P {}}} { if {!$t} {return "0[lindex $dividers 1]"} foreach {_D _S _U} $dividers { lassign [list [expr {$t/$_D}] [expr {$t%$_D}]] $_U $_S if {[set $_S]} {set _P [append $_S $_s $_S [expr {[string length $_P]?" $_P":$_P}]]} if {![set t [set $_U]]} {return $_P} } return "$t$_s$_U $_P" }t is the amount you want to split, _s is an optional string between value and unit._P is a dirty trick to save one line of code for initializing a local variable - don't use _P.Some properties of the implementation:
- If one of the intermediate values is zero it is not included in the resulting string.
- No leading or trailing spaces are generated.
- If the amount is zero, the returned string is the '0' followed by the smallest unit.
- The dividers are specified in a small language with triplets which answer the question how much of a smaller unit yield the next one, think '60 s m' as: 60 seconds a minute.
# Elapsed time # interp alias {} elapsed_s {} _split_amount_ { 60 s m 60 m h 24 h d 7 d w 4 w mon 12 mon y } interp alias {} elapsed_ms {} _split_amount_ { 1000 ms s 60 s m 60 m h 24 h d 7 d w 4 w mon 12 mon y } interp alias {} elapsed_s_months_only {} _split_amount_ { 60 s m 60 m h 24 h d 30 d mon 12 mon y } # this is the same splitting as in the first example from MJ interp alias {} elapsed_s_years_only {} _split_amount_ { 60 s m 60 m h 24 h d 365 d y } # Imperial lengths # interp alias {} split_inch {} _split_amount_ { 12 in ft 3 ft yrd 1760 yrd mi } interp alias {} split_mil {} _split_amount_ { 1000 mil in 12 in ft 3 ft yrd 1760 yrd mi } # call as: split_1/32in $amount " " # or: split_1/32in [expr {$amount_float_inch/32.}] " " interp alias {} split_1/32in {} _split_amount_ { 2 1/32in 1/16in 2 1/16in 1/8in 2 1/8in 1/4in 2 1/4in 1/2in 2 1/2in in 12 in ft 3 ft yrd 1760 yrd mi } # Imperial gallon interp alias {} split_ounces {} _split_amount_ { 20 "imp fl oz" pint 2 pint quart 4 quart "imp gal" } # US liquid gallon interp alias {} split_ounces {} _split_amount_ { 16 "fl oz" pint 2 pint quart 4 quart "imp gal" } # Data amounts # interp alias {} split_binary_JEDEC {} _split_amount_ { 1024 B KB 1024 KB MB 1024 GB MB 1024 GB TiB 1024 TiB PiB 1024 PiB EiB 1024 EiB ZiB 1024 ZiB YiB } interp alias {} split_binary_IEC {} _split_amount_ { 1024 B KiB 1024 KiB MiB 1024 GiB MiB 1024 GiB TiB 1024 TiB PiB 1024 PiB EiB 1024 EiB ZiB 1024 ZiB YiB } # call as: split_bibyte $amount " " interp alias {} split_bibyte {} _split_amount_ { 1024 byte kibibyte 1024 kibibyte mebibyte 1024 mebibyte gibibyte 1024 gibibyte tebibyte 1024 tebibyte pebibyte 1024 pebibyte exibyte 1024 exibyte zebibyte 1024 zebibyte yobibyte } interp alias {} split_byte_decimal {} _split_amount_ { 1000 B kB 1000 KB MB 1000 GB MB 1000 GB TB 1000 TB PB 1000 PB EB 1000 EB ZB 1000 ZB YB } # Degrees (angles) # interp alias {} split_arcseconds {} _split_amount_ { 60 \" ' 60 ' ° } #"# # Quantities as dozens # interp alias {} split_dozens {} _split_amount_ { 12 pcs dz 12 dz gross 12 gross "great gross" }Example usage:
% split_arcseconds 34000 9° 26' 40" % split_bibyte [expr {1024*1024*1024+4*1024}] " " 1 gibibyte 4 kibibyte