Version 13 of Formatting durations

Updated 2004-06-20 23:04:16

The following proc converts an integer number of seconds to culture-dependent units (days, hrs, mins, secs) specification. From a news:comp.lang.tcl posting by David Gravereaux:

Here's a duration proc I wrote that should give you a better sense of what I think you're asking 'clock format' for:

 % proc duration { int_time } {
     set timeList [list]
     foreach div {86400 3600 60 1} mod {0 24 60 60} name {day hr min sec} {
         set n [expr {$int_time / $div}]
         if {$mod > 0} {set n [expr {$n % $mod}]}
         if {$n > 1} {
             lappend timeList "$n ${name}s"
         } elseif {$n == 1} {
             lappend timeList "$n $name"
         }
     }
     return [join $timeList]
 }
 % duration 67
 1 min 7 secs

Updated by DKF


For a version of the same that handles floats and avoids OCTAL, see:

 http://inferno.slug.org/wiki/Duration

Culture-independent hh:mm:ss notation by RS:

 proc hh:mm:ss {secs} {
        set h [expr {$secs/3600}]
        incr secs [expr {$h*-3600}]
        set m [expr {$secs/60}]
        set s [expr {$secs%60}]
        format "%02.2d:%02.2d:%02.2d" $h $m $s
 }
 hh:mm:ss 67
 00:01:07

Oops, forget it. Tcl has it built in (if you force GMT):

 clock format 67 -gmt 1 -format %H:%M:%S   ;#RS
 00:01:07

Bah! That chokes on Octal and fractional seconds, Richard!! - RS: I'm not sure whether we're talking about the same - the original question was to format an integer number of seconds into hour/min/sec format. Fractionals are excluded by the "integer", but octals just work as always:

 % clock format 012345 -gmt 1 -format %H:%M:%S   
 01:29:09

You can redeem yourself by making spamMap work, however...

Or octals don't work as always, depending on your perspective... I suppose we are talking about two different things; I added support for decimal seconds and stripping the leading 0 from octals because my user base required it.


KBK All of the above have problems with dealing with units of variable length. Consider the problem of "what's the day one month after 30 January," or even "what's the time at one day after noon on the day before the clocks go to Daylight Saving Time (Sommerzeit)." For that sort of problem, you need to compute the years, months, days, etc. between two dates.


NEM A little toy proc I knocked up today - gives you the time since a given point in a vague way:

 proc howlongago {time} {
     # Returns the difference between $time and now in vague terms
     set diff [expr {[clock seconds] - $time}]
     # What units are we dealing with (don't care about leap years -
     # we're being vague, after all :)
     foreach {div unit} {
         "60*60*24*365"        year
         "60*60*24*30"         month
         "60*60*24*7"          week
         "60*60*24"            day
         "60*60"               hour
         "60"                  minute
         "1"                   second
      } {
          if {[set num [expr $diff / ($div)]] > 0} {
              break
          }
      }
      if {$num != 1} { append unit "s" }
      if {$num == 0} { return "now" }
      if {$num == 1} { return "a $unit ago" }
      if {$num == 2} { return "a couple of $unit ago" }
      if {$num > 2 && $num < 5} { return "a few $unit ago" }
      return "$num $unit ago"
 }

Some tests:

 tclsh8.4 [~/tclstuff] howlongago [clock seconds]
 now
 tclsh8.4 [~/tclstuff] howlongago [expr [clock seconds] - 3]
 a few seconds ago
 tclsh8.4 [~/tclstuff] howlongago [expr [clock seconds] - 20]
 20 seconds ago
 tclsh8.4 [~/tclstuff] howlongago [clock scan "1 January 2003"]
 a month ago
 tclsh8.4 [~/tclstuff] howlongago [clock scan "1 January 2002"]
 a year ago
 tclsh8.4 [~/tclstuff] howlongago [clock scan "1 November 2002"]
 a few months ago
 tclsh8.4 [~/tclstuff] howlongago [clock scan "1 December 2002"]
 a couple of months ago

Second Formatter - similar to clock format, but instead of a time it works on just a number of seconds. The codes for seconds/minutes/hours/days are %s, %m, %h and %d, with the upper-case of each left-padding with 0's to make it two characters long. - MG (moved from Bag of number/time spellers and given a default format, Jun 04) Apr 29th 2004 - improved to remove the un-needed 'pad' proc

 proc timefmt {secs {fmt "%H:%M:%S"} {

   if { ![string is integer -strict $secs] } {
        error "first argument must be integer"
      }

   set d [expr {$secs/86400}] ; set D [string range "0$d" end-1 end]
   set h [expr {($secs%86400)/3600}] ; set H [string range "0$h" end-1 end]
   set m [expr {(($secs%86400)%3600)/60}] ; set M [string range "0$m" end-1 end]
   set s [expr {(($secs%86400)%3600)%60}] ; set S [string range "0$s" end-2 end]

   set p "%% % %s $s %S $S %m $m %M $M %h $h %H $H %d $d %D $D"
   set str [string map $p $fmt]
   return $str;

 };# timefmt

 % timefmt 527 {%h:%M:%S}
 0:08:47
 % timefmt 527
 00:08:47
 % timefmt 999 {There are %d days, %h hours, %m minutes and %s seconds}
 There are 0 days, 0 hours, 16 minutes and 39 seconds