diff --git a/go.mod b/go.mod index 14ea36417..298291c20 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/google/flatbuffers v2.0.6+incompatible // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/handlers v1.5.1 + github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d github.com/lib/pq v1.10.6 github.com/lomik/og-rek v0.0.0-20170411191824-628eefeb8d80 github.com/lomik/zapwriter v0.0.0-20210624082824-c1161d1eb463 diff --git a/go.sum b/go.sum index 556211458..6dd260335 100644 --- a/go.sum +++ b/go.sum @@ -361,6 +361,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d h1:2puqoOQwi3Ai1oznMOsFIbifm6kIfJaLLyYzWD4IzTs= +github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d/go.mod h1:hO90vCP2x3exaSH58BIAowSKvV+0OsY21TtzuFGHON4= github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lomik/og-rek v0.0.0-20170411191824-628eefeb8d80 h1:KVyDGUXjVOdHQt24wIgY4ZdGFXHtQHLWw0L/MAK3Kb0= diff --git a/pkg/parser/time_parser.go b/pkg/parser/time_parser.go new file mode 100644 index 000000000..681693ab5 --- /dev/null +++ b/pkg/parser/time_parser.go @@ -0,0 +1,272 @@ +package parser + +import ( + "fmt" + "github.com/leekchan/timeutil" + "strconv" + "strings" + "time" +) + +var months = []string{"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"} +var weekdays = []string{"sun", "mon", "tue", "wed", "thu", "fri", "sat"} + +type dateTime struct { + year int + month int + day int + seconds int + nanoseconds int + location time.Location +} + +func ParseDateTime(dateTime string, defaultSign int, now time.Time) (int64, error) { + var parsed []string + var offset string + var ref string + r := strings.NewReplacer( + " ", "", + ",", "", + "_", "", + ) + parsedTime := strings.TrimSpace(dateTime) + parsedTime = r.Replace(parsedTime) + + val, err := strconv.Atoi(parsedTime) + if err == nil { + year, _ := strconv.Atoi(parsedTime[:4]) + month, _ := strconv.Atoi(parsedTime[4:6]) + day, _ := strconv.Atoi(parsedTime[6:]) + if len(parsedTime) != 8 || year < 1900 || month > 13 || day > 32 { + return int64(val), nil + } + } + if strings.Contains(parsedTime, "-") { + parsed = strings.SplitN(parsedTime, "-", 2) + offset = "-" + parsed[1] + ref = parsed[0] + } else if strings.Contains(parsedTime, "+") { + parsed = strings.SplitN(parsedTime, "+", 2) + offset = "+" + parsed[1] + ref = parsed[0] + } else { + offset = "" + ref = parsedTime + } + + refTime, _ := parseTimeReference(ref, now) + interval, _ := parseInterval(offset, defaultSign) + + total := refTime + interval + return total, nil + +} + +func parseTimeReference(ref string, now time.Time) (int64, error) { + if ref == "" || ref == "now" { + return now.Unix(), nil + } + + var hour int + var minute int + var rawRef = ref + //var err error + var refDate = now + + hour, minute, ref = getReferenceDate(ref) + if ref == "" { + refDate = time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, time.UTC) + return refDate.Unix(), nil + } + + // Day reference + if strings.Contains(ref, "today") || strings.Contains(ref, "yesterday") || strings.Contains(ref, "tomorrow") { + if strings.Contains(ref, "yesterday") { + refDate = time.Date(now.Year(), now.Month(), -1, hour, minute, 0, 0, time.UTC) + } else if strings.Contains(ref, "tomorrow") { + refDate = time.Date(now.Year(), now.Month(), 1, hour, minute, 0, 0, time.UTC) + } + } else if strings.Count(ref, "/") == 2 { // MM/DD/YY[YY] format + parsed := strings.SplitN(ref, "/", 3) + year, _ := strconv.Atoi(parsed[2]) + month, _ := strconv.Atoi(parsed[0]) + day, _ := strconv.Atoi(parsed[1]) + if year < 1900 { + year += 1900 + } + if year < 1970 { + year += 100 + } + refDate = time.Date(year, time.Month(month), day, hour, minute, 0, 0, time.UTC) + } else if _, err := strconv.Atoi(ref); err == nil && len(ref) == 8 { // YYYYMMDD format + year, _ := strconv.Atoi(ref[:4]) + month, _ := strconv.Atoi(ref[4:6]) + day, _ := strconv.Atoi(ref[6:]) + refDate = time.Date(year, time.Month(month), day, hour, minute, 0, 0, time.UTC) + } else if len(ref) >= 3 && stringMatchesList(ref[:3], months) { // MonthName DayOfMonth + var day int + if val, err := strconv.Atoi(ref[(len(ref) - 2):]); err == nil { + day = val + } else if val, err := strconv.Atoi(ref[(len(ref) - 1):]); err == nil { + day = val + } else { + return 0, fmt.Errorf("Day of month required after month name: %s", rawRef) + } + refDate = refDate.AddDate(0, 0, day) + } else if len(ref) >= 3 && stringMatchesList(ref[:3], weekdays) { // DayOfWeek (Monday, etc) + dayName := timeutil.Strftime(&refDate, "%a") + dayName = strings.ToLower(dayName) + today := stringMatchesListIndex(dayName, weekdays) + twoWeeks := append(weekdays, weekdays...) + dayOffset := today - stringMatchesListIndex(ref[:3], twoWeeks) + if dayOffset < 0 { + dayOffset += 7 + } + refDate = refDate.AddDate(0, 0, -(dayOffset)) + } else { + return 0, fmt.Errorf("Unknown day reference: %s", rawRef) + } + + return refDate.Unix(), nil +} + +// IntervalString converts a sign and string into a number of seconds +func parseInterval(s string, defaultSign int) (int64, error) { + if len(s) == 0 { + return 0, nil + } + sign := defaultSign + + switch s[0] { + case '-': + sign = -1 + s = s[1:] + case '+': + sign = 1 + s = s[1:] + } + + var totalInterval int64 + for len(s) > 0 { + var j int + for j < len(s) && '0' <= s[j] && s[j] <= '9' { + j++ + } + var offsetStr string + offsetStr, s = s[:j], s[j:] + + j = 0 + for j < len(s) && (s[j] < '0' || '9' < s[j]) { + j++ + } + var unitStr string + unitStr, s = s[:j], s[j:] + + var units int + switch unitStr { + case "s", "sec", "secs", "second", "seconds": + units = 1 + case "m", "min", "mins", "minute", "minutes": + units = 60 + case "h", "hour", "hours": + units = 60 * 60 + case "d", "day", "days": + units = 24 * 60 * 60 + case "w", "week", "weeks": + units = 7 * 24 * 60 * 60 + case "mon", "month", "months": + units = 30 * 24 * 60 * 60 + case "y", "year", "years": + units = 365 * 24 * 60 * 60 + default: + return 0, ErrUnknownTimeUnits + } + + offset, err := strconv.Atoi(offsetStr) + if err != nil { + return 0, err + } + totalInterval += int64(sign * offset * units) + } + + return totalInterval, nil +} + +func stringMatchesList(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +func getReferenceDate(ref string) (hr int, min int, remaining string) { + // Time-of-day reference + var hour = 0 + var minute = 0 + i := strings.Index(ref, ":") + if i > 0 && i < 3 { + hour, _ = strconv.Atoi(ref[:i]) + minute, _ = strconv.Atoi(ref[i+1 : i+3]) + ref = ref[i+3:] + if ref == "" { + return hour, minute, ref + } else if ref[:2] == "am" { + ref = ref[2:] + } else if ref[:2] == "pm" { + hour = (hour + 12) % 24 + ref = ref[2:] + } + } + + // X am or XXam + i = strings.Index(ref, "am") + if i > 0 && i < 3 { + hour, _ = strconv.Atoi(ref[:i]) + ref = ref[i+2:] + } + + // X pm or XX pm + i = strings.Index(ref, "pm") + if i > 0 && i < 3 { + hr, _ := strconv.Atoi(ref[:i]) + hour = (hr + 12) % 24 + ref = ref[i+2:] + } + + if strings.HasPrefix(ref, "noon") { + hour = 12 + minute = 0 + ref = ref[4:] + } else if strings.HasPrefix(ref, "midnight") { + hour = 0 + minute = 0 + ref = ref[8:] + } else if strings.HasPrefix(ref, "teatime") { + hour = 16 + minute = 16 + ref = ref[7:] + } + + //now := time.Now().UTC() + //timeZone := time.UTC + //refDate := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, timeZone) + + return hour, minute, ref +} + +func setHourMinute(hour, minute int) time.Time { + now := time.Now().UTC() + timeZone := time.UTC + return time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, timeZone) +} + +func stringMatchesListIndex(a string, list []string) int { + for i, b := range list { + if b == a { + return i + } + } + return -1 +} diff --git a/pkg/parser/time_parser_test.go b/pkg/parser/time_parser_test.go new file mode 100644 index 000000000..b67293ecf --- /dev/null +++ b/pkg/parser/time_parser_test.go @@ -0,0 +1,150 @@ +package parser + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestParseTime(t *testing.T) { + now := time.Date(2015, time.Month(3), 8, 12, 0, 0, 0, time.UTC) + tests := []struct { + s string + expectedResult int64 + }{ + { + s: "12:0020150308", + expectedResult: 1425816000, + }, + { + s: "9:0020150308", + expectedResult: 1425805200, + }, + { + s: "20150110", + expectedResult: 1420848000, + }, + { + s: "midnight", + expectedResult: 1425772800, + }, + { + s: "midnight+1h", + expectedResult: 1425776400, + }, + { + s: "midnight_tomorrow", + expectedResult: 1425168000, + }, + { + s: "midnight_tomorrow+3h", + expectedResult: 1425178800, + }, + { + s: "now", + expectedResult: 1425816000, + }, + { + s: "-1h", + expectedResult: 1425812400, + }, + { + s: "8:50", + expectedResult: 1425804600, + }, + { + s: "8:50am", + expectedResult: 1425804600, + }, + { + s: "8:50pm", + expectedResult: 1425847800, + }, + { + s: "8am", + expectedResult: 1425801600, + }, + { + s: "10pm", + expectedResult: 1425852000, + }, + { + s: "noon", + expectedResult: 1425816000, + }, + { + s: "midnight", + expectedResult: 1425772800, + }, + { + s: "teatime", + expectedResult: 1425831360, + }, + { + s: "yesterday", + expectedResult: 1424995200, + }, + { + s: "today", + expectedResult: 1425816000, + }, + { + s: "tomorrow", + expectedResult: 1425168000, + }, + { + s: "02/25/15", + expectedResult: 1424822400, + }, + { + s: "20140606", + expectedResult: 1402012800, + }, + { + s: "january8", + expectedResult: 1426507200, + }, + { + s: "january10", + expectedResult: 1426680000, + }, + } + + for _, tt := range tests { + t.Run(tt.s, func(t *testing.T) { + assert := assert.New(t) + + time, err := ParseDateTime(tt.s, -1, now) + assert.NoError(err) + fmt.Println("time: ", time) + assert.Equal(tt.expectedResult, time, tt.s) + }) + } +} + +func testInvalidTimes(t *testing.T) { + now := time.Date(2015, time.Month(3), 8, 12, 0, 0, 0, time.UTC) + + tests := []struct { + s string + expectedResult int64 + }{ + { + s: "12:0020150308", + expectedResult: 1664985600, + }, + } + + for _, tt := range tests { + t.Run(tt.s, func(t *testing.T) { + assert := assert.New(t) + + time, err := ParseDateTime(tt.s, -1, now) + assert.NoError(err) + fmt.Println("time: ", time) + assert.Equal(tt.expectedResult, time, tt.s) + }) + } +} diff --git a/vendor/github.com/leekchan/timeutil/.gitignore b/vendor/github.com/leekchan/timeutil/.gitignore new file mode 100644 index 000000000..daf913b1b --- /dev/null +++ b/vendor/github.com/leekchan/timeutil/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/leekchan/timeutil/.travis.yml b/vendor/github.com/leekchan/timeutil/.travis.yml new file mode 100644 index 000000000..b50016540 --- /dev/null +++ b/vendor/github.com/leekchan/timeutil/.travis.yml @@ -0,0 +1,7 @@ +language: go +go: + - tip +before_install: + - go get github.com/axw/gocov/gocov + - go get github.com/mattn/goveralls + - if ! go get code.google.com/p/go.tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi \ No newline at end of file diff --git a/vendor/github.com/leekchan/timeutil/LICENSE b/vendor/github.com/leekchan/timeutil/LICENSE new file mode 100644 index 000000000..244f4d8ed --- /dev/null +++ b/vendor/github.com/leekchan/timeutil/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Kyoung-chan Lee (leekchan@gmail.com) + +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/leekchan/timeutil/README.md b/vendor/github.com/leekchan/timeutil/README.md new file mode 100644 index 000000000..48900257c --- /dev/null +++ b/vendor/github.com/leekchan/timeutil/README.md @@ -0,0 +1,225 @@ +# timeutil - useful extensions to the golang's time package +[![Build Status](https://travis-ci.org/leekchan/timeutil.svg?branch=master)](https://travis-ci.org/leekchan/timeutil) +[![Coverage Status](https://coveralls.io/repos/leekchan/timeutil/badge.svg?branch=master&service=github)](https://coveralls.io/github/leekchan/timeutil?branch=master) +[![GoDoc](https://godoc.org/github.com/leekchan/timeutil?status.svg)](https://godoc.org/github.com/leekchan/timeutil) + +timeutil provides useful extensions (Timedelta, Strftime, ...) to the golang's time package. + + +## Quick Start + +``` +go get github.com/leekchan/timeutil +``` + +example.go + +```Go +package main + +import ( + "fmt" + "time" + + "github.com/leekchan/timeutil" +) + +func main() { + // Timedelta + // A basic usage. + base := time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC) + td := timeutil.Timedelta{Days: 10, Minutes: 17, Seconds: 56} + + result := base.Add(td.Duration()) + fmt.Println(result) // "2015-02-13 00:17:56 +0000 UTC" + + // Operation : Add + base = time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC) + + td = timeutil.Timedelta{Days: 1, Minutes: 1, Seconds: 1} + td2 := timeutil.Timedelta{Days: 2, Minutes: 2, Seconds: 2} + td = td.Add(&td2) // td = td + td2 + + result = base.Add(td.Duration()) + fmt.Println(result) // "2015-02-06 00:03:03 +0000 UTC" + + // Operation : Subtract + base = time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC) + + td = timeutil.Timedelta{Days: 2, Minutes: 2, Seconds: 2} + td2 = timeutil.Timedelta{Days: 1, Minutes: 1, Seconds: 1} + td = td.Subtract(&td2) // td = td - td2 + + result = base.Add(td.Duration()) + fmt.Println(result) // "2015-02-04 00:01:01 +0000 UTC" + + // Operation : Abs + base = time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC) + + td = timeutil.Timedelta{Days: 1, Minutes: 1, Seconds: 1} + td2 = timeutil.Timedelta{Days: 2, Minutes: 2, Seconds: 2} + td = td.Subtract(&td2) // td = td - td2 + td = td.Abs() // td = |td| + + result = base.Add(td.Duration()) + fmt.Println(result) // "2015-02-04 00:01:01 +0000 UTC" + + + // Strftime + date := time.Date(2015, 7, 2, 15, 24, 30, 35, time.UTC) + str := timeutil.Strftime(&date, "%a %b %d %I:%M:%S %p %Y") + fmt.Println(str) // "Thu Jul 02 03:24:30 PM 2015" + + // Unicode support + str = timeutil.Strftime(&date, "작성일 : %a %b %d %I:%M:%S %p %Y") + fmt.Println(str) // "작성일 : Thu Jul 02 03:24:30 PM 2015" +} +``` + + +## Timedelta + +Timedelta represents a duration between two dates. (inspired by python's timedelta) + +### Timedelta struct + +```Go +type Timedelta struct { + Days, Seconds, Microseconds, Milliseconds, Minutes, Hours, Weeks time.Duration +} +``` + +### Initialization + +All fields are optional and default to 0. You can initialize any type of timedelta by specifying field values which you want to use. + +**Examples:** + +```Go +td := timeutil.Timedelta{Days: 10} +td = timeutil.Timedelta{Minutes: 17} +td = timeutil.Timedelta{Seconds: 56} +td = timeutil.Timedelta{Days: 10, Minutes: 17, Seconds: 56} +td = timeutil.Timedelta{Days: 1, Seconds: 1, Microseconds: 1, Milliseconds: 1, Minutes: 1, Hours: 1, Weeks: 1} +``` + +### func (t *Timedelta) Duration() time.Duration + +Duration() returns time.Duration. time.Duration can be added to time.Date. + +**Examples:** + +```Go +base := time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC) +td := timeutil.Timedelta{Days: 10, Minutes: 17, Seconds: 56} + +result := base.Add(td.Duration()) +fmt.Println(result) // "2015-02-13 00:17:56 +0000 UTC" +``` + +### Operations + +#### func (t *Timedelta) Add(t2 *Timedelta) + +Add returns the Timedelta t+t2. + +**Examples:** + +```Go +base := time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC) +td := timeutil.Timedelta{Days: 1, Minutes: 1, Seconds: 1} +td2 := timeutil.Timedelta{Days: 2, Minutes: 2, Seconds: 2} +td = td.Add(&td2) // td = td + td2 + +result = base.Add(td.Duration()) +fmt.Println(result) // "2015-02-06 00:03:03 +0000 UTC" +``` + +#### func (t *Timedelta) Subtract(t2 *Timedelta) Timedelta + +Subtract returns the Timedelta t-t2. + +**Examples:** + +```Go +base := time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC) + +td := timeutil.Timedelta{Days: 2, Minutes: 2, Seconds: 2} +td2 := timeutil.Timedelta{Days: 1, Minutes: 1, Seconds: 1} +td = td.Subtract(&td2) // td = td - td2 + +result = base.Add(td.Duration()) +fmt.Println(result) // "2015-02-04 00:01:01 +0000 UTC" +``` + +#### func (t *Timedelta) Abs() Timedelta + +Abs returns the absolute value of t + +**Examples:** + +```Go +base := time.Date(2015, 2, 3, 0, 0, 0, 0, time.UTC) + +td := timeutil.Timedelta{Days: 1, Minutes: 1, Seconds: 1} +td2 := timeutil.Timedelta{Days: 2, Minutes: 2, Seconds: 2} +td = td.Subtract(&td2) // td = td - td2 +td = td.Abs() // td = |td| + +result = base.Add(td.Duration()) +fmt.Println(result) // "2015-02-04 00:01:01 +0000 UTC" +``` + + +## Strftime + +Strftime formats time.Date according to the directives in the given format string. The directives begins with a percent (%) character. + +(Strftime supports unicode format string.) + + +Directive | Meaning | Example +-------------| ------------- | ------------- +%a | Weekday as locale’s abbreviated name. | Sun, Mon, ..., Sat +%A | Weekday as locale’s full name. | Sunday, Monday, ..., Saturday +%w | Weekday as a decimal number, where 0 is Sunday and 6 is Saturday | 0, 1, ..., 6 +%d | Day of the month as a zero-padded decimal number. | 01, 02, ..., 31 +%b | Month as locale’s abbreviated name. | Jan, Feb, ..., Dec +%B | Month as locale’s full name. | January, February, ..., December +%m | Month as a zero-padded decimal number. | 01, 02, ..., 12 +%y | Year without century as a zero-padded decimal number. | 00, 01, ..., 99 +%Y | Year with century as a decimal number. | 1970, 1988, 2001, 2013 +%H | Hour (24-hour clock) as a zero-padded decimal number. | 00, 01, ..., 23 +%I | Hour (12-hour clock) as a zero-padded decimal number. | 01, 02, ..., 12 +%p | Meridian indicator. (AM or PM.) | AM, PM +%M | Minute as a zero-padded decimal number. | 00, 01, ..., 59 +%S | Second as a zero-padded decimal number. | 00, 01, ..., 59 +%f | Microsecond as a decimal number, zero-padded on the left. | 000000, 000001, ..., 999999 +%z | UTC offset in the form +HHMM or -HHMM | +0000 +%Z | Time zone name | UTC +%j | Day of the year as a zero-padded decimal number | 001, 002, ..., 366 +%U | Week number of the year (Sunday as the first day of the week) as a zero padded decimal number. All days in a new year preceding the first Sunday are considered to be in week 0. | 00, 01, ..., 53 +%W | Week number of the year (Monday as the first day of the week) as a decimal number. All days in a new year preceding the first Monday are considered to be in week 0. | 00, 01, ..., 53 +%c | Date and time representation. | Tue Aug 16 21:30:00 1988 +%x | Date representation. | 08/16/88 +%X | Time representation. | 21:30:00 +%% | A literal '%' character. | % + +**Examples:** + +```Go +date := time.Date(2015, 7, 2, 15, 24, 30, 35, time.UTC) +str := timeutil.Strftime(&date, "%a %b %d %I:%M:%S %p %Y") +fmt.Println(str) // "Thu Jul 02 03:24:30 PM 2015" + +// Unicode support +str = timeutil.Strftime(&date, "작성일 : %a %b %d %I:%M:%S %p %Y") +fmt.Println(str) // "작성일 : Thu Jul 02 03:24:30 PM 2015" +``` + +## TODO + +* Locale support +* Strptime - a function which returns a time.Date parsed according to a format string +* Auto date parser - a generic string parser which is able to parse most known formats to represent a date +* And other useful features... \ No newline at end of file diff --git a/vendor/github.com/leekchan/timeutil/strftime.go b/vendor/github.com/leekchan/timeutil/strftime.go new file mode 100644 index 000000000..643b79472 --- /dev/null +++ b/vendor/github.com/leekchan/timeutil/strftime.go @@ -0,0 +1,157 @@ +package timeutil + +import ( + "fmt" + "time" + "strings" +) + +var longDayNames = []string{ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +} + +var shortDayNames = []string{ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", +} + +var shortMonthNames = []string{ + "---", + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +} + +var longMonthNames = []string{ + "---", + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +} + +func weekNumber(t *time.Time, char int) int { + weekday := int(t.Weekday()) + + if char == 'W' { + // Monday as the first day of the week + if weekday == 0 { + weekday = 6 + } else { + weekday -= 1 + } + } + + return (t.YearDay() + 6 - weekday) / 7 +} + +// Strftime formats time.Date according to the directives in the given format string. The directives begins with a percent (%) character. +func Strftime(t *time.Time, f string) string { + var result []string + format := []rune(f) + + add := func(str string) { + result = append(result, str) + } + + for i := 0; i < len(format); i++ { + switch format[i] { + case '%': + if i < len(format)-1 { + switch format[i+1] { + case 'a': + add(shortDayNames[t.Weekday()]) + case 'A': + add(longDayNames[t.Weekday()]) + case 'w': + add(fmt.Sprintf("%d", t.Weekday())) + case 'd': + add(fmt.Sprintf("%02d", t.Day())) + case 'b': + add(shortMonthNames[t.Month()]) + case 'B': + add(longMonthNames[t.Month()]) + case 'm': + add(fmt.Sprintf("%02d", t.Month())) + case 'y': + add(fmt.Sprintf("%02d", t.Year()%100)) + case 'Y': + add(fmt.Sprintf("%02d", t.Year())) + case 'H': + add(fmt.Sprintf("%02d", t.Hour())) + case 'I': + if t.Hour() == 0 { + add(fmt.Sprintf("%02d", 12)) + } else if t.Hour() > 12 { + add(fmt.Sprintf("%02d", t.Hour()-12)) + } else { + add(fmt.Sprintf("%02d", t.Hour())) + } + case 'p': + if t.Hour() < 12 { + add("AM") + } else { + add("PM") + } + case 'M': + add(fmt.Sprintf("%02d", t.Minute())) + case 'S': + add(fmt.Sprintf("%02d", t.Second())) + case 'f': + add(fmt.Sprintf("%06d", t.Nanosecond()/1000)) + case 'z': + add(t.Format("-0700")) + case 'Z': + add(t.Format("MST")) + case 'j': + add(fmt.Sprintf("%03d", t.YearDay())) + case 'U': + add(fmt.Sprintf("%02d", weekNumber(t, 'U'))) + case 'W': + add(fmt.Sprintf("%02d", weekNumber(t, 'W'))) + case 'c': + add(t.Format("Mon Jan 2 15:04:05 2006")) + case 'x': + add(fmt.Sprintf("%02d/%02d/%02d", t.Month(), t.Day(), t.Year()%100)) + case 'X': + add(fmt.Sprintf("%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second())) + case '%': + add("%") + } + i += 1 + } + default: + add(string(format[i])) + } + } + + return strings.Join(result, "") +} diff --git a/vendor/github.com/leekchan/timeutil/timedelta.go b/vendor/github.com/leekchan/timeutil/timedelta.go new file mode 100644 index 000000000..8d2dd11b1 --- /dev/null +++ b/vendor/github.com/leekchan/timeutil/timedelta.go @@ -0,0 +1,73 @@ +package timeutil + +import ( + "time" +) + +func abs(v time.Duration) time.Duration { + if v < 0 { + v *= -1 + } + return v +} + +// Timedelta represents a duration between two dates. +// All fields are optional and default to 0. You can initialize any type of timedelta by specifying field values which you want to use. +type Timedelta struct { + Days, Seconds, Microseconds, Milliseconds, Minutes, Hours, Weeks time.Duration +} + +// Add returns the Timedelta t+t2. +func (t *Timedelta) Add(t2 *Timedelta) Timedelta { + return Timedelta{ + Days: t.Days + t2.Days, + Seconds: t.Seconds + t2.Seconds, + Microseconds: t.Microseconds + t2.Microseconds, + Milliseconds: t.Milliseconds + t2.Milliseconds, + Minutes: t.Minutes + t2.Minutes, + Hours: t.Hours + t2.Hours, + Weeks: t.Weeks + t2.Weeks, + } +} + +// Subtract returns the Timedelta t-t2. +func (t *Timedelta) Subtract(t2 *Timedelta) Timedelta { + return Timedelta{ + Days: t.Days - t2.Days, + Seconds: t.Seconds - t2.Seconds, + Microseconds: t.Microseconds - t2.Microseconds, + Milliseconds: t.Milliseconds - t2.Milliseconds, + Minutes: t.Minutes - t2.Minutes, + Hours: t.Hours - t2.Hours, + Weeks: t.Weeks - t2.Weeks, + } +} + +// Abs returns the absolute value of t +func (t *Timedelta) Abs() Timedelta { + return Timedelta{ + Days: abs(t.Days), + Seconds: abs(t.Seconds), + Microseconds: abs(t.Microseconds), + Milliseconds: abs(t.Milliseconds), + Minutes: abs(t.Minutes), + Hours: abs(t.Hours), + Weeks: abs(t.Weeks), + } +} + +// Duration() returns time.Duration. time.Duration can be added to time.Date. +func (t *Timedelta) Duration() time.Duration { + return t.Days*24*time.Hour + + t.Seconds*time.Second + + t.Microseconds*time.Microsecond + + t.Milliseconds*time.Millisecond + + t.Minutes*time.Minute + + t.Hours*time.Hour + + t.Weeks*7*24*time.Hour +} + +// String returns a string representing the Timedelta's duration in the form "72h3m0.5s". +func (t *Timedelta) String() string { + return t.Duration().String() +} diff --git a/vendor/modules.txt b/vendor/modules.txt index eb29b2d38..cfe19e8e0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -94,6 +94,9 @@ github.com/hashicorp/hcl/hcl/token github.com/hashicorp/hcl/json/parser github.com/hashicorp/hcl/json/scanner github.com/hashicorp/hcl/json/token +# github.com/leekchan/timeutil v0.0.0-20150802142658-28917288c48d +## explicit +github.com/leekchan/timeutil # github.com/lib/pq v1.10.6 ## explicit github.com/lib/pq