MJ - A frequent question on the Tcl Chat is how to split the number of seconds in the number of days, minutes etc. The following procedure provides this and allows you to specifiy the divisors of the different units, making it very flexible. The largest unit should not have a divider, this indicates that this unit should not be split any further (see examples below) The lead and inter parameters indicate whether amounts that are 0 should be displayed.
For example the following {a x b y c z d} would translate to: A d is z c's which are y b's consisting of x a's.
Note that this only works for integer amounts and dividers.
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:
And now a bunch of splitters, mostly taken from Wikipedia and not tested thoroughly:
# 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