Measuring time intervals (between two timestamps) with months etc

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. All measured units are positive values.
 * interval_ymdhs_w_units - includes units with interval_ymdhs output
 * interval_remains_ymdhs - counts from latest date backward. All measured units are negative values.
 * interval_remains_ymdhs_w_units - includes units with interval_remains_ymdhs

Sample output

The comments in the output identify when an audit finds and corrects an inconsistent measurement.

Test cases for interval_ymdhs from https://wiki.tcl-lang.org/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 https://wiki.tcl-lang.org/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_ymdhs: debug s1 795544645 s2 1300104282 y 15 m 11 d 23 h 19 s 17 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 20
........
................................................................................
interval_ymdhs: debug s1 773868169 s2 972852115 y 6 m 3 d 19 h 1 s 6 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 0

......................................
interval_ymdhs: debug s1 802514196 s2 1446499190 y 20 m 4 d 26 h 12 s 14 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 11
..........................................
................................................................................
...................
interval_ymdhs: debug s1 614028963 s2 1583772170 y 30 m 8 d 21 h 20 s 47 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 21
.........................................
interval_ymdhs: debug s1 1221969960 s2 1225732036 y 0 m 1 d 13 h 13 s 16 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 12
....................
................................................................................
...............
interval_remains_ymdhs: debug s1 330594997 s2 104885460 y -7 m -1 d -24 h -9 s -37 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -10
.................................................................
................................................................................
................................................................................
................................................................................
............
interval_remains_ymdhs: debug s1 952707597 s2 199166607 y -23 m -10 d -16 h -11 s -30 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -12
...
interval_ymdhs: debug s1 104042908 s2 1225764937 y 35 m 6 d 15 h 22 s 9 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 21
.................................................................
............................................
interval_remains_ymdhs: debug s1 1362525788 s2 1035648131 y -10 m -4 d -10 h -7 s -57 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -6
.......
interval_remains_ymdhs: debug s1 495054013 s2 309898501 y -5 m -10 d -12 h -1 s -12 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to 0
.............................
...............................................
interval_ymdhs: debug s1 1010658157 s2 1268724192 y 8 m 2 d 5 h 21 s 35 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 22
.............
interval_ymdhs: debug s1 448023037 s2 1464414828 y 32 m 2 d 14 h 19 s 11 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 20
....................
................................................................................
.........................
interval_remains_ymdhs: debug s1 474635690 s2 215502457 y -8 m -2 d -16 h -5 s -13 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -4
................................................
interval_ymdhs: debug s1 393806198 s2 1478513072 y 34 m 4 d 13 h 11 s 54 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 10
.......
...................
interval_ymdhs: debug s1 344370119 s2 576118453 y 7 m 4 d 5 h 6 s 14 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 7
.......................................................
interval_remains_ymdhs: debug s1 995503285 s2 451961965 y -17 m -2 d -20 h -23 s 0 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -24
......
..............................................
interval_remains_ymdhs: debug s1 1455289699 s2 860169235 y -18 m -10 d -7 h -22 s -24 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -23
..
interval_remains_ymdhs: debug s1 1251552496 s2 404811884 y -26 m -9 d -30 h -6 s -32 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -5
.........................
interval_ymdhs: debug s1 117510593 s2 1067240517 y 30 m 1 d 5 h 5 s 4 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 4
.......
...............
interval_ymdhs: debug s1 678261625 s2 1112681187 y 13 m 9 d 5 h 23 s 2 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 24
...........................................
interval_remains_ymdhs: debug s1 1463595238 s2 576002387 y -28 m -1 d -16 h -1 s -11 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -2
......................
................................................................................
................................................................................
................................................................................
.........................
interval_ymdhs: debug s1 948119157 s2 1236591234 y 9 m 1 d 19 h 19 s 57 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 20
.......................................................
................................................................................
.........
interval_remains_ymdhs: debug s1 673575922 s2 309880710 y -11 m -6 d -10 h -11 s -52 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -10
.......................................................................
................................................................................
................................................................................
..........
interval_ymdhs: debug s1 173639883 s2 972907883 y 25 m 3 d 26 h 18 s 20 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 17
........................................
interval_ymdhs: debug s1 587734762 s2 1049684715 y 14 m 7 d 21 h 14 s 53 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 15
..............................
..........................................................
interval_remains_ymdhs: debug s1 1588066576 s2 458558268 y -35 m -9 d -15 h -1 s -28 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to 0
......................
...
interval_remains_ymdhs: debug s1 1424495656 s2 278445616 y -36 m -3 d -23 h -11 s 0 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -10
................
interval_ymdhs: debug s1 980893987 s2 1049706645 y 2 m 2 d 7 h 10 s 38 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 11
.............................................................
.................................
interval_remains_ymdhs: debug s1 1276131460 s2 1004209162 y -8 m -7 d -13 h -6 s -18 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -5
...
interval_ymdhs: debug s1 226268871 s2 986208849 y 24 m 0 d 29 h 14 s 18 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 15
............................................
................................................................................
.......................................
interval_remains_ymdhs: debug s1 573202583 s2 260816170 y -9 m -10 d -21 h -14 s -13 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -22
................
interval_ymdhs: debug s1 5047667 s2 891887413 y 28 m 1 d 9 h 8 s 26 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 9
.........................
......................................................................
interval_remains_ymdhs: debug s1 936015978 s2 814802018 y -3 m -10 d -2 h -23 s -40 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -22
..........
..............................................
interval_remains_ymdhs: debug s1 1038058494 s2 688431795 y -11 m 0 d -28 h -14 s -39 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -13
..................................
...................................................
interval_remains_ymdhs: debug s1 1123517493 s2 1035613334 y -2 m -9 d -13 h -10 s -19 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -9
.............................
..................................................................
interval_ymdhs: debug s1 954550381 s2 1225703272 y 8 m 7 d 2 h 9 s 51 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 8
..............
.....................................................................
interval_remains_ymdhs: debug s1 1009316566 s2 89091984 y -29 m -1 d -28 h -17 s -22 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -16
...........
................................................................................
................................................................................
................................................................................
.......................................................
interval_remains_ymdhs: debug s1 425285642 s2 294170776 y -4 m -1 d -25 h -12 s -46 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -13
......
interval_ymdhs: debug s1 113900171 s2 341526335 y 7 m 2 d 16 h 13 s 24 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 12
...................
......................
interval_remains_ymdhs: debug s1 1583047629 s2 1354434706 y -7 m -2 d -26 h -23 s -23 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -27
..........................................................
................................................................................
................................................................................
................................................................................
................................................................................
....
interval_ymdhs: debug s1 173512015 s2 230858746 y 1 m 9 d 23 h 16 s 51 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 17
............................................................................
...........
interval_remains_ymdhs: debug s1 1559989177 s2 467660052 y -34 m -7 d -12 h -17 s -25 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -16
.....................................................................
................................................................................
...........
interval_ymdhs: debug s1 259336665 s2 452147506 y 6 m 1 d 8 h 14 s 1 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 15
..........................
interval_ymdhs: debug s1 411279019 s2 1363026452 y 30 m 1 d 26 h 14 s 13 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 15
.................................
interval_remains_ymdhs: debug s1 1306264869 s2 419898270 y -28 m -1 d -1 h -20 s -39 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -21
..........
.................................
interval_remains_ymdhs: debug s1 1236515704 s2 1194069620 y -1 m -4 d -5 h -7 s -44 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -6
...............................................
................................................................................
................................................................................
.............................................................
interval_remains_ymdhs: debug s1 462557747 s2 357019911 y -3 m -4 d -3 h -12 s -56 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -13
...................
................................................................................
................................................................................
................................................................................
..........
interval_remains_ymdhs: debug s1 1264311047 s2 294154280 y -30 m -8 d -25 h -14 s -27 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -15
......................................................................
................................................................................
................................................................................
................................................................................
................................................................................
.....................................................................
interval_ymdhs: debug s1 1265707034 s2 1320666744 y 1 m 8 d 29 h 3 s 10 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 2
...........
........................
interval_ymdhs: debug s1 251047541 s2 1446419544 y 37 m 10 d 17 h 8 s 43 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 7
........................................................
................................................................................
...................................
interval_ymdhs: debug s1 68253130 s2 355659189 y 9 m 1 d 11 h 11 s 59 s2_diff 86400
interval_ymdhs: debug, audit adjustment. decreasing 1 day to 10
........
interval_remains_ymdhs: debug s1 1441134558 s2 1236440237 y -6 m -5 d -25 h -3 s -1 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -4
.
interval_remains_ymdhs: debug s1 1456786781 s2 326730870 y -35 m -9 d -19 h -7 s -11 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -20
........
interval_remains_ymdhs: debug s1 1162068452 s2 923162339 y -7 m -6 d -25 h -2 s -33 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -3
............................
...........................................................................
interval_remains_ymdhs: debug s1 281397091 s2 41337765 y -7 m -7 d -7 h -10 s -46 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -11
.....
...................
interval_ymdhs: debug s1 1214582303 s2 1583754060 y 11 m 8 d 10 h 18 s 37 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 19
..................................
interval_remains_ymdhs: debug s1 780334419 s2 575964275 y -6 m -5 d -21 h -9 s -4 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -10
.....................
interval_ymdhs: debug s1 136318146 s2 639024117 y 15 m 11 d 5 h 8 s 51 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 9

interval_remains_ymdhs: debug s1 639024117 s2 136318146 y -15 m -11 d -4 h -8 s -51 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -9
......
................................................................
interval_ymdhs: debug s1 1171369585 s2 1289190431 y 3 m 8 d 25 h 17 s 46 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 16
................
................................................................................
................................................................................
................................................................................
................................................................................
..........................................................................
interval_remains_ymdhs: debug s1 1568391576 s2 783421810 y -24 m -10 d -15 h -8 s -26 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -7
......
................................................................................
........
interval_remains_ymdhs: debug s1 1387472622 s2 483447418 y -28 m -7 d -22 h -5 s -44 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -6
..
interval_remains_ymdhs: debug s1 1285140592 s2 909246434 y -11 m -10 d -28 h -16 s -38 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -15
........................................
interval_remains_ymdhs: debug s1 670168621 s2 25607094 y -20 m -5 d -4 h -4 s -7 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -3
..............................
.....................................................................
interval_ymdhs: debug s1 386971903 s2 1425883279 y 32 m 11 d 2 h 10 s 36 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 11
...........
...........................................................................
interval_ymdhs: debug s1 939241184 s2 1415012035 y 15 m 0 d 27 h 14 s 11 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 13
.....
............................
interval_remains_ymdhs: debug s1 264532516 s2 246965431 y 0 m -6 d -22 h -8 s -45 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -7
.........
interval_remains_ymdhs: debug s1 1078069690 s2 658426798 y -13 m -3 d -15 h -23 s -12 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -16
...........................................
.....
interval_remains_ymdhs: debug s1 1429258008 s2 1162095551 y -8 m -5 d -19 h -4 s -37 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -3
...........................................................................
...........................
interval_ymdhs: debug s1 657388149 s2 1018273118 y 11 m 5 d 7 h 21 s 29 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 22
.................................
interval_ymdhs: debug s1 488701862 s2 752213849 y 8 m 4 d 5 h 21 s 27 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 20
....................
................................................................................
................................................................................
..
interval_remains_ymdhs: debug s1 1277929532 s2 702367945 y -18 m -2 d -26 h -14 s -7 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -15
............................
interval_remains_ymdhs: debug s1 777656299 s2 607386051 y -5 m -4 d -22 h -17 s -28 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -18
..................................................
..............................
interval_remains_ymdhs: debug s1 887532904 s2 783473080 y -3 m -3 d -16 h -9 s -24 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -8
.......................
interval_remains_ymdhs: debug s1 1278349167 s2 278437464 y -31 m -8 d -8 h -2 s -3 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -1
.
interval_remains_ymdhs: debug s1 845617012 s2 388546009 y -14 m -5 d -23 h -4 s -3 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -5
..........................
................................................................................
................................................................................
.....
interval_remains_ymdhs: debug s1 702682379 s2 514931472 y -5 m -11 d -11 h -1 s -47 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -2
....................................
interval_ymdhs: debug s1 502149976 s2 1414974163 y 28 m 11 d 4 h 3 s 27 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 2
.......................................
................................................................................
...........
interval_ymdhs: debug s1 803414336 s2 1583721272 y 24 m 8 d 20 h 6 s 36 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 7
.......................................
interval_remains_ymdhs: debug s1 582890609 s2 151968859 y -13 m -7 d -26 h -13 s -10 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -12
..............................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
................................................................................
........................
interval_remains_ymdhs: debug s1 451606026 s2 151971556 y -9 m -5 d -28 h -23 s -50 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -22
........................................................
....................
interval_remains_ymdhs: debug s1 442145683 s2 151995178 y -9 m -2 d -10 h -5 s -45 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -4
............................................................
.............
interval_ymdhs: debug s1 168262202 s2 515011744 y 10 m 11 d 25 h 6 s 2 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 7
...................................................................
...................
interval_ymdhs: debug s1 43140100 s2 89340182 y 1 m 5 d 15 h 17 s 22 s2_diff 3600
interval_ymdhs: debug, audit adjustment. decreasing 1 hour to 16
........
interval_remains_ymdhs: debug s1 889323577 s2 783473180 y -3 m -4 d -9 h -2 s -17 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -1
.......................................
interval_remains_ymdhs: debug s1 673134688 s2 372739398 y -9 m -6 d -8 h -20 s -10 s2_diff -3600
interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to -19
..............
................................................................................
................................................................................
................................................................................
..................................
interval_ymdhs: debug s1 448021260 s2 1486197443 y 32 m 10 d 21 h 21 s 23 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 22
.............................
interval_ymdhs: debug s1 13015962 s2 986196954 y 30 m 10 d 1 h 15 s 12 s2_diff -3600
interval_ymdhs: debug, audit adjustment. increasing 1 hour to 16
..........
interval_ymdhs: debug s1 194446513 s2 1133377054 y 29 m 9 d 2 h 6 s 21 s2_diff 86400
interval_ymdhs: debug, audit adjustment. decreasing 1 day to 1
.......
interval_remains_ymdhs: debug s1 618148164 s2 230600053 y -12 m -3 d -10 h -12 s -11 s2_diff 3600
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to -13

................................................................................
...................................................................
interval_remains_ymdhs: debug s1 699359768 s2 148923404 y -17 m -5 d -7 h -17 s -24 s2_diff 86400
interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to -8
.............
................................................................................
................................................................................
................................................................................
..............................% 

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 { int( ( $s2 - $s1_p2 ) / 86400. ) } ]
    # Ideally, this should always be true, but daylight savings..
    # so, go backward one day and make hourly steps for last day.
    if { $d > 0 } {
        incr d -1
    }

    # 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 { int( ( $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


    # 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 over 24 or 48 hours, push it up to a day unit
    set h_in_days [expr { int( $h / 24. ) } ]
    if { $h >= 1 } {
        # adjust hours to less than a day
        set h [expr { $h - ( 24 * $h_in_days ) } ]
        incr d $h_in_days
        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 { int( ( $s2 - $s1_p4 ) / 60. ) } ]
    # Move interval from s1_p4 to s1_p5
    set s1_p5 [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_p5 to s2 in seconds
    set s [expr { int( $s2 - $s1_p5 ) } ]

    # 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"]
    set signs_inconsistent_p 0
    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"
                set signs_inconsistent_p 1
            }
        }
        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 || $signs_inconsistent_p ) && ( $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 { $signs_inconsistent_p } {
        puts "\ninterval_ymdhs: signs inconsistent y $y m $m d $d h $h mm $mm s $s"
    }
    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 mm $mm s $s"
        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 { int( ceil( ( $s2 - $s1_p2 ) / 86400. ) ) } ]
    # Ideally, this should always be true, but daylight savings..
    # so, go backward one day and make hourly steps for last day.
    if { $d < 0 } {
        incr d
    }

    # 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 { int( ceil( ( $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

    # Sanity check: if over 24 or 48 hours, push it up to a day unit
    set h_in_days [expr { int( ceil( $h / 24. ) )  } ]
    if { $h_in_days <= -1 } {
        # adjust hours to less than a day
        set h [expr { $h - ( 24 * $h_in_days ) } ]
        incr d $h_in_days
        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 { int( ceil( ( $s2 - $s1_p4 ) / 60. ) ) } ]
    # Move interval from s1_p4 to s1_p5
    set s1_p5 [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_p5 to s2 in seconds
    set s [expr { $s2 - $s1_p5 } ]

    # 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"]
    set signs_inconsistent_p 0
    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"
                set signs_inconsistent_p 1
            } else {
                append s1_test " - [expr { abs( $t_term ) } ] $unit"
            }
        }
        incr i
    }
    set s2_test [clock scan $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 || $signs_inconsistent_p ) && ( $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 { $signs_inconsistent_p } {
        puts "\ninterval_remains_ymdhs: signs inconsistent y $y m $m d $d h $h mm $mm s $s"
    }
    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 mm $mm s $s"
        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 https://wiki.tcl-lang.org/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 https://wiki.tcl-lang.org/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
    }
}