[Larry Virden] wrote:
> 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.
This is actually a very hard problem, owing to the complexity of defining
"time until deadline". There are a number of issues to be considered:
* 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?
And so on... It's truly a mess.
That said, here's one possibility. It uses the same rules that [[clock scan]]
uses to define the answers to the questions above. It generates one set
of answers that give the difference between two times.
Note that the answers don't always follow the principle of least
astonishment. In particular, look at the result of comparing
2002-01-31 01:23:45
with
2002-02-28 12:34:56.
The result is
0 years 1 months 0 days 11 hours 11 minutes 11 seconds
The 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 seconds
but I fail to see an easy way to come up with
a general rule that never generates results that look anomalous.
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 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 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 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 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 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"
}
----
[Category Package] | [Category Date and Time]