Handling time is always a headache, no matter what time it is. The time format is too diverse, and it is even more difficult to deal with time zones, daylight saving time, leap seconds and other minor details. Therefore, we usually use standard libraries or time libraries provided by third parties to handle time in our programs. The dateparse we are going to introduce today focuses on a very small area of time processing - parsing strings in date time format.

Quick Use

Create the directory and initialize.

1
2
$ mkdir dateparse && cd dateparse
$ go mod init github.com/darjun/go-daily-lib/dateparse

Install the dateparse library

1
go get -u github.com/araddon/dateparse

Use.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
  "fmt"
  "log"
  "github.com/araddon/dateparse"
)

func main() {
  t1, err := dateparse.ParseAny("3/1/2014")
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println(t1.Format("2006-01-02 15:04:05"))

  t2, err := dateparse.ParseAny("mm/dd/yyyy")
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println(t2.Format("2006-01-02 15:04:05"))
}

The ParseAny() method takes a datetime string, parses the string, and returns a value of type time.Time. If the passed string dateparse is not recognized by the library, an error is returned. The above program runs the output.

1
2
3
4
$ go run main.go
2014-03-01 00:00:00
2021/06/24 14:52:39 Could not find format for "mm/dd/yyyy"
exit status 1

Note that when we write the time “3/1/2014”, it can be interpreted as either March 1, 2014 or January 3, 2014. This has a different meaning, as dateparse defaults to mm/dd/yyyy, which is March 1, 2014. We can also make such strings with different meanings fail to parse using the ParseStrict() function.

1
2
3
4
5
6
7
func main() {
  t, err := dateparse.ParseStrict("3/1/2014")
  if err != nil {
    log.Fatal(err)
  }
  fmt.Println(t.Format("2006-01-02 15:04:05"))
}

run

1
2
3
$ go run main.go
2021/06/24 14:57:18 This date has ambiguous mm/dd vs dd/mm type format
exit status 1

Format

dateparse supports a rich set of datetime formats, including basically all commonly used formats. It supports all the formats predefined in the standard library time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// src/time/format.go
const (
  ANSIC       = "Mon Jan _2 15:04:05 2006"
  UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
  RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
  RFC822      = "02 Jan 06 15:04 MST"
  RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
  RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
  RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
  RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
  RFC3339     = "2006-01-02T15:04:05Z07:00"
  RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
  Kitchen     = "3:04PM"
  // Handy time stamps.
  Stamp      = "Jan _2 15:04:05"
  StampMilli = "Jan _2 15:04:05.000"
  StampMicro = "Jan _2 15:04:05.000000"
  StampNano  = "Jan _2 15:04:05.000000000"
)

See dateparse README

for the full format supported.

Time Zone

dateparse supports parsing datetime strings in specific time zones. We can get a time zone object by calling the time.LoadLocation() method of the standard library and passing in a time zone identifier string. The time zone identifier string is something like Asia/Shanghai, America/Chicago, which represents a specific time zone, Shanghai for the former and Los Angeles for the latter. The dateparse.ParseIn() method is called to pass in a timezone object to be parsed in the specified timezone. There are also two time zone objects predefined in the time package, time.Local for the local time zone and time.UTC for the UTC time zone. See IANA for authoritative data on time zones.

1
2
3
4
5
6
7
8
func main() {
  tz1, _ := time.LoadLocation("America/Chicago")
  t1, _ := dateparse.ParseIn("2021-06-24 15:50:30", tz1)
  fmt.Println(t1.Local().Format("2006-01-02 15:04:05"))

  t2, _ := dateparse.ParseIn("2021-06-24 15:50:30", time.Local)
  fmt.Println(t2.Local().Format("2006-01-02 15:04:05"))
}

run

1
2
3
$ go run main.go
2021-06-25 04:50:30
2021-06-24 15:50:30

June 24, 2021 15:30:30 in the Los Angeles time zone is equal to June 25, 2021 04:50:30 in the local time zone (Beijing time).

cli

dateparse also provides a command line tool for extremely fast viewing of date time formats. Installation.

1
$ go install github.com/araddon/dateparse/dateparse

By default it will be installed under $GOPATH path, I used to put $GOPATH/bin into $PATH. So the dateparse command can be used directly.

The dateparse command takes a string, and an optional timezone option.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ dateparse --timezone="Asia/Shanghai" "2021-06-24 06:46:08"

Your Current time.Local zone is CST

Layout String: dateparse.ParseFormat() => 2006-01-02 15:04:05

Your Using time.Local set to location=Asia/Shanghai CST

+-------------+---------------------------+-------------------------------+-------------------------------------+
| method      | Zone Source               | Parsed                        | Parsed: t.In(time.UTC)              |
+-------------+---------------------------+-------------------------------+-------------------------------------+
| ParseAny    | time.Local = nil          | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC day=4 |
| ParseAny    | time.Local = timezone arg | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC day=4 |
| ParseAny    | time.Local = time.UTC     | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC day=4 |
| ParseIn     | time.Local = nil          | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseIn     | time.Local = timezone arg | 2021-06-24 06:46:08 +0800 CST | 2021-06-23 22:46:08 +0000 UTC       |
| ParseIn     | time.Local = time.UTC     | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseLocal  | time.Local = nil          | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseLocal  | time.Local = timezone arg | 2021-06-24 06:46:08 +0800 CST | 2021-06-23 22:46:08 +0000 UTC       |
| ParseLocal  | time.Local = time.UTC     | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseStrict | time.Local = nil          | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseStrict | time.Local = timezone arg | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
| ParseStrict | time.Local = time.UTC     | 2021-06-24 06:46:08 +0000 UTC | 2021-06-24 06:46:08 +0000 UTC       |
+-------------+---------------------------+-------------------------------+-------------------------------------+

Outputs the current local time zone, a format string (which can be used to generate a datetime string in the same format) and a table. The data inside the table is the result of calling ParseAny/ParseIn/ParseLocal/ParseStrict for different time zones respectively.

The method column indicates the method called, the Zone Source column indicates the value set to the local time zone, the Parsed column is the result of calling the Format() method on the time.Time object returned by ParseAny() with a datetime string, and the Parsed: t.In(time. UTC) column converts the returned time.Time object to UTC time before the Format() method is called on it.

Since ParseAny/ParseStrict does not take into account the local time zone and parses the string under UTC, the last two columns of these 6 rows have the same result.

In the second line of ParseIn, set time.Local to the time zone we set via the command line option, which I set to Asia/Shanghai above, corresponding to an 8-hour difference in UTC time. The same is true for ParseLocal.

The following is part of the source code of the dateparse command line, you can check it against.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
func main() {
  parsers := map[string]parser{
    "ParseAny":    parseAny,
    "ParseIn":     parseIn,
    "ParseLocal":  parseLocal,
    "ParseStrict": parseStrict,
  }

  for name, parser := range parsers {
    time.Local = nil
    table.AddRow(name, "time.Local = nil", parser(datestr, nil, false), parser(datestr, nil, true))
    if timezone != "" {
      time.Local = loc
      table.AddRow(name, "time.Local = timezone arg", parser(datestr, loc, false), parser(datestr, loc, true))
    }
    time.Local = time.UTC
    table.AddRow(name, "time.Local = time.UTC", parser(datestr, time.UTC, false), parser(datestr, time.UTC, true))
  }
}

func parseIn(datestr string, loc *time.Location, utc bool) string {
  t, err := dateparse.ParseIn(datestr, loc)
  if err != nil {
    return err.Error()
  }
  if utc {
    return t.In(time.UTC).String()
  }
  return t.String()
}

Note that the local time zone of the output is CST, which can represent different time zones.

1
2
3
4
Central Standard Time (USA) UT-6:00
Central Standard Time (Australia) UT+9:30
China Standard Time UT+8:00
Cuba Standard Time UT-4:00

CST can represent the standard time of four countries at the same time: USA, Australia, China and Cuba.

Summary

Using dateparse it is easy to parse time objects and formats (layouts) from datetime strings. Also dateparse command line is a great little tool to quickly view and convert the time in the corresponding time zone.


Reference https://darjun.github.io/2021/06/24/godailylib/dateparse/