Doc improvements (#6)

* add default datetime format, fix default format usage
* fixed format tests
* make reference time configurable
* improve doc
This commit is contained in:
T.v.Dein
2025-09-25 22:13:40 +02:00
committed by GitHub
parent ef4cc8b84b
commit d138af85f3
7 changed files with 162 additions and 40 deletions

155
README.md
View File

@@ -2,11 +2,137 @@
generic cli timestamp parser and calculator tool generic cli timestamp parser and calculator tool
## Usage ## Introduction
This little utility is a commandline frontent to the amazing datetime
parser module [anytime](https://github.com/ijt/go-anytime). It uses
two other modules as fallback if anytime might fail:
[now](https://github.com/jinzhu/now) and
[dateparse](github.com/araddon/dateparse).
You can use it to print timestamps from plain english phrases like
`next December 23rd AT 5:25 PM` or `two minutes from now`. In addition
you can calculate the difference between two timestamps and you can
add a duration to a timestamp.
## Example Usage
In these examples the current time is always **2025-09-17T07:30:00+01:00**.
Show current date and time (same as `date`):
```default
% ts now
Wed Sep 17 07:30:00 +0100 2025
```
show timestamp for minus 1 hour
```default
% ts "1 hour ago"
Wed Sep 17 06:30:00 +0100 2025
```
... or from a couple days ago:
```default
% ts "4 days ago"
Sat Sep 13 07:30:00 +0100 2025
```
There are much more ways to get timestamps, see `ts -e`.
We can also add times to timestamps, here we want to know the
timestamp from now plus 10 days and 4 hours in the future:
```default
% ts -a now 10d4h
Sat Sep 27 11:30:00 +0100 2025
```
It doesn't make a difference where you position the `-a` parameter:
```default
% ts now -a 10d4h
Sat Sep 27 11:30:00 +0100 2025
```
Of course you can also calculate the difference between two
dates. Here we have two timestamps (maybe we took them from a log
file) and want to know the dime elapsed between them:
```default ```default
This is ts, a timestamp tool. % ts 2025-09-17T07:30:00+01:00 2025-09-15T12:45:00+01:00
42h45m0s
```
As you can see, if you do not provide a parameter, the default is to
calculate the difference between the two args. To explicitly calculate
the difference, use the `-d` parameter.
You can of course use english phrases for time differences as well:
```default
% ts "today 9 am" 2025-09-15T12:45:00+01:00
44h15m0s
```
Lets talk a little bit about formatting. You may have already
recognized, that `ts` prints either whole timestamps or
durations. Both output types can be modified with the `-f`
parameter. There are predefined formats for timestamps:
```default
% ts now
Wed Sep 17 07:30:00 +0100 2025
% ts now -f rfc3339
2025-09-17T07:30:00+01:00
% ts now -f date
2025-09-17
% ts now -f unix
1758090600
```
But you can also specify your own, you have to follow the [golang
rules for timestamp formats](https://pkg.go.dev/time#Layout),
basically:
* Year: "2006" "06"
* Month: "Jan" "January" "01" "1"
* Day of the week: "Mon" "Monday"
* Day of the month: "2" "_2" "02"
* Day of the year: "__2" "002"
* Hour: "15" "3" "03" (PM or AM)
* Minute: "4" "04"
* Second: "5" "05"
* AM/PM mark: "PM"
for example:
```default
% ts now -f "Mon, 02.January 2006"
Wed, 17.September 2025
```
Ok I admit look is kinda weird, complaints go the the golang dev team
:).
Duration formatting is also customizable. By default a duration looks
like we have seen above: `44h15m0s`. But sometimes we want to know the
number of hours or minutes. Easy:
```default
% ts now 2025-09-15T12:45:00+01:00 -f hours
42.75
% ts now 2025-09-15T12:45:00+01:00 -f minutes
2565.00
```
You may also add the `-u` parameter to have the unit shown as well:
```default
% ts now 2025-09-15T12:45:00+01:00 -f hours -u
42.75 hours
% ts now 2025-09-15T12:45:00+01:00 -f minutes -u
2565.00 minutes
```
## Commandline parameters
Here is the list of all supported parameters:
```default
Usage: ts <time string> [<time string>] Usage: ts <time string> [<time string>]
-d --diff Calculate difference between two timestamps (default). -d --diff Calculate difference between two timestamps (default).
-a --add Add two timestamps (second parameter must be a time). -a --add Add two timestamps (second parameter must be a time).
@@ -21,31 +147,6 @@ Usage: ts <time string> [<time string>]
-e --examples Show examples or supported inputs. -e --examples Show examples or supported inputs.
``` ```
## Examples
```default
# diff between to day and yesterday 10 am
% date && ts today "10am yesterday"
Wed Sep 24 02:05:03 PM CEST 2025
14h0m0s
# show timestamp from a couple days ago
% date && ts "3 days ago"
Wed Sep 24 02:04:42 PM CEST 2025
2025-09-21 14:04:42.428910108 +0200 CEST
# show timestamp of one hour and 45 minutes before (-d is the defaul)
% date && ts -d now 1h45m
Wed Sep 24 02:04:14 PM CEST 2025
2025-09-24 12:19:14.932440045 +0200 CEST
# 10 hours from now
% date && ts --add now 10h
Wed Sep 24 02:03:31 PM CEST 2025
2025-09-25 00:03:31.304518854 +0200 CEST
```
To see a comprehensive list of supported inputs, call `ts -e`.
## Installation ## Installation

View File

@@ -23,6 +23,7 @@ import (
"io" "io"
"log" "log"
"os" "os"
"time"
"github.com/knadh/koanf/providers/posflag" "github.com/knadh/koanf/providers/posflag"
"github.com/knadh/koanf/v2" "github.com/knadh/koanf/v2"
@@ -31,7 +32,7 @@ import (
) )
const ( const (
VERSIONstring = "0.0.3" VERSIONstring = "0.0.4"
Usage string = `This is ts, a timestamp tool. Usage string = `This is ts, a timestamp tool.
Usage: ts <time string> [<time string>] Usage: ts <time string> [<time string>]
@@ -76,8 +77,11 @@ noon Yesterday at 10:15am Mon, 02 Jan 2006 15:
Example durations for second parameter: Example durations for second parameter:
2d1h30m 2 days, one and a half hour 2d1h30m 2 days, one and a half hour
30m 30 minutes` 30m 30 minutes`
ModeDiff int = iota ModeDiff int = iota
ModeAdd ModeAdd
DefaultFormat string = "Mon Jan 02 15:04:05 MST 2006"
) )
type Config struct { type Config struct {
@@ -92,7 +96,10 @@ type Config struct {
Args []string Args []string
Output io.Writer Output io.Writer
Mode int Mode int
TZ string // for unit tests
// internal flags for [unit] tests
tz string // has to be set directly in code
refTime time.Time // must be set via env var $TSREFTIME
} }
func InitConfig(output io.Writer) (*Config, error) { func InitConfig(output io.Writer) (*Config, error) {
@@ -127,11 +134,23 @@ func InitConfig(output io.Writer) (*Config, error) {
} }
// fetch values // fetch values
conf := &Config{Output: output} conf := &Config{Output: output, refTime: time.Now()}
if err := kloader.Unmarshal("", &conf); err != nil { if err := kloader.Unmarshal("", &conf); err != nil {
return nil, fmt.Errorf("error unmarshalling: %w", err) return nil, fmt.Errorf("error unmarshalling: %w", err)
} }
// check internal env var[s], if any
reftime, present := os.LookupEnv("TSREFTIME")
if present {
// e.g: 2014-01-03T00:00:00+01:00
ts, err := time.Parse(time.RFC3339Nano, reftime)
if err != nil {
os.Exit(Die("failed to set reference time from $TSREFTIME", err))
}
conf.refTime = ts
}
// want examples? // want examples?
if conf.Examples { if conf.Examples {
_, err := fmt.Fprintln(output, Examples) _, err := fmt.Fprintln(output, Examples)

View File

@@ -45,9 +45,10 @@ func NewTP(conf *Config, ref ...time.Time) *TimestampProccessor {
modnow.TimeFormats = append(modnow.TimeFormats, formats...) modnow.TimeFormats = append(modnow.TimeFormats, formats...)
tp := &TimestampProccessor{Config: *conf, Reference: time.Now()} tp := &TimestampProccessor{Config: *conf, Reference: conf.refTime}
if len(ref) == 1 { if len(ref) == 1 {
// overwritten externally by unit test
tp.Reference = ref[0] tp.Reference = ref[0]
} }
@@ -73,9 +74,9 @@ func (tp *TimestampProccessor) Parse(timestamp string) (time.Time, error) {
return ts, err return ts, err
} }
if tp.TZ != "" { if tp.tz != "" {
// apply custom timezone // apply custom timezone
zone, _ := time.LoadLocation(tp.TZ) zone, _ := time.LoadLocation(tp.tz)
ts = ts.In(zone) ts = ts.In(zone)
} }
@@ -105,7 +106,8 @@ func (tp *TimestampProccessor) SingleTimestamp(timestamp string) error {
return err return err
} }
tp.Print(ts) tp.Print(TPdatetime{TimestampProccessor: *tp, Data: ts})
//tp.Print(ts)
return nil return nil
} }

View File

@@ -73,7 +73,7 @@ func TestParseTimestamps(t *testing.T) {
testname := fmt.Sprintf("parsetimestamp-%s", strings.ReplaceAll(tt.input, " ", "-")) testname := fmt.Sprintf("parsetimestamp-%s", strings.ReplaceAll(tt.input, " ", "-"))
t.Run(testname, func(t *testing.T) { t.Run(testname, func(t *testing.T) {
var writer bytes.Buffer var writer bytes.Buffer
tp := NewTP(&Config{Args: []string{tt.input}, Output: &writer, TZ: "UTC"}, now) tp := NewTP(&Config{Args: []string{tt.input}, Output: &writer, tz: "UTC"}, now)
// writer.String() // writer.String()
ts, err := tp.Parse(tt.input) ts, err := tp.Parse(tt.input)
@@ -82,7 +82,7 @@ func TestParseTimestamps(t *testing.T) {
err = tp.ProcessTimestamps() err = tp.ProcessTimestamps()
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, tt.want.String()+"\n", writer.String()) assert.EqualValues(t, tt.want.Format(DefaultFormat)+"\n", writer.String())
}) })
} }
} }
@@ -148,7 +148,7 @@ func TestAddTimestamps(t *testing.T) {
err := tp.ProcessTimestamps() err := tp.ProcessTimestamps()
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, tt.want.String()+"\n", writer.String()) assert.EqualValues(t, tt.want.Format(DefaultFormat)+"\n", writer.String())
}) })
} }
} }

View File

@@ -87,7 +87,7 @@ func (datetime TPdatetime) String() string {
case "datetime": case "datetime":
fallthrough fallthrough
case "": case "":
return datetime.Data.String() return datetime.Data.Format(DefaultFormat)
default: default:
return datetime.Data.Format(datetime.Format) return datetime.Data.Format(datetime.Format)
} }

View File

@@ -65,7 +65,7 @@ func TestDatetime(t *testing.T) {
{"date", now, "2025-09-25"}, {"date", now, "2025-09-25"},
{"time", now, "12:30:00"}, {"time", now, "12:30:00"},
{"unix", now, "1758803400"}, {"unix", now, "1758803400"},
{"datetime", now, "2025-09-25 12:30:00 +0000 UTC"}, {"datetime", now, "Thu Sep 25 12:30:00 UTC 2025"},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@@ -5,6 +5,6 @@ exec ts -e
stdout 'yesterday' stdout 'yesterday'
exec ts 3/1/2014 exec ts 3/1/2014
stdout 2014-01-03 stdout 'Fri Jan 03'