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.

On a couple of occasions, drafts of this code identified discrepancies.

procs:

 1. clock_scan_interval outputs a timestamp in seconds given a timestammp in seconds and a relative time unit
 1. interval_ymdhs counts from earliest date forward
 1. interval_ymdhs_w_units includes units with interval_ymdhs output
 1. interval_remains_ymdhs counts from latest date backward
 1. interval_remains_ymdhs_w_units includes units with interval_remains_ymdhs
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
    }
}

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
.................................................
................................................................................
..............................