From 9368c4dbd71c75a744bc911cc1f22522105f95ab Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Wed, 24 Sep 2025 19:39:36 +0200 Subject: [PATCH] added more tests, fixed hour format output --- cmd/config.go | 1 + cmd/times.go | 21 ++++++++++-- cmd/times_test.go | 4 ++- cmd/writer.go | 2 +- cmd/writer_test.go | 81 ++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 4 +++ go.sum | 15 +++++++++ main_test.go | 38 ++++++++++++++++++++++ t/simple.txtar | 10 ++++++ 9 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 cmd/writer_test.go create mode 100644 main_test.go create mode 100644 t/simple.txtar diff --git a/cmd/config.go b/cmd/config.go index 2ace4e4..b86941c 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -92,6 +92,7 @@ type Config struct { Args []string Output io.Writer Mode int + TZ string // for unit tests } func InitConfig(output io.Writer) (*Config, error) { diff --git a/cmd/times.go b/cmd/times.go index 006dc65..8303fe2 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -24,8 +24,8 @@ import ( "strconv" "time" + "github.com/araddon/dateparse" "github.com/ijt/go-anytime" - "github.com/itlightning/dateparse" modnow "github.com/jinzhu/now" ) @@ -65,8 +65,25 @@ func (tp *TimestampProccessor) ProcessTimestamps() error { return nil } -// Parse uses 3 different timestamp parser modules to provide maximum flexibility +// a post processor for ParseTimestamp() to apply custom time zone, if any func (tp *TimestampProccessor) Parse(timestamp string) (time.Time, error) { + ts, err := tp.ParseTimestamp(timestamp) + + if err != nil { + return ts, err + } + + if tp.TZ != "" { + // apply custom timezone + zone, _ := time.LoadLocation(tp.TZ) + ts = ts.In(zone) + } + + return ts, nil +} + +// Parse uses 3 different timestamp parser modules to provide maximum flexibility +func (tp *TimestampProccessor) ParseTimestamp(timestamp string) (time.Time, error) { ts, err := anytime.Parse(timestamp, tp.Reference) if err == nil { return ts, nil diff --git a/cmd/times_test.go b/cmd/times_test.go index 788d827..9db07b7 100644 --- a/cmd/times_test.go +++ b/cmd/times_test.go @@ -65,13 +65,15 @@ func TestParseTimestamps(t *testing.T) { {`One year ago`, now.AddDate(-1, 0, 0)}, {`03:15`, dateAtTime(now, 3, 15, 0)}, {`Wed Sep 25 12:30:00 PM UTC 2025`, now}, + {`Wed Sep 25 00:30:00 PM CEST 2025`, now}, + {`Wed Sep 25 2025 13:30:00 GMT+0100 (GMT Daylight Time)`, now}, } for _, tt := range datetimes { testname := fmt.Sprintf("parsetimestamp-%s", strings.ReplaceAll(tt.input, " ", "-")) t.Run(testname, func(t *testing.T) { var writer bytes.Buffer - tp := NewTP(&Config{Args: []string{tt.input}, Output: &writer}, now) + tp := NewTP(&Config{Args: []string{tt.input}, Output: &writer, TZ: "UTC"}, now) // writer.String() ts, err := tp.Parse(tt.input) diff --git a/cmd/writer.go b/cmd/writer.go index 4710758..b951179 100644 --- a/cmd/writer.go +++ b/cmd/writer.go @@ -57,7 +57,7 @@ func (duration TPduration) String() string { // duration, days, hour, min, sec, msec switch duration.Format { case "d", "day", "days": - return fmt.Sprintf("%.02f%s", duration.Data.Hours()/24+(duration.Data.Minutes()/60), unit) + return fmt.Sprintf("%.02f%s", duration.Data.Hours()/24, unit) case "h", "hour", "hours": return fmt.Sprintf("%.02f%s", duration.Data.Hours(), unit) case "m", "min", "mins", "minutes": diff --git a/cmd/writer_test.go b/cmd/writer_test.go new file mode 100644 index 0000000..37e3678 --- /dev/null +++ b/cmd/writer_test.go @@ -0,0 +1,81 @@ +/* +Copyright © 2025 Thomas von Dein + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package cmd + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestDuration(t *testing.T) { + var tests = []struct { + format string + duration time.Duration + want string + }{ + {"day", time.Hour * 24, "1.00 days"}, + {"dur", time.Hour * 24, "24h0m0s"}, + {"hour", time.Hour * 24, "24.00 hours"}, + {"min", time.Minute * 20, "20.00 minutes"}, + {"min", time.Minute*20 + time.Second*30, "20.50 minutes"}, + {"sec", time.Second * 30, "30.00 seconds"}, + {"ms", time.Second * 30, "30000 milliseconds"}, + } + + for _, tt := range tests { + testname := fmt.Sprintf("formatduration-%s", tt.format) + t.Run(testname, func(t *testing.T) { + tpdur := TPduration{Data: tt.duration} + tpdur.Unit = true + tpdur.Format = tt.format + + out := tpdur.String() + assert.Equal(t, tt.want, out) + }) + } +} + +func TestDatetime(t *testing.T) { + var now = time.Date(2025, 9, 25, 12, 30, 00, 0, time.UTC) + + var tests = []struct { + format string + datetime time.Time + want string + }{ + {"rfc3339", now, "2025-09-25T12:30:00Z"}, + {"date", now, "2025-09-25"}, + {"time", now, "12:30:00"}, + {"unix", now, "1758803400"}, + {"datetime", now, "2025-09-25 12:30:00 +0000 UTC"}, + } + + for _, tt := range tests { + testname := fmt.Sprintf("formatdatetime-%s", tt.format) + t.Run(testname, func(t *testing.T) { + tpdat := TPdatetime{Data: tt.datetime} + tpdat.Format = tt.format + + out := tpdat.String() + assert.Equal(t, tt.want, out) + }) + } +} diff --git a/go.mod b/go.mod index e86d343..6392998 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.23.0 toolchain go1.23.5 require ( + github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/ijt/go-anytime v1.9.2 // indirect @@ -17,7 +18,10 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/testify v1.11.1 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/tools v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 142938b..c3c2fb2 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,6 @@ +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhPwqqXc4/vE0f7GvRjuAsbW+HOIe8KnA= +github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= +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/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= @@ -16,16 +19,28 @@ github.com/knadh/koanf/providers/posflag v1.0.1 h1:EnMxHSrPkYCFnKgBUl5KBgrjed8gV github.com/knadh/koanf/providers/posflag v1.0.1/go.mod h1:3Wn3+YG3f4ljzRyCUgIwH7G0sZ1pMjCOsNBovrbKmAk= github.com/knadh/koanf/v2 v2.3.0 h1:Qg076dDRFHvqnKG97ZEsi9TAg2/nFTa9hCdcSa1lvlM= github.com/knadh/koanf/v2 v2.3.0/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..56924f9 --- /dev/null +++ b/main_test.go @@ -0,0 +1,38 @@ +/* +Copyright © 2025 Thomas von Dein + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package main + +import ( + "testing" + + "github.com/rogpeppe/go-internal/testscript" +) + +// see https://bitfieldconsulting.com/golang/test-scripts + +func TestMain(m *testing.M) { + testscript.Main(m, map[string]func(){ + "ts": main, + }) +} + +func Test_TS(t *testing.T) { + testscript.Run(t, testscript.Params{ + Dir: "t", + }) +} diff --git a/t/simple.txtar b/t/simple.txtar new file mode 100644 index 0000000..fac44d1 --- /dev/null +++ b/t/simple.txtar @@ -0,0 +1,10 @@ +exec ts -h +stdout 'This is ts' + +exec ts -e +stdout 'yesterday' + +exec ts 3/1/2014 +stdout 2014-01-03 + +