Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package klog for openSUSE:Factory checked in 
at 2025-07-02 12:12:58
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/klog (Old)
 and      /work/SRC/openSUSE:Factory/.klog.new.7067 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "klog"

Wed Jul  2 12:12:58 2025 rev:4 rq:1289751 version:6.6

Changes:
--------
--- /work/SRC/openSUSE:Factory/klog/klog.changes        2024-11-30 
13:28:59.448566796 +0100
+++ /work/SRC/openSUSE:Factory/.klog.new.7067/klog.changes      2025-07-02 
12:15:57.574853286 +0200
@@ -1,0 +2,14 @@
+Wed Jul 02 05:20:24 UTC 2025 - Johannes Kastl 
<opensuse_buildserv...@ojkastl.de>
+
+- Update to version 6.6:
+  * [ FEATURE ] Add --chart (-c) flag to klog report command, which
+    includes bar chart renderings in the output, to allow for
+    convenient visual comparison at a glance. (See also --chart-res
+    for the chart resolution.)
+  * [ FEATURE ] Add --with-untagged (-u) flag to klog tags command,
+    which takes into account the remainder of any untagged entries.
+  * [ FIX ] Implement internal protection mechanism against integer
+    overflow. (This, however, is only relevant when dealing with a
+    few trillion years worth of time tracking data.)
+
+-------------------------------------------------------------------

Old:
----
  klog-6.5.obscpio

New:
----
  klog-6.6.obscpio

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ klog.spec ++++++
--- /var/tmp/diff_new_pack.8yTM9r/_old  2025-07-02 12:15:58.410887945 +0200
+++ /var/tmp/diff_new_pack.8yTM9r/_new  2025-07-02 12:15:58.410887945 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package klog
 #
-# Copyright (c) 2024 SUSE LLC
+# Copyright (c) 2025 SUSE LLC
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -17,7 +17,7 @@
 
 
 Name:           klog
-Version:        6.5
+Version:        6.6
 Release:        0
 Summary:        Time tracking in a human-readable, plain-text file format
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.8yTM9r/_old  2025-07-02 12:15:58.446889438 +0200
+++ /var/tmp/diff_new_pack.8yTM9r/_new  2025-07-02 12:15:58.450889604 +0200
@@ -3,19 +3,22 @@
     <param name="url">https://github.com/jotaen/klog</param>
     <param name="scm">git</param>
     <param name="exclude">.git</param>
-    <param name="revision">v6.5</param>
+    <param name="revision">v6.6</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="versionrewrite-pattern">v(.*)</param>
     <param name="changesgenerate">enable</param>
   </service>
   <service name="set_version" mode="manual">
   </service>
-  <service name="tar" mode="buildtime"/>
+  <service name="go_modules" mode="manual">
+    <param name="basename">klog-6.6</param>
+  </service>
+  <!-- services below are running at buildtime -->
+  <service name="tar" mode="buildtime">
+  </service>
   <service name="recompress" mode="buildtime">
     <param name="file">*.tar</param>
     <param name="compression">gz</param>
   </service>
-  <service name="go_modules" mode="manual">
-  </service>
 </services>
 

++++++ _servicedata ++++++
--- /var/tmp/diff_new_pack.8yTM9r/_old  2025-07-02 12:15:58.474890599 +0200
+++ /var/tmp/diff_new_pack.8yTM9r/_new  2025-07-02 12:15:58.478890764 +0200
@@ -1,6 +1,6 @@
 <servicedata>
 <service name="tar_scm">
                 <param name="url">https://github.com/jotaen/klog</param>
-              <param 
name="changesrevision">6f2c7a19f86b701b23cead08ac6b9a917a594e15</param></service></servicedata>
+              <param 
name="changesrevision">7b3cc55b96ab55203a6375c6eb6dff7ec7e12cd5</param></service></servicedata>
 (No newline at EOF)
 

++++++ klog-6.5.obscpio -> klog-6.6.obscpio ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/.github/ISSUE_TEMPLATE/config.yml 
new/klog-6.6/.github/ISSUE_TEMPLATE/config.yml
--- old/klog-6.5/.github/ISSUE_TEMPLATE/config.yml      2024-11-28 
11:22:57.000000000 +0100
+++ new/klog-6.6/.github/ISSUE_TEMPLATE/config.yml      2025-07-01 
17:05:19.000000000 +0200
@@ -1,3 +1,4 @@
+blank_issues_enabled: false
 contact_links:
   - name: Feature ideas, feedback, or questions
     url: https://github.com/jotaen/klog/discussions
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/.github/benchmark.go 
new/klog-6.6/.github/benchmark.go
--- old/klog-6.5/.github/benchmark.go   2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/.github/benchmark.go   2025-07-01 17:05:19.000000000 +0200
@@ -25,6 +25,10 @@
        // Generate records
        date := klog.Ɀ_Date_(0, 1, 1)
        for i := 0; i < iterations; i++ {
+               if date.IsEqualTo(klog.Ɀ_Date_(9999, 12, 31)) {
+                       // Prevent date overflow
+                       date = klog.Ɀ_Date_(0, 1, 1)
+               }
                date = date.PlusDays(1)
                r := klog.NewRecord(date)
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/.github/workflows/ci.yml 
new/klog-6.6/.github/workflows/ci.yml
--- old/klog-6.5/.github/workflows/ci.yml       2024-11-28 11:22:57.000000000 
+0100
+++ new/klog-6.6/.github/workflows/ci.yml       2025-07-01 17:05:19.000000000 
+0200
@@ -1,9 +1,9 @@
 name: CI
 on: [push, pull_request]
 env:
-  GO_VERSION: '1.23'
-  STATIC_CHECK_VERSION: '2024.1.1'
-  COUNT_LOC_DOCKER_IMAGE: 'aldanial/cloc:1.98'
+  GO_VERSION: '1.24'
+  STATIC_CHECK_VERSION: '2025.1'
+  COUNT_LOC_DOCKER_IMAGE: 'aldanial/cloc:2.02'
 jobs:
   statistics:
     name: Statistics
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/.github/workflows/release.yml 
new/klog-6.6/.github/workflows/release.yml
--- old/klog-6.5/.github/workflows/release.yml  2024-11-28 11:22:57.000000000 
+0100
+++ new/klog-6.6/.github/workflows/release.yml  2025-07-01 17:05:19.000000000 
+0200
@@ -6,7 +6,7 @@
         description: 'Release id (tag name)'
         required: true
 env:
-  GO_VERSION: '1.23'
+  GO_VERSION: '1.24'
 jobs:
   create_release:
     name: Create release draft
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/CHANGELOG.md new/klog-6.6/CHANGELOG.md
--- old/klog-6.5/CHANGELOG.md   2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/CHANGELOG.md   2025-07-01 17:05:19.000000000 +0200
@@ -1,6 +1,16 @@
 # Changelog
 **Summary of changes of the command line tool**
 
+## v6.6 (2025-07-01)
+- **[ FEATURE ]** Add `--chart` (`-c`) flag to `klog report` command, which
+  includes bar chart renderings in the output, to allow for convenient visual
+  comparison at a glance. (See also `--chart-res` for the chart resolution.)
+- **[ FEATURE ]** Add `--with-untagged` (`-u`) flag to `klog tags` command,
+  which takes into account the remainder of any untagged entries.
+- **[ FIX ]** Implement internal protection mechanism against integer overflow.
+  (This, however, is only relevant when dealing with a few trillion years 
worth of
+  time tracking data.)
+
 ## v6.5 (2024-11-28)
 - **[ FEATURE ]** Introduce `basic` colour scheme based on the basic 8-bit ANSI
   colours – see `colour_scheme` entry in `config.ini` file. (Run `klog config` 
to
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/go.mod new/klog-6.6/go.mod
--- old/klog-6.5/go.mod 2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/go.mod 2025-07-01 17:05:19.000000000 +0200
@@ -1,15 +1,16 @@
 module github.com/jotaen/klog
 
-go 1.23
+go 1.24
 
 require (
-       cloud.google.com/go v0.116.0
-       github.com/alecthomas/kong v1.4.0
+       cloud.google.com/go v0.121.3
+       github.com/alecthomas/kong v1.12.0
        github.com/jotaen/genie v0.0.1
        github.com/jotaen/kong-completion v0.0.6
+       github.com/jotaen/safemath v0.0.1
        github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
        github.com/posener/complete v1.2.3
-       github.com/stretchr/testify v1.9.0
+       github.com/stretchr/testify v1.10.0
 )
 
 require (
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/go.sum new/klog-6.6/go.sum
--- old/klog-6.5/go.sum 2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/go.sum 2025-07-01 17:05:19.000000000 +0200
@@ -1,16 +1,16 @@
-cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
-cloud.google.com/go v0.116.0/go.mod 
h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
+cloud.google.com/go v0.121.3 h1:84RD+hQXNdY5Sw/MWVAx5O9Aui/rd5VQ9HEcdN19afo=
+cloud.google.com/go v0.121.3/go.mod 
h1:6vWF3nJWRrEUv26mMB3FEIU/o1MQNVPG1iHdisa2SJc=
 github.com/alecthomas/assert/v2 v2.11.0 
h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
 github.com/alecthomas/assert/v2 v2.11.0/go.mod 
h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
-github.com/alecthomas/kong v1.4.0 
h1:UL7tzGMnnY0YRMMvJyITIRX1EpO6RbBRZDNcCevy3HA=
-github.com/alecthomas/kong v1.4.0/go.mod 
h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
+github.com/alecthomas/kong v1.12.0 
h1:oKd/0fHSdajj5PfGDd3ScvEvpVJf9mT2mb5r9xYadYM=
+github.com/alecthomas/kong v1.12.0/go.mod 
h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
 github.com/alecthomas/repr v0.4.0 
h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
 github.com/alecthomas/repr v0.4.0/go.mod 
h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 github.com/davecgh/go-spew v1.1.0/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 
h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod 
h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod 
h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod 
h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/hashicorp/errwrap v1.0.0/go.mod 
h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
 github.com/hashicorp/errwrap v1.1.0 
h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
 github.com/hashicorp/errwrap v1.1.0/go.mod 
h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -23,6 +23,8 @@
 github.com/jotaen/genie v0.0.1/go.mod 
h1:bu+PbJDEJ9915yp4xml7OXoM4iBsSDfgtGVwv5Ag0Gg=
 github.com/jotaen/kong-completion v0.0.6 
h1:VP1KGvXPeB7MytYR+zZQoWw1gf/HIV1/EvWC38BHZN4=
 github.com/jotaen/kong-completion v0.0.6/go.mod 
h1:fuWw9snL6joY5mXbI0Dd5FWEZODaWXAeqaRxo6dAvLk=
+github.com/jotaen/safemath v0.0.1 
h1:YcUhSIUtwQY1rUUT3AeP+alzTHUAsM4Pap8ZMn3GOlc=
+github.com/jotaen/safemath v0.0.1/go.mod 
h1:KlKBnI3qvGcr3+iuvp3vABBZNFRjRcwRUVQa/jM38xQ=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 
h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod 
h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 github.com/pmezard/go-difflib v1.0.0 
h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -33,8 +35,8 @@
 github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab/go.mod 
h1:/PfPXh0EntGc3QAAyUaviy4S9tzy4Zp0e2ilq4voC6E=
 github.com/stretchr/objx v0.1.0/go.mod 
h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.4.0/go.mod 
h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.9.0 
h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
-github.com/stretchr/testify v1.9.0/go.mod 
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.10.0 
h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod 
h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 
h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod 
h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/app/cli/report.go 
new/klog-6.6/klog/app/cli/report.go
--- old/klog-6.5/klog/app/cli/report.go 2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/klog/app/cli/report.go 2025-07-01 17:05:19.000000000 +0200
@@ -8,12 +8,15 @@
        "github.com/jotaen/klog/klog/app/cli/util"
        "github.com/jotaen/klog/klog/service"
        "github.com/jotaen/klog/klog/service/period"
+       "math"
        "strings"
 )
 
 type Report struct {
-       AggregateBy string `name:"aggregate" placeholder:"KIND" short:"a" 
help:"How to aggregate the data. KIND can be 'day' (default), 'week', 'month', 
'quarter' or 'year'." 
enum:"DAY,day,d,WEEK,week,w,MONTH,month,m,QUARTER,quarter,q,YEAR,year,y," 
default:"day"`
-       Fill        bool   `name:"fill" short:"f" help:"Fill the gaps and show 
a consecutive stream."`
+       AggregateBy     string `name:"aggregate" placeholder:"KIND" short:"a" 
help:"How to aggregate the data. KIND can be 'day' (default), 'week', 'month', 
'quarter' or 'year'." 
enum:"DAY,day,d,WEEK,week,w,MONTH,month,m,QUARTER,quarter,q,YEAR,year,y," 
default:"day"`
+       Fill            bool   `name:"fill" short:"f" help:"Fill any calendar 
gaps and show a consecutive sequence of dates."`
+       Chart           bool   `name:"chart" short:"c" help:"Includes a bar 
chart rendering, to aid visual comparison."`
+       ChartResolution int    `name:"chart-res" help:"Configure the chart 
resolution. INT must be a positive integer, denoting the minutes per rendered 
block."`
        util.DiffArgs
        util.FilterArgs
        util.NowArgs
@@ -36,6 +39,10 @@
 func (opt *Report) Run(ctx app.Context) app.Error {
        opt.DecimalArgs.Apply(&ctx)
        opt.NoStyleArgs.Apply(&ctx)
+       cErr := opt.canonicaliseOpts()
+       if cErr != nil {
+               return cErr
+       }
        _, serialiser := ctx.Serialise()
        records, err := ctx.ReadInputs(opt.File...)
        if err != nil {
@@ -51,7 +58,7 @@
                return nErr
        }
        records = service.Sort(records, true)
-       aggregator := opt.findAggregator()
+       aggregator := opt.aggregator()
        recordGroups, dates := groupByDate(aggregator.DateHash, records)
        if opt.Fill {
                dates = allDatesRange(records[0].Date(), 
records[len(records)-1].Date())
@@ -59,10 +66,14 @@
 
        // Table setup
        numberOfValueColumns := func() int {
+               n := 1
                if opt.Diff {
-                       return 3
+                       n += 2
+               }
+               if opt.Chart {
+                       n += 1
                }
-               return 1
+               return n
        }()
        table := tf.NewTable(
                aggregator.NumberOfPrefixColumns()+numberOfValueColumns,
@@ -75,6 +86,9 @@
        if opt.Diff {
                table.CellR("   Should").CellR("    Diff")
        }
+       if opt.Chart {
+               table.Skip(1)
+       }
 
        // Rows
        hashesAlreadyProcessed := make(map[period.Hash]bool)
@@ -99,6 +113,9 @@
                        diff := service.Diff(should, total)
                        
table.CellR(serialiser.ShouldTotal(should)).CellR(serialiser.SignedDuration(diff))
                }
+               if opt.Chart {
+                       table.CellL(" " + renderBar(opt.ChartResolution, total))
+               }
        }
 
        // Line
@@ -106,9 +123,12 @@
        if opt.Diff {
                table.Fill("=").Fill("=")
        }
-       grandTotal := service.Total(records...)
+       if opt.Chart {
+               table.Skip(1)
+       }
 
        // Footer
+       grandTotal := service.Total(records...)
        table.Skip(aggregator.NumberOfPrefixColumns())
        table.CellR(serialiser.Duration(grandTotal))
        if opt.Diff {
@@ -116,21 +136,50 @@
                grandDiff := service.Diff(grandShould, grandTotal)
                
table.CellR(serialiser.ShouldTotal(grandShould)).CellR(serialiser.SignedDuration(grandDiff))
        }
+       if opt.Chart {
+               table.Skip(1)
+       }
 
        table.Collect(ctx.Print)
        opt.WarnArgs.PrintWarnings(ctx, records, opt.GetNowWarnings())
        return nil
 }
 
-func (opt *Report) findAggregator() report.Aggregator {
-       category := (func() string {
-               if opt.AggregateBy == "" {
-                       return "d"
-               } else {
-                       return strings.ToLower(opt.AggregateBy[:1])
-               }
-       })()
-       switch category {
+func (opt *Report) canonicaliseOpts() app.Error {
+       if opt.AggregateBy == "" {
+               opt.AggregateBy = "d"
+       } else {
+               opt.AggregateBy = strings.ToLower(opt.AggregateBy[:1])
+       }
+
+       if opt.ChartResolution == 0 {
+               // If the resolution wasn’t explicitly specified, use a default 
value
+               // that aims for a good balance between granularity and overall 
row width
+               // in the context of the desired aggregation mode.
+               switch opt.AggregateBy {
+               case "y":
+                       opt.ChartResolution = 60 * 8 * 7 // Full working week
+               case "q":
+                       opt.ChartResolution = 60 * 8 // Full working day
+               case "m":
+                       opt.ChartResolution = 60 * 4 // Half working day
+               case "w":
+                       opt.ChartResolution = 60
+               default: // "d"
+                       opt.ChartResolution = 15
+               }
+       } else if opt.ChartResolution > 0 {
+               // When chart resolution is specified, automatically assume 
--chart
+               // to be given as well.
+               opt.Chart = true
+       } else if opt.ChartResolution < 0 {
+               return app.NewErrorWithCode(app.LOGICAL_ERROR, "Invalid 
resolution", "The resolution must be a positive integer", nil)
+       }
+       return nil
+}
+
+func (opt *Report) aggregator() report.Aggregator {
+       switch opt.AggregateBy {
        case "y":
                return report.NewYearAggregator()
        case "q":
@@ -169,3 +218,15 @@
        }
        return days, order
 }
+
+func renderBar(minutesPerUnit int, d klog.Duration) string {
+       block := "▇"
+       blocksCount := func() int {
+               mins := d.InMinutes()
+               if mins <= 0 {
+                       return 0
+               }
+               return int(math.Ceil(float64(mins) / float64(minutesPerUnit)))
+       }()
+       return strings.Repeat(block, blocksCount)
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/app/cli/report_test.go 
new/klog-6.6/klog/app/cli/report_test.go
--- old/klog-6.5/klog/app/cli/report_test.go    2024-11-28 11:22:57.000000000 
+0100
+++ new/klog-6.6/klog/app/cli/report_test.go    2025-07-01 17:05:19.000000000 
+0200
@@ -280,3 +280,228 @@
        15h20m   15h49m!     -29m
 `, state.printBuffer)
 }
+
+func TestReportWithChart(t *testing.T) {
+       t.Run("Daily (default) aggregation", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+2025-01-11
+       -2h
+
+2025-01-11
+       0m
+
+2025-01-13
+       1m
+
+2025-01-14
+       5h
+
+2025-01-16
+       5h1m
+
+2025-01-17
+       5h15m
+
+2025-01-18
+       5h30m
+
+2025-01-20
+       5h51m
+
+2025-01-22
+       6h
+
+2025-01-25
+       9h
+`)._Run((&Report{Chart: true}).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+                       Total                                      
+2025 Jan    Sat 11.      -2h                                      
+            Mon 13.       1m  ▇                                   
+            Tue 14.       5h  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇                
+            Thu 16.     5h1m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇               
+            Fri 17.    5h15m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇               
+            Sat 18.    5h30m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇              
+            Mon 20.    5h51m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇            
+            Wed 22.       6h  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇            
+            Sat 25.       9h  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇
+                    ========                                      
+                      39h38m                                      
+`, state.printBuffer)
+       })
+
+       t.Run("Weekly aggregation", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+2025-01-01
+       40h
+
+2025-01-08
+       48h45m
+
+2025-01-15
+       31h15m
+`)._Run((&Report{Chart: true, AggregateBy: "w", WarnArgs: 
util.WarnArgs{NoWarn: true}}).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+                 Total                                                   
+2025  Week  1      40h  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇         
+      Week  2   48h45m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇
+      Week  3   31h15m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇                 
+              ========                                                   
+                  120h                                                   
+`, state.printBuffer)
+       })
+
+       t.Run("Monthly aggregation", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+2025-01-01
+       173h
+
+2025-02-01
+       208h30m
+
+2025-03-01
+       126h15m
+`)._Run((&Report{Chart: true, AggregateBy: "m", WarnArgs: 
util.WarnArgs{NoWarn: true}}).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+            Total                                                       
+2025 Jan     173h  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇         
+     Feb  208h30m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇
+     Mar  126h15m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇                     
+         ========                                                       
+          507h45m                                                       
+`, state.printBuffer)
+       })
+
+       t.Run("Quarterly aggregation", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+2025-01-01
+       316h
+
+2025-04-01
+       392h30m
+
+2025-07-01
+       237h45m
+`)._Run((&Report{Chart: true, AggregateBy: "q", WarnArgs: 
util.WarnArgs{NoWarn: true}}).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+           Total                                                    
+2025 Q1     316h  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇          
+     Q2  392h30m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇
+     Q3  237h45m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇                    
+        ========                                                    
+         946h15m                                                    
+`, state.printBuffer)
+       })
+
+       t.Run("Yearly aggregation", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+2025-01-01
+       1735h
+
+2026-01-01
+       2154h45m
+
+2027-01-01
+       1189h15m
+`)._Run((&Report{Chart: true, AggregateBy: "y", WarnArgs: 
util.WarnArgs{NoWarn: true}}).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+        Total                                         
+2025    1735h  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇        
+2026 2154h45m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇
+2027 1189h15m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇                 
+     ========                                         
+        5079h                                         
+`, state.printBuffer)
+       })
+}
+
+func TestReportWithScaledChart(t *testing.T) {
+       t.Run("Custom resolution (large)", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+2025-01-14
+       12h
+
+2025-01-16
+       18h37m
+`)._Run((&Report{Chart: true, ChartResolution: 120}).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+                       Total            
+2025 Jan    Tue 14.      12h  ▇▇▇▇▇▇    
+            Thu 16.   18h37m  ▇▇▇▇▇▇▇▇▇▇
+                    ========            
+                      30h37m            
+`, state.printBuffer)
+       })
+
+       t.Run("Custom resolution (small)", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+2025-01-14
+       1h30m
+
+2025-01-16
+       45m
+`)._Run((&Report{Chart: true, ChartResolution: 5}).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+                       Total                    
+2025 Jan    Tue 14.    1h30m  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇
+            Thu 16.      45m  ▇▇▇▇▇▇▇▇▇         
+                    ========                    
+                       2h15m                    
+`, state.printBuffer)
+       })
+
+       t.Run("Setting resolution implies --chart", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+2025-01-14
+       12h
+
+2025-01-16
+       18h37m
+`)._Run((&Report{ChartResolution: 120}).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+                       Total            
+2025 Jan    Tue 14.      12h  ▇▇▇▇▇▇    
+            Thu 16.   18h37m  ▇▇▇▇▇▇▇▇▇▇
+                    ========            
+                      30h37m            
+`, state.printBuffer)
+       })
+
+       t.Run("With --diff flag", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+2025-01-14 (2h!)
+       1h30m
+
+2025-01-16 (1h!)
+       45m
+`)._Run((&Report{Chart: true, DiffArgs: util.DiffArgs{Diff: true}}).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+                       Total    Should     Diff        
+2025 Jan    Tue 14.    1h30m       2h!     -30m  ▇▇▇▇▇▇
+            Thu 16.      45m       1h!     -15m  ▇▇▇   
+                    ======== ========= ========        
+                       2h15m       3h!     -45m        
+`, state.printBuffer)
+       })
+
+       t.Run("Invalid resolution", func(t *testing.T) {
+               _, err := NewTestingContext()._SetRecords(`
+2025-01-14
+       12h
+
+2025-01-16
+       18h37m
+`)._Run((&Report{ChartResolution: -10}).Run)
+               require.Error(t, err)
+               assert.Equal(t, "Invalid resolution", err.Error())
+       })
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/app/cli/tags.go 
new/klog-6.6/klog/app/cli/tags.go
--- old/klog-6.5/klog/app/cli/tags.go   2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/klog/app/cli/tags.go   2025-07-01 17:05:19.000000000 +0200
@@ -9,8 +9,9 @@
 )
 
 type Tags struct {
-       Values bool `name:"values" short:"v" help:"Display breakdown of tag 
values (if the data contains any; e.g.: '#tag=value')."`
-       Count  bool `name:"count" short:"c" help:"Display the number of 
matching entries per tag."`
+       Values       bool `name:"values" short:"v" help:"Display breakdown of 
tag values (if the data contains any; e.g.: '#tag=value')."`
+       Count        bool `name:"count" short:"c" help:"Display the number of 
matching entries per tag."`
+       WithUntagged bool `name:"with-untagged" short:"u" help:"Display 
remainder of any untagged entries"`
        util.FilterArgs
        util.NowArgs
        util.DecimalArgs
@@ -46,10 +47,7 @@
        if nErr != nil {
                return nErr
        }
-       totalByTag := service.AggregateTotalsByTags(records...)
-       if len(totalByTag) == 0 {
-               return nil
-       }
+       tagStats, untagged := service.AggregateTotalsByTags(records...)
        numberOfColumns := 2
        if opt.Values {
                numberOfColumns++
@@ -57,10 +55,12 @@
        if opt.Count {
                numberOfColumns++
        }
+       countString := func(c int) string {
+               return styler.Props(tf.StyleProps{Color: 
tf.TEXT_SUBDUED}).Format(fmt.Sprintf(" (%d)", c))
+       }
        table := tf.NewTable(numberOfColumns, " ")
-       for _, t := range totalByTag {
+       for _, t := range tagStats {
                totalString := serialiser.Duration(t.Total)
-               countString := styler.Props(tf.StyleProps{Color: 
tf.TEXT_SUBDUED}).Format(fmt.Sprintf(" (%d)", t.Count))
                if t.Tag.Value() == "" {
                        table.CellL("#" + t.Tag.Name())
                        table.CellL(totalString)
@@ -68,17 +68,27 @@
                                table.Skip(1)
                        }
                        if opt.Count {
-                               table.CellL(countString)
+                               table.CellL(countString(t.Count))
                        }
                } else if opt.Values {
                        table.CellL(" " + styler.Props(tf.StyleProps{Color: 
tf.TEXT_SUBDUED}).Format(t.Tag.Value()))
                        table.Skip(1)
                        table.CellL(totalString)
                        if opt.Count {
-                               table.CellL(countString)
+                               table.CellL(countString(t.Count))
                        }
                }
        }
+       if opt.WithUntagged {
+               table.CellL("(untagged)")
+               table.CellL(serialiser.Duration(untagged.Total))
+               if opt.Values {
+                       table.Skip(1)
+               }
+               if opt.Count {
+                       table.CellL(countString(untagged.Count))
+               }
+       }
        table.Collect(ctx.Print)
        opt.WarnArgs.PrintWarnings(ctx, records, opt.GetNowWarnings())
        return nil
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/app/cli/tags_test.go 
new/klog-6.6/klog/app/cli/tags_test.go
--- old/klog-6.5/klog/app/cli/tags_test.go      2024-11-28 11:22:57.000000000 
+0100
+++ new/klog-6.6/klog/app/cli/tags_test.go      2025-07-01 17:05:19.000000000 
+0200
@@ -19,12 +19,12 @@
                - Sort output alphabetically
                - Print in tabular manner
        */
-       state, err := NewTestingContext()._SetRecords(`
+       ctx := NewTestingContext()._SetRecords(`
 1995-03-17
 #sports
        3h #badminton
-       1h #running
-       1h #running
+       1h #running=home-trail
+       1h #running=river-route
 
 1995-03-28
 Was #sick, need to compensate later
@@ -38,59 +38,206 @@
 #sports #running (Don’t count that twice!)
        14:00 - 17:00 #sports #running
        
-`)._Run((&Tags{}).Run)
-       require.Nil(t, err)
-       assert.Equal(t, `
+`)
+
+       t.Run("Without argument", func(t *testing.T) {
+               state, err := ctx._Run((&Tags{}).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
 #badminton 3h45m
 #running   4h30m
 #sick      -30m 
 #sports    8h   
 `, state.printBuffer)
+       })
+
+       t.Run("With count", func(t *testing.T) {
+               state, err := ctx._Run((&Tags{
+                       Count: true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+#badminton 3h45m  (2)
+#running   4h30m  (4)
+#sick      -30m   (1)
+#sports    8h     (4)
+`, state.printBuffer)
+       })
+
+       t.Run("With values", func(t *testing.T) {
+               state, err := ctx._Run((&Tags{
+                       Values: true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+#badminton   3h45m   
+#running     4h30m   
+ home-trail        1h
+ river-route       1h
+#sick        -30m    
+#sports      8h      
+`, state.printBuffer)
+       })
+
+       t.Run("With values and count", func(t *testing.T) {
+               state, err := ctx._Run((&Tags{
+                       Values: true,
+                       Count:  true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+#badminton   3h45m     (2)
+#running     4h30m     (4)
+ home-trail        1h  (1)
+ river-route       1h  (1)
+#sick        -30m      (1)
+#sports      8h        (4)
+`, state.printBuffer)
+       })
+
+       t.Run("With untagged", func(t *testing.T) {
+               state, err := ctx._Run((&Tags{
+                       WithUntagged: true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+#badminton 3h45m
+#running   4h30m
+#sick      -30m 
+#sports    8h   
+(untagged) 9h   
+`, state.printBuffer)
+       })
+
+       t.Run("With untagged and count", func(t *testing.T) {
+               state, err := ctx._Run((&Tags{
+                       WithUntagged: true,
+                       Count:        true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+#badminton 3h45m  (2)
+#running   4h30m  (4)
+#sick      -30m   (1)
+#sports    8h     (4)
+(untagged) 9h     (1)
+`, state.printBuffer)
+       })
+
+       t.Run("With values and untagged", func(t *testing.T) {
+               state, err := ctx._Run((&Tags{
+                       Values:       true,
+                       WithUntagged: true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+#badminton   3h45m   
+#running     4h30m   
+ home-trail        1h
+ river-route       1h
+#sick        -30m    
+#sports      8h      
+(untagged)   9h      
+`, state.printBuffer)
+       })
+
+       t.Run("With values and untagged and count", func(t *testing.T) {
+               state, err := ctx._Run((&Tags{
+                       Values:       true,
+                       WithUntagged: true,
+                       Count:        true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+#badminton   3h45m     (2)
+#running     4h30m     (4)
+ home-trail        1h  (1)
+ river-route       1h  (1)
+#sick        -30m      (1)
+#sports      8h        (4)
+(untagged)   9h        (1)
+`, state.printBuffer)
+       })
+}
+
+func TestPrintUntaggedIfNoTags(t *testing.T) {
+       t.Run("No tags present", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+1995-03-17
+       1h
+`)._Run((&Tags{
+                       WithUntagged: true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+(untagged) 1h
+`, state.printBuffer)
+       })
+
+       t.Run("Empty file", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+`)._Run((&Tags{
+                       WithUntagged: true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+(untagged) 0m
+`, state.printBuffer)
+       })
+
+       t.Run("Empty file (with count)", func(t *testing.T) {
+               state, err := NewTestingContext()._SetRecords(`
+`)._Run((&Tags{
+                       WithUntagged: true,
+                       Count:        true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+(untagged) 0m  (0)
+`, state.printBuffer)
+       })
 }
 
 func TestPrintTagsWithUnicodeCharacters(t *testing.T) {
        state, err := NewTestingContext()._SetRecords(`
 1995-03-17
        1h #ascii
-       2h #üñïčödę
+       2h #üñïčöδę
 `)._Run((&Tags{}).Run)
        require.Nil(t, err)
        assert.Equal(t, `
 #ascii   1h
-#üñïčödę 2h
+#üñïčöδę 2h
 `, state.printBuffer)
 }
 
-func TestPrintTagsWithCount(t *testing.T) {
-       state, err := NewTestingContext()._SetRecords(`
+func TestPrintTagsOverviewWithUntaggedEmptyStates(t *testing.T) {
+       ctx := NewTestingContext()._SetRecords(`
 1995-03-17
-#sports
-       3h #badminton
-       1h #running
-       1h #running
-
-1995-03-28
-Was #sick, need to compensate later
-       -30m #running
-
-1995-04-02
-       9h something untagged
-       45m #badminton
+       3h #ticket
+`)
+       t.Run("Include 0 line", func(t *testing.T) {
+               state, err := ctx._Run((&Tags{
+                       WithUntagged: true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+#ticket    3h
+(untagged) 0m
+`, state.printBuffer)
+       })
 
-1995-04-19
-#sports #running (Don’t count that twice!)
-       14:00 - 17:00 #sports #running
-       
-`)._Run((&Tags{
-               Count: true,
-       }).Run)
-       require.Nil(t, err)
-       assert.Equal(t, `
-#badminton 3h45m  (2)
-#running   4h30m  (4)
-#sick      -30m   (1)
-#sports    8h     (4)
+       t.Run("Include 0 count", func(t *testing.T) {
+               state, err := ctx._Run((&Tags{
+                       WithUntagged: true,
+                       Count:        true,
+               }).Run)
+               require.Nil(t, err)
+               assert.Equal(t, `
+#ticket    3h  (1)
+(untagged) 0m  (0)
 `, state.printBuffer)
+       })
 }
 
 func TestPrintTagsOverviewWithValueGrouping(t *testing.T) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/app/cli/terminalformat/table.go 
new/klog-6.6/klog/app/cli/terminalformat/table.go
--- old/klog-6.5/klog/app/cli/terminalformat/table.go   2024-11-28 
11:22:57.000000000 +0100
+++ new/klog-6.6/klog/app/cli/terminalformat/table.go   2025-07-01 
17:05:19.000000000 +0200
@@ -103,5 +103,7 @@
                        }
                }
        }
-       fn("\n")
+       if len(t.cells) > 0 {
+               fn("\n")
+       }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/app/cli/terminalformat/table_test.go 
new/klog-6.6/klog/app/cli/terminalformat/table_test.go
--- old/klog-6.5/klog/app/cli/terminalformat/table_test.go      2024-11-28 
11:22:57.000000000 +0100
+++ new/klog-6.6/klog/app/cli/terminalformat/table_test.go      2025-07-01 
17:05:19.000000000 +0200
@@ -29,6 +29,14 @@
 `, result)
 }
 
+func TestPrintEmptyTable(t *testing.T) {
+       // If the table is empty, it shouldn’t print a trailing newline.
+       result := ""
+       table := NewTable(3, " ")
+       table.Collect(func(x string) { result += x })
+       assert.Equal(t, ``, result)
+}
+
 func TestPrintTableWithUnicode(t *testing.T) {
        result := ""
        table := NewTable(3, " ")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/app/config.go 
new/klog-6.6/klog/app/config.go
--- old/klog-6.5/klog/app/config.go     2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/klog/app/config.go     2025-07-01 17:05:19.000000000 +0200
@@ -207,7 +207,7 @@
                        case string(tf.COLOUR_THEME_BASIC):
                                
config.ColourScheme.override(tf.COLOUR_THEME_BASIC, configOriginFile)
                        default:
-                               return errors.New("The value must be `dark`, 
`light` or `no_colour`")
+                               return errors.New("The value must be `dark`, 
`light`, `basic`, or `no_colour`")
                        }
                        return nil
                },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/date.go new/klog-6.6/klog/date.go
--- old/klog-6.5/klog/date.go   2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/klog/date.go   2025-07-01 17:05:19.000000000 +0200
@@ -98,7 +98,7 @@
        d, err := NewDate(t.Year(), int(t.Month()), t.Day())
        if err != nil {
                // This can/should never occur
-               panic("ILLEGAL_DATE")
+               panic("Illegal date")
        }
        return d
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/date_test.go 
new/klog-6.6/klog/date_test.go
--- old/klog-6.5/klog/date_test.go      2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/klog/date_test.go      2025-07-01 17:05:19.000000000 +0200
@@ -114,6 +114,9 @@
                "20-12-12",
                "asdf",
                "01.01.2000",
+               "⠃⠚⠚⠚-⠁⠃-⠚⠛", // Braille digits
+               "二〇〇〇-一二-〇四", // Japanese digits
+               "᠒᠐᠐᠐-᠑᠒-᠐᠗", // Mongolean digits
        } {
                d, err := NewDateFromString(s)
                assert.Nil(t, d)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/duration.go 
new/klog-6.6/klog/duration.go
--- old/klog-6.5/klog/duration.go       2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/klog/duration.go       2025-07-01 17:05:19.000000000 +0200
@@ -5,6 +5,8 @@
        "fmt"
        "regexp"
        "strconv"
+
+       "github.com/jotaen/safemath/safemath"
 )
 
 // Duration represents a time span.
@@ -49,7 +51,12 @@
 }
 
 func NewDurationWithFormat(amountHours int, amountMinutes int, format 
DurationFormat) Duration {
-       return &duration{minutes: amountHours*60 + amountMinutes, format: 
format}
+       hoursToMins, err1 := safemath.Multiply(amountHours, 60)
+       totalMins, err2 := safemath.Add(hoursToMins, amountMinutes)
+       if err1 != nil || err2 != nil {
+               panic("Integer overflow")
+       }
+       return &duration{minutes: totalMins, format: format}
 }
 
 type duration struct {
@@ -69,11 +76,15 @@
 }
 
 func (d duration) Plus(additional Duration) Duration {
-       return NewDuration(0, d.InMinutes()+additional.InMinutes())
+       mins, err := safemath.Add(d.InMinutes(), additional.InMinutes())
+       if err != nil {
+               panic("Integer overflow")
+       }
+       return NewDuration(0, mins)
 }
 
 func (d duration) Minus(deductible Duration) Duration {
-       return NewDuration(0, d.InMinutes()-deductible.InMinutes())
+       return d.Plus(NewDuration(0, deductible.InMinutes()*-1))
 }
 
 func (d duration) ToString() string {
@@ -111,7 +122,7 @@
        return s
 }
 
-var durationPattern = regexp.MustCompile(`^(-|\+)?((\d+)h)?((\d+)m)?$`)
+var durationPattern = regexp.MustCompile(`^([-+])?((\d+)h)?((\d+)m)?$`)
 
 func NewDurationFromString(hhmm string) (Duration, error) {
        match := durationPattern.FindStringSubmatch(hhmm)
@@ -129,9 +140,15 @@
        if match[3] == "" && match[5] == "" {
                return nil, errors.New("MALFORMED_DURATION")
        }
-       amountOfHours, _ := strconv.Atoi(match[3])
-       amountOfMinutes, _ := strconv.Atoi(match[5])
-       if amountOfHours != 0 && amountOfMinutes >= 60 {
+       amountOfHours, a1Err := strconv.Atoi(match[3])
+       if match[3] != "" && a1Err != nil {
+               panic(a1Err)
+       }
+       amountOfMinutes, a2Err := strconv.Atoi(match[5])
+       if match[5] != "" && a2Err != nil {
+               panic(a2Err)
+       }
+       if match[3] != "" && amountOfMinutes >= 60 {
                return nil, errors.New("UNREPRESENTABLE_DURATION")
        }
        if amountOfHours == 0 && amountOfMinutes == 0 && match[1] != "" {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/duration_test.go 
new/klog-6.6/klog/duration_test.go
--- old/klog-6.5/klog/duration_test.go  2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/klog/duration_test.go  2025-07-01 17:05:19.000000000 +0200
@@ -2,17 +2,21 @@
 
 import (
        "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
        "testing"
 )
 
 func TestSerialiseDurationOnlyWithMeaningfulValues(t *testing.T) {
        assert.Equal(t, "0m", NewDuration(0, 0).ToString())
        assert.Equal(t, "1m", NewDuration(0, 1).ToString())
+       assert.Equal(t, "34m", NewDuration(0, 34).ToString())
+       assert.Equal(t, "59m", NewDuration(0, 59).ToString())
+       assert.Equal(t, "1h", NewDuration(1, 0).ToString())
        assert.Equal(t, "15h", NewDuration(15, 0).ToString())
-}
-
-func TestSerialiseDurationOfLargeHourValues(t *testing.T) {
+       assert.Equal(t, "15h3m", NewDuration(15, 3).ToString())
        assert.Equal(t, "265h45m", NewDuration(265, 45).ToString())
+       assert.Equal(t, "4716278h48m", NewDuration(4716278, 48).ToString())
+       assert.Equal(t, "153722867280912930h7m", NewDuration(0, 
9223372036854775807).ToString())
 }
 
 func TestSerialiseDurationWithoutLeadingZeros(t *testing.T) {
@@ -20,9 +24,11 @@
 }
 
 func TestSerialiseDurationOfNegativeValues(t *testing.T) {
+       assert.Equal(t, "-2h4m", NewDuration(-2, -4).ToString())
        assert.Equal(t, "-3h18m", NewDuration(-3, -18).ToString())
-       assert.Equal(t, "-3h", NewDuration(-3, 0).ToString())
+       assert.Equal(t, "-812747h", NewDuration(-812747, 0).ToString())
        assert.Equal(t, "-18m", NewDuration(0, -18).ToString())
+       assert.Equal(t, "-153722867280912930h7m", NewDuration(0, 
-9223372036854775807).ToString())
 }
 
 func TestSerialiseDurationWithSign(t *testing.T) {
@@ -70,13 +76,19 @@
 }
 
 func TestParsingDurationWithHourValueOnly(t *testing.T) {
-       for _, d := range []string{
-               "13h",
-               "13h0m",
+       for _, d := range []struct {
+               text   string
+               expect Duration
+       }{
+               {"0h", NewDuration(0, 0)},
+               {"1h", NewDuration(1, 0)},
+               {"13h", NewDuration(13, 0)},
+               {"9882187612h", NewDuration(9882187612, 0)},
+               {"13h0m", NewDuration(13, 0)},
        } {
-               duration, err := NewDurationFromString(d)
+               duration, err := NewDurationFromString(d.text)
                assert.Nil(t, err)
-               assert.Equal(t, NewDuration(13, 0), duration)
+               assert.Equal(t, d.expect, duration)
        }
 }
 
@@ -85,12 +97,16 @@
                text   string
                expect Duration
        }{
+               {"1m", NewDuration(0, 1)},
                {"48m", NewDuration(0, 48)},
+               {"59m", NewDuration(0, 59)},
+
                {"0h48m", NewDuration(0, 48)},
 
                // Minutes >60 are okay if there is no hour part present
+               {"60m", NewDuration(1, 0)},
                {"120m", NewDuration(2, 0)},
-               {"150m", NewDuration(2, 30)},
+               {"568721940327m", NewDuration(0, 568721940327)},
        } {
                duration, err := NewDurationFromString(d.text)
                assert.Nil(t, err)
@@ -130,6 +146,9 @@
                "asdf",
                "6h asdf",
                "qwer 30m",
+               "⠙⠛m",   // Braille digits
+               "四二h",   // Japanese digits
+               "᠒h᠐᠒m", // Mongolean digits
        } {
                duration, err := NewDurationFromString(d)
                assert.EqualError(t, err, "MALFORMED_DURATION")
@@ -140,6 +159,7 @@
 func TestParsingFailsWithMinutesGreaterThan60WhenHourPartPresent(t *testing.T) 
{
        for _, d := range []string{
                "1h60m",
+               "0h60m",
                "8h1653m",
                "-8h1653m",
        } {
@@ -148,3 +168,72 @@
                assert.Equal(t, nil, duration)
        }
 }
+
+func TestParsingDurationWithMaxValue(t *testing.T) {
+       t.Run("max", func(t *testing.T) {
+               d, err := NewDurationFromString("9223372036854775807m")
+               require.Nil(t, err)
+               assert.Equal(t, NewDuration(0, 9223372036854775807), d)
+       })
+       t.Run("max", func(t *testing.T) {
+               d, err := NewDurationFromString("153722867280912930h7m")
+               require.Nil(t, err)
+               assert.Equal(t, NewDuration(153722867280912930, 7), d)
+       })
+       t.Run("min", func(t *testing.T) {
+               d, err := NewDurationFromString("-9223372036854775807m")
+               require.Nil(t, err)
+               assert.Equal(t, NewDuration(0, -9223372036854775807), d)
+       })
+       t.Run("max", func(t *testing.T) {
+               d, err := NewDurationFromString("-153722867280912930h7m")
+               require.Nil(t, err)
+               assert.Equal(t, NewDuration(-153722867280912930, -7), d)
+       })
+}
+
+func TestParsingDurationTooBigToRepresent(t *testing.T) {
+       for _, d := range []string{
+               "9223372036854775808m",
+               "-9223372036854775808m",
+               "9223372036854775808h",
+               "-9223372036854775808h",
+               "153722867280912930h08m",
+               "-153722867280912930h08m",
+       } {
+               assert.Panics(t, func() {
+                       _, _ = NewDurationFromString(d)
+               }, d)
+       }
+}
+
+func TestDurationPlusMinus(t *testing.T) {
+       for _, d := range []struct {
+               sum    Duration
+               expect int
+       }{
+               {NewDuration(0, 0).Plus(NewDuration(0, 0)), 0},
+               {NewDuration(0, 0).Plus(NewDuration(0, 1)), 1},
+               {NewDuration(0, 0).Plus(NewDuration(1, 2)), 62},
+               {NewDuration(1382, 9278).Plus(NewDuration(4718, 5010)), 380288},
+               {NewDuration(0, 9223372036854775806).Plus(NewDuration(0, 1)), 
9223372036854775807},
+               {NewDuration(0, 0).Plus(NewDuration(0, -9223372036854775807)), 
-9223372036854775807},
+
+               {NewDuration(0, 0).Minus(NewDuration(0, 0)), 0},
+               {NewDuration(0, 0).Minus(NewDuration(0, 1)), -1},
+               {NewDuration(0, 0).Minus(NewDuration(1, 2)), -62},
+               {NewDuration(1382, 9278).Minus(NewDuration(4718, 5010)), 
-195892},
+       } {
+               assert.Equal(t, d.sum.InMinutes(), d.expect)
+       }
+}
+
+func TestPanicsIfAdditionOverflows(t *testing.T) {
+       assert.Panics(t, func() {
+               NewDuration(0, 9223372036854775807).Plus(NewDuration(0, 1))
+       })
+
+       assert.Panics(t, func() {
+               NewDuration(0, -9223372036854775807).Plus(NewDuration(0, -1))
+       })
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/parser/engine/parallel.go 
new/klog-6.6/klog/parser/engine/parallel.go
--- old/klog-6.5/klog/parser/engine/parallel.go 2024-11-28 11:22:57.000000000 
+0100
+++ new/klog-6.6/klog/parser/engine/parallel.go 2025-07-01 17:05:19.000000000 
+0200
@@ -23,7 +23,7 @@
 
 func (p ParallelBatchParser[T]) Parse(text string) ([]T, []txt.Block, 
[]txt.Error) {
        if p.NumberOfWorkers <= 0 {
-               panic("ILLEGAL_WORKER_SIZE")
+               panic("Illegal number of workers")
        }
        batches := splitIntoChunks(text, p.NumberOfWorkers)
        allResults := p.processAsync(batches, func(batchIndex int, batchText 
string) batchResult[T] {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/parser/reconciling/start_open_range.go 
new/klog-6.6/klog/parser/reconciling/start_open_range.go
--- old/klog-6.5/klog/parser/reconciling/start_open_range.go    2024-11-28 
11:22:57.000000000 +0100
+++ new/klog-6.6/klog/parser/reconciling/start_open_range.go    2025-07-01 
17:05:19.000000000 +0200
@@ -14,7 +14,7 @@
                // Re-parse time to apply format.
                reformattedTime, err := 
klog.NewTimeFromString(startTime.ToStringWithFormat(f))
                if err != nil {
-                       panic("INVALID_TIME")
+                       panic("Invalid time")
                }
                startTime = reformattedTime
        })
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/parser/reconciling/style.go 
new/klog-6.6/klog/parser/reconciling/style.go
--- old/klog-6.5/klog/parser/reconciling/style.go       2024-11-28 
11:22:57.000000000 +0100
+++ new/klog-6.6/klog/parser/reconciling/style.go       2025-07-01 
17:05:19.000000000 +0200
@@ -133,7 +133,7 @@
 // `base` style that had been set explicitly take precedence.
 func elect(base style, rs []klog.Record, bs []txt.Block) *style {
        if len(rs) != len(bs) {
-               panic("ASSERTION_ERROR")
+               panic("Internal error")
        }
        lineEndingElection := newElection[string]()
        indentationElection := newElection[string]()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/service/tags.go 
new/klog-6.6/klog/service/tags.go
--- old/klog-6.5/klog/service/tags.go   2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/klog/service/tags.go   2025-07-01 17:05:19.000000000 +0200
@@ -8,7 +8,7 @@
 type TagStats struct {
        Tag klog.Tag
 
-       // Total is the total duration alloted to the tag.
+       // Total is the total duration allotted to the tag.
        Total klog.Duration
 
        // Count is the total number of matching entries for that tag.
@@ -20,21 +20,32 @@
 
 // AggregateTotalsByTags returns a list of tags (sorted by tag, 
alphanumerically)
 // that contains statistics about the tags appearing in the data.
-func AggregateTotalsByTags(rs ...klog.Record) []*TagStats {
-       result := make(totalByTag)
+func AggregateTotalsByTags(rs ...klog.Record) ([]TagStats, TagStats) {
+       tagStats := make(totalByTag)
+       untagged := TagStats{
+               Tag:        klog.NewTagOrPanic("_", ""),
+               Total:      klog.NewDuration(0, 0),
+               Count:      0,
+               keyForSort: "",
+       }
        for _, r := range rs {
                for _, e := range r.Entries() {
-                       alreadyCounted := make(map[klog.Tag]bool)
                        allTags := klog.Merge(r.Summary().Tags(), 
e.Summary().Tags())
+                       if allTags.IsEmpty() {
+                               untagged.Count += 1
+                               untagged.Total = 
untagged.Total.Plus(e.Duration())
+                               continue
+                       }
+                       alreadyCounted := make(map[klog.Tag]bool)
                        for tag := range allTags.ForLookup() {
                                if alreadyCounted[tag] {
                                        continue
                                }
-                               result.put(tag, e.Duration())
+                               tagStats.put(tag, e.Duration())
                        }
                }
        }
-       return result.toSortedList()
+       return tagStats.toSortedList(), untagged
 }
 
 // Structure: "tagName":"tagValue":TagStats
@@ -59,11 +70,11 @@
        stats.Count++
 }
 
-func (tbt totalByTag) toSortedList() []*TagStats {
-       var result []*TagStats
+func (tbt totalByTag) toSortedList() []TagStats {
+       var result []TagStats
        for _, ts := range tbt {
                for _, t := range ts {
-                       result = append(result, t)
+                       result = append(result, *t)
                }
        }
        sort.Slice(result, func(i int, j int) bool {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/service/tags_test.go 
new/klog-6.6/klog/service/tags_test.go
--- old/klog-6.5/klog/service/tags_test.go      2024-11-28 11:22:57.000000000 
+0100
+++ new/klog-6.6/klog/service/tags_test.go      2025-07-01 17:05:19.000000000 
+0200
@@ -8,29 +8,54 @@
 )
 
 func TestAggregateTotalTimesByTag(t *testing.T) {
-       r := klog.NewRecord(klog.Ɀ_Date_(2020, 1, 1))
-       r.SetSummary(klog.Ɀ_RecordSummary_("#foo"))
-       r.AddDuration(klog.NewDuration(1, 0), klog.Ɀ_EntrySummary_("#foo=1"))
-       r.AddDuration(klog.NewDuration(3, 0), klog.Ɀ_EntrySummary_("#foo"))
-       r.AddDuration(klog.NewDuration(0, 30), klog.Ɀ_EntrySummary_("#test"))
+       rs := []klog.Record{
+               func() klog.Record {
+                       r := klog.NewRecord(klog.Ɀ_Date_(2020, 1, 1))
+                       r.SetSummary(klog.Ɀ_RecordSummary_("#foo"))
+                       r.AddDuration(klog.NewDuration(1, 0), 
klog.Ɀ_EntrySummary_("#foo=1"))
+                       r.AddDuration(klog.NewDuration(3, 0), 
klog.Ɀ_EntrySummary_("#foo"))
+                       r.AddDuration(klog.NewDuration(0, 30), 
klog.Ɀ_EntrySummary_("#test"))
+                       return r
+               }(),
+               func() klog.Record {
+                       r := klog.NewRecord(klog.Ɀ_Date_(2020, 1, 2))
+                       r.AddDuration(klog.NewDuration(1, 0), 
klog.Ɀ_EntrySummary_("#foo=2"))
+                       r.AddDuration(klog.NewDuration(8, 0), 
klog.Ɀ_EntrySummary_("#bar"))
+                       r.AddDuration(klog.NewDuration(0, 45), 
klog.Ɀ_EntrySummary_("no tag"))
+                       return r
+               }(),
+       }
+
+       tagStats, untagged := AggregateTotalsByTags(rs...)
+       require.Len(t, tagStats, 5)
 
-       totals := AggregateTotalsByTags(r)
-       require.Len(t, totals, 3)
+       assert.Equal(t, klog.NewDuration(0, 45), untagged.Total)
+       assert.Equal(t, 1, untagged.Count)
 
        i := 0
-       assert.Equal(t, klog.NewTagOrPanic("foo", ""), totals[i].Tag)
-       assert.Equal(t, klog.NewDuration(4, 30), totals[i].Total)
-       assert.Equal(t, 3, totals[i].Count)
+       assert.Equal(t, klog.NewTagOrPanic("bar", ""), tagStats[i].Tag)
+       assert.Equal(t, klog.NewDuration(8, 0), tagStats[i].Total)
+       assert.Equal(t, 1, tagStats[i].Count)
+
+       i++
+       assert.Equal(t, klog.NewTagOrPanic("foo", ""), tagStats[i].Tag)
+       assert.Equal(t, klog.NewDuration(5, 30), tagStats[i].Total)
+       assert.Equal(t, 4, tagStats[i].Count)
+
+       i++
+       assert.Equal(t, klog.NewTagOrPanic("foo", "1"), tagStats[i].Tag)
+       assert.Equal(t, klog.NewDuration(1, 0), tagStats[i].Total)
+       assert.Equal(t, 1, tagStats[i].Count)
 
        i++
-       assert.Equal(t, klog.NewTagOrPanic("foo", "1"), totals[i].Tag)
-       assert.Equal(t, klog.NewDuration(1, 0), totals[i].Total)
-       assert.Equal(t, 1, totals[i].Count)
+       assert.Equal(t, klog.NewTagOrPanic("foo", "2"), tagStats[i].Tag)
+       assert.Equal(t, klog.NewDuration(1, 0), tagStats[i].Total)
+       assert.Equal(t, 1, tagStats[i].Count)
 
        i++
-       assert.Equal(t, klog.NewTagOrPanic("test", ""), totals[i].Tag)
-       assert.Equal(t, klog.NewDuration(0, 30), totals[i].Total)
-       assert.Equal(t, 1, totals[i].Count)
+       assert.Equal(t, klog.NewTagOrPanic("test", ""), tagStats[i].Tag)
+       assert.Equal(t, klog.NewDuration(0, 30), tagStats[i].Total)
+       assert.Equal(t, 1, tagStats[i].Count)
 }
 
 func TestAggregateTotalIgnoresRedundantTags(t *testing.T) {
@@ -39,23 +64,23 @@
        r.AddDuration(klog.NewDuration(1, 0), klog.Ɀ_EntrySummary_("#foo=1 
#foo"))
        r.AddDuration(klog.NewDuration(3, 0), klog.Ɀ_EntrySummary_("#foo=2 
#foo=1 #foo"))
 
-       totals := AggregateTotalsByTags(r)
-       require.Len(t, totals, 3)
+       tagStats, _ := AggregateTotalsByTags(r)
+       require.Len(t, tagStats, 3)
 
        i := 0
-       assert.Equal(t, klog.NewTagOrPanic("foo", ""), totals[i].Tag)
-       assert.Equal(t, klog.NewDuration(4, 0), totals[i].Total)
-       assert.Equal(t, 2, totals[i].Count)
+       assert.Equal(t, klog.NewTagOrPanic("foo", ""), tagStats[i].Tag)
+       assert.Equal(t, klog.NewDuration(4, 0), tagStats[i].Total)
+       assert.Equal(t, 2, tagStats[i].Count)
 
        i++
-       assert.Equal(t, klog.NewTagOrPanic("foo", "1"), totals[i].Tag)
-       assert.Equal(t, klog.NewDuration(4, 0), totals[i].Total)
-       assert.Equal(t, 2, totals[i].Count)
+       assert.Equal(t, klog.NewTagOrPanic("foo", "1"), tagStats[i].Tag)
+       assert.Equal(t, klog.NewDuration(4, 0), tagStats[i].Total)
+       assert.Equal(t, 2, tagStats[i].Count)
 
        i++
-       assert.Equal(t, klog.NewTagOrPanic("foo", "2"), totals[i].Tag)
-       assert.Equal(t, klog.NewDuration(3, 0), totals[i].Total)
-       assert.Equal(t, 1, totals[i].Count)
+       assert.Equal(t, klog.NewTagOrPanic("foo", "2"), tagStats[i].Tag)
+       assert.Equal(t, klog.NewDuration(3, 0), tagStats[i].Total)
+       assert.Equal(t, 1, tagStats[i].Count)
 }
 
 func TestAggregateTotalTimesByTagSortsAlphabetically(t *testing.T) {
@@ -68,19 +93,19 @@
        r2.AddDuration(klog.NewDuration(0, 30), klog.Ɀ_EntrySummary_("#ccc=1"))
        r2.AddDuration(klog.NewDuration(0, 30), klog.Ɀ_EntrySummary_("#ccc=2"))
 
-       totals := AggregateTotalsByTags(r1, r2)
-       require.Len(t, totals, 6)
+       tagStats, _ := AggregateTotalsByTags(r1, r2)
+       require.Len(t, tagStats, 6)
 
        i := 0
-       assert.Equal(t, klog.NewTagOrPanic("aaa", ""), totals[i].Tag)
+       assert.Equal(t, klog.NewTagOrPanic("aaa", ""), tagStats[i].Tag)
        i += 1
-       assert.Equal(t, klog.NewTagOrPanic("bbb", ""), totals[i].Tag)
+       assert.Equal(t, klog.NewTagOrPanic("bbb", ""), tagStats[i].Tag)
        i += 1
-       assert.Equal(t, klog.NewTagOrPanic("ccc", ""), totals[i].Tag)
+       assert.Equal(t, klog.NewTagOrPanic("ccc", ""), tagStats[i].Tag)
        i += 1
-       assert.Equal(t, klog.NewTagOrPanic("ccc", "1"), totals[i].Tag)
+       assert.Equal(t, klog.NewTagOrPanic("ccc", "1"), tagStats[i].Tag)
        i += 1
-       assert.Equal(t, klog.NewTagOrPanic("ccc", "2"), totals[i].Tag)
+       assert.Equal(t, klog.NewTagOrPanic("ccc", "2"), tagStats[i].Tag)
        i += 1
-       assert.Equal(t, klog.NewTagOrPanic("ddd", ""), totals[i].Tag)
+       assert.Equal(t, klog.NewTagOrPanic("ddd", ""), tagStats[i].Tag)
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/time.go new/klog-6.6/klog/time.go
--- old/klog-6.5/klog/time.go   2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/klog/time.go   2025-07-01 17:05:19.000000000 +0200
@@ -128,7 +128,7 @@
        time, err := NewTime(t.Hour(), t.Minute())
        if err != nil {
                // This can/should never occur
-               panic("ILLEGAL_TIME")
+               panic("Illegal time")
        }
        return time
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/klog-6.5/klog/time_test.go 
new/klog-6.6/klog/time_test.go
--- old/klog-6.5/klog/time_test.go      2024-11-28 11:22:57.000000000 +0100
+++ new/klog-6.6/klog/time_test.go      2025-07-01 17:05:19.000000000 +0200
@@ -207,6 +207,9 @@
                "13:3",   // Minutes must have 2 digits
                "-14:12", // Hours cannot be negative
                "14:-12", // Minutes cannot be negative
+               "⠃⠚:⠙⠛",  // Braille digits
+               "四:二八",   // Japanese digits
+               "᠒᠐:᠑᠒",  // Mongolean digits
        } {
                tm, err := NewTimeFromString(s)
                require.Nil(t, tm, s)

++++++ klog.obsinfo ++++++
--- /var/tmp/diff_new_pack.8yTM9r/_old  2025-07-02 12:15:58.654898062 +0200
+++ /var/tmp/diff_new_pack.8yTM9r/_new  2025-07-02 12:15:58.658898228 +0200
@@ -1,5 +1,5 @@
 name: klog
-version: 6.5
-mtime: 1732789377
-commit: 6f2c7a19f86b701b23cead08ac6b9a917a594e15
+version: 6.6
+mtime: 1751382319
+commit: 7b3cc55b96ab55203a6375c6eb6dff7ec7e12cd5
 

++++++ vendor.tar.gz ++++++
++++ 4339 lines of diff (skipped)

Reply via email to