From c94f4cc0f6c3865ea4ac79436c2cbef54365cf8c Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Sun, 16 Mar 2025 23:48:32 +1100 Subject: [PATCH] punk::timeinterval and winlibreoffice fixes --- src/modules/punk/timeinterval-999999.0a1.0.tm | 423 ++++++++++++------ src/modules/winlibreoffice-999999.0a1.0.tm | 180 ++++++-- 2 files changed, 436 insertions(+), 167 deletions(-) diff --git a/src/modules/punk/timeinterval-999999.0a1.0.tm b/src/modules/punk/timeinterval-999999.0a1.0.tm index 8429b65d..cb021427 100644 --- a/src/modules/punk/timeinterval-999999.0a1.0.tm +++ b/src/modules/punk/timeinterval-999999.0a1.0.tm @@ -27,18 +27,24 @@ # namespace eval punk::timeinterval { - 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] - } - + #The free-form 'clock scan' is deprecated. It worked in 8.4 to 8.6/8.7 (and earlier?) - but doesn't work in tcl9 + #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 clock_scan_interval { seconds delta units } { + # #8.6+ + # clock add $seconds $delta $units + #} + namespace export difference #wrap in dict @@ -46,21 +52,36 @@ namespace eval punk::timeinterval { lassign [interval_ymdhs $s1 $s2] Y M D h m s return [dict create years $Y months $M days $D hours $h minutes $m seconds $s] } - - 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. + lappend PUNKARGS [list { + @id -id "::punk::timeinterval::interval_ymdhs" + @cmd -name "punk::timeinterval::interval_ymdhs" -help\ + "interval_ymdhs calculates the interval of time between + the earliest date and the last date + by starting to count at the earliest date." + @opts + -maxunit -default years -choices {years months days hours minutes seconds} + -timezone -default "" + @values -min 2 -max 2 + s1 + s2 + }] + proc interval_ymdhs {args} { + set argd [punk::args::parse $args withid ::punk::timeinterval::interval_ymdhs] + lassign [dict values $argd] leaders opts values received + set maxunit [dict get $opts -maxunit] + set timezone [dict get $opts -timezone] + set s1 [dict get $values s1] + set s2 [dict get $values s2] # 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] + set s1 [clock scan $s1 -timezone $timezone] } if { ![string is integer -strict $s2] } { - set s2 [clock scan $s2] + set s2 [clock scan $s2 -timezone $timezone] } # postgreSQL intervals determine month length based on earliest date in interval calculations. @@ -68,7 +89,7 @@ namespace eval punk::timeinterval { 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. @@ -77,64 +98,82 @@ namespace eval punk::timeinterval { # 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 + if {$maxunit eq "years"} { + 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 add $s1_p0 $y_count years -timezone $timezone] + } + # 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 + # is the base offset incremented one too much? + set s2_y_check [clock add $s1 $y years -timezone $timezone] + if { $s2_y_check > $s2 } { + set y [expr { $y - 1 } ] + set s2_y_check [clock add $s1 $y years -timezone $timezone] + } + # increment s1 (s1_p0) forward y years to s1_p1 + if { $y == 0 } { + set s1_p1 $s1 + } else { + set s1_p1 [clock add $s1 $y years -timezone $timezone] + } } else { - set s1_p1 [clock_scan_interval $s1 $y years] + set y 0 + set s1_p1 $s1 } # 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] + set s1_p2 $s1_p1 ;#? + set m 0 + if {$maxunit in {years months}} { + while { $s2_m_check <= $s2 } { + set s1_p2 $s2_m_check + set m $m_count + incr m_count + set s2_m_check [clock add $s1_p1 $m_count months -timezone $timezone] + } } # 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 + + set d 0 + set s1_p3 $s1_p2 + if {$maxunit in {years months days}} { + # 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 add $s1_p2 $d days -timezone $timezone] } - # 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 + set h 0 + set s1_p4 $s1_p3 + if {$maxunit in {years months days hours}} { + # 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 add $s1_p3 $h hours -timezone $timezone] + # s1_p4 is less than an hour from s2 + } # Sometimes h = 24, yet is already included as a day! @@ -143,81 +182,97 @@ namespace eval punk::timeinterval { # 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 + # 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 + #puts "s2=s1+16007days? [clock format [clock add $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/5A86CA18-593F-4517-BB83-995115A6A402@morth.org # http://www.postgresql.org/message-id/200707060844.l668i89w097496@wwwmaster.postgresql.org # 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 + if {$maxunit in {years months days}} { + # 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 + set mm 0 + set s1_p5 $s1_p4 + if {$maxunit in {years months days hours minutes}} { + # 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 add $s1_p4 $mm minutes -timezone $timezone] + } + + if {$maxunit in {years months days hours}} { + # 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 + if {$maxunit in {years months days hours minutes}} { + # 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 s1_test [clock format $s1 -format "%Y%m%dT%H%M%S"] set signs_inconsistent_p 0 + set diffterms [list] 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" + #append s1_test " +$t_term $unit" + lappend diffterms +$t_term $unit } else { - append s1_test " - [expr { abs( $t_term ) } ] $unit" + #append s1_test " -[expr { abs( $t_term ) } ] $unit" + lappend diffterms -[expr { abs( $t_term ) }] $unit set signs_inconsistent_p 1 } } incr i } - - set s2_test [clock scan $s1_test] + + #set s2_test [clock scan $s1_test] + set s2_test [clock add $s1 {*}$diffterms -timezone $timezone] + # puts "test s2 '$s2_test' from: '$s1_test'" set counter 0 while { $s2 ne $s2_test && $counter < 30 } { @@ -257,29 +312,33 @@ namespace eval punk::timeinterval { 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"] + #set s1_test [clock format $s1 -format "%Y%m%dT%H%M%S"] + set diffterms [list] 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" + #append s1_test " +$t_term $unit" + lappend diffterms +$t_term $unit } else { - append s1_test " - [expr { abs( $t_term ) } ] $unit" + #append s1_test " -[expr { abs( $t_term ) } ] $unit" + lappend diffterms -[expr { abs( $t_term ) }] $unit } } incr i } - set s2_test [clock scan $s1_test] + #set s2_test [clock scan $s1_test] + set s2_test [clock add $s1 {*}$diffterms -timezone $timezone] 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}" + # 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" @@ -328,7 +387,7 @@ namespace eval punk::timeinterval { 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. @@ -342,7 +401,7 @@ namespace eval punk::timeinterval { 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] + set s2_y_check [clock add $s1_p0 $y_count years] } # interval s1_p0 to s1_p1 counted in y years @@ -354,7 +413,7 @@ namespace eval punk::timeinterval { 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] + set s2_m_check [clock add $s1_p1 $m_count months] } # interval s1_p1 to s1_p2 counted in m months @@ -371,7 +430,7 @@ namespace eval punk::timeinterval { } # Move interval from s1_p2 to s1_p3 - set s1_p3 [clock_scan_interval $s1_p2 $d days] + set s1_p3 [clock add $s1_p2 $d days] # s1_p3 is less than a day from s2 @@ -380,7 +439,7 @@ namespace eval punk::timeinterval { # 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] + set s1_p4 [clock add $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 @@ -399,7 +458,7 @@ namespace eval punk::timeinterval { # 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] + set s1_p5 [clock add $s1_p4 $mm minutes] # Sanity check: if 60 minutes, push it up to an hour unit if { $mm <= -60 } { @@ -430,21 +489,25 @@ namespace eval punk::timeinterval { # 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 s1_test [clock format $s1 -format "%Y%m%dT%H%M%S"] set signs_inconsistent_p 0 + set diffterms [list] 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" + #append s1_test " + $t_term $unit" + lappend diffterms +$t_term $unit set signs_inconsistent_p 1 } else { - append s1_test " - [expr { abs( $t_term ) } ] $unit" + #append s1_test " - [expr { abs( $t_term ) } ] $unit" + lappend diffterms -[expr { abs( $t_term ) } ] $unit } } incr i } - set s2_test [clock scan $s1_test] + #set s2_test [clock scan $s1_test] + set s2_test [clock add $s1 {*}$diffterms] set counter 0 while { $s2 ne $s2_test && $counter < 3 } { @@ -484,29 +547,33 @@ namespace eval punk::timeinterval { 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"] + #set s1_test [clock format $s1 -format "%Y%m%dT%H%M%S"] + set diffterms [list] 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" + #append s1_test " + $t_term $unit" + lappend diffterms +$t_term $unit } else { - append s1_test " - [expr { abs( $t_term ) } ] $unit" + #append s1_test " - [expr { abs( $t_term ) } ] $unit" + lappend diffterms -[expr { abs( $t_term ) } ] $unit } } incr i } - set s2_test [clock scan $s1_test] + #set s2_test [clock scan $s1_test] + set s2_test [clock add $s1 {*}$diffterms] 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}" + # 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" @@ -526,7 +593,7 @@ namespace eval punk::timeinterval { 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 v_list [interval_remains_ymdhs $t2 $t1] set i 0 set a "" foreach f {years months days hours minutes seconds} { @@ -543,7 +610,105 @@ namespace eval punk::timeinterval { +# == === === === === === === === === === === === === === === +# Sample 'about' function with punk::args documentation +# == === === === === === === === === === === === === === === +tcl::namespace::eval punk::timeinterval { + tcl::namespace::export {[a-zA-Z]*} ;# Convention: export all lowercase + variable PUNKARGS + variable PUNKARGS_aliases + + lappend PUNKARGS [list { + @id -id "(package)punk::timeinterval" + @package -name "punk::timeinterval" -help\ + "time interval from wiki" + }] + + namespace eval argdoc { + #namespace for custom argument documentation + proc package_name {} { + return punk::timeinterval + } + proc about_topics {} { + #info commands results are returned in an arbitrary order (like array keys) + set topic_funs [info commands [namespace current]::get_topic_*] + set about_topics [list] + foreach f $topic_funs { + set tail [namespace tail $f] + lappend about_topics [string range $tail [string length get_topic_] end] + } + #Adjust this function or 'default_topics' if a different order is required + return [lsort $about_topics] + } + proc default_topics {} {return [list Description *]} + + # ------------------------------------------------------------- + # get_topic_ functions add more to auto-include in about topics + # ------------------------------------------------------------- + proc get_topic_Description {} { + punk::args::lib::tstr [string trim { + package punk::timeinterval + basic time interval calculations + } \n] + } + proc get_topic_License {} { + return "X11" + } + proc get_topic_Version {} { + return "$::punk::timeinterval::version" + } + proc get_topic_Contributors {} { + set authors {{various "https://wiki.tcl-lang.org/page/Measuring+time+intervals+%28between+two+timestamps%29+with+months+etc"} {Julian Noble }} + set contributors "" + foreach a $authors { + append contributors $a \n + } + if {[string index $contributors end] eq "\n"} { + set contributors [string range $contributors 0 end-1] + } + return $contributors + } + proc get_topic_notes {} { + punk::args::lib::tstr -return string { + X11 license - is MIT with additional clause regarding use of contributor names. + } + } + # ------------------------------------------------------------- + } + # we re-use the argument definition from punk::args::standard_about and override some items + set overrides [dict create] + dict set overrides @id -id "::punk::timeinterval::about" + dict set overrides @cmd -name "punk::timeinterval::about" + dict set overrides @cmd -help [string trim [punk::args::lib::tstr { + About punk::timeinterval + }] \n] + dict set overrides topic -choices [list {*}[punk::timeinterval::argdoc::about_topics] *] + dict set overrides topic -choicerestricted 1 + dict set overrides topic -default [punk::timeinterval::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict + set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *] + lappend PUNKARGS [list $newdef] + proc about {args} { + package require punk::args + #standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on + set argd [punk::args::parse $args withid ::punk::timeinterval::about] + lassign [dict values $argd] _leaders opts values _received + punk::args::package::standard_about -package_about_namespace ::punk::timeinterval::argdoc {*}$opts {*}[dict get $values topic] + } +} +# end of sample 'about' function +# == === === === === === === === === === === === === === === + + +# ----------------------------------------------------------------------------- +# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked +# ----------------------------------------------------------------------------- +# variable PUNKARGS +# variable PUNKARGS_aliases +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES ::punk::timeinterval +} @@ -553,9 +718,9 @@ namespace eval punk::timeinterval { # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -## Ready +## Ready package provide punk::timeinterval [namespace eval punk::timeinterval { variable version - set version 999999.0a1.0 + set version 999999.0a1.0 }] return diff --git a/src/modules/winlibreoffice-999999.0a1.0.tm b/src/modules/winlibreoffice-999999.0a1.0.tm index c31fb711..57954a0b 100644 --- a/src/modules/winlibreoffice-999999.0a1.0.tm +++ b/src/modules/winlibreoffice-999999.0a1.0.tm @@ -28,15 +28,16 @@ if {"windows" eq $::tcl_platform(platform)} { puts stderr "Minimal functionality - only some utils may work" } } else { - puts stderr "Package requires twapi. No current equivalent on non-windows platform. Try tcluno http://sf.net/projets/tcluno " + puts stderr "Package requires twapi. No current equivalent on non-windows platform. Try tcluno http://sf.net/projets/tcluno" puts stderr "Minimal functionality - only some utils may work" } # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ namespace eval winlibreoffice { + namespace export from_libre_date to_libre_date #--- - #todo: investigate tcluno package http://sf.net/projects/tcluno + #todo: investigate tcluno package http://sf.net/projects/tcluno #CPlusPlus - platforms? #--- # @@ -48,10 +49,10 @@ namespace eval winlibreoffice { #sometimes a com object may support $obj -print #see also - # $obj -destroy + # $obj -destroy # $obj Quit # $collection -iterate ?options? varname script - + variable uno "" ;# service manager object variable psm "" ;# process service manager @@ -91,7 +92,7 @@ namespace eval winlibreoffice { return $fpath } - # + # proc convertFromUrl {fileuri} { if {[string match "file:/*" $fileuri]} { set finfo [uri::split $fileuri] @@ -100,7 +101,7 @@ namespace eval winlibreoffice { return "//${host}${path}" } else { #the leading slash in path indicates a local path and we strip on windows - set p [dict get $finfo path] + set p [dict get $finfo path] if {[string index $p 0] eq "/"} { set p [string range $p 1 end] } @@ -111,12 +112,12 @@ namespace eval winlibreoffice { #?? review - how are file uris to other hosts handled? error "convertFromUrl doesn't handle non-local file uris on this platform" } else { - return [dict get $finfo path] + return [dict get $finfo path] } } } - } + } # -- --- --- --- # custom functions @@ -127,7 +128,7 @@ namespace eval winlibreoffice { #$dt setName odk_officedev_desk #$dt getName return $dt - } + } proc blankdoc {{type scalc}} { set known_types [list scalc swriter simpress sdraw smath] @@ -139,7 +140,7 @@ namespace eval winlibreoffice { puts "doc title: [$doc Title]" #title can be set with [$doc settitle "titletext"] return $doc - } + } proc file_open_dialog {{title "pick a libreoffice file"}} { set filepicker [createUnoService "com.sun.star.ui.dialogs.FilePicker"] @@ -175,7 +176,7 @@ namespace eval winlibreoffice { $cell setValue $value } proc calccell_setPropertyValue {cell propset} { - $cell setPropertyValue {*}$propset + $cell setPropertyValue {*}$propset #e.g "NumberFormat" 49 # YYYY-MM-DD @@ -192,7 +193,7 @@ namespace eval winlibreoffice { set dec [punk::lib::hex2dec $rgb] $cell setPropertyValue "CharColor" [expr {$dec}] } - + #cell charFontName @@ -201,7 +202,7 @@ namespace eval winlibreoffice { #https://api.libreoffice.org/docs/idl/ref/FontWeight_8idl.html # values are listed with 6 DPs - but one seems to work # only setting to normal and bold seem to result in a value (regular & bold) in the format->font style dialog for the cell. - #DONTKNOW 0.0 + #DONTKNOW 0.0 #THIN 50.0 #ULTRALIGHT 60.0 #LIGHT 75.0 @@ -212,31 +213,132 @@ namespace eval winlibreoffice { #ULTRABOLD 175.0 #BLACK 200.0 + lappend PUNKARGS [list { + @id -id ::winlibreoffice::to_libre_date + @cmd -name winlibreoffice::to_libre_date -help\ + "Return an internal Libre Office date/time floating point + number representing the number of days since 1899-12-30. + + e.g + % to_libre_date 2025-02-28T060000 + 45716.25 + % to_libre_date 2025-01-01T060101 + 45658.250706018516 + " + @opts + -timezone -default "" -help\ + "If unspecified, the timezone will be the + current time zone on the system" + @values -min 1 -max 1 + time -type string -help\ + "A unix timestamp as output by 'clock seconds' + or a text timestamp such as 2025-01-03T000000 + parseable by 'clock scan'" + }] + + proc to_libre_date {args} { + package require punk::args + set argd [punk::args::parse $args withid ::winlibreoffice::to_libre_date] + lassign [dict values $argd] leaders opts values received + + set tz [dict get $opts -timezone] + set time [dict get $values time] + if {![string is integer -strict $time]} { + set ts [clock scan $time -timezone $tz] + } else { + set ts $time + } - - #a hack - #return libreoffice date in days since 1899.. - proc date_from_clockseconds_approx {cs} { variable datebase - set tbase [clock scan $datebase] + set tbase [clock scan $datebase -timezone $tz] package require punk::timeinterval - set diff [punk::timeinterval::difference $tbase $cs] + set info [punk::timeinterval::interval_ymdhs -maxunit days -timezone $tz $tbase $ts] + lassign $info Y m days h m s - set Y [dict get $diff years] - set M [dict get $diff months] - set D [dict get $diff days] - set yeardays [expr 365.25 * $Y] - set monthdays [expr 30.437 * $M] + return [expr {$days + ((($h *3600) + ($m * 60) + $s)/86400.0)}] + } + + lappend PUNKARGS [list { + @id -id ::winlibreoffice::from_libre_date + @cmd -name winlibreoffice::from_libre_date -help\ + "Convert an internal Libre Office date floating point value + representing the number of days since 1899-12-30 to a format + understood by Tcl such as 'clock seconds', 'clock milliseconds' + as specified in the -format option. + " + @opts + -format -default "clockseconds" -choices {clockseconds clockmillis ISO8601} -choicerestricted 0 -help\ + "Aside from the special values listed -format accepts a format string + as accepted by the Tcl 'clock format' command's -format option." + -timezone -default "" -help\ + "If unspecified, the timezone will be the + current time zone on the system" + @values -min 1 -max 1 + libredatetime -type float -help\ + "Floating point number representing the number of + days since 1899-12-30." + }] + #review - we don't expect sci notation for any float values here + #but we could easily get them.. e.g 0.000000001 * 86400.0 => 8.64e-5 + #todo - clockmicros ? + proc from_libre_date {args} { + package require punk::args + set argd [punk::args::parse $args withid ::winlibreoffice::from_libre_date] + lassign [dict values $argd] leaders opts values received + set format [dict get $opts -format] + set tz [dict get $opts -timezone] + set libredatetime [dict get $values libredatetime] - #yes.. this is horrible.. just a test really - but gets in the ballpark. - return [expr int($yeardays + $monthdays + $D)] + variable datebase + set tbase [clock scan $datebase -timezone $tz] + set intdays [expr {int($libredatetime)}] + set fracdays [lindex [split $libredatetime .] 1] + if {$fracdays ne ""} { + set fracdays "0.$fracdays" + set floatsecs [expr {$fracdays * 86400.0}] ;#assuming not a leap-second day + if {$format eq "clockmillis"} { + set wholesecs [expr {int($floatsecs)}] + set msfrac [lindex [split $floatsecs .] 1] + if {$msfrac ne ""} { + set msfrac "0.$msfrac" ;#could also be something like 0.64e-5 which should still work + set ms [expr {round(1000 * $msfrac)}] + if {$ms == 1000} { + set ms 0 + incr wholesecs + } + } else { + set ms 0 + } + } else { + set wholesecs [expr {round($floatsecs)}] + set ms 0 + } + } else { + set wholesecs 0 + set ms 0 + } + + set cs [clock add $tbase +$intdays days +$wholesecs seconds -timezone $tz] + switch -- $format { + clockseconds { + return $cs + } + clockmillis { + return [expr {($cs * 1000) + $ms}] + } + ISO8601 { + set format "%Y%m%dT%H%M%S" + } + } + return [clock format $cs -format $format] } + #time is represented on a scale of 0 to 1 6:00am = 0.25 (24/4) - - proc date_from_clockseconds {cs} { - puts stderr "unimplemented" + #return libreoffice date as a floating point number of days since 1899.. (1899-12-30) + proc to_libre_date_from_clockseconds_gmt {cs} { + return [expr {($cs/86400.0) + 25569}] } #see also: https://wiki.tcl-lang.org/page/Tcom+examples+for+Microsoft+Outlook @@ -265,19 +367,21 @@ namespace eval winlibreoffice { - - - - - - - +# ----------------------------------------------------------------------------- +# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked +# ----------------------------------------------------------------------------- +# variable PUNKARGS +# variable PUNKARGS_aliases +namespace eval ::punk::args::register { + #use fully qualified so 8.6 doesn't find existing var in global namespace + lappend ::punk::args::register::NAMESPACES ::winlibreoffice +} # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ -## Ready +## Ready package provide winlibreoffice [namespace eval winlibreoffice { variable version - set version 999999.0a1.0 + set version 999999.0a1.0 }] return