Time interval calculations

This page is spawned from Years, months, days, etc. between two dates

This code might provide some useful insight for regression tests with tcl time features. It's been tested on tcl 8.5.

On a couple of occasions, drafts of this code may have identified discrepancies. Future ones will be reported to bug tracker.

procs

 * clock_scan_interval - outputs a timestamp in seconds given a timestammp in seconds and a relative time unit
 * interval_ymdhs counts - from earliest date forward
 * interval_ymdhs_w_units - includes units with interval_ymdhs output
 * interval_remains_ymdhs - counts from latest date backward
 * interval_remains_ymdhs_w_units - includes units with interval_remains_ymdhs

Sample output

Test cases for interval_ymdhs from http://wiki.tcl.tk/3189 :
t1 2002-01-31 01:23:45, t2 2002-02-28 12:34:56, 0 years 
1 months 
0 days 
11 hours 
11 minutes 
11 seconds 
.
t1 20020131T012345, t2 20020228T123456, 0 years 
1 months 
0 days 
11 hours 
11 minutes 
11 seconds 
.


Examples of Age() interval calculations from PostgreSQL docs
t1 2001-04-10, t2 1957-06-13, 43 years 
9 months 
28 days 
0 hours 
0 minutes 
0 seconds 
.
t1 2001-04-10 14:39:53, t2 1957-06-13, 43 years 
9 months 
28 days 
14 hours 
39 minutes 
53 seconds 
.
t1 2013-12-25, t2 1955-12-10, 58 years 
0 months 
15 days 
0 hours 
0 minutes 
0 seconds 
.
t1 2007-04-14, t2 2007-02-15, 0 years 
1 months 
30 days 
0 hours 
0 minutes 
0 seconds 
 'should not be 1 month 27 days.'
t1 2007-03-14, t2 2007-01-15, 0 years 
1 months 
27 days 
0 hours 
0 minutes 
0 seconds 
 'should not be 1 mon 30 days.'


Test cases for interval_remains_ymdhs from http://wiki.tcl.tk/3189 :
t1 2002-01-31 01:23:45, t2 2002-02-28 12:34:56, 0 years 
1 months 
0 days 
11 hours 
11 minutes 
11 seconds 
.
t1 20020131T012345, t2 20020228T123456, 0 years 
1 months 
0 days 
11 hours 
11 minutes 
11 seconds 
.
Examples of Age() interval calculations from PostgreSQL docs
t1 2001-04-10, t2 1957-06-13, 43 years 
9 months 
28 days 
0 hours 
0 minutes 
0 seconds 
.
t1 2001-04-10 14:39:53, t2 1957-06-13, 43 years 
9 months 
28 days 
14 hours 
39 minutes 
53 seconds 
.
t1 2013-12-25, t2 1955-12-10, 58 years 
0 months 
15 days 
0 hours 
0 minutes 
0 seconds 
.
The following match:
t1 2007-04-14, t2 2007-02-15, 0 years 
1 months 
30 days 
0 hours 
0 minutes 
0 seconds 
 'should not be 1 month 27 days.'
t1 2007-03-14, t2 2007-01-15, 0 years 
1 months 
27 days 
0 hours 
0 minutes 
0 seconds 
 'should not be 1 mon 30 days.'
..providing insight into the quote on reference 1 that these results shouldn't match.
1.  http://www.postgresql.org/message-id/[email protected] 


A block of random intervals tests:
Tested intervals will show audit and/or error with debug info, if/when it finds discrepancies.
Test begins.
.......................................................
interval_remains_ymdhs: debug s1 951871459 s2 643858447 y -9 m -9 d -1 h 1 s 48 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -2
.........................
................................................................................
...
..........................................................................
interval_remains_ymdhs: debug s1 1582985380 s2 714433513 y -27 m -6 d -7 h 8 s 33 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -8
......
...
.....................
interval_ymdhs: debug s1 446964886 s2 1292254360 y 26 m 9 d 14 h 10 s 54 s2_diff 86400
interval_ymdhs: debug, audit adjustment. decreasing 1 day to 13
...........................................................
.............................................
interval_remains_ymdhs: debug s1 1583047695 s2 880525026 y -22 m -3 d -4 h 22 s 51 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -5
...................................
...
...............................................
interval_remains_ymdhs: debug s1 1330568667 s2 1111396883 y -6 m -11 d -8 h 6 s 56 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -9
.................................
...
....................................................................
interval_remains_ymdhs: debug s1 1078061549 s2 512480664 y -17 m -10 d -31 h 22 s 55 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -32
............
................................................................................
..................
interval_ymdhs: debug s1 344582275 s2 1289124951 y 29 m 11 d 5 h 5 s 3596 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 4
interval_ymdhs: Corrections in the main calculation were applied: h 0, mm 0, s 1
..............................................................
...
.....................................................
interval_remains_ymdhs: debug s1 699351114 s2 196296522 y -15 m -11 d -7 h 14 s 48 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -8
....................
interval_ymdhs: debug s1 699404316 s2 947563690 y 7 m 10 d 13 h 5 s 34 s2_diff 86400
interval_ymdhs: debug, audit adjustment. decreasing 1 day to 12
.......
...
......
interval_remains_ymdhs: debug s1 951874792 s2 716183280 y -7 m -5 d -18 h 3 s 8 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -19
..........................................................................
...
...........
interval_remains_ymdhs: debug s1 1330544329 s2 750118842 y -18 m -4 d -20 h 3 s 53 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -21
.....................................................................
...
...............................
interval_ymdhs: debug s1 97181499 s2 972814369 y 27 m 8 d 29 h 16 s 3550 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 15
interval_ymdhs: Corrections in the main calculation were applied: h 0, mm 0, s 1
.................................................
................................................................................
.............................. 

Code

proc clock_scan_interval { seconds delta units } {
    # clock_scan_interval formats $seconds to a string for processing by clock scan
    # then returns new timestamp in seconds
    set stamp [clock format $seconds -format "%Y%m%dT%H%M%S"]
    if { $delta < 0 } {
        append stamp " - " [expr { abs( $delta ) } ] " " $units
    } else {
        append stamp " + " $delta " " $units
    }
    return [clock scan $stamp]
}

proc interval_ymdhs { s1 s2 } {
    # interval_ymdhs calculates the interval of time between
    # the earliest date and the last date
    # by starting to count at the earliest date.

    # This proc has audit features. It will automatically
    # attempt to correct and report any discrepancies it finds.

    # if s1 and s2 aren't in seconds, convert to seconds.
    if { ![string is integer -strict $s1] } {
        set s1 [clock scan $s1]
    }
    if { ![string is integer -strict $s2] } {
        set s2 [clock scan $s2]
    }
    # postgreSQL intervals determine month length based on earliest date in interval calculations.

    # set s1 to s2 in chronological sequence
    set sn_list [lsort -integer [list $s1 $s2]]
    set s1 [lindex $sn_list 0]
    set s2 [lindex $sn_list 1]
    
    # Arithmetic is done from most significant to least significant
    # The interval is spanned in largest units first.
    # A new position s1_pN is calculated for the Nth move along the interval.
    # s1 is s1_p0

    # Calculate years from s1_p0 to s2
    set y_count 0
    set s1_p0 $s1
    set s2_y_check $s1_p0
    while { $s2_y_check <= $s2  } {
        set s1_p1 $s2_y_check
        set y $y_count
        incr y_count
        set s2_y_check [clock_scan_interval $s1_p0 $y_count years]
    }
    # interval s1_p0 to s1_p1 counted in y years

    # is the base offset incremented one too much?
    set s2_y_check [clock_scan_interval $s1 $y years]
    if { $s2_y_check > $s2 } {
        set y [expr { $y - 1 } ]
        set s2_y_check [clock_scan_interval $s1 $y years]
    }
    # increment s1 (s1_p0) forward y years to s1_p1
    if { $y == 0 } {
        set s1_p1 $s1
    } else {
        set s1_p1 [clock_scan_interval $s1 $y years]
    }
    # interval s1 to s1_p1 counted in y years

    # Calculate months from s1_p1 to s2
    set m_count 0
    set s2_m_check $s1_p1
    while { $s2_m_check <= $s2  } {
        set s1_p2 $s2_m_check
        set m $m_count
        incr m_count
        set s2_m_check [clock_scan_interval $s1_p1 $m_count months]
    }
    # interval s1_p1 to s1_p2 counted in m months

    # Calculate interval s1_p2 to s2 in days
    # day_in_sec [expr { 60 * 60 * 24 } ]
    # 86400
    # Since length of month is not relative, use math.
    # Clip any fractional part.
    set d [expr { ( $s2 - $s1_p2 ) / 86400 } ]
    # Move interval from s1_p2 to s1_p3
    set s1_p3 [clock_scan_interval $s1_p2 $d days]
    # s1_p3 is less than a day from s2


    # Calculate interval s1_p3 to s2 in hours
    # hour_in_sec [expr { 60 * 60 } ]
    # 3600
    set h [expr { ( $s2 - $s1_p3 ) / 3600 } ]
    # Move interval from s1_p3 to s1_p4
    set s1_p4 [clock_scan_interval $s1_p3 $h hours]
    # s1_p4 is less than an hour from s2

#    puts "debug s1 $s1 s1_p1 $s1_p1 s1_p2 $s1_p2 s1_p3 $s1_p3 s1_p4 $s1_p4"
#    puts "debug y $y m $m d $d h $h"

    # Sometimes h = 24, yet is already included as a day!
    # For example, this case:
    # interval_ymdhs 20010410T000000 19570613T000000
    # from Age() example in PostgreSQL documentation:
    # http://www.postgresql.org/docs/9.1/static/functions-datetime.html
    # psql test=# select age(timestamp '2001-04-10', timestamp '1957-06-13');
    #       age           
    # -------------------------
    # 43 years 9 mons 27 days
    # (1 row)
    # According to LibreCalc, the difference is 16007 days
    #puts "s2=s1+16007days? [clock format [clock_scan_interval $s1 16007 days] -format %Y%m%dT%H%M%S]"
    # ^ this calc is consistent with 16007 days 
    # So, let's ignore the Postgresql irregularity for now.
    # Here's more background:
    # http://www.postgresql.org/message-id/[email protected]
    # http://www.postgresql.org/message-id/[email protected]
    # So, Postgres had a bug..

    # Sanity check: if 24 hours, push it up to a day unit
    if { $h >= 24 } {
        # adjust 24 hours to 1 day
        # puts "interval_ymdhs: debug info h -24, d + 1"
        set h [expr { $h - 24 } ]
        incr d
        set h_correction_p 1
    } else {
        set h_correction_p 0
    }

    # Calculate interval s1_p4 to s2 in minutes
    # minute_in_sec [expr { 60 } ]
    # 60
    set mm [expr { ( $s2 - $s1_p4 ) / 60 } ]
    # Move interval from s1_p4 to s1_p5
    set s1_p6 [clock_scan_interval $s1_p4 $mm minutes]

    # Sanity check: if 60 minutes, push it up to an hour unit
    if { $mm >= 60 } {
        # adjust 60 minutes to 1 hour
        # puts "interval_ymdhs: debug info mm - 60, h + 1"
        set mm [expr { $mm - 60 } ]
        incr h
        set mm_correction_p 1
    } else {
        set mm_correction_p 0
    }

    # Calculate interval s1_p6 to s2 in seconds
    set s [expr { int( $s2 - $s1_p6 ) } ]

    # Sanity check: if 60 seconds, push it up to one minute unit
    if { $s >= 60 } {
        # adjust 60 minutes to 1 hour
        set s [expr { $s - 60 } ]
        incr mm
        set s_correction_p 1
    } else {
        set s_correction_p 0
    }

    set return_list [list $y $m $d $h $mm $s]

    # test results by adding difference to s1 to get s2:
    set i 0
    set s1_test [clock format $s1 -format "%Y%m%dT%H%M%S"]
    foreach unit {years months days hours minutes seconds} {
        set t_term [lindex $return_list $i]
        if { $t_term != 0 } {
            if { $t_term > 0 } {
                append s1_test " + $t_term $unit"
            } else {
                append s1_test " - [expr { abs( $t_term ) } ] $unit"
            }
        }
        incr i
    }
    
    set s2_test [clock scan $s1_test]
#  puts "test s2 '$s2_test' from: '$s1_test'"
   set counter 0
    while { $s2 ne $s2_test && $counter < 30 } {
        set s2_diff [expr { $s2_test - $s2 } ]
        puts "\ninterval_ymdhs: debug s1 $s1 s2 $s2 y $y m $m d $d h $h s $s s2_diff $s2_diff"
        if { [expr { abs($s2_diff) } ] > 86399 } {
            if { $s2_diff > 0 } {
                incr d -1
                puts "interval_ymdhs: debug, audit adjustment. decreasing 1 day to $d"
            } else {
                incr d
                puts "interval_ymdhs: debug, audit adjustment. increasing 1 day to $d"
            }
        } elseif { [expr { abs($s2_diff) } ] > 3599 } {
            if { $s2_diff > 0 } {
                incr h -1
                puts "interval_ymdhs: debug, audit adjustment. decreasing 1 hour to $h"
            } else {
                incr h
                puts "interval_ymdhs: debug, audit adjustment. increasing 1 hour to $h"
            }
        } elseif { [expr { abs($s2_diff) } ] > 59 } {
            if { $s2_diff > 0 } {
                incr mm -1
                puts "interval_ymdhs: debug, audit adjustment. decreasing 1 minute to $mm"
            } else {
                incr mm
                puts "interval_ymdhs: debug, audit adjustment. increasing 1 minute to $mm"
            }
        } elseif { [expr { abs($s2_diff) } ] > 0 } {
            if { $s2_diff > 0 } {
                incr s -1
                puts "interval_ymdhs: debug, audit adjustment. decreasing 1 second to $s"
            } else {
                incr s
                puts "interval_ymdhs: debug, audit adjustment. increasing 1 second to $s"
            }
        }
        
        set return_list [list $y $m $d $h $mm $s]
        #    set return_list [list [expr { abs($y) } ] [expr { abs($m) } ] [expr { abs($d) } ] [expr { abs($h) } ] [expr { abs($mm) } ] [expr { abs($s) } ]]
        
        # test results by adding difference to s1 to get s2:
        set i 0
        set s1_test [clock format $s1 -format "%Y%m%dT%H%M%S"]
        foreach unit {years months days hours minutes seconds} {
            set t_term [lindex $return_list $i]
            if { $t_term != 0 } {
                if { $t_term > 0 } {
                    append s1_test " + $t_term $unit"
                } else {
                    append s1_test " - [expr { abs( $t_term ) } ] $unit"
                }
            }
            incr i
        }
        set s2_test [clock scan $s1_test]
        incr counter
    }
    if { $counter > 0 && ( $h_correction_p || $mm_correction_p || $s_correction_p ) } {
        puts "interval_ymdhs: Corrections in the main calculation were applied: h ${h_correction_p}, mm ${mm_correction_p}, s ${s_correction_p}" 
    }
    if { $s2 eq $s2_test } {
        return $return_list
    } else {
        set s2_diff [expr { $s2_test - $s2 } ]
        puts "debug s1 $s1 s1_p1 $s1_p1 s1_p2 $s1_p2 s1_p3 $s1_p3 s1_p4 $s1_p4"
        puts "debug y $y m $m d $d h $h"
        puts "interval_ymdhs error: s2 is '$s2' but s2_test is '$s2_test' a difference of ${s2_diff} from s1 '$s1_test'."
#        error "result audit fails" "error: s2 is $s2 but s2_test is '$s2_test' a difference of ${s2_diff} from: '$s1_test'."
    }
}

proc interval_ymdhs_w_units { t1 t2 } {
    # interval_ymdhs_w_units
    # returns interval_ymdhs values with units
    set v_list [interval_ymdhs $t2 $t1]
    set i 0
    set a ""
    foreach f {years months days hours minutes seconds} {
        append a "[lindex $v_list $i] $f \n"
        incr i
    }
    return $a
}


proc interval_remains_ymdhs { s1 s2 } {
    # interval_remains_ymdhs calculates the interval of time between
    # the earliest date and the last date
    # by starting to count at the last date and work backwards in time.

    # This proc has audit features. It will automatically
    # attempt to correct and report any discrepancies it finds.

    # if s1 and s2 aren't in seconds, convert to seconds.
    if { ![string is integer -strict $s1] } {
        set s1 [clock scan $s1]
    }
    if { ![string is integer -strict $s2] } {
        set s2 [clock scan $s2]
    }
    # set s1 to s2 in reverse chronological sequence
    set sn_list [lsort -decreasing -integer [list $s1 $s2]]
    set s1 [lindex $sn_list 0]
    set s2 [lindex $sn_list 1]
    
    # Arithmetic is done from most significant to least significant
    # The interval is spanned in largest units first.
    # A new position s1_pN is calculated for the Nth move along the interval.
    # s1 is s1_p0

    # Calculate years from s1_p0 to s2
    set y_count 0
    set s1_p0 $s1
    set s2_y_check $s1_p0
    while { $s2_y_check > $s2  } {
        set s1_p1 $s2_y_check
        set y $y_count
        incr y_count -1
        set s2_y_check [clock_scan_interval $s1_p0 $y_count years]
    }
    # interval s1_p0 to s1_p1 counted in y years


    # Calculate months from s1_p1 to s2
    set m_count 0
    set s2_m_check $s1_p1
    while { $s2_m_check > $s2  } {
        set s1_p2 $s2_m_check
        set m $m_count
        incr m_count -1
        set s2_m_check [clock_scan_interval $s1_p1 $m_count months]
    }
    # interval s1_p1 to s1_p2 counted in m months

    # Calculate interval s1_p2 to s2 in days
    # day_in_sec [expr { 60 * 60 * 24 } ]
    # 86400
    # Since length of month is not relative, use math.
    # Clip any fractional part.
    set d [expr { ( $s2 - $s1_p2 ) / 86400 } ]
    # Move interval from s1_p2 to s1_p3
    set s1_p3 [clock_scan_interval $s1_p2 $d days]
    # s1_p3 is less than a day from s2


    # Calculate interval s1_p3 to s2 in hours
    # hour_in_sec [expr { 60 * 60 } ]
    # 3600
    set h [expr { ( $s2 - $s1_p3 ) / 3600 } ]
    # Move interval from s1_p3 to s1_p4
    set s1_p4 [clock_scan_interval $s1_p3 $h hours]
    # s1_p4 is less than an hour from s2

#    puts "debug s1 $s1 s1_p1 $s1_p1 s1_p2 $s1_p2 s1_p3 $s1_p3 s1_p4 $s1_p4"
#    puts "debug y $y m $m d $d h $h"

    # Sanity check: if 24 hours, push it up to a day unit
    if { $h <= -24 } {
        # adjust 24 hours to 1 day
        # puts "interval_remains_ymdhs: debug info h + 24, d - 1"
        set h [expr { $h + 24 } ]
        incr d -1
        set h_correction_p 1
    } else {
        set h_correction_p 0
    }

    # Calculate interval s1_p4 to s2 in minutes
    # minute_in_sec [expr { 60 } ]
    # 60
    set mm [expr { ( $s2 - $s1_p4 ) / 60 } ]
    # Move interval from s1_p4 to s1_p5
    set s1_p6 [clock_scan_interval $s1_p4 $mm minutes]

    # Sanity check: if 60 minutes, push it up to an hour unit
    if { $mm <= -60 } {
        # adjust 60 minutes to 1 hour
        # puts "interval_remains_ymdhs: debug info mm + 60, h - 1"
        set mm [expr { $mm + 60 } ]
        incr h -1
        set mm_correction_p 1
    } else {
        set mm_correction_p 0
    }

    # Calculate interval s1_p6 to s2 in seconds
    set s [expr { int( $s2 - $s1_p6 ) } ]

    # Sanity check: if 60 seconds, push it up to one minute unit
    if { $s <= -60 } {
        # adjust 60 minutes to 1 hour
        set s [expr { $s + 60 } ]
        incr mm -1
        set s_correction_p 1
    } else {
        set s_correction_p 0
    }

    set return_list [list $y $m $d $h $mm $s]
    #    set return_list [list [expr { abs($y) } ] [expr { abs($m) } ] [expr { abs($d) } ] [expr { abs($h) } ] [expr { abs($mm) } ] [expr { abs($s) } ]]

    # test results by adding difference to s1 to get s2:
    set i 0
    set s1_test [clock format $s1 -format "%Y%m%dT%H%M%S"]
    foreach unit {years months days hours minutes seconds} {
        set t_term [lindex $return_list $i]
        if { $t_term != 0 } {
            if { $t_term > 0 } {
                append s1_test " + $t_term $unit"
            } else {
                append s1_test " - [expr { abs( $t_term ) } ] $unit"
            }
        }
        incr i
    }
    
    set s2_test [clock scan $s1_test]
#  puts "test s2 '$s2_test' from: '$s1_test'"
    set counter 0
    while { $s2 ne $s2_test && $counter < 3 } {
        set s2_diff [expr { $s2_test - $s2 } ]
        puts "\ninterval_remains_ymdhs: debug s1 $s1 s2 $s2 y $y m $m d $d h $h s $s s2_diff $s2_diff"
        if { [expr { abs($s2_diff) } ] >= 86399 } {
            if { $s2_diff > 0 } {
                incr d -1
                puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to $d"
            } else {
                incr d
                puts "interval_remains_ymdhs: debug, audit adjustment. increasing 1 day to $d"
            }
        } elseif { [expr { abs($s2_diff) } ] > 3599 } {
            if { $s2_diff > 0 } {
                incr h -1
                puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to $h"
            } else {
                incr h
                puts "interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to $h"
            }
        } elseif { [expr { abs($s2_diff) } ] > 59 } {
            if { $s2_diff > 0 } {
                incr mm -1
                puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 minute to $mm"
            } else {
                incr mm
                puts "interval_remains_ymdhs: debug, audit adjustment. increasing 1 minute to $mm"
            }
        } elseif { [expr { abs($s2_diff) } ] > 0 } {
            if { $s2_diff > 0 } {
                incr s -1
                puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 second to $s"
            } else {
                incr s
                puts "interval_remains_ymdhs: debug, audit adjustment. increasing 1 second to $s"
            }
        }
        
        set return_list [list $y $m $d $h $mm $s]
        #    set return_list [list [expr { abs($y) } ] [expr { abs($m) } ] [expr { abs($d) } ] [expr { abs($h) } ] [expr { abs($mm) } ] [expr { abs($s) } ]]
        
        # test results by adding difference to s1 to get s2:
        set i 0
        set s1_test [clock format $s1 -format "%Y%m%dT%H%M%S"]
        foreach unit {years months days hours minutes seconds} {
            set t_term [lindex $return_list $i]
            if { $t_term != 0 } {
                if { $t_term > 0 } {
                    append s1_test " + $t_term $unit"
                } else {
                    append s1_test " - [expr { abs( $t_term ) } ] $unit"
                }
            }
            incr i
        }
        set s2_test [clock scan $s1_test]
        incr counter
    }
    if { $counter > 0 && ( $h_correction_p || $mm_correction_p || $s_correction_p ) } {
        puts "interval_remains_ymdhs: Corrections in the main calculation were applied: h ${h_correction_p}, mm ${mm_correction_p}, s ${s_correction_p}" 
    }
    if { $s2 eq $s2_test } {
        return $return_list
    } else {
        set s2_diff [expr { $s2_test - $s2 } ]
        puts "debug s1 $s1 s1_p1 $s1_p1 s1_p2 $s1_p2 s1_p3 $s1_p3 s1_p4 $s1_p4"
        puts "debug y $y m $m d $d h $h"
        puts "interval_remains_ymdhs error: s2 is '$s2' but s2_test is '$s2_test' a difference of ${s2_diff} from s1 '$s1_test'."
#        error "result audit fails" "error: s2 is $s2 but s2_test is '$s2_test' a difference of ${s2_diff} from: '$s1_test'."
    }

}

proc interval_remains_ymdhs_w_units { t1 t2 } {
    # interval_remains_ymdhs_w_units
    # returns interval_remains_ymdhs values with units
    set v_list [interval_ymdhs $t2 $t1]
    set i 0
    set a ""
    foreach f {years months days hours minutes seconds} {
        append a "[lindex $v_list $i] $f \n"
        incr i
    }
    return $a
}


puts "\n\nTest cases for interval_ymdhs from http://wiki.tcl.tk/3189 :"

set t1 "2002-01-31 01:23:45" 
set t2 "2002-02-28 12:34:56"
puts "t1 $t1, t2 $t2, [interval_ymdhs_w_units $t1 $t2]."
set t1 "20020131T012345"
set t2 "20020228T123456"
puts "t1 $t1, t2 $t2, [interval_ymdhs_w_units $t1 $t2]."

puts "\n\nExamples of Age() interval calculations from PostgreSQL docs"
# http://www.postgresql.org/docs/9.1/static/functions-datetime.html
set t1 "2001-04-10"
set t2 "1957-06-13"
puts "t1 $t1, t2 $t2, [interval_ymdhs_w_units $t1 $t2]."
set t1 "2001-04-10 14:39:53"
set t2 "1957-06-13"
puts "t1 $t1, t2 $t2, [interval_ymdhs_w_units $t1 $t2]."
set t1 "2013-12-25"
set t2 "1955-12-10"
puts "t1 $t1, t2 $t2, [interval_ymdhs_w_units $t1 $t2]."
# following test per http://www.postgresql.org/message-id/[email protected]
set t1 "2007-04-14"
set t2 "2007-02-15"
puts "t1 $t1, t2 $t2, [interval_ymdhs_w_units $t1 $t2] 'should not be 1 month 27 days.'"
set t1 "2007-03-14"
set t2 "2007-01-15"
puts "t1 $t1, t2 $t2, [interval_ymdhs_w_units $t1 $t2] 'should not be 1 mon 30 days.'"

puts "\n\nTest cases for interval_remains_ymdhs from http://wiki.tcl.tk/3189 :"

set t1 "2002-01-31 01:23:45" 
set t2 "2002-02-28 12:34:56"
puts "t1 $t1, t2 $t2, [interval_remains_ymdhs_w_units $t1 $t2]."
set t1 "20020131T012345"
set t2 "20020228T123456"
puts "t1 $t1, t2 $t2, [interval_remains_ymdhs_w_units $t1 $t2]."

puts "Examples of Age() interval calculations from PostgreSQL docs"
# http://www.postgresql.org/docs/9.1/static/functions-datetime.html
set t1 "2001-04-10"
set t2 "1957-06-13"
puts "t1 $t1, t2 $t2, [interval_remains_ymdhs_w_units $t1 $t2]."
set t1 "2001-04-10 14:39:53"
set t2 "1957-06-13"
puts "t1 $t1, t2 $t2, [interval_remains_ymdhs_w_units $t1 $t2]."
set t1 "2013-12-25"
set t2 "1955-12-10"
puts "t1 $t1, t2 $t2, [interval_remains_ymdhs_w_units $t1 $t2]."
# following test per http://www.postgresql.org/message-id/[email protected]
set t1 "2007-04-14"
set t2 "2007-02-15"
puts "The following match:"
puts "t1 $t1, t2 $t2, [interval_remains_ymdhs_w_units $t1 $t2] 'should not be 1 month 27 days.'"
set t1 "2007-03-14"
set t2 "2007-01-15"
puts "t1 $t1, t2 $t2, [interval_remains_ymdhs_w_units $t1 $t2] 'should not be 1 mon 30 days.'"
puts "..providing insight into the quote on reference 1 that these results shouldn't match."
puts "1.  http://www.postgresql.org/message-id/[email protected] "

puts "\n\nA block of random intervals tests:"
puts "Tested intervals will show audit and/or error with debug info, if/when it finds discrepancies."
puts -nonewline "Test begins"
set counter 0
for {set ii 1} {$ii < 10} {incr ii} {
#    puts "\nTesting loop $counter."
    for {set i 1} {$i < 1000} {incr i} {
        set t1 [clock format [expr { round( rand() * 1500000000 ) } ] -format "%Y%m%dT%H%M%S"]
        set t2 [clock format [expr { round( rand() * 1600000000 ) } ] -format "%Y%m%dT%H%M%S"]
        puts -nonewline "."
        set a [interval_ymdhs $t1 $t2]
        set b [interval_remains_ymdhs $t1 $t2]
        if { [expr { $counter / 80. } ] == [expr { $counter / 80 } ] } {
            # linebreak on 80 columns
            puts ""
        }
        incr counter
    }
}