From 582d2685e079022cc7be426f9e77180b82545b7d Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 25 Oct 2019 15:39:37 +0200 Subject: [PATCH 01/19] add `tea times` subcommand --- cmd/times.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 1 + 2 files changed, 77 insertions(+) create mode 100644 cmd/times.go diff --git a/cmd/times.go b/cmd/times.go new file mode 100644 index 0000000..f3d6d81 --- /dev/null +++ b/cmd/times.go @@ -0,0 +1,76 @@ +// Copyright 2019 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package cmd + +import ( + "log" + "os" + "strconv" + "time" + + "code.gitea.io/sdk/gitea" + + "github.com/urfave/cli" +) + +// CmdLabels represents to operate repositories' labels. +var CmdTrackedTimes = cli.Command{ + Name: "times", + Usage: `Operate on tracked times of the repository's issues`, + Description: `Operate on tracked times of the repository's issues`, + Action: runTrackedTimes, + Subcommands: []cli.Command{ + // CmdTimeAdd, + }, + Flags: AllDefaultFlags, +} + +func runTrackedTimes(ctx *cli.Context) error { + login, owner, repo := initCommand() + + var times []*gitea.TrackedTime + var err error + + var outputValues [][]string + headers := []string{ + "Index", + "Created", + "Issue", // FIXME: this is the internal issue ID, not the one of the repo.... + "User", // FIXME: we should print a username! + "Duration", + } + + if len(os.Args) == 3 { + times, err = login.Client().GetUserTrackedTimes(owner, repo, os.Args[2]) + } else { + times, err = login.Client().GetRepoTrackedTimes(owner, repo) + } + + if err != nil { + log.Fatal(err) + } + + localLoc, err := time.LoadLocation("Local") + if err != nil { + log.Fatal(err) + } + + for _, t := range times { + + outputValues = append( + outputValues, + []string{ + strconv.FormatInt(t.ID, 10), + t.Created.In(localLoc).Format("2006-01-02 15:04:05"), + "#" + strconv.FormatInt(t.IssueID, 10), + strconv.FormatInt(t.UserID, 10), + time.Duration(1e9 * t.Time).String(), + }, + ) + } + Output(outputValue, headers, outputValues) + + return nil +} diff --git a/main.go b/main.go index 1b22a43..e7da0c1 100644 --- a/main.go +++ b/main.go @@ -41,6 +41,7 @@ func main() { &cmd.CmdReleases, &cmd.CmdRepos, &cmd.CmdLabels, + &cmd.CmdTrackedTimes, } app.EnableBashCompletion = true err := app.Run(os.Args) -- 2.40.1 From 1eadf37b2bd0f3534022be56644f2f2dba819572 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 25 Oct 2019 16:17:54 +0200 Subject: [PATCH 02/19] implement `tea times add` --- cmd/times.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/cmd/times.go b/cmd/times.go index f3d6d81..e6eced6 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -5,9 +5,10 @@ package cmd import ( + "fmt" "log" - "os" "strconv" + "strings" "time" "code.gitea.io/sdk/gitea" @@ -18,11 +19,11 @@ import ( // CmdLabels represents to operate repositories' labels. var CmdTrackedTimes = cli.Command{ Name: "times", - Usage: `Operate on tracked times of the repository's issues`, + Usage: "Operate on tracked times of the repository's issues", Description: `Operate on tracked times of the repository's issues`, Action: runTrackedTimes, Subcommands: []cli.Command{ - // CmdTimeAdd, + CmdTrackedTimesAdd, }, Flags: AllDefaultFlags, } @@ -42,8 +43,9 @@ func runTrackedTimes(ctx *cli.Context) error { "Duration", } - if len(os.Args) == 3 { - times, err = login.Client().GetUserTrackedTimes(owner, repo, os.Args[2]) + user := ctx.Args().First() + if user != "" { + times, err = login.Client().GetUserTrackedTimes(owner, repo, user) } else { times, err = login.Client().GetRepoTrackedTimes(owner, repo) } @@ -58,7 +60,6 @@ func runTrackedTimes(ctx *cli.Context) error { } for _, t := range times { - outputValues = append( outputValues, []string{ @@ -74,3 +75,43 @@ func runTrackedTimes(ctx *cli.Context) error { return nil } + +// CmdIssuesCreate represents a sub command of issues to create issue +var CmdTrackedTimesAdd = cli.Command{ + Name: "add", + Usage: "Track spent time on an issue", + UsageText: "tea times add ", + Description: `Track spent time on an issue + Example: + tea times add 1 1h25m + `, + Action: runTrackedTimesAdd, + Flags: LoginRepoFlags, +} + +func runTrackedTimesAdd(ctx *cli.Context) error { + login, owner, repo := initCommand() + + if len(ctx.Args()) < 2 { + return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText) + } + + issue, err := strconv.ParseInt(ctx.Args().First(), 10, 64) + if err != nil { + log.Fatal(err) + } + + duration, err := time.ParseDuration(strings.Join(ctx.Args().Tail(), "")) + if err != nil { + log.Fatal(err) + } + + _, err = login.Client().AddTime(owner, repo, issue, gitea.AddTimeOption{ + Time: int64(duration.Seconds()), + }) + if err != nil { + log.Fatal(err) + } + + return nil +} -- 2.40.1 From 8ff7ae5b9cbfca1136fab8e29f47bf703d9a946d Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 25 Oct 2019 16:27:49 +0200 Subject: [PATCH 03/19] add --total flag --- cmd/times.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/cmd/times.go b/cmd/times.go index e6eced6..96e3bee 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -25,12 +25,19 @@ var CmdTrackedTimes = cli.Command{ Subcommands: []cli.Command{ CmdTrackedTimesAdd, }, - Flags: AllDefaultFlags, + Flags: append([]cli.Flag{ + // TODO: add --from --to filters on t.Created + cli.BoolFlag{ + Name: "total", + Usage: "Print the total duration at the end", + }, + }, AllDefaultFlags...), } func runTrackedTimes(ctx *cli.Context) error { login, owner, repo := initCommand() + var totalDuration int64 var times []*gitea.TrackedTime var err error @@ -60,6 +67,8 @@ func runTrackedTimes(ctx *cli.Context) error { } for _, t := range times { + totalDuration += t.Time + outputValues = append( outputValues, []string{ @@ -71,8 +80,14 @@ func runTrackedTimes(ctx *cli.Context) error { }, ) } - Output(outputValue, headers, outputValues) + if ctx.Bool("total") { + outputValues = append(outputValues, []string{ + "TOTAL", "", "", "", time.Duration(1e9 * totalDuration).String(), + }) + } + + Output(outputValue, headers, outputValues) return nil } -- 2.40.1 From 58be920759ad3c4ab3ff0b698908edcc0efe0053 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 25 Oct 2019 16:29:17 +0200 Subject: [PATCH 04/19] make app name lower case to make the help texts consistent with the binary name --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index e7da0c1..9b4062e 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,7 @@ func init() { func main() { app := cli.NewApp() - app.Name = "Tea" + app.Name = "tea" app.Usage = "Command line tool to interact with Gitea" app.Description = `` app.Version = Version + formatBuiltWith(Tags) -- 2.40.1 From 600a7aa1de88901d64d6434cb2705cba15666ef1 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 25 Oct 2019 23:05:57 +0200 Subject: [PATCH 05/19] allow filtering by issue index --- cmd/times.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/cmd/times.go b/cmd/times.go index 96e3bee..b98704c 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -16,11 +16,12 @@ import ( "github.com/urfave/cli" ) -// CmdLabels represents to operate repositories' labels. +// CmdTrackedTimes represents the command to operate repositories' times. var CmdTrackedTimes = cli.Command{ Name: "times", Usage: "Operate on tracked times of the repository's issues", Description: `Operate on tracked times of the repository's issues`, + ArgsUsage: "[username | #issue]", Action: runTrackedTimes, Subcommands: []cli.Command{ CmdTrackedTimesAdd, @@ -40,7 +41,6 @@ func runTrackedTimes(ctx *cli.Context) error { var totalDuration int64 var times []*gitea.TrackedTime var err error - var outputValues [][]string headers := []string{ "Index", @@ -51,10 +51,20 @@ func runTrackedTimes(ctx *cli.Context) error { } user := ctx.Args().First() - if user != "" { - times, err = login.Client().GetUserTrackedTimes(owner, repo, user) - } else { + fmt.Println(ctx.Command.ArgsUsage) + if user == "" { + // get all tracked times on the repo times, err = login.Client().GetRepoTrackedTimes(owner, repo) + } else if strings.HasPrefix(user, "#") { + // get all tracked times on the specified issue + issue, err2 := strconv.ParseInt(user[1:], 10, 64) + if err2 != nil { + log.Fatal(err2) + } + times, err = login.Client().ListTrackedTimes(owner, repo, issue) + } else { + // get all tracked times by the specified user + times, err = login.Client().GetUserTrackedTimes(owner, repo, user) } if err != nil { @@ -91,7 +101,7 @@ func runTrackedTimes(ctx *cli.Context) error { return nil } -// CmdIssuesCreate represents a sub command of issues to create issue +// CmdTrackedTimesAdd represents a sub command of times to add time to an issue var CmdTrackedTimesAdd = cli.Command{ Name: "add", Usage: "Track spent time on an issue", @@ -111,7 +121,11 @@ func runTrackedTimesAdd(ctx *cli.Context) error { return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText) } - issue, err := strconv.ParseInt(ctx.Args().First(), 10, 64) + issueStr := ctx.Args().First() + if strings.HasPrefix(issueStr, "#") { + issueStr = issueStr[1:] + } + issue, err := strconv.ParseInt(issueStr, 10, 64) if err != nil { log.Fatal(err) } -- 2.40.1 From ddaa34e3572a6dea7bda3547a6541f5050d3caf3 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 25 Oct 2019 23:28:56 +0200 Subject: [PATCH 06/19] add --from, --until flags --- cmd/times.go | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/cmd/times.go b/cmd/times.go index b98704c..6e01d40 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -28,8 +28,16 @@ var CmdTrackedTimes = cli.Command{ }, Flags: append([]cli.Flag{ // TODO: add --from --to filters on t.Created + cli.StringFlag{ + Name: "from, f", + Usage: "Show only times tracked after this date", + }, + cli.StringFlag{ + Name: "until, u", + Usage: "Show only times tracked before this date", + }, cli.BoolFlag{ - Name: "total", + Name: "total, t", Usage: "Print the total duration at the end", }, }, AllDefaultFlags...), @@ -76,7 +84,28 @@ func runTrackedTimes(ctx *cli.Context) error { log.Fatal(err) } + var from, until time.Time + if ctx.String("from") != "" { + from, err = time.Parse("2006-01-02 15:04:05", ctx.String("from")) + if err != nil { + return err + } + } + if ctx.String("until") != "" { + until, err = time.Parse("2006-01-02 15:04:05", ctx.String("until")) + if err != nil { + return err + } + } + for _, t := range times { + if ctx.String("from") != "" && from.After(t.Created) { + continue + } + if ctx.String("until") != "" && until.Before(t.Created) { + continue + } + totalDuration += t.Time outputValues = append( -- 2.40.1 From 2984ce9588bbeef53c2e0a5395176ed9574ec610 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 25 Oct 2019 23:40:15 +0200 Subject: [PATCH 07/19] use araddon/dateparse for arbitrary date inputs --- cmd/times.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/times.go b/cmd/times.go index 6e01d40..6288da0 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/sdk/gitea" + "github.com/araddon/dateparse" "github.com/urfave/cli" ) @@ -86,13 +87,13 @@ func runTrackedTimes(ctx *cli.Context) error { var from, until time.Time if ctx.String("from") != "" { - from, err = time.Parse("2006-01-02 15:04:05", ctx.String("from")) + from, err = dateparse.ParseLocal(ctx.String("from")) if err != nil { return err } } if ctx.String("until") != "" { - until, err = time.Parse("2006-01-02 15:04:05", ctx.String("until")) + until, err = dateparse.ParseLocal(ctx.String("until")) if err != nil { return err } -- 2.40.1 From ab9197b941911a5844123730268f7805eb6eb7e2 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 25 Oct 2019 23:56:28 +0200 Subject: [PATCH 08/19] vendor araddon/dateparse --- go.mod | 1 + go.sum | 2 + .../github.com/araddon/dateparse/.travis.yml | 14 + vendor/github.com/araddon/dateparse/LICENSE | 21 + vendor/github.com/araddon/dateparse/README.md | 282 +++ .../github.com/araddon/dateparse/parseany.go | 1864 +++++++++++++++++ vendor/modules.txt | 2 + 7 files changed, 2186 insertions(+) create mode 100644 vendor/github.com/araddon/dateparse/.travis.yml create mode 100644 vendor/github.com/araddon/dateparse/LICENSE create mode 100644 vendor/github.com/araddon/dateparse/README.md create mode 100644 vendor/github.com/araddon/dateparse/parseany.go diff --git a/go.mod b/go.mod index fd0861d..edd1744 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.12 require ( code.gitea.io/sdk/gitea v0.11.0 + github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/go-gitea/yaml v0.0.0-20170812160011-eb3733d160e7 github.com/mattn/go-runewidth v0.0.4 // indirect diff --git a/go.sum b/go.sum index 7b94bcf..e18acf8 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,8 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBb github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 h1:c4mLfegoDw6OhSJXTd2jUEQgZUQuJWtocudb97Qn9EM= +github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= diff --git a/vendor/github.com/araddon/dateparse/.travis.yml b/vendor/github.com/araddon/dateparse/.travis.yml new file mode 100644 index 0000000..f071cf9 --- /dev/null +++ b/vendor/github.com/araddon/dateparse/.travis.yml @@ -0,0 +1,14 @@ +language: go + +go: + - 1.10.x + - 1.11.x + +before_install: + - go get -t -v ./... + +script: + - go test -race -coverprofile=coverage.txt -covermode=atomic + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/araddon/dateparse/LICENSE b/vendor/github.com/araddon/dateparse/LICENSE new file mode 100644 index 0000000..f675ed3 --- /dev/null +++ b/vendor/github.com/araddon/dateparse/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2017 Aaron Raddon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/araddon/dateparse/README.md b/vendor/github.com/araddon/dateparse/README.md new file mode 100644 index 0000000..005e456 --- /dev/null +++ b/vendor/github.com/araddon/dateparse/README.md @@ -0,0 +1,282 @@ +Go Date Parser +--------------------------- + +Parse many date strings without knowing format in advance. Uses a scanner to read bytes and use a state machine to find format. Much faster than shotgun based parse methods. See [bench_test.go](https://github.com/araddon/dateparse/blob/master/bench_test.go) for performance comparison. + + +[![Code Coverage](https://codecov.io/gh/araddon/dateparse/branch/master/graph/badge.svg)](https://codecov.io/gh/araddon/dateparse) +[![GoDoc](https://godoc.org/github.com/araddon/dateparse?status.svg)](http://godoc.org/github.com/araddon/dateparse) +[![Build Status](https://travis-ci.org/araddon/dateparse.svg?branch=master)](https://travis-ci.org/araddon/dateparse) +[![Go ReportCard](https://goreportcard.com/badge/araddon/dateparse)](https://goreportcard.com/report/araddon/dateparse) + +**MM/DD/YYYY VS DD/MM/YYYY** Right now this uses mm/dd/yyyy WHEN ambiguous if this is not desired behavior, use `ParseStrict` which will fail on ambiguous date strings. + +**Timezones** The location your server is configured affects the results! See example or https://play.golang.org/p/IDHRalIyXh and last paragraph here https://golang.org/pkg/time/#Parse. + + +```go + +// Normal parse. Equivalent Timezone rules as time.Parse() +t, err := dateparse.ParseAny("3/1/2014") + +// Parse Strict, error on ambigous mm/dd vs dd/mm dates +t, err := dateparse.ParseStrict("3/1/2014") +> returns error + +// Return a string that represents the layout to parse the given date-time. +layout, err := dateparse.ParseFormat("May 8, 2009 5:57:51 PM") +> "Jan 2, 2006 3:04:05 PM" + +``` + +cli tool for testing dateformats +---------------------------------- + +[Date Parse CLI](https://github.com/araddon/dateparse/blob/master/dateparse) + + +Extended example +------------------- + +https://github.com/araddon/dateparse/blob/master/example/main.go + +```go +package main + +import ( + "flag" + "fmt" + "time" + + "github.com/apcera/termtables" + "github.com/araddon/dateparse" +) + +var examples = []string{ + "May 8, 2009 5:57:51 PM", + "oct 7, 1970", + "oct 7, '70", + "oct. 7, 1970", + "oct. 7, 70", + "Mon Jan 2 15:04:05 2006", + "Mon Jan 2 15:04:05 MST 2006", + "Mon Jan 02 15:04:05 -0700 2006", + "Monday, 02-Jan-06 15:04:05 MST", + "Mon, 02 Jan 2006 15:04:05 MST", + "Tue, 11 Jul 2017 16:28:13 +0200 (CEST)", + "Mon, 02 Jan 2006 15:04:05 -0700", + "Thu, 4 Jan 2018 17:53:36 +0000", + "Mon Aug 10 15:44:11 UTC+0100 2015", + "Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time)", + "September 17, 2012 10:09am", + "September 17, 2012 at 10:09am PST-08", + "September 17, 2012, 10:10:09", + "October 7, 1970", + "October 7th, 1970", + "12 Feb 2006, 19:17", + "12 Feb 2006 19:17", + "7 oct 70", + "7 oct 1970", + "03 February 2013", + "1 July 2013", + "2013-Feb-03", + // mm/dd/yy + "3/31/2014", + "03/31/2014", + "08/21/71", + "8/1/71", + "4/8/2014 22:05", + "04/08/2014 22:05", + "4/8/14 22:05", + "04/2/2014 03:00:51", + "8/8/1965 12:00:00 AM", + "8/8/1965 01:00:01 PM", + "8/8/1965 01:00 PM", + "8/8/1965 1:00 PM", + "8/8/1965 12:00 AM", + "4/02/2014 03:00:51", + "03/19/2012 10:11:59", + "03/19/2012 10:11:59.3186369", + // yyyy/mm/dd + "2014/3/31", + "2014/03/31", + "2014/4/8 22:05", + "2014/04/08 22:05", + "2014/04/2 03:00:51", + "2014/4/02 03:00:51", + "2012/03/19 10:11:59", + "2012/03/19 10:11:59.3186369", + // Chinese + "2014年04月08日", + // yyyy-mm-ddThh + "2006-01-02T15:04:05+0000", + "2009-08-12T22:15:09-07:00", + "2009-08-12T22:15:09", + "2009-08-12T22:15:09Z", + // yyyy-mm-dd hh:mm:ss + "2014-04-26 17:24:37.3186369", + "2012-08-03 18:31:59.257000000", + "2014-04-26 17:24:37.123", + "2013-04-01 22:43", + "2013-04-01 22:43:22", + "2014-12-16 06:20:00 UTC", + "2014-12-16 06:20:00 GMT", + "2014-04-26 05:24:37 PM", + "2014-04-26 13:13:43 +0800", + "2014-04-26 13:13:43 +0800 +08", + "2014-04-26 13:13:44 +09:00", + "2012-08-03 18:31:59.257000000 +0000 UTC", + "2015-09-30 18:48:56.35272715 +0000 UTC", + "2015-02-18 00:12:00 +0000 GMT", + "2015-02-18 00:12:00 +0000 UTC", + "2015-02-08 03:02:00 +0300 MSK m=+0.000000001", + "2015-02-08 03:02:00.001 +0300 MSK m=+0.000000001", + "2017-07-19 03:21:51+00:00", + "2014-04-26", + "2014-04", + "2014", + "2014-05-11 08:20:13,787", + // mm.dd.yy + "3.31.2014", + "03.31.2014", + "08.21.71", + "2014.03", + "2014.03.30", + // yyyymmdd and similar + "20140601", + "20140722105203", + // unix seconds, ms, micro, nano + "1332151919", + "1384216367189", + "1384216367111222", + "1384216367111222333", +} + +var ( + timezone = "" +) + +func main() { + flag.StringVar(&timezone, "timezone", "UTC", "Timezone aka `America/Los_Angeles` formatted time-zone") + flag.Parse() + + if timezone != "" { + // NOTE: This is very, very important to understand + // time-parsing in go + loc, err := time.LoadLocation(timezone) + if err != nil { + panic(err.Error()) + } + time.Local = loc + } + + table := termtables.CreateTable() + + table.AddHeaders("Input", "Parsed, and Output as %v") + for _, dateExample := range examples { + t, err := dateparse.ParseLocal(dateExample) + if err != nil { + panic(err.Error()) + } + table.AddRow(dateExample, fmt.Sprintf("%v", t)) + } + fmt.Println(table.Render()) +} + +/* ++-------------------------------------------------------+-----------------------------------------+ +| Input | Parsed, and Output as %v | ++-------------------------------------------------------+-----------------------------------------+ +| May 8, 2009 5:57:51 PM | 2009-05-08 17:57:51 +0000 UTC | +| oct 7, 1970 | 1970-10-07 00:00:00 +0000 UTC | +| oct 7, '70 | 1970-10-07 00:00:00 +0000 UTC | +| oct. 7, 1970 | 1970-10-07 00:00:00 +0000 UTC | +| oct. 7, 70 | 1970-10-07 00:00:00 +0000 UTC | +| Mon Jan 2 15:04:05 2006 | 2006-01-02 15:04:05 +0000 UTC | +| Mon Jan 2 15:04:05 MST 2006 | 2006-01-02 15:04:05 +0000 MST | +| Mon Jan 02 15:04:05 -0700 2006 | 2006-01-02 15:04:05 -0700 -0700 | +| Monday, 02-Jan-06 15:04:05 MST | 2006-01-02 15:04:05 +0000 MST | +| Mon, 02 Jan 2006 15:04:05 MST | 2006-01-02 15:04:05 +0000 MST | +| Tue, 11 Jul 2017 16:28:13 +0200 (CEST) | 2017-07-11 16:28:13 +0200 +0200 | +| Mon, 02 Jan 2006 15:04:05 -0700 | 2006-01-02 15:04:05 -0700 -0700 | +| Thu, 4 Jan 2018 17:53:36 +0000 | 2018-01-04 17:53:36 +0000 UTC | +| Mon Aug 10 15:44:11 UTC+0100 2015 | 2015-08-10 15:44:11 +0000 UTC | +| Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) | 2015-07-03 18:04:07 +0100 GMT | +| September 17, 2012 10:09am | 2012-09-17 10:09:00 +0000 UTC | +| September 17, 2012 at 10:09am PST-08 | 2012-09-17 10:09:00 -0800 PST | +| September 17, 2012, 10:10:09 | 2012-09-17 10:10:09 +0000 UTC | +| October 7, 1970 | 1970-10-07 00:00:00 +0000 UTC | +| October 7th, 1970 | 1970-10-07 00:00:00 +0000 UTC | +| 12 Feb 2006, 19:17 | 2006-02-12 19:17:00 +0000 UTC | +| 12 Feb 2006 19:17 | 2006-02-12 19:17:00 +0000 UTC | +| 7 oct 70 | 1970-10-07 00:00:00 +0000 UTC | +| 7 oct 1970 | 1970-10-07 00:00:00 +0000 UTC | +| 03 February 2013 | 2013-02-03 00:00:00 +0000 UTC | +| 1 July 2013 | 2013-07-01 00:00:00 +0000 UTC | +| 2013-Feb-03 | 2013-02-03 00:00:00 +0000 UTC | +| 3/31/2014 | 2014-03-31 00:00:00 +0000 UTC | +| 03/31/2014 | 2014-03-31 00:00:00 +0000 UTC | +| 08/21/71 | 1971-08-21 00:00:00 +0000 UTC | +| 8/1/71 | 1971-08-01 00:00:00 +0000 UTC | +| 4/8/2014 22:05 | 2014-04-08 22:05:00 +0000 UTC | +| 04/08/2014 22:05 | 2014-04-08 22:05:00 +0000 UTC | +| 4/8/14 22:05 | 2014-04-08 22:05:00 +0000 UTC | +| 04/2/2014 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | +| 8/8/1965 12:00:00 AM | 1965-08-08 00:00:00 +0000 UTC | +| 8/8/1965 01:00:01 PM | 1965-08-08 13:00:01 +0000 UTC | +| 8/8/1965 01:00 PM | 1965-08-08 13:00:00 +0000 UTC | +| 8/8/1965 1:00 PM | 1965-08-08 13:00:00 +0000 UTC | +| 8/8/1965 12:00 AM | 1965-08-08 00:00:00 +0000 UTC | +| 4/02/2014 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | +| 03/19/2012 10:11:59 | 2012-03-19 10:11:59 +0000 UTC | +| 03/19/2012 10:11:59.3186369 | 2012-03-19 10:11:59.3186369 +0000 UTC | +| 2014/3/31 | 2014-03-31 00:00:00 +0000 UTC | +| 2014/03/31 | 2014-03-31 00:00:00 +0000 UTC | +| 2014/4/8 22:05 | 2014-04-08 22:05:00 +0000 UTC | +| 2014/04/08 22:05 | 2014-04-08 22:05:00 +0000 UTC | +| 2014/04/2 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | +| 2014/4/02 03:00:51 | 2014-04-02 03:00:51 +0000 UTC | +| 2012/03/19 10:11:59 | 2012-03-19 10:11:59 +0000 UTC | +| 2012/03/19 10:11:59.3186369 | 2012-03-19 10:11:59.3186369 +0000 UTC | +| 2014年04月08日 | 2014-04-08 00:00:00 +0000 UTC | +| 2006-01-02T15:04:05+0000 | 2006-01-02 15:04:05 +0000 UTC | +| 2009-08-12T22:15:09-07:00 | 2009-08-12 22:15:09 -0700 -0700 | +| 2009-08-12T22:15:09 | 2009-08-12 22:15:09 +0000 UTC | +| 2009-08-12T22:15:09Z | 2009-08-12 22:15:09 +0000 UTC | +| 2014-04-26 17:24:37.3186369 | 2014-04-26 17:24:37.3186369 +0000 UTC | +| 2012-08-03 18:31:59.257000000 | 2012-08-03 18:31:59.257 +0000 UTC | +| 2014-04-26 17:24:37.123 | 2014-04-26 17:24:37.123 +0000 UTC | +| 2013-04-01 22:43 | 2013-04-01 22:43:00 +0000 UTC | +| 2013-04-01 22:43:22 | 2013-04-01 22:43:22 +0000 UTC | +| 2014-12-16 06:20:00 UTC | 2014-12-16 06:20:00 +0000 UTC | +| 2014-12-16 06:20:00 GMT | 2014-12-16 06:20:00 +0000 UTC | +| 2014-04-26 05:24:37 PM | 2014-04-26 17:24:37 +0000 UTC | +| 2014-04-26 13:13:43 +0800 | 2014-04-26 13:13:43 +0800 +0800 | +| 2014-04-26 13:13:43 +0800 +08 | 2014-04-26 13:13:43 +0800 +0800 | +| 2014-04-26 13:13:44 +09:00 | 2014-04-26 13:13:44 +0900 +0900 | +| 2012-08-03 18:31:59.257000000 +0000 UTC | 2012-08-03 18:31:59.257 +0000 UTC | +| 2015-09-30 18:48:56.35272715 +0000 UTC | 2015-09-30 18:48:56.35272715 +0000 UTC | +| 2015-02-18 00:12:00 +0000 GMT | 2015-02-18 00:12:00 +0000 UTC | +| 2015-02-18 00:12:00 +0000 UTC | 2015-02-18 00:12:00 +0000 UTC | +| 2015-02-08 03:02:00 +0300 MSK m=+0.000000001 | 2015-02-08 03:02:00 +0300 +0300 | +| 2015-02-08 03:02:00.001 +0300 MSK m=+0.000000001 | 2015-02-08 03:02:00.001 +0300 +0300 | +| 2017-07-19 03:21:51+00:00 | 2017-07-19 03:21:51 +0000 UTC | +| 2014-04-26 | 2014-04-26 00:00:00 +0000 UTC | +| 2014-04 | 2014-04-01 00:00:00 +0000 UTC | +| 2014 | 2014-01-01 00:00:00 +0000 UTC | +| 2014-05-11 08:20:13,787 | 2014-05-11 08:20:13.787 +0000 UTC | +| 3.31.2014 | 2014-03-31 00:00:00 +0000 UTC | +| 03.31.2014 | 2014-03-31 00:00:00 +0000 UTC | +| 08.21.71 | 1971-08-21 00:00:00 +0000 UTC | +| 2014.03 | 2014-03-01 00:00:00 +0000 UTC | +| 2014.03.30 | 2014-03-30 00:00:00 +0000 UTC | +| 20140601 | 2014-06-01 00:00:00 +0000 UTC | +| 20140722105203 | 2014-07-22 10:52:03 +0000 UTC | +| 1332151919 | 2012-03-19 10:11:59 +0000 UTC | +| 1384216367189 | 2013-11-12 00:32:47.189 +0000 UTC | +| 1384216367111222 | 2013-11-12 00:32:47.111222 +0000 UTC | +| 1384216367111222333 | 2013-11-12 00:32:47.111222333 +0000 UTC | ++-------------------------------------------------------+-----------------------------------------+ +*/ + +``` diff --git a/vendor/github.com/araddon/dateparse/parseany.go b/vendor/github.com/araddon/dateparse/parseany.go new file mode 100644 index 0000000..5e66aa6 --- /dev/null +++ b/vendor/github.com/araddon/dateparse/parseany.go @@ -0,0 +1,1864 @@ +// Package dateparse parses date-strings without knowing the format +// in advance, using a fast lex based approach to eliminate shotgun +// attempts. It leans towards US style dates when there is a conflict. +package dateparse + +import ( + "fmt" + "strconv" + "strings" + "time" + "unicode" + "unicode/utf8" +) + +// func init() { +// gou.SetupLogging("debug") +// gou.SetColorOutput() +// } + +var months = []string{ + "january", + "february", + "march", + "april", + "may", + "june", + "july", + "august", + "september", + "october", + "november", + "december", +} + +type dateState uint8 +type timeState uint8 + +const ( + dateStart dateState = iota // 0 + dateDigit + dateYearDash + dateYearDashAlphaDash + dateYearDashDash + dateYearDashDashWs // 5 + dateYearDashDashT + dateDigitDash + dateDigitDashAlpha + dateDigitDashAlphaDash + dateDigitDot // 10 + dateDigitDotDot + dateDigitSlash + dateDigitChineseYear + dateDigitChineseYearWs + dateDigitWs // 15 + dateDigitWsMoYear + dateDigitWsMolong + dateAlpha + dateAlphaWs + dateAlphaWsDigit // 20 + dateAlphaWsDigitMore + dateAlphaWsDigitMoreWs + dateAlphaWsDigitMoreWsYear + dateAlphaWsMonth + dateAlphaWsMonthMore + dateAlphaWsMonthSuffix + dateAlphaWsMore + dateAlphaWsAtTime + dateAlphaWsAlpha + dateAlphaWsAlphaYearmaybe + dateAlphaPeriodWsDigit + dateWeekdayComma + dateWeekdayAbbrevComma +) +const ( + // Time state + timeIgnore timeState = iota // 0 + timeStart + timeWs + timeWsAlpha + timeWsAlphaWs + timeWsAlphaZoneOffset // 5 + timeWsAlphaZoneOffsetWs + timeWsAlphaZoneOffsetWsYear + timeWsAlphaZoneOffsetWsExtra + timeWsAMPMMaybe + timeWsAMPM // 10 + timeWsOffset + timeWsOffsetWs // 12 + timeWsOffsetColonAlpha + timeWsOffsetColon + timeWsYear // 15 + timeOffset + timeOffsetColon + timeAlpha + timePeriod + timePeriodOffset // 20 + timePeriodOffsetColon + timePeriodOffsetColonWs + timePeriodWs + timePeriodWsAlpha + timePeriodWsOffset // 25 + timePeriodWsOffsetWs + timePeriodWsOffsetWsAlpha + timePeriodWsOffsetColon + timePeriodWsOffsetColonAlpha + timeZ + timeZDigit +) + +var ( + // ErrAmbiguousMMDD for date formats such as 04/02/2014 the mm/dd vs dd/mm are + // ambiguous, so it is an error for strict parse rules. + ErrAmbiguousMMDD = fmt.Errorf("This date has ambiguous mm/dd vs dd/mm type format") +) + +func unknownErr(datestr string) error { + return fmt.Errorf("Could not find format for %q", datestr) +} + +// ParseAny parse an unknown date format, detect the layout. +// Normal parse. Equivalent Timezone rules as time.Parse(). +// NOTE: please see readme on mmdd vs ddmm ambiguous dates. +func ParseAny(datestr string) (time.Time, error) { + p, err := parseTime(datestr, nil) + if err != nil { + return time.Time{}, err + } + return p.parse() +} + +// ParseIn with Location, equivalent to time.ParseInLocation() timezone/offset +// rules. Using location arg, if timezone/offset info exists in the +// datestring, it uses the given location rules for any zone interpretation. +// That is, MST means one thing when using America/Denver and something else +// in other locations. +func ParseIn(datestr string, loc *time.Location) (time.Time, error) { + p, err := parseTime(datestr, loc) + if err != nil { + return time.Time{}, err + } + return p.parse() +} + +// ParseLocal Given an unknown date format, detect the layout, +// using time.Local, parse. +// +// Set Location to time.Local. Same as ParseIn Location but lazily uses +// the global time.Local variable for Location argument. +// +// denverLoc, _ := time.LoadLocation("America/Denver") +// time.Local = denverLoc +// +// t, err := dateparse.ParseLocal("3/1/2014") +// +// Equivalent to: +// +// t, err := dateparse.ParseIn("3/1/2014", denverLoc) +// +func ParseLocal(datestr string) (time.Time, error) { + p, err := parseTime(datestr, time.Local) + if err != nil { + return time.Time{}, err + } + return p.parse() +} + +// MustParse parse a date, and panic if it can't be parsed. Used for testing. +// Not recommended for most use-cases. +func MustParse(datestr string) time.Time { + p, err := parseTime(datestr, nil) + if err != nil { + panic(err.Error()) + } + t, err := p.parse() + if err != nil { + panic(err.Error()) + } + return t +} + +// ParseFormat parse's an unknown date-time string and returns a layout +// string that can parse this (and exact same format) other date-time strings. +// +// layout, err := dateparse.ParseFormat("2013-02-01 00:00:00") +// // layout = "2006-01-02 15:04:05" +// +func ParseFormat(datestr string) (string, error) { + p, err := parseTime(datestr, nil) + if err != nil { + return "", err + } + _, err = p.parse() + if err != nil { + return "", err + } + return string(p.format), nil +} + +// ParseStrict parse an unknown date format. IF the date is ambigous +// mm/dd vs dd/mm then return an error. These return errors: 3.3.2014 , 8/8/71 etc +func ParseStrict(datestr string) (time.Time, error) { + p, err := parseTime(datestr, nil) + if err != nil { + return time.Time{}, err + } + if p.ambiguousMD { + return time.Time{}, ErrAmbiguousMMDD + } + return p.parse() +} + +func parseTime(datestr string, loc *time.Location) (*parser, error) { + + p := newParser(datestr, loc) + i := 0 + + // General strategy is to read rune by rune through the date looking for + // certain hints of what type of date we are dealing with. + // Hopefully we only need to read about 5 or 6 bytes before + // we figure it out and then attempt a parse +iterRunes: + for ; i < len(datestr); i++ { + //r := rune(datestr[i]) + r, bytesConsumed := utf8.DecodeRuneInString(datestr[i:]) + if bytesConsumed > 1 { + i += (bytesConsumed - 1) + } + + //gou.Debugf("i=%d r=%s state=%d %s", i, string(r), p.stateDate, datestr) + switch p.stateDate { + case dateStart: + if unicode.IsDigit(r) { + p.stateDate = dateDigit + } else if unicode.IsLetter(r) { + p.stateDate = dateAlpha + } else { + return nil, unknownErr(datestr) + } + case dateDigit: + + switch r { + case '-', '\u2212': + // 2006-01-02 + // 2013-Feb-03 + // 13-Feb-03 + // 29-Jun-2016 + if i == 4 { + p.stateDate = dateYearDash + p.yeari = 0 + p.yearlen = i + p.moi = i + 1 + p.set(0, "2006") + } else { + p.stateDate = dateDigitDash + } + case '/': + // 03/31/2005 + // 2014/02/24 + p.stateDate = dateDigitSlash + if i == 4 { + p.yearlen = i + p.moi = i + 1 + p.setYear() + } else { + p.ambiguousMD = true + if p.preferMonthFirst { + if p.molen == 0 { + p.molen = i + p.setMonth() + p.dayi = i + 1 + } + } + } + + case '.': + // 3.31.2014 + // 08.21.71 + // 2014.05 + p.stateDate = dateDigitDot + if i == 4 { + p.yearlen = i + p.moi = i + 1 + p.setYear() + } else { + p.ambiguousMD = true + p.moi = 0 + p.molen = i + p.setMonth() + p.dayi = i + 1 + } + + case ' ': + // 18 January 2018 + // 8 January 2018 + // 8 jan 2018 + // 02 Jan 2018 23:59 + // 02 Jan 2018 23:59:34 + // 12 Feb 2006, 19:17 + // 12 Feb 2006, 19:17:22 + p.stateDate = dateDigitWs + p.dayi = 0 + p.daylen = i + case '年': + // Chinese Year + p.stateDate = dateDigitChineseYear + case ',': + return nil, unknownErr(datestr) + default: + continue + } + p.part1Len = i + + case dateYearDash: + // dateYearDashDashT + // 2006-01-02T15:04:05Z07:00 + // dateYearDashDashWs + // 2013-04-01 22:43:22 + // dateYearDashAlphaDash + // 2013-Feb-03 + switch r { + case '-': + p.molen = i - p.moi + p.dayi = i + 1 + p.stateDate = dateYearDashDash + p.setMonth() + default: + if unicode.IsLetter(r) { + p.stateDate = dateYearDashAlphaDash + } + } + + case dateYearDashDash: + // dateYearDashDashT + // 2006-01-02T15:04:05Z07:00 + // dateYearDashDashWs + // 2013-04-01 22:43:22 + switch r { + case ' ': + p.daylen = i - p.dayi + p.stateDate = dateYearDashDashWs + p.stateTime = timeStart + p.setDay() + break iterRunes + case 'T': + p.daylen = i - p.dayi + p.stateDate = dateYearDashDashT + p.stateTime = timeStart + p.setDay() + break iterRunes + } + case dateYearDashAlphaDash: + // 2013-Feb-03 + switch r { + case '-': + p.molen = i - p.moi + p.set(p.moi, "Jan") + p.dayi = i + 1 + } + case dateDigitDash: + // 13-Feb-03 + // 29-Jun-2016 + if unicode.IsLetter(r) { + p.stateDate = dateDigitDashAlpha + p.moi = i + } else { + return nil, unknownErr(datestr) + } + case dateDigitDashAlpha: + // 13-Feb-03 + // 28-Feb-03 + // 29-Jun-2016 + switch r { + case '-': + p.molen = i - p.moi + p.set(p.moi, "Jan") + p.yeari = i + 1 + p.stateDate = dateDigitDashAlphaDash + } + + case dateDigitDashAlphaDash: + // 13-Feb-03 ambiguous + // 28-Feb-03 ambiguous + // 29-Jun-2016 + switch r { + case ' ': + // we need to find if this was 4 digits, aka year + // or 2 digits which makes it ambiguous year/day + length := i - (p.moi + p.molen + 1) + if length == 4 { + p.yearlen = 4 + p.set(p.yeari, "2006") + // We now also know that part1 was the day + p.dayi = 0 + p.daylen = p.part1Len + p.setDay() + } else if length == 2 { + // We have no idea if this is + // yy-mon-dd OR dd-mon-yy + // + // We are going to ASSUME (bad, bad) that it is dd-mon-yy which is a horible assumption + p.ambiguousMD = true + p.yearlen = 2 + p.set(p.yeari, "06") + // We now also know that part1 was the day + p.dayi = 0 + p.daylen = p.part1Len + p.setDay() + } + p.stateTime = timeStart + break iterRunes + } + + case dateDigitSlash: + // 2014/07/10 06:55:38.156283 + // 03/19/2012 10:11:59 + // 04/2/2014 03:00:37 + // 3/1/2012 10:11:59 + // 4/8/2014 22:05 + // 3/1/2014 + // 10/13/2014 + // 01/02/2006 + // 1/2/06 + + switch r { + case ' ': + p.stateTime = timeStart + if p.yearlen == 0 { + p.yearlen = i - p.yeari + p.setYear() + } else if p.daylen == 0 { + p.daylen = i - p.dayi + p.setDay() + } + break iterRunes + case '/': + if p.yearlen > 0 { + // 2014/07/10 06:55:38.156283 + if p.molen == 0 { + p.molen = i - p.moi + p.setMonth() + p.dayi = i + 1 + } + } else if p.preferMonthFirst { + if p.daylen == 0 { + p.daylen = i - p.dayi + p.setDay() + p.yeari = i + 1 + } + } + } + + case dateDigitWs: + // 18 January 2018 + // 8 January 2018 + // 8 jan 2018 + // 1 jan 18 + // 02 Jan 2018 23:59 + // 02 Jan 2018 23:59:34 + // 12 Feb 2006, 19:17 + // 12 Feb 2006, 19:17:22 + switch r { + case ' ': + p.yeari = i + 1 + //p.yearlen = 4 + p.dayi = 0 + p.daylen = p.part1Len + p.setDay() + p.stateTime = timeStart + if i > p.daylen+len(" Sep") { // November etc + // If len greather than space + 3 it must be full month + p.stateDate = dateDigitWsMolong + } else { + // If len=3, the might be Feb or May? Ie ambigous abbreviated but + // we can parse may with either. BUT, that means the + // format may not be correct? + // mo := strings.ToLower(datestr[p.daylen+1 : i]) + p.moi = p.daylen + 1 + p.molen = i - p.moi + p.set(p.moi, "Jan") + p.stateDate = dateDigitWsMoYear + } + } + + case dateDigitWsMoYear: + // 8 jan 2018 + // 02 Jan 2018 23:59 + // 02 Jan 2018 23:59:34 + // 12 Feb 2006, 19:17 + // 12 Feb 2006, 19:17:22 + switch r { + case ',': + p.yearlen = i - p.yeari + p.setYear() + i++ + break iterRunes + case ' ': + p.yearlen = i - p.yeari + p.setYear() + break iterRunes + } + case dateDigitWsMolong: + // 18 January 2018 + // 8 January 2018 + + case dateDigitChineseYear: + // dateDigitChineseYear + // 2014年04月08日 + // weekday %Y年%m月%e日 %A %I:%M %p + // 2013年07月18日 星期四 10:27 上午 + if r == ' ' { + p.stateDate = dateDigitChineseYearWs + break + } + case dateDigitDot: + // This is the 2nd period + // 3.31.2014 + // 08.21.71 + // 2014.05 + // 2018.09.30 + if r == '.' { + if p.moi == 0 { + // 3.31.2014 + p.daylen = i - p.dayi + p.yeari = i + 1 + p.setDay() + p.stateDate = dateDigitDotDot + } else { + // 2018.09.30 + //p.molen = 2 + p.molen = i - p.moi + p.dayi = i + 1 + p.setMonth() + p.stateDate = dateDigitDotDot + } + } + case dateDigitDotDot: + // iterate all the way through + case dateAlpha: + // dateAlphaWS + // Mon Jan _2 15:04:05 2006 + // Mon Jan _2 15:04:05 MST 2006 + // Mon Jan 02 15:04:05 -0700 2006 + // Mon Aug 10 15:44:11 UTC+0100 2015 + // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) + // dateAlphaWSDigit + // May 8, 2009 5:57:51 PM + // oct 1, 1970 + // dateAlphaWsMonth + // April 8, 2009 + // dateAlphaWsMore + // dateAlphaWsAtTime + // January 02, 2006 at 3:04pm MST-07 + // + // dateAlphaPeriodWsDigit + // oct. 1, 1970 + // dateWeekdayComma + // Monday, 02 Jan 2006 15:04:05 MST + // Monday, 02-Jan-06 15:04:05 MST + // Monday, 02 Jan 2006 15:04:05 -0700 + // Monday, 02 Jan 2006 15:04:05 +0100 + // dateWeekdayAbbrevComma + // Mon, 02 Jan 2006 15:04:05 MST + // Mon, 02 Jan 2006 15:04:05 -0700 + // Thu, 13 Jul 2017 08:58:40 +0100 + // Tue, 11 Jul 2017 16:28:13 +0200 (CEST) + // Mon, 02-Jan-06 15:04:05 MST + switch { + case r == ' ': + // X + // April 8, 2009 + if i > 3 { + // Check to see if the alpha is name of month? or Day? + month := strings.ToLower(datestr[0:i]) + if isMonthFull(month) { + p.fullMonth = month + // len(" 31, 2018") = 9 + if len(datestr[i:]) < 10 { + // April 8, 2009 + p.stateDate = dateAlphaWsMonth + } else { + p.stateDate = dateAlphaWsMore + } + p.dayi = i + 1 + break + } + + } else { + // This is possibly ambiguous? May will parse as either though. + // So, it could return in-correct format. + // May 05, 2005, 05:05:05 + // May 05 2005, 05:05:05 + // Jul 05, 2005, 05:05:05 + p.stateDate = dateAlphaWs + } + + case r == ',': + // Mon, 02 Jan 2006 + // p.moi = 0 + // p.molen = i + if i == 3 { + p.stateDate = dateWeekdayAbbrevComma + p.set(0, "Mon") + } else { + p.stateDate = dateWeekdayComma + p.skip = i + 2 + i++ + // TODO: lets just make this "skip" as we don't need + // the mon, monday, they are all superfelous and not needed + // just lay down the skip, no need to fill and then skip + } + case r == '.': + // sept. 28, 2017 + // jan. 28, 2017 + p.stateDate = dateAlphaPeriodWsDigit + if i == 3 { + p.molen = i + p.set(0, "Jan") + } else if i == 4 { + // gross + datestr = datestr[0:i-1] + datestr[i:] + return parseTime(datestr, loc) + } else { + return nil, unknownErr(datestr) + } + } + + case dateAlphaWs: + // dateAlphaWsAlpha + // Mon Jan _2 15:04:05 2006 + // Mon Jan _2 15:04:05 MST 2006 + // Mon Jan 02 15:04:05 -0700 2006 + // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) + // Mon Aug 10 15:44:11 UTC+0100 2015 + // dateAlphaWsDigit + // May 8, 2009 5:57:51 PM + // May 8 2009 5:57:51 PM + // oct 1, 1970 + // oct 7, '70 + switch { + case unicode.IsLetter(r): + p.set(0, "Mon") + p.stateDate = dateAlphaWsAlpha + p.set(i, "Jan") + case unicode.IsDigit(r): + p.set(0, "Jan") + p.stateDate = dateAlphaWsDigit + p.dayi = i + } + + case dateAlphaWsDigit: + // May 8, 2009 5:57:51 PM + // May 8 2009 5:57:51 PM + // oct 1, 1970 + // oct 7, '70 + // oct. 7, 1970 + if r == ',' { + p.daylen = i - p.dayi + p.setDay() + p.stateDate = dateAlphaWsDigitMore + } else if r == ' ' { + p.daylen = i - p.dayi + p.setDay() + p.yeari = i + 1 + p.stateDate = dateAlphaWsDigitMoreWs + } else if unicode.IsLetter(r) { + p.stateDate = dateAlphaWsMonthSuffix + i-- + } + case dateAlphaWsDigitMore: + // x + // May 8, 2009 5:57:51 PM + // May 05, 2005, 05:05:05 + // May 05 2005, 05:05:05 + // oct 1, 1970 + // oct 7, '70 + if r == ' ' { + p.yeari = i + 1 + p.stateDate = dateAlphaWsDigitMoreWs + } + case dateAlphaWsDigitMoreWs: + // x + // May 8, 2009 5:57:51 PM + // May 05, 2005, 05:05:05 + // oct 1, 1970 + // oct 7, '70 + switch r { + case '\'': + p.yeari = i + 1 + case ' ', ',': + // x + // May 8, 2009 5:57:51 PM + // x + // May 8, 2009, 5:57:51 PM + p.stateDate = dateAlphaWsDigitMoreWsYear + p.yearlen = i - p.yeari + p.setYear() + p.stateTime = timeStart + break iterRunes + } + + case dateAlphaWsAlpha: + // Mon Jan _2 15:04:05 2006 + // Mon Jan 02 15:04:05 -0700 2006 + // Mon Jan _2 15:04:05 MST 2006 + // Mon Aug 10 15:44:11 UTC+0100 2015 + // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) + if r == ' ' { + if p.dayi > 0 { + p.daylen = i - p.dayi + p.setDay() + p.yeari = i + 1 + p.stateDate = dateAlphaWsAlphaYearmaybe + p.stateTime = timeStart + } + } else if unicode.IsDigit(r) { + if p.dayi == 0 { + p.dayi = i + } + } + + case dateAlphaWsAlphaYearmaybe: + // x + // Mon Jan _2 15:04:05 2006 + // Fri Jul 03 2015 18:04:07 GMT+0100 (GMT Daylight Time) + if r == ':' { + i = i - 3 + p.stateDate = dateAlphaWsAlpha + p.yeari = 0 + break iterRunes + } else if r == ' ' { + // must be year format, not 15:04 + p.yearlen = i - p.yeari + p.setYear() + break iterRunes + } + + case dateAlphaWsMonth: + // April 8, 2009 + // April 8 2009 + switch r { + case ' ', ',': + // x + // June 8, 2009 + // x + // June 8 2009 + if p.daylen == 0 { + p.daylen = i - p.dayi + p.setDay() + } + case 's', 'S', 'r', 'R', 't', 'T', 'n', 'N': + // st, rd, nd, st + i-- + p.stateDate = dateAlphaWsMonthSuffix + default: + if p.daylen > 0 && p.yeari == 0 { + p.yeari = i + } + } + case dateAlphaWsMonthMore: + // X + // January 02, 2006, 15:04:05 + // January 02 2006, 15:04:05 + // January 02, 2006 15:04:05 + // January 02 2006 15:04:05 + switch r { + case ',': + p.yearlen = i - p.yeari + p.setYear() + p.stateTime = timeStart + i++ + break iterRunes + case ' ': + p.yearlen = i - p.yeari + p.setYear() + p.stateTime = timeStart + break iterRunes + } + case dateAlphaWsMonthSuffix: + // x + // April 8th, 2009 + // April 8th 2009 + switch r { + case 't', 'T': + if p.nextIs(i, 'h') || p.nextIs(i, 'H') { + if len(datestr) > i+2 { + return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc) + } + } + case 'n', 'N': + if p.nextIs(i, 'd') || p.nextIs(i, 'D') { + if len(datestr) > i+2 { + return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc) + } + } + case 's', 'S': + if p.nextIs(i, 't') || p.nextIs(i, 'T') { + if len(datestr) > i+2 { + return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc) + } + } + case 'r', 'R': + if p.nextIs(i, 'd') || p.nextIs(i, 'D') { + if len(datestr) > i+2 { + return parseTime(fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+2:]), loc) + } + } + } + case dateAlphaWsMore: + // January 02, 2006, 15:04:05 + // January 02 2006, 15:04:05 + // January 2nd, 2006, 15:04:05 + // January 2nd 2006, 15:04:05 + // September 17, 2012 at 5:00pm UTC-05 + switch { + case r == ',': + // x + // January 02, 2006, 15:04:05 + if p.nextIs(i, ' ') { + p.daylen = i - p.dayi + p.setDay() + p.yeari = i + 2 + p.stateDate = dateAlphaWsMonthMore + i++ + } + + case r == ' ': + // x + // January 02 2006, 15:04:05 + p.daylen = i - p.dayi + p.setDay() + p.yeari = i + 1 + p.stateDate = dateAlphaWsMonthMore + case unicode.IsDigit(r): + // XX + // January 02, 2006, 15:04:05 + continue + case unicode.IsLetter(r): + // X + // January 2nd, 2006, 15:04:05 + p.daylen = i - p.dayi + p.setDay() + p.stateDate = dateAlphaWsMonthSuffix + i-- + } + + case dateAlphaPeriodWsDigit: + // oct. 7, '70 + switch { + case r == ' ': + // continue + case unicode.IsDigit(r): + p.stateDate = dateAlphaWsDigit + p.dayi = i + default: + return p, unknownErr(datestr) + } + case dateWeekdayComma: + // Monday, 02 Jan 2006 15:04:05 MST + // Monday, 02 Jan 2006 15:04:05 -0700 + // Monday, 02 Jan 2006 15:04:05 +0100 + // Monday, 02-Jan-06 15:04:05 MST + if p.dayi == 0 { + p.dayi = i + } + switch r { + case ' ', '-': + if p.moi == 0 { + p.moi = i + 1 + p.daylen = i - p.dayi + p.setDay() + } else if p.yeari == 0 { + p.yeari = i + 1 + p.molen = i - p.moi + p.set(p.moi, "Jan") + } else { + p.stateTime = timeStart + break iterRunes + } + } + case dateWeekdayAbbrevComma: + // Mon, 02 Jan 2006 15:04:05 MST + // Mon, 02 Jan 2006 15:04:05 -0700 + // Thu, 13 Jul 2017 08:58:40 +0100 + // Thu, 4 Jan 2018 17:53:36 +0000 + // Tue, 11 Jul 2017 16:28:13 +0200 (CEST) + // Mon, 02-Jan-06 15:04:05 MST + switch r { + case ' ', '-': + if p.dayi == 0 { + p.dayi = i + 1 + } else if p.moi == 0 { + p.daylen = i - p.dayi + p.setDay() + p.moi = i + 1 + } else if p.yeari == 0 { + p.molen = i - p.moi + p.set(p.moi, "Jan") + p.yeari = i + 1 + } else { + p.yearlen = i - p.yeari + p.setYear() + p.stateTime = timeStart + break iterRunes + } + } + + default: + break iterRunes + } + } + p.coalesceDate(i) + if p.stateTime == timeStart { + // increment first one, since the i++ occurs at end of loop + if i < len(p.datestr) { + i++ + } + // ensure we skip any whitespace prefix + for ; i < len(datestr); i++ { + r := rune(datestr[i]) + if r != ' ' { + break + } + } + + iterTimeRunes: + for ; i < len(datestr); i++ { + r := rune(datestr[i]) + + //gou.Debugf("%d %s %d iterTimeRunes %s %s", i, string(r), p.stateTime, p.ds(), p.ts()) + + switch p.stateTime { + case timeStart: + // 22:43:22 + // 22:43 + // timeComma + // 08:20:13,787 + // timeWs + // 05:24:37 PM + // 06:20:00 UTC + // 06:20:00 UTC-05 + // 00:12:00 +0000 UTC + // 22:18:00 +0000 UTC m=+0.000000001 + // 15:04:05 -0700 + // 15:04:05 -07:00 + // 15:04:05 2008 + // timeOffset + // 03:21:51+00:00 + // 19:55:00+0100 + // timePeriod + // 17:24:37.3186369 + // 00:07:31.945167 + // 18:31:59.257000000 + // 00:00:00.000 + // timePeriodOffset + // 19:55:00.799+0100 + // timePeriodOffsetColon + // 15:04:05.999-07:00 + // timePeriodWs + // timePeriodWsOffset + // 00:07:31.945167 +0000 + // 00:00:00.000 +0000 + // timePeriodWsOffsetAlpha + // 00:07:31.945167 +0000 UTC + // 22:18:00.001 +0000 UTC m=+0.000000001 + // 00:00:00.000 +0000 UTC + // timePeriodWsAlpha + // 06:20:00.000 UTC + if p.houri == 0 { + p.houri = i + } + switch r { + case ',': + // hm, lets just swap out comma for period. for some reason go + // won't parse it. + // 2014-05-11 08:20:13,787 + ds := []byte(p.datestr) + ds[i] = '.' + return parseTime(string(ds), loc) + case '-', '+': + // 03:21:51+00:00 + p.stateTime = timeOffset + if p.seci == 0 { + // 22:18+0530 + p.minlen = i - p.mini + } else { + p.seclen = i - p.seci + } + p.offseti = i + case '.': + p.stateTime = timePeriod + p.seclen = i - p.seci + p.msi = i + 1 + case 'Z': + p.stateTime = timeZ + if p.seci == 0 { + p.minlen = i - p.mini + } else { + p.seclen = i - p.seci + } + case 'a', 'A': + if p.nextIs(i, 't') || p.nextIs(i, 'T') { + // x + // September 17, 2012 at 5:00pm UTC-05 + i++ // skip t + if p.nextIs(i, ' ') { + // x + // September 17, 2012 at 5:00pm UTC-05 + i++ // skip ' + p.houri = 0 // reset hour + } + } else { + switch { + case r == 'a' && p.nextIs(i, 'm'): + p.coalesceTime(i) + p.set(i, "am") + case r == 'A' && p.nextIs(i, 'M'): + p.coalesceTime(i) + p.set(i, "PM") + } + } + + case 'p', 'P': + // Could be AM/PM + switch { + case r == 'p' && p.nextIs(i, 'm'): + p.coalesceTime(i) + p.set(i, "pm") + case r == 'P' && p.nextIs(i, 'M'): + p.coalesceTime(i) + p.set(i, "PM") + } + case ' ': + p.coalesceTime(i) + p.stateTime = timeWs + case ':': + if p.mini == 0 { + p.mini = i + 1 + p.hourlen = i - p.houri + } else if p.seci == 0 { + p.seci = i + 1 + p.minlen = i - p.mini + } + } + case timeOffset: + // 19:55:00+0100 + // timeOffsetColon + // 15:04:05+07:00 + // 15:04:05-07:00 + if r == ':' { + p.stateTime = timeOffsetColon + } + case timeWs: + // timeWsAlpha + // 06:20:00 UTC + // 06:20:00 UTC-05 + // 15:44:11 UTC+0100 2015 + // 18:04:07 GMT+0100 (GMT Daylight Time) + // 17:57:51 MST 2009 + // timeWsAMPMMaybe + // 05:24:37 PM + // timeWsOffset + // 15:04:05 -0700 + // 00:12:00 +0000 UTC + // timeWsOffsetColon + // 15:04:05 -07:00 + // 17:57:51 -0700 2009 + // timeWsOffsetColonAlpha + // 00:12:00 +00:00 UTC + // timeWsYear + // 00:12:00 2008 + // timeZ + // 15:04:05.99Z + switch r { + case 'A', 'P': + // Could be AM/PM or could be PST or similar + p.tzi = i + p.stateTime = timeWsAMPMMaybe + case '+', '-': + p.offseti = i + p.stateTime = timeWsOffset + default: + if unicode.IsLetter(r) { + // 06:20:00 UTC + // 06:20:00 UTC-05 + // 15:44:11 UTC+0100 2015 + // 17:57:51 MST 2009 + p.tzi = i + p.stateTime = timeWsAlpha + //break iterTimeRunes + } else if unicode.IsDigit(r) { + // 00:12:00 2008 + p.stateTime = timeWsYear + p.yeari = i + } + } + case timeWsAlpha: + // 06:20:00 UTC + // 06:20:00 UTC-05 + // timeWsAlphaWs + // 17:57:51 MST 2009 + // timeWsAlphaZoneOffset + // timeWsAlphaZoneOffsetWs + // timeWsAlphaZoneOffsetWsExtra + // 18:04:07 GMT+0100 (GMT Daylight Time) + // timeWsAlphaZoneOffsetWsYear + // 15:44:11 UTC+0100 2015 + switch r { + case '+', '-': + p.tzlen = i - p.tzi + if p.tzlen == 4 { + p.set(p.tzi, " MST") + } else if p.tzlen == 3 { + p.set(p.tzi, "MST") + } + p.stateTime = timeWsAlphaZoneOffset + p.offseti = i + case ' ': + // 17:57:51 MST 2009 + p.tzlen = i - p.tzi + if p.tzlen == 4 { + p.set(p.tzi, " MST") + } else if p.tzlen == 3 { + p.set(p.tzi, "MST") + } + p.stateTime = timeWsAlphaWs + p.yeari = i + 1 + } + case timeWsAlphaWs: + // 17:57:51 MST 2009 + + case timeWsAlphaZoneOffset: + // 06:20:00 UTC-05 + // timeWsAlphaZoneOffset + // timeWsAlphaZoneOffsetWs + // timeWsAlphaZoneOffsetWsExtra + // 18:04:07 GMT+0100 (GMT Daylight Time) + // timeWsAlphaZoneOffsetWsYear + // 15:44:11 UTC+0100 2015 + switch r { + case ' ': + p.set(p.offseti, "-0700") + p.yeari = i + 1 + p.stateTime = timeWsAlphaZoneOffsetWs + } + case timeWsAlphaZoneOffsetWs: + // timeWsAlphaZoneOffsetWs + // timeWsAlphaZoneOffsetWsExtra + // 18:04:07 GMT+0100 (GMT Daylight Time) + // timeWsAlphaZoneOffsetWsYear + // 15:44:11 UTC+0100 2015 + if unicode.IsDigit(r) { + p.stateTime = timeWsAlphaZoneOffsetWsYear + } else { + p.extra = i - 1 + p.stateTime = timeWsAlphaZoneOffsetWsExtra + } + case timeWsAlphaZoneOffsetWsYear: + // 15:44:11 UTC+0100 2015 + if unicode.IsDigit(r) { + p.yearlen = i - p.yeari + 1 + if p.yearlen == 4 { + p.setYear() + } + } + case timeWsAMPMMaybe: + // timeWsAMPMMaybe + // timeWsAMPM + // 05:24:37 PM + // timeWsAlpha + // 00:12:00 PST + // 15:44:11 UTC+0100 2015 + if r == 'M' { + //return parse("2006-01-02 03:04:05 PM", datestr, loc) + p.stateTime = timeWsAMPM + p.set(i-1, "PM") + if p.hourlen == 2 { + p.set(p.houri, "03") + } else if p.hourlen == 1 { + p.set(p.houri, "3") + } + } else { + p.stateTime = timeWsAlpha + } + + case timeWsOffset: + // timeWsOffset + // 15:04:05 -0700 + // timeWsOffsetWsOffset + // 17:57:51 -0700 -07 + // timeWsOffsetWs + // 17:57:51 -0700 2009 + // 00:12:00 +0000 UTC + // timeWsOffsetColon + // 15:04:05 -07:00 + // timeWsOffsetColonAlpha + // 00:12:00 +00:00 UTC + switch r { + case ':': + p.stateTime = timeWsOffsetColon + case ' ': + p.set(p.offseti, "-0700") + p.yeari = i + 1 + p.stateTime = timeWsOffsetWs + } + case timeWsOffsetWs: + // 17:57:51 -0700 2009 + // 00:12:00 +0000 UTC + // 22:18:00.001 +0000 UTC m=+0.000000001 + // w Extra + // 17:57:51 -0700 -07 + switch r { + case '=': + // eff you golang + if datestr[i-1] == 'm' { + p.extra = i - 2 + p.trimExtra() + break + } + case '+', '-': + // This really doesn't seem valid, but for some reason when round-tripping a go date + // their is an extra +03 printed out. seems like go bug to me, but, parsing anyway. + // 00:00:00 +0300 +03 + // 00:00:00 +0300 +0300 + p.extra = i - 1 + p.stateTime = timeWsOffset + p.trimExtra() + break + default: + switch { + case unicode.IsDigit(r): + p.yearlen = i - p.yeari + 1 + if p.yearlen == 4 { + p.setYear() + } + case unicode.IsLetter(r): + if p.tzi == 0 { + p.tzi = i + } + } + } + + case timeWsOffsetColon: + // timeWsOffsetColon + // 15:04:05 -07:00 + // timeWsOffsetColonAlpha + // 2015-02-18 00:12:00 +00:00 UTC + if unicode.IsLetter(r) { + // 2015-02-18 00:12:00 +00:00 UTC + p.stateTime = timeWsOffsetColonAlpha + break iterTimeRunes + } + case timePeriod: + // 15:04:05.999999999+07:00 + // 15:04:05.999999999-07:00 + // 15:04:05.999999+07:00 + // 15:04:05.999999-07:00 + // 15:04:05.999+07:00 + // 15:04:05.999-07:00 + // timePeriod + // 17:24:37.3186369 + // 00:07:31.945167 + // 18:31:59.257000000 + // 00:00:00.000 + // timePeriodOffset + // 19:55:00.799+0100 + // timePeriodOffsetColon + // 15:04:05.999-07:00 + // timePeriodWs + // timePeriodWsOffset + // 00:07:31.945167 +0000 + // 00:00:00.000 +0000 + // With Extra + // 00:00:00.000 +0300 +03 + // timePeriodWsOffsetAlpha + // 00:07:31.945167 +0000 UTC + // 00:00:00.000 +0000 UTC + // 22:18:00.001 +0000 UTC m=+0.000000001 + // timePeriodWsAlpha + // 06:20:00.000 UTC + switch r { + case ' ': + p.mslen = i - p.msi + p.stateTime = timePeriodWs + case '+', '-': + // This really shouldn't happen + p.mslen = i - p.msi + p.offseti = i + p.stateTime = timePeriodOffset + default: + if unicode.IsLetter(r) { + // 06:20:00.000 UTC + p.mslen = i - p.msi + p.stateTime = timePeriodWsAlpha + } + } + case timePeriodOffset: + // timePeriodOffset + // 19:55:00.799+0100 + // timePeriodOffsetColon + // 15:04:05.999-07:00 + // 13:31:51.999-07:00 MST + if r == ':' { + p.stateTime = timePeriodOffsetColon + } + case timePeriodOffsetColon: + // timePeriodOffset + // timePeriodOffsetColon + // 15:04:05.999-07:00 + // 13:31:51.999 -07:00 MST + switch r { + case ' ': + p.set(p.offseti, "-07:00") + p.stateTime = timePeriodOffsetColonWs + p.tzi = i + 1 + } + case timePeriodOffsetColonWs: + // continue + case timePeriodWs: + // timePeriodWs + // timePeriodWsOffset + // 00:07:31.945167 +0000 + // 00:00:00.000 +0000 + // timePeriodWsOffsetAlpha + // 00:07:31.945167 +0000 UTC + // 00:00:00.000 +0000 UTC + // timePeriodWsOffsetColon + // 13:31:51.999 -07:00 MST + // timePeriodWsAlpha + // 06:20:00.000 UTC + if p.offseti == 0 { + p.offseti = i + } + switch r { + case '+', '-': + p.mslen = i - p.msi - 1 + p.stateTime = timePeriodWsOffset + default: + if unicode.IsLetter(r) { + // 00:07:31.945167 +0000 UTC + // 00:00:00.000 +0000 UTC + p.stateTime = timePeriodWsOffsetWsAlpha + break iterTimeRunes + } + } + + case timePeriodWsOffset: + // timePeriodWs + // timePeriodWsOffset + // 00:07:31.945167 +0000 + // 00:00:00.000 +0000 + // With Extra + // 00:00:00.000 +0300 +03 + // timePeriodWsOffsetAlpha + // 00:07:31.945167 +0000 UTC + // 00:00:00.000 +0000 UTC + // 03:02:00.001 +0300 MSK m=+0.000000001 + // timePeriodWsOffsetColon + // 13:31:51.999 -07:00 MST + // timePeriodWsAlpha + // 06:20:00.000 UTC + switch r { + case ':': + p.stateTime = timePeriodWsOffsetColon + case ' ': + p.set(p.offseti, "-0700") + case '+', '-': + // This really doesn't seem valid, but for some reason when round-tripping a go date + // their is an extra +03 printed out. seems like go bug to me, but, parsing anyway. + // 00:00:00.000 +0300 +03 + // 00:00:00.000 +0300 +0300 + p.extra = i - 1 + p.trimExtra() + break + default: + if unicode.IsLetter(r) { + // 00:07:31.945167 +0000 UTC + // 00:00:00.000 +0000 UTC + // 03:02:00.001 +0300 MSK m=+0.000000001 + p.stateTime = timePeriodWsOffsetWsAlpha + } + } + case timePeriodWsOffsetWsAlpha: + // 03:02:00.001 +0300 MSK m=+0.000000001 + // eff you golang + if r == '=' && datestr[i-1] == 'm' { + p.extra = i - 2 + p.trimExtra() + break + } + + case timePeriodWsOffsetColon: + // 13:31:51.999 -07:00 MST + switch r { + case ' ': + p.set(p.offseti, "-07:00") + default: + if unicode.IsLetter(r) { + // 13:31:51.999 -07:00 MST + p.tzi = i + p.stateTime = timePeriodWsOffsetColonAlpha + } + } + case timePeriodWsOffsetColonAlpha: + // continue + case timeZ: + // timeZ + // 15:04:05.99Z + // With a time-zone at end after Z + // 2006-01-02T15:04:05.999999999Z07:00 + // 2006-01-02T15:04:05Z07:00 + // RFC3339 = "2006-01-02T15:04:05Z07:00" + // RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00" + if unicode.IsDigit(r) { + p.stateTime = timeZDigit + } + + } + } + + switch p.stateTime { + case timeWsAlphaWs: + p.yearlen = i - p.yeari + p.setYear() + case timeWsYear: + p.yearlen = i - p.yeari + p.setYear() + case timeWsAlphaZoneOffsetWsExtra: + p.trimExtra() + case timeWsAlphaZoneOffset: + // 06:20:00 UTC-05 + if i-p.offseti < 4 { + p.set(p.offseti, "-07") + } else { + p.set(p.offseti, "-0700") + } + + case timePeriod: + p.mslen = i - p.msi + case timeOffset: + // 19:55:00+0100 + p.set(p.offseti, "-0700") + case timeWsOffset: + p.set(p.offseti, "-0700") + case timeWsOffsetWs: + // 17:57:51 -0700 2009 + // 00:12:00 +0000 UTC + case timeWsOffsetColon: + // 17:57:51 -07:00 + p.set(p.offseti, "-07:00") + case timeOffsetColon: + // 15:04:05+07:00 + p.set(p.offseti, "-07:00") + case timePeriodOffset: + // 19:55:00.799+0100 + p.set(p.offseti, "-0700") + case timePeriodOffsetColon: + p.set(p.offseti, "-07:00") + case timePeriodWsOffsetColonAlpha: + p.tzlen = i - p.tzi + switch p.tzlen { + case 3: + p.set(p.tzi, "MST") + case 4: + p.set(p.tzi, "MST ") + } + case timePeriodWsOffset: + p.set(p.offseti, "-0700") + } + p.coalesceTime(i) + } + + switch p.stateDate { + case dateDigit: + // unixy timestamps ish + // example ct type + // 1499979655583057426 19 nanoseconds + // 1499979795437000 16 micro-seconds + // 20180722105203 14 yyyyMMddhhmmss + // 1499979795437 13 milliseconds + // 1332151919 10 seconds + // 20140601 8 yyyymmdd + // 2014 4 yyyy + t := time.Time{} + if len(datestr) == len("1499979655583057426") { // 19 + // nano-seconds + if nanoSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { + t = time.Unix(0, nanoSecs) + } + } else if len(datestr) == len("1499979795437000") { // 16 + // micro-seconds + if microSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { + t = time.Unix(0, microSecs*1000) + } + } else if len(datestr) == len("yyyyMMddhhmmss") { // 14 + // yyyyMMddhhmmss + p.format = []byte("20060102150405") + return p, nil + } else if len(datestr) == len("1332151919000") { // 13 + if miliSecs, err := strconv.ParseInt(datestr, 10, 64); err == nil { + t = time.Unix(0, miliSecs*1000*1000) + } + } else if len(datestr) == len("1332151919") { //10 + if secs, err := strconv.ParseInt(datestr, 10, 64); err == nil { + t = time.Unix(secs, 0) + } + } else if len(datestr) == len("20140601") { + p.format = []byte("20060102") + return p, nil + } else if len(datestr) == len("2014") { + p.format = []byte("2006") + return p, nil + } else if len(datestr) < 4 { + return nil, fmt.Errorf("unrecognized format, too short %v", datestr) + } + if !t.IsZero() { + if loc == nil { + p.t = &t + return p, nil + } + t = t.In(loc) + p.t = &t + return p, nil + } + + case dateYearDash: + // 2006-01 + return p, nil + + case dateYearDashDash: + // 2006-01-02 + // 2006-1-02 + // 2006-1-2 + // 2006-01-2 + return p, nil + + case dateYearDashAlphaDash: + // 2013-Feb-03 + // 2013-Feb-3 + p.daylen = i - p.dayi + p.setDay() + return p, nil + + case dateYearDashDashWs: + // 2013-04-01 + return p, nil + + case dateYearDashDashT: + return p, nil + + case dateDigitDashAlphaDash: + // 13-Feb-03 ambiguous + // 28-Feb-03 ambiguous + // 29-Jun-2016 + length := len(datestr) - (p.moi + p.molen + 1) + if length == 4 { + p.yearlen = 4 + p.set(p.yeari, "2006") + // We now also know that part1 was the day + p.dayi = 0 + p.daylen = p.part1Len + p.setDay() + } else if length == 2 { + // We have no idea if this is + // yy-mon-dd OR dd-mon-yy + // + // We are going to ASSUME (bad, bad) that it is dd-mon-yy which is a horible assumption + p.ambiguousMD = true + p.yearlen = 2 + p.set(p.yeari, "06") + // We now also know that part1 was the day + p.dayi = 0 + p.daylen = p.part1Len + p.setDay() + } + + return p, nil + + case dateDigitDot: + // 2014.05 + p.molen = i - p.moi + p.setMonth() + return p, nil + + case dateDigitDotDot: + // 03.31.1981 + // 3.31.2014 + // 3.2.1981 + // 3.2.81 + // 08.21.71 + // 2018.09.30 + return p, nil + + case dateDigitWsMoYear: + // 2 Jan 2018 + // 2 Jan 18 + // 2 Jan 2018 23:59 + // 02 Jan 2018 23:59 + // 12 Feb 2006, 19:17 + return p, nil + + case dateDigitWsMolong: + // 18 January 2018 + // 8 January 2018 + if p.daylen == 2 { + p.format = []byte("02 January 2006") + return p, nil + } + p.format = []byte("2 January 2006") + return p, nil // parse("2 January 2006", datestr, loc) + + case dateAlphaWsMonth: + p.yearlen = i - p.yeari + p.setYear() + return p, nil + + case dateAlphaWsMonthMore: + return p, nil + + case dateAlphaWsDigitMoreWs: + // oct 1, 1970 + p.yearlen = i - p.yeari + p.setYear() + return p, nil + + case dateAlphaWsDigitMoreWsYear: + // May 8, 2009 5:57:51 PM + // Jun 7, 2005, 05:57:51 + return p, nil + + case dateAlphaWsAlpha: + return p, nil + + case dateAlphaWsAlphaYearmaybe: + return p, nil + + case dateDigitSlash: + // 3/1/2014 + // 10/13/2014 + // 01/02/2006 + // 2014/10/13 + return p, nil + + case dateDigitChineseYear: + // dateDigitChineseYear + // 2014年04月08日 + p.format = []byte("2006年01月02日") + return p, nil + + case dateDigitChineseYearWs: + p.format = []byte("2006年01月02日 15:04:05") + return p, nil + + case dateWeekdayComma: + // Monday, 02 Jan 2006 15:04:05 -0700 + // Monday, 02 Jan 2006 15:04:05 +0100 + // Monday, 02-Jan-06 15:04:05 MST + return p, nil + + case dateWeekdayAbbrevComma: + // Mon, 02-Jan-06 15:04:05 MST + // Mon, 02 Jan 2006 15:04:05 MST + return p, nil + + } + + return nil, unknownErr(datestr) +} + +type parser struct { + loc *time.Location + preferMonthFirst bool + ambiguousMD bool + stateDate dateState + stateTime timeState + format []byte + datestr string + fullMonth string + skip int + extra int + part1Len int + yeari int + yearlen int + moi int + molen int + dayi int + daylen int + houri int + hourlen int + mini int + minlen int + seci int + seclen int + msi int + mslen int + offseti int + offsetlen int + tzi int + tzlen int + t *time.Time +} + +func newParser(dateStr string, loc *time.Location) *parser { + p := parser{ + stateDate: dateStart, + stateTime: timeIgnore, + datestr: dateStr, + loc: loc, + preferMonthFirst: true, + } + p.format = []byte(dateStr) + return &p +} + +func (p *parser) nextIs(i int, b byte) bool { + if len(p.datestr) > i+1 && p.datestr[i+1] == b { + return true + } + return false +} + +func (p *parser) set(start int, val string) { + if start < 0 { + return + } + if len(p.format) < start+len(val) { + return + } + for i, r := range val { + p.format[start+i] = byte(r) + } +} +func (p *parser) setMonth() { + if p.molen == 2 { + p.set(p.moi, "01") + } else if p.molen == 1 { + p.set(p.moi, "1") + } +} + +func (p *parser) setDay() { + if p.daylen == 2 { + p.set(p.dayi, "02") + } else if p.daylen == 1 { + p.set(p.dayi, "2") + } +} +func (p *parser) setYear() { + if p.yearlen == 2 { + p.set(p.yeari, "06") + } else if p.yearlen == 4 { + p.set(p.yeari, "2006") + } +} +func (p *parser) coalesceDate(end int) { + if p.yeari > 0 { + if p.yearlen == 0 { + p.yearlen = end - p.yeari + } + p.setYear() + } + if p.moi > 0 && p.molen == 0 { + p.molen = end - p.moi + p.setMonth() + } + if p.dayi > 0 && p.daylen == 0 { + p.daylen = end - p.dayi + p.setDay() + } +} +func (p *parser) ts() string { + return fmt.Sprintf("h:(%d:%d) m:(%d:%d) s:(%d:%d)", p.houri, p.hourlen, p.mini, p.minlen, p.seci, p.seclen) +} +func (p *parser) ds() string { + return fmt.Sprintf("%s d:(%d:%d) m:(%d:%d) y:(%d:%d)", p.datestr, p.dayi, p.daylen, p.moi, p.molen, p.yeari, p.yearlen) +} +func (p *parser) coalesceTime(end int) { + // 03:04:05 + // 15:04:05 + // 3:04:05 + // 3:4:5 + // 15:04:05.00 + if p.houri > 0 { + if p.hourlen == 2 { + p.set(p.houri, "15") + } else if p.hourlen == 1 { + p.set(p.houri, "3") + } + } + if p.mini > 0 { + if p.minlen == 0 { + p.minlen = end - p.mini + } + if p.minlen == 2 { + p.set(p.mini, "04") + } else { + p.set(p.mini, "4") + } + } + if p.seci > 0 { + if p.seclen == 0 { + p.seclen = end - p.seci + } + if p.seclen == 2 { + p.set(p.seci, "05") + } else { + p.set(p.seci, "5") + } + } + + if p.msi > 0 { + for i := 0; i < p.mslen; i++ { + p.format[p.msi+i] = '0' + } + } +} +func (p *parser) setFullMonth(month string) { + if p.moi == 0 { + p.format = []byte(fmt.Sprintf("%s%s", "January", p.format[len(month):])) + } +} + +func (p *parser) trimExtra() { + if p.extra > 0 && len(p.format) > p.extra { + p.format = p.format[0:p.extra] + p.datestr = p.datestr[0:p.extra] + } +} + +// func (p *parser) remove(i, length int) { +// if len(p.format) > i+length { +// //append(a[:i], a[j:]...) +// p.format = append(p.format[0:i], p.format[i+length:]...) +// } +// if len(p.datestr) > i+length { +// //append(a[:i], a[j:]...) +// p.datestr = fmt.Sprintf("%s%s", p.datestr[0:i], p.datestr[i+length:]) +// } +// } + +func (p *parser) parse() (time.Time, error) { + if p.t != nil { + return *p.t, nil + } + if len(p.fullMonth) > 0 { + p.setFullMonth(p.fullMonth) + } + if p.skip > 0 && len(p.format) > p.skip { + p.format = p.format[p.skip:] + p.datestr = p.datestr[p.skip:] + } + //gou.Debugf("parse %q AS %q", p.datestr, string(p.format)) + if p.loc == nil { + return time.Parse(string(p.format), p.datestr) + } + return time.ParseInLocation(string(p.format), p.datestr, p.loc) +} +func isMonthFull(alpha string) bool { + for _, month := range months { + if alpha == month { + return true + } + } + return false +} diff --git a/vendor/modules.txt b/vendor/modules.txt index eb45a9c..5b6f962 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,5 +1,7 @@ # code.gitea.io/sdk/gitea v0.11.0 code.gitea.io/sdk/gitea +# github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 +github.com/araddon/dateparse # github.com/cpuguy83/go-md2man/v2 v2.0.0 github.com/cpuguy83/go-md2man/v2/md2man # github.com/davecgh/go-spew v1.1.1 -- 2.40.1 From d889f61a68c15a44088d4bb6fc018c25c9a81e62 Mon Sep 17 00:00:00 2001 From: Norwin Date: Sun, 5 Jan 2020 21:11:16 +0100 Subject: [PATCH 09/19] switch to urface/cli/v2 --- cmd/flags.go | 12 ++++++++---- cmd/issues.go | 10 ++++++---- cmd/labels.go | 5 +++-- cmd/login.go | 16 ++++++++++------ cmd/logout.go | 5 +++-- cmd/releases.go | 25 +++++++++++++++---------- cmd/times.go | 24 +++++++++++++----------- 7 files changed, 58 insertions(+), 39 deletions(-) diff --git a/cmd/flags.go b/cmd/flags.go index 21a684c..998d992 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -21,28 +21,32 @@ var ( // LoginFlag provides flag to specify tea login profile var LoginFlag = cli.StringFlag{ - Name: "login, l", + Name: "login", + Aliases: []string{"l"}, Usage: "Use a different Gitea login. Optional", Destination: &loginValue, } // RepoFlag provides flag to specify repository var RepoFlag = cli.StringFlag{ - Name: "repo, r", + Name: "repo", + Aliases: []string{"r"}, Usage: "Repository to interact with. Optional", Destination: &repoValue, } // RemoteFlag provides flag to specify remote repository var RemoteFlag = cli.StringFlag{ - Name: "remote, R", + Name: "remote", + Aliases: []string{"R"}, Usage: "Discover Gitea login from remote. Optional", Destination: &remoteValue, } // OutputFlag provides flag to specify output type var OutputFlag = cli.StringFlag{ - Name: "output, o", + Name: "output", + Aliases: []string{"o"}, Usage: "Output format. (csv, simple, table, tsv, yaml)", Destination: &outputValue, } diff --git a/cmd/issues.go b/cmd/issues.go index a3ccecf..5fba5a9 100644 --- a/cmd/issues.go +++ b/cmd/issues.go @@ -125,12 +125,14 @@ var CmdIssuesCreate = cli.Command{ Action: runIssuesCreate, Flags: append([]cli.Flag{ &cli.StringFlag{ - Name: "title, t", - Usage: "issue title to create", + Name: "title", + Aliases: []string{"t"}, + Usage: "issue title to create", }, &cli.StringFlag{ - Name: "body, b", - Usage: "issue body to create", + Name: "body", + Aliases: []string{"b"}, + Usage: "issue body to create", }, }, LoginRepoFlags...), } diff --git a/cmd/labels.go b/cmd/labels.go index 2419a93..95a5bdb 100644 --- a/cmd/labels.go +++ b/cmd/labels.go @@ -30,8 +30,9 @@ var CmdLabels = cli.Command{ }, Flags: append([]cli.Flag{ &cli.StringFlag{ - Name: "save, s", - Usage: "Save all the labels as a file", + Name: "save, s", + Aliases: []string{"s"}, + Usage: "Save all the labels as a file", }, }, AllDefaultFlags...), } diff --git a/cmd/login.go b/cmd/login.go index 771490e..1a5cb83 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -34,24 +34,28 @@ var cmdLoginAdd = cli.Command{ Description: `Add a Gitea login`, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "name, n", - Usage: "Login name", + Name: "name", + Aliases: []string{"n"}, + Usage: "Login name", }, &cli.StringFlag{ - Name: "url, u", + Name: "url", + Aliases: []string{"u"}, Value: "https://try.gitea.io", EnvVars: []string{"GITEA_SERVER_URL"}, Usage: "Server URL", }, &cli.StringFlag{ - Name: "token, t", + Name: "token", + Aliases: []string{"t"}, Value: "", EnvVars: []string{"GITEA_SERVER_TOKEN"}, Usage: "Access token. Can be obtained from Settings > Applications", }, &cli.BoolFlag{ - Name: "insecure, i", - Usage: "Disable TLS verification", + Name: "insecure", + Aliases: []string{"i"}, + Usage: "Disable TLS verification", }, }, Action: runLoginAdd, diff --git a/cmd/logout.go b/cmd/logout.go index 0335bed..192d519 100644 --- a/cmd/logout.go +++ b/cmd/logout.go @@ -20,8 +20,9 @@ var CmdLogout = cli.Command{ Action: runLogout, Flags: []cli.Flag{ &cli.StringFlag{ - Name: "name, n", - Usage: "Login name to remove", + Name: "name", + Aliases: []string{"n"}, + Usage: "Login name to remove", }, }, } diff --git a/cmd/releases.go b/cmd/releases.go index 09b874f..788f831 100644 --- a/cmd/releases.go +++ b/cmd/releases.go @@ -80,24 +80,29 @@ var CmdReleaseCreate = cli.Command{ Usage: "Target refs, branch name or commit id", }, &cli.StringFlag{ - Name: "title, t", - Usage: "Release title", + Name: "title", + Aliases: []string{"t"}, + Usage: "Release title", }, &cli.StringFlag{ - Name: "note, n", - Usage: "Release notes", + Name: "note", + Aliases: []string{"n"}, + Usage: "Release notes", }, &cli.BoolFlag{ - Name: "draft, d", - Usage: "Is a draft", + Name: "draft", + Aliases: []string{"d"}, + Usage: "Is a draft", }, &cli.BoolFlag{ - Name: "prerelease, p", - Usage: "Is a pre-release", + Name: "prerelease", + Aliases: []string{"p"}, + Usage: "Is a pre-release", }, &cli.StringSliceFlag{ - Name: "asset, a", - Usage: "List of files to attach", + Name: "asset", + Aliases: []string{"a"}, + Usage: "List of files to attach", }, }, LoginRepoFlags...), } diff --git a/cmd/times.go b/cmd/times.go index 6288da0..c27565c 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -14,7 +14,7 @@ import ( "code.gitea.io/sdk/gitea" "github.com/araddon/dateparse" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) // CmdTrackedTimes represents the command to operate repositories' times. @@ -24,21 +24,23 @@ var CmdTrackedTimes = cli.Command{ Description: `Operate on tracked times of the repository's issues`, ArgsUsage: "[username | #issue]", Action: runTrackedTimes, - Subcommands: []cli.Command{ - CmdTrackedTimesAdd, + Subcommands: []*cli.Command{ + &CmdTrackedTimesAdd, }, Flags: append([]cli.Flag{ - // TODO: add --from --to filters on t.Created - cli.StringFlag{ - Name: "from, f", + &cli.StringFlag{ + Name: "from", + Aliases: []string{"f"}, Usage: "Show only times tracked after this date", }, - cli.StringFlag{ - Name: "until, u", + &cli.StringFlag{ + Name: "until", + Aliases: []string{"u"}, Usage: "Show only times tracked before this date", }, - cli.BoolFlag{ - Name: "total, t", + &cli.BoolFlag{ + Name: "total", + Aliases: []string{"t"}, Usage: "Print the total duration at the end", }, }, AllDefaultFlags...), @@ -147,7 +149,7 @@ var CmdTrackedTimesAdd = cli.Command{ func runTrackedTimesAdd(ctx *cli.Context) error { login, owner, repo := initCommand() - if len(ctx.Args()) < 2 { + if ctx.Args().Len() < 2 { return fmt.Errorf("No issue or duration specified.\nUsage:\t%s", ctx.Command.UsageText) } -- 2.40.1 From 54d587e881fe41be4766606225687000ca1a4cbc Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Sun, 5 Jan 2020 21:28:50 +0100 Subject: [PATCH 10/19] print username & issue index instead of IDs --- cmd/times.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/times.go b/cmd/times.go index c27565c..7ca35e4 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -56,8 +56,8 @@ func runTrackedTimes(ctx *cli.Context) error { headers := []string{ "Index", "Created", - "Issue", // FIXME: this is the internal issue ID, not the one of the repo.... - "User", // FIXME: we should print a username! + "Issue", + "User", "Duration", } @@ -116,8 +116,8 @@ func runTrackedTimes(ctx *cli.Context) error { []string{ strconv.FormatInt(t.ID, 10), t.Created.In(localLoc).Format("2006-01-02 15:04:05"), - "#" + strconv.FormatInt(t.IssueID, 10), - strconv.FormatInt(t.UserID, 10), + "#" + strconv.FormatInt(t.Issue.Index, 10), + t.UserName, time.Duration(1e9 * t.Time).String(), }, ) -- 2.40.1 From 9f6901429afb1e5deae489eebc87737c7230e772 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Sun, 5 Jan 2020 21:39:28 +0100 Subject: [PATCH 11/19] dont print TrackedTime ID --- cmd/times.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/times.go b/cmd/times.go index 7ca35e4..a809000 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -54,7 +54,6 @@ func runTrackedTimes(ctx *cli.Context) error { var err error var outputValues [][]string headers := []string{ - "Index", "Created", "Issue", "User", @@ -114,7 +113,6 @@ func runTrackedTimes(ctx *cli.Context) error { outputValues = append( outputValues, []string{ - strconv.FormatInt(t.ID, 10), t.Created.In(localLoc).Format("2006-01-02 15:04:05"), "#" + strconv.FormatInt(t.Issue.Index, 10), t.UserName, @@ -125,7 +123,7 @@ func runTrackedTimes(ctx *cli.Context) error { if ctx.Bool("total") { outputValues = append(outputValues, []string{ - "TOTAL", "", "", "", time.Duration(1e9 * totalDuration).String(), + "TOTAL", "", "", time.Duration(1e9 * totalDuration).String(), }) } -- 2.40.1 From aecc29b82e07d1d5e44d868a40675a31d3fcd750 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Sun, 5 Jan 2020 22:06:53 +0100 Subject: [PATCH 12/19] refactor times.go --- cmd/times.go | 47 ++++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/cmd/times.go b/cmd/times.go index a809000..e444efe 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. @@ -49,16 +49,8 @@ var CmdTrackedTimes = cli.Command{ func runTrackedTimes(ctx *cli.Context) error { login, owner, repo := initCommand() - var totalDuration int64 var times []*gitea.TrackedTime var err error - var outputValues [][]string - headers := []string{ - "Created", - "Issue", - "User", - "Duration", - } user := ctx.Args().First() fmt.Println(ctx.Command.ArgsUsage) @@ -69,7 +61,7 @@ func runTrackedTimes(ctx *cli.Context) error { // get all tracked times on the specified issue issue, err2 := strconv.ParseInt(user[1:], 10, 64) if err2 != nil { - log.Fatal(err2) + return err2 } times, err = login.Client().ListTrackedTimes(owner, repo, issue) } else { @@ -78,12 +70,7 @@ func runTrackedTimes(ctx *cli.Context) error { } if err != nil { - log.Fatal(err) - } - - localLoc, err := time.LoadLocation("Local") - if err != nil { - log.Fatal(err) + return err } var from, until time.Time @@ -100,11 +87,24 @@ func runTrackedTimes(ctx *cli.Context) error { } } + printTrackedTimes(times, outputValue, from, until, ctx.Bool("total")) + return nil +} + +func printTrackedTimes(times []*gitea.TrackedTime, outputType string, from, until time.Time, printTotal bool) { + var outputValues [][]string + var totalDuration int64 + + localLoc, err := time.LoadLocation("Local") // local timezone for time formatting + if err != nil { + log.Fatal(err) + } + for _, t := range times { - if ctx.String("from") != "" && from.After(t.Created) { + if !from.IsZero() && from.After(t.Created) { continue } - if ctx.String("until") != "" && until.Before(t.Created) { + if !until.IsZero() && until.Before(t.Created) { continue } @@ -121,14 +121,19 @@ func runTrackedTimes(ctx *cli.Context) error { ) } - if ctx.Bool("total") { + if printTotal { outputValues = append(outputValues, []string{ "TOTAL", "", "", time.Duration(1e9 * totalDuration).String(), }) } - Output(outputValue, headers, outputValues) - return nil + headers := []string{ + "Created", + "Issue", + "User", + "Duration", + } + Output(outputType, headers, outputValues) } // CmdTrackedTimesAdd represents a sub command of times to add time to an issue -- 2.40.1 From 4340f117ac550abca421ea10486ab8b318321cae Mon Sep 17 00:00:00 2001 From: Norwin Date: Sun, 5 Jan 2020 22:44:03 +0100 Subject: [PATCH 13/19] Check gitea server version for times endpoint --- cmd/config.go | 23 +++++++++++++++++++++++ cmd/times.go | 4 ++++ go.mod | 1 + 3 files changed, 28 insertions(+) diff --git a/cmd/config.go b/cmd/config.go index 255ebeb..b722922 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -23,6 +23,7 @@ import ( go_git "gopkg.in/src-d/go-git.v4" "github.com/go-gitea/yaml" + "github.com/hashicorp/go-version" ) // Login represents a login to a gitea server, you even could add multiple logins for one gitea server @@ -35,6 +36,28 @@ type Login struct { Insecure bool `yaml:"insecure"` } +// Checks the logins server against a version constraint such as ">= 1.11.0+dev" +func (l *Login) CheckServerVersionConstraint (constraint string) error { + c, err := version.NewConstraint(constraint) + if err != nil { + return err + } + serverVersionRaw, err := l.Client().ServerVersion() + if err != nil { + return err + } + serverVersion, err := version.NewVersion(serverVersionRaw) + if err != nil { + return err + } + if !c.Check(serverVersion) { + return fmt.Errorf("gitea server at %s does not satisfy version constraint %s", l.URL, constraint) + } + + return nil +} + + // Client returns a client to operate Gitea API func (l *Login) Client() *gitea.Client { client := gitea.NewClient(l.URL, l.Token) diff --git a/cmd/times.go b/cmd/times.go index e444efe..8428445 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -49,6 +49,10 @@ var CmdTrackedTimes = cli.Command{ func runTrackedTimes(ctx *cli.Context) error { login, owner, repo := initCommand() + if err := login.CheckServerVersionConstraint(">= 1.11"); err != nil { + return err + } + var times []*gitea.TrackedTime var err error diff --git a/go.mod b/go.mod index edd1744..114c0ac 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/go-gitea/yaml v0.0.0-20170812160011-eb3733d160e7 + github.com/hashicorp/go-version v1.2.0 github.com/mattn/go-runewidth v0.0.4 // indirect github.com/olekukonko/tablewriter v0.0.1 github.com/stretchr/testify v1.4.0 -- 2.40.1 From d3b9aa5e1a165f6b331d95ec2307f3e89a572b6f Mon Sep 17 00:00:00 2001 From: Norwin Date: Sun, 5 Jan 2020 22:46:39 +0100 Subject: [PATCH 14/19] make fmt --- cmd/config.go | 6 +++--- cmd/times.go | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index b722922..e043d1c 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -36,8 +36,9 @@ type Login struct { Insecure bool `yaml:"insecure"` } -// Checks the logins server against a version constraint such as ">= 1.11.0+dev" -func (l *Login) CheckServerVersionConstraint (constraint string) error { +// CheckServerVersionConstraint validates that the login's server satisfies a +// given version constraint such as ">= 1.11.0+dev" +func (l *Login) CheckServerVersionConstraint(constraint string) error { c, err := version.NewConstraint(constraint) if err != nil { return err @@ -57,7 +58,6 @@ func (l *Login) CheckServerVersionConstraint (constraint string) error { return nil } - // Client returns a client to operate Gitea API func (l *Login) Client() *gitea.Client { client := gitea.NewClient(l.URL, l.Token) diff --git a/cmd/times.go b/cmd/times.go index 8428445..d52be57 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -29,19 +29,19 @@ var CmdTrackedTimes = cli.Command{ }, Flags: append([]cli.Flag{ &cli.StringFlag{ - Name: "from", - Aliases: []string{"f"}, - Usage: "Show only times tracked after this date", + Name: "from", + Aliases: []string{"f"}, + Usage: "Show only times tracked after this date", }, &cli.StringFlag{ - Name: "until", - Aliases: []string{"u"}, - Usage: "Show only times tracked before this date", + Name: "until", + Aliases: []string{"u"}, + Usage: "Show only times tracked before this date", }, &cli.BoolFlag{ - Name: "total", - Aliases: []string{"t"}, - Usage: "Print the total duration at the end", + Name: "total", + Aliases: []string{"t"}, + Usage: "Print the total duration at the end", }, }, AllDefaultFlags...), } -- 2.40.1 From e89ddfa878a7ba3f5ef3e4b68a224952faf75558 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 28 Feb 2020 16:18:58 +0100 Subject: [PATCH 15/19] use version check implemented in SDK instead --- cmd/config.go | 23 ----------------------- cmd/times.go | 9 +++++---- 2 files changed, 5 insertions(+), 27 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index e043d1c..255ebeb 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -23,7 +23,6 @@ import ( go_git "gopkg.in/src-d/go-git.v4" "github.com/go-gitea/yaml" - "github.com/hashicorp/go-version" ) // Login represents a login to a gitea server, you even could add multiple logins for one gitea server @@ -36,28 +35,6 @@ type Login struct { Insecure bool `yaml:"insecure"` } -// CheckServerVersionConstraint validates that the login's server satisfies a -// given version constraint such as ">= 1.11.0+dev" -func (l *Login) CheckServerVersionConstraint(constraint string) error { - c, err := version.NewConstraint(constraint) - if err != nil { - return err - } - serverVersionRaw, err := l.Client().ServerVersion() - if err != nil { - return err - } - serverVersion, err := version.NewVersion(serverVersionRaw) - if err != nil { - return err - } - if !c.Check(serverVersion) { - return fmt.Errorf("gitea server at %s does not satisfy version constraint %s", l.URL, constraint) - } - - return nil -} - // Client returns a client to operate Gitea API func (l *Login) Client() *gitea.Client { client := gitea.NewClient(l.URL, l.Token) diff --git a/cmd/times.go b/cmd/times.go index d52be57..a6015eb 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -48,8 +48,9 @@ var CmdTrackedTimes = cli.Command{ func runTrackedTimes(ctx *cli.Context) error { login, owner, repo := initCommand() + client := login.Client() - if err := login.CheckServerVersionConstraint(">= 1.11"); err != nil { + if err := client.CheckServerVersionConstraint(">= 1.11"); err != nil { return err } @@ -60,17 +61,17 @@ func runTrackedTimes(ctx *cli.Context) error { fmt.Println(ctx.Command.ArgsUsage) if user == "" { // get all tracked times on the repo - times, err = login.Client().GetRepoTrackedTimes(owner, repo) + times, err = client.GetRepoTrackedTimes(owner, repo) } else if strings.HasPrefix(user, "#") { // get all tracked times on the specified issue issue, err2 := strconv.ParseInt(user[1:], 10, 64) if err2 != nil { return err2 } - times, err = login.Client().ListTrackedTimes(owner, repo, issue) + times, err = client.ListTrackedTimes(owner, repo, issue) } else { // get all tracked times by the specified user - times, err = login.Client().GetUserTrackedTimes(owner, repo, user) + times, err = client.GetUserTrackedTimes(owner, repo, user) } if err != nil { -- 2.40.1 From 8f15abba154abb738853390b4c48f555684ea60d Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 28 Feb 2020 16:19:10 +0100 Subject: [PATCH 16/19] times: reword help --- cmd/times.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/times.go b/cmd/times.go index a6015eb..ad65a1e 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -20,8 +20,11 @@ import ( // CmdTrackedTimes represents the command to operate repositories' times. var CmdTrackedTimes = cli.Command{ Name: "times", - Usage: "Operate on tracked times of the repository's issues", - Description: `Operate on tracked times of the repository's issues`, + Aliases: []string{"time"}, + Usage: "Operate on tracked times of a repository's issues & pulls", + Description: `Operate on tracked times of a repository's issues & pulls. + Depending on your permissions on the repository, only your own tracked + times might be listed.`, ArgsUsage: "[username | #issue]", Action: runTrackedTimes, Subcommands: []*cli.Command{ -- 2.40.1 From 4bb7c9b2c3054bd6b5491b0b5b3baaa605c02428 Mon Sep 17 00:00:00 2001 From: Norwin Roosen Date: Fri, 28 Feb 2020 16:39:00 +0100 Subject: [PATCH 17/19] add subcmds: `tea times (delete|reset)` fixes #87 fixes #88 --- cmd/times.go | 93 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/cmd/times.go b/cmd/times.go index ad65a1e..9d8d32e 100644 --- a/cmd/times.go +++ b/cmd/times.go @@ -19,16 +19,18 @@ import ( // CmdTrackedTimes represents the command to operate repositories' times. var CmdTrackedTimes = cli.Command{ - Name: "times", + Name: "times", Aliases: []string{"time"}, - Usage: "Operate on tracked times of a repository's issues & pulls", + Usage: "Operate on tracked times of a repository's issues & pulls", Description: `Operate on tracked times of a repository's issues & pulls. Depending on your permissions on the repository, only your own tracked times might be listed.`, - ArgsUsage: "[username | #issue]", - Action: runTrackedTimes, + ArgsUsage: "[username | #issue]", + Action: runTrackedTimes, Subcommands: []*cli.Command{ &CmdTrackedTimesAdd, + &CmdTrackedTimesDelete, + &CmdTrackedTimesReset, }, Flags: append([]cli.Flag{ &cli.StringFlag{ @@ -187,3 +189,86 @@ func runTrackedTimesAdd(ctx *cli.Context) error { return nil } + +// CmdTrackedTimesDelete is a sub command of CmdTrackedTimes, and removes time from an issue +var CmdTrackedTimesDelete = cli.Command{ + Name: "delete", + Aliases: []string{"rm"}, + Usage: "Delete a single tracked time on an issue", + UsageText: "tea times delete