- My ultimate goal is to have a simple GUI that allows someone to define, per instance of the app, a deadline (date/time) and then show the years, months, days, hours and minutes until that deadline. Each instance of the app would save off its deadline date and title, so that one could start a series of these things off when you log in and you would see the countdowns.
- Short months: what is "one month from now" on 30 January?
- Leap years: what is "one year from now" on 29 February?
- Daylight Saving Time rollover: what is "one day from now" on the day before the clocks change? Is it the same as "24 hours from now", or different?
- Leap seconds. What's "60 seconds from now" when it's less than a minute to the next leap second?
2002-01-31 01:23:45with
2002-02-28 12:34:56.The result is
0 years 1 months 0 days 11 hours 11 minutes 11 secondsThe reason is that 2002-01-31 plus one month is 2002-02-28; adding the remaining offsets gives the right answer. It would be more natural to report this particular case as
0 years 0 months 28 days 11 hours 11 minutes 11 secondsbut I fail to see an easy way to come up with a general rule that never generates results that look anomalous.tekbasse writes: Results may differ between counting forward or backward. Code with checks, auditing and related sample output for backward and forward calculations are at Measuring time intervals (between two timestamps) with months etc.Code herewith:
proc clockarith { seconds delta units } { set stamp [clock format $seconds -format "%Y%m%dT%H%M%S"] if { $delta < 0 } { append stamp " " - [expr { - $delta }] " " $units } else { append stamp "+ " $delta " " $units } return [clock scan $stamp] } proc difftimes { s1 s2 } { # Arithmetic has to be done left to right here! # Calculate the offset of years. set y1 [clock format $s1 -format %Y] set y2 [clock format $s2 -format %Y] set y [expr { $y1 - $y2 - 1 }] set s2new $s2 set yOut $y set s [clockarith $s2 $y years] while { $s <= $s1 } { set s2new $s set yOut $y incr y set s [clockarith $s2 $y years] } set s2 $s2new # OK, now we know that s2 <= s1. It's easiest to do months # just by counting from 0. set m 0 set mOut 0 set s [clockarith $s2 $m months] while { $s <= $s1 } { set s2new $s set mOut $m incr m set s [clockarith $s2 $m months] } set s2 $s2new # s2 is still <= s1, now do days. set d [expr { ( ( $s2 - $s1 ) / 86400 ) - 1 }] set dOut $d set s [clockarith $s2 $d days] while { $s <= $s1 } { set s2new $s set dOut $d incr d set s [clockarith $s2 $d days] } set s2 $s2new # Hours set hh [expr { ( ( $s2 - $s1 ) / 3600 ) - 1 }] set hhOut $hh set s [clockarith $s2 $hh hours] while { $s <= $s1 } { set s2new $s set hhOut $hh incr hh set s [clockarith $s2 $hh hours] } set s2 $s2new # Minutes set mm [expr { ( ( $s2 - $s1 ) / 60 ) - 1 }] set mmOut $hh set s [clockarith $s2 $mm minutes] while { $s <= $s1 } { set s2new $s set mmOut $mm incr mm set s [clockarith $s2 $mm minutes] } set s2 $s2new # Seconds set ssOut [expr { $s1 - $s2 }] return [list $yOut $mOut $dOut $hhOut $mmOut $ssOut] } set t2 [clock scan "20020131T012345"] set t1 [clock scan "20020228T123456"] foreach f {years months days hours minutes seconds} \ v [difftimes $t2 $t1] { puts "$v $f" }
LV difftimes arguments are position dependant. That is to say, even though t1 and t2 in the example above are full dates - if you reverse the values you get a different answer. One might normally think that the difference between Jan 31, 2002 and Feb 28, 2002 would be the same as the difference between Feb 28, 2002 and Jan 31, 2002 ...
MPJ ~ Using the above code here is a little procedure that gives you a formatted output of delta to the event. It also excludes any item that has a zero.
proc howlong {time} { if {[set now [clock scan now]] > $time} {set post ago} {set post until} foreach t [difftimes [clock scan now] $time] \ u {year month day hour minute second} { if {$t == 1} { append out "$t $u " } elseif {$t > 1} { append out "$t ${u}s " } } if {[info exist out]} {return "$out$post"} return "now" }Example:
% howlong [clock scan now] now % howlong [clock scan 10/11/1991] 13 years 11 hours 10 minutes 19 seconds ago % howlong [clock scan yesterday] 1 day ago % howlong [clock scan "75 minutes ago"] 1 hour 15 minutes ago % howlong [clock scan 1/23/2007] 8 months 18 days 11 hours 25 minutes 28 seconds until
Silas - 2006-11-12 - The following procedure defines years elapsed between two dates (timestamps):
# yearsBetween -- # # Returns the years elapsed between two given timestamps # # Arguments: # oldertime # newertime proc yearsBetween {oldertime newertime} { set i 1 while {$oldertime < [clock scan "$i year ago" -base $newertime]} { incr i } return [expr {$i - 1}] }# Example:
set date1 [clock scan 1987-01-18] set date2 [clock seconds] puts [yearsBetween $date1 $date2]The above one is better because it uses [clock scan] calculations instead of a predefined user calculation. So, the following is less precise.
set date1 [clock scan 1987-01-18] set date2 [clock seconds] set elapsedDays [expr {($date2 - $date1) / (60 * 60 * 24)}] set elapsedYears [expr {$elapsedDays / 365.25}] puts $elapsedYears---willdye 2007-11-05. Here's a strange problem. I took the 'clockarith' and 'difftimes' code above, and put it into a file. Then at the end of the file, I added these lines:
puts "(i'm in the U.S. CST, which was GMT -0500 before the date below," puts "and is now shifted to GMT -0600.)" puts "for me, this works in tclsh8.3, but fails in tclsh8.5" set when [clock scan {2007-11-04 01:30}] puts "[difftimes $when [expr {$when - 1}]]" puts "for me, this works in both tclsh versions." set when [clock scan {2007-11-04 02:30}] puts "[difftimes $when [expr {$when - 1}]]" puts "sometimes once i run the command that works, the one which failed the" puts "first time will start working, so here's the first command again:" set when [clock scan {2007-11-04 01:30}]; puts "[difftimes $when [expr {$when - 1}]]"When I run the code in tclsh8.3, it works as expected. When I run it in tclsh8.5, however, it fails with the message shown below. I'm not sure if this is a bug in my own code, difftimes, or tclsh8.5, but I'm reporting it here in case it reveals something worth fixing. Note that I'm in a time zone that "fell back" one hour on the date and time used in the example. If I use a different date, the code works fine in both 8.3 and 8.5. Here's the output that I get:
# tclsh8.3 difftimes.tcl (i'm in the U.S. CST, which was GMT -0500 before the date below, and is now shifted to GMT -0600.) for me, this works in tclsh8.3, but fails in tclsh8.5 0 0 0 0 0 1 for me, this works in both tclsh versions. 0 0 0 0 0 1 sometimes once i run the command that works, the one which failed the first time will start working, so here's the first command again: 0 0 0 0 0 1 # tclsh8.5 difftimes.tcl (i'm in the U.S. CST, which was GMT -0500 before the date below, and is now shifted to GMT -0600.) for me, this works in tclsh8.3, but fails in tclsh8.5 can't read "mmOut": no such variable while executing "list $yOut $mOut $dOut $hhOut $mmOut $ssOut" (procedure "difftimes" line 73) invoked from within "difftimes $when [expr {$when - 1}]" invoked from within "puts "[difftimes $when [expr {$when - 1}]]"" (file "difftimes.tcl" line 93)As noted in the code, sometimes in 8.5 I can get the same difftimes command which failed the first time to succeed in subsequent attempts.LV Be certain to submit a bug report at http://tcl.sf.net/ about this as soon as you can - so that if there is a bug, it can be fixed before release".Also, I've made a minor change to the difftimes code above - can you check that out to see if it fixes your problem?