diff --git a/cmd/humanized-duration.go b/cmd/humanized-duration.go deleted file mode 100644 index 6abd764f9..000000000 --- a/cmd/humanized-duration.go +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2016 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "fmt" - "math" - "time" -) - -// humanizedDuration container to capture humanized time. -type humanizedDuration struct { - Days int64 `json:"days,omitempty"` - Hours int64 `json:"hours,omitempty"` - Minutes int64 `json:"minutes,omitempty"` - Seconds int64 `json:"seconds,omitempty"` -} - -// StringShort() humanizes humanizedDuration to human readable short format. -// This does not print at seconds. -func (r humanizedDuration) StringShort() string { - if r.Days == 0 && r.Hours == 0 { - return fmt.Sprintf("%d minutes", r.Minutes) - } - if r.Days == 0 { - return fmt.Sprintf("%d hours %d minutes", r.Hours, r.Minutes) - } - return fmt.Sprintf("%d days %d hours %d minutes", r.Days, r.Hours, r.Minutes) -} - -// String() humanizes humanizedDuration to human readable, -func (r humanizedDuration) String() string { - if r.Days == 0 && r.Hours == 0 && r.Minutes == 0 { - return fmt.Sprintf("%d seconds", r.Seconds) - } - if r.Days == 0 && r.Hours == 0 { - return fmt.Sprintf("%d minutes %d seconds", r.Minutes, r.Seconds) - } - if r.Days == 0 { - return fmt.Sprintf("%d hours %d minutes %d seconds", r.Hours, r.Minutes, r.Seconds) - } - return fmt.Sprintf("%d days %d hours %d minutes %d seconds", r.Days, r.Hours, r.Minutes, r.Seconds) -} - -// timeDurationToHumanizedDuration convert golang time.Duration to a custom more readable humanizedDuration. -func timeDurationToHumanizedDuration(duration time.Duration) humanizedDuration { - r := humanizedDuration{} - if duration.Seconds() < 60.0 { - r.Seconds = int64(duration.Seconds()) - return r - } - if duration.Minutes() < 60.0 { - remainingSeconds := math.Mod(duration.Seconds(), 60) - r.Seconds = int64(remainingSeconds) - r.Minutes = int64(duration.Minutes()) - return r - } - if duration.Hours() < 24.0 { - remainingMinutes := math.Mod(duration.Minutes(), 60) - remainingSeconds := math.Mod(duration.Seconds(), 60) - r.Seconds = int64(remainingSeconds) - r.Minutes = int64(remainingMinutes) - r.Hours = int64(duration.Hours()) - return r - } - remainingHours := math.Mod(duration.Hours(), 24) - remainingMinutes := math.Mod(duration.Minutes(), 60) - remainingSeconds := math.Mod(duration.Seconds(), 60) - r.Hours = int64(remainingHours) - r.Minutes = int64(remainingMinutes) - r.Seconds = int64(remainingSeconds) - r.Days = int64(duration.Hours() / 24) - return r -} diff --git a/cmd/humanized-duration_test.go b/cmd/humanized-duration_test.go deleted file mode 100644 index 193710767..000000000 --- a/cmd/humanized-duration_test.go +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Minio Cloud Storage, (C) 2016 Minio, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package cmd - -import ( - "testing" - "time" -) - -// Test humanized duration. -func TestHumanizedDuration(t *testing.T) { - duration := time.Duration(90487000000000) - humanDuration := timeDurationToHumanizedDuration(duration) - if !hasSuffix(humanDuration.String(), "seconds") { - t.Fatal("Stringer method for humanized duration should have seconds.", humanDuration.String()) - } - if hasSuffix(humanDuration.StringShort(), "seconds") { - t.Fatal("StringShorter method for humanized duration should not have seconds.", humanDuration.StringShort()) - } - - // Test humanized duration for seconds. - humanSecDuration := timeDurationToHumanizedDuration(time.Duration(5 * time.Second)) - expectedHumanSecDuration := humanizedDuration{ - Seconds: 5, - } - if humanSecDuration != expectedHumanSecDuration { - t.Fatalf("Expected %#v, got %#v incorrect conversion of duration to humanized form", - expectedHumanSecDuration, humanSecDuration) - } - if hasSuffix(humanSecDuration.String(), "days") || - hasSuffix(humanSecDuration.String(), "hours") || - hasSuffix(humanSecDuration.String(), "minutes") { - t.Fatal("Stringer method for humanized duration should have only seconds.", humanSecDuration.String()) - } - - // Test humanized duration for minutes. - humanMinDuration := timeDurationToHumanizedDuration(10 * time.Minute) - expectedHumanMinDuration := humanizedDuration{ - Minutes: 10, - } - if humanMinDuration != expectedHumanMinDuration { - t.Fatalf("Expected %#v, got %#v incorrect conversion of duration to humanized form", - expectedHumanMinDuration, humanMinDuration) - } - if hasSuffix(humanMinDuration.String(), "hours") { - t.Fatal("Stringer method for humanized duration should have only minutes.", humanMinDuration.String()) - } - - // Test humanized duration for hours. - humanHourDuration := timeDurationToHumanizedDuration(10 * time.Hour) - expectedHumanHourDuration := humanizedDuration{ - Hours: 10, - } - if humanHourDuration != expectedHumanHourDuration { - t.Fatalf("Expected %#v, got %#v incorrect conversion of duration to humanized form", - expectedHumanHourDuration, humanHourDuration) - } - if hasSuffix(humanHourDuration.String(), "days") { - t.Fatal("Stringer method for humanized duration should have hours.", humanHourDuration.String()) - } -} diff --git a/cmd/update-notifier.go b/cmd/update-notifier.go index 046933594..3584f9ed9 100644 --- a/cmd/update-notifier.go +++ b/cmd/update-notifier.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2015 Minio, Inc. + * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import ( "time" "github.com/cheggaaa/pb" + humanize "github.com/dustin/go-humanize" "github.com/fatih/color" ) @@ -36,56 +37,56 @@ func colorizeUpdateMessage(updateString string, newerThan time.Duration) string // Calculate length without color coding, due to ANSI color // characters padded to actual string the final length is wrong // than the original string length. - hTime := timeDurationToHumanizedDuration(newerThan) - line1Str := fmt.Sprintf(" Minio is %s old ", hTime.StringShort()) + newerThanStr := humanize.Time(UTCNow().Add(newerThan)) + + line1Str := fmt.Sprintf(" You are running an older version of Minio released %s ", newerThanStr) line2Str := fmt.Sprintf(" Update: %s ", updateString) line1Length := len(line1Str) line2Length := len(line2Str) // Populate lines with color coding. - line1InColor := fmt.Sprintf(" Minio is %s old ", yellow(hTime.StringShort())) + line1InColor := fmt.Sprintf(" You are running an older version of Minio released %s ", yellow(newerThanStr)) line2InColor := fmt.Sprintf(" Update: %s ", cyan(updateString)) // calculate the rectangular box size. maxContentWidth := int(math.Max(float64(line1Length), float64(line2Length))) - line1Rest := maxContentWidth - line1Length - line2Rest := maxContentWidth - line2Length // termWidth is set to a default one to use when we are // not able to calculate terminal width via OS syscalls termWidth := 25 - if width, err := pb.GetTerminalWidth(); err == nil { termWidth = width } - var message string - switch { - case len(line2Str) > termWidth: - message = "\n" + line1InColor + "\n" + line2InColor + "\n" - default: - // on windows terminal turn off unicode characters. - var top, bottom, sideBar string - if runtime.GOOS == globalWindowsOSName { - top = yellow("*" + strings.Repeat("*", maxContentWidth) + "*") - bottom = yellow("*" + strings.Repeat("*", maxContentWidth) + "*") - sideBar = yellow("|") - } else { - // color the rectangular box, use unicode characters here. - top = yellow("┏" + strings.Repeat("━", maxContentWidth) + "┓") - bottom = yellow("┗" + strings.Repeat("━", maxContentWidth) + "┛") - sideBar = yellow("┃") - } - // fill spaces to the rest of the area. - spacePaddingLine1 := strings.Repeat(" ", line1Rest) - spacePaddingLine2 := strings.Repeat(" ", line2Rest) + // Box cannot be printed if terminal width is small than maxContentWidth + if maxContentWidth > termWidth { + return "\n" + line1InColor + "\n" + line2InColor + "\n" + "\n" + } - // construct the final message. - message = "\n" + top + "\n" + - sideBar + line1InColor + spacePaddingLine1 + sideBar + "\n" + - sideBar + line2InColor + spacePaddingLine2 + sideBar + "\n" + - bottom + "\n" + topLeftChar := "┏" + topRightChar := "┓" + bottomLeftChar := "┗" + bottomRightChar := "┛" + horizBarChar := "━" + vertBarChar := "┃" + // on windows terminal turn off unicode characters. + if runtime.GOOS == globalWindowsOSName { + topLeftChar = "+" + topRightChar = "+" + bottomLeftChar = "+" + bottomRightChar = "+" + horizBarChar = "-" + vertBarChar = "|" } - // Return the final message. + + message := "\n" + // Add top line + message += yellow(topLeftChar+strings.Repeat(horizBarChar, maxContentWidth)+topRightChar) + "\n" + // Add message lines + message += vertBarChar + line1InColor + strings.Repeat(" ", maxContentWidth-line1Length) + vertBarChar + "\n" + message += vertBarChar + line2InColor + strings.Repeat(" ", maxContentWidth-line2Length) + vertBarChar + "\n" + // Add bottom line + message += yellow(bottomLeftChar+strings.Repeat(horizBarChar, maxContentWidth)+bottomRightChar) + "\n" + return message } diff --git a/cmd/update-notifier_test.go b/cmd/update-notifier_test.go index ff6a6408d..6ea79288f 100644 --- a/cmd/update-notifier_test.go +++ b/cmd/update-notifier_test.go @@ -1,5 +1,5 @@ /* - * Minio Cloud Storage, (C) 2015 Minio, Inc. + * Minio Cloud Storage, (C) 2015, 2016, 2017 Minio, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,18 +17,34 @@ package cmd import ( + "runtime" "strings" "testing" "time" + + "github.com/fatih/color" ) // Tests update notifier string builder. func TestUpdateNotifier(t *testing.T) { - colorUpdateMsg := colorizeUpdateMessage(minioReleaseURL, time.Duration(72*time.Hour)) - if !strings.Contains(colorUpdateMsg, "minutes") { - t.Fatal("Duration string not found in colorized update message", colorUpdateMsg) + plainMsg := "You are running an older version of Minio released " + colorMsg := plainMsg + yellow := color.New(color.FgYellow, color.Bold).SprintfFunc() + if runtime.GOOS == "windows" { + plainMsg += "3 days from now" + colorMsg += yellow("3 days from now") + } else { + plainMsg += "2 days from now" + colorMsg += yellow("2 days from now") } - if !strings.Contains(colorUpdateMsg, minioReleaseURL) { + + updateMsg := colorizeUpdateMessage(minioReleaseURL, time.Duration(72*time.Hour)) + + if !(strings.Contains(updateMsg, plainMsg) || strings.Contains(updateMsg, colorMsg)) { + t.Fatal("Duration string not found in colorized update message", updateMsg) + } + + if !strings.Contains(updateMsg, minioReleaseURL) { t.Fatal("Update message not found in colorized update message", minioReleaseURL) } } diff --git a/vendor/github.com/dustin/go-humanize/README.markdown b/vendor/github.com/dustin/go-humanize/README.markdown index 23dfee0ac..f69d3c870 100644 --- a/vendor/github.com/dustin/go-humanize/README.markdown +++ b/vendor/github.com/dustin/go-humanize/README.markdown @@ -3,7 +3,7 @@ Just a few functions for helping humanize times and sizes. `go get` it as `github.com/dustin/go-humanize`, import it as -`"github.com/dustin/go-humanize"`, use it as `humanize` +`"github.com/dustin/go-humanize"`, use it as `humanize`. See [godoc](https://godoc.org/github.com/dustin/go-humanize) for complete documentation. @@ -11,12 +11,12 @@ complete documentation. ## Sizes This lets you take numbers like `82854982` and convert them to useful -strings like, `83MB` or `79MiB` (whichever you prefer). +strings like, `83 MB` or `79 MiB` (whichever you prefer). Example: ```go -fmt.Printf("That file is %s.", humanize.Bytes(82854982)) +fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB. ``` ## Times @@ -27,11 +27,11 @@ For example, `12 seconds ago` or `3 days from now`. Example: ```go -fmt.Printf("This was touched %s", humanize.Time(someTimeInstance)) +fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago. ``` Thanks to Kyle Lemons for the time implementation from an IRC -conversation one day. It's pretty neat. +conversation one day. It's pretty neat. ## Ordinals @@ -48,12 +48,12 @@ to label ordinals. Example: ```go -fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) +fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend. ``` ## Commas -Want to shove commas into numbers? Be my guest. +Want to shove commas into numbers? Be my guest. 0 -> 0 100 -> 100 @@ -64,7 +64,7 @@ Want to shove commas into numbers? Be my guest. Example: ```go -fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) +fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491. ``` ## Ftoa @@ -72,10 +72,10 @@ fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) Nicer float64 formatter that removes trailing zeros. ```go -fmt.Printf("%f", 2.24) // 2.240000 -fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24 -fmt.Printf("%f", 2.0) // 2.000000 -fmt.Printf("%s", humanize.Ftoa(2.0)) // 2 +fmt.Printf("%f", 2.24) // 2.240000 +fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24 +fmt.Printf("%f", 2.0) // 2.000000 +fmt.Printf("%s", humanize.Ftoa(2.0)) // 2 ``` ## SI notation @@ -85,7 +85,7 @@ Format numbers with [SI notation][sinotation]. Example: ```go -humanize.SI(0.00000000223, "M") // 2.23nM +humanize.SI(0.00000000223, "M") // 2.23 nM ``` [odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion diff --git a/vendor/github.com/dustin/go-humanize/bigbytes.go b/vendor/github.com/dustin/go-humanize/bigbytes.go index 67ea5c823..1a2bf6172 100644 --- a/vendor/github.com/dustin/go-humanize/bigbytes.go +++ b/vendor/github.com/dustin/go-humanize/bigbytes.go @@ -113,7 +113,7 @@ func humanateBigBytes(s, base *big.Int, sizes []string) string { // // See also: ParseBigBytes. // -// BigBytes(82854982) -> 83MB +// BigBytes(82854982) -> 83 MB func BigBytes(s *big.Int) string { sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} return humanateBigBytes(s, bigSIExp, sizes) @@ -123,7 +123,7 @@ func BigBytes(s *big.Int) string { // // See also: ParseBigBytes. // -// BigIBytes(82854982) -> 79MiB +// BigIBytes(82854982) -> 79 MiB func BigIBytes(s *big.Int) string { sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} return humanateBigBytes(s, bigIECExp, sizes) @@ -134,19 +134,28 @@ func BigIBytes(s *big.Int) string { // // See also: BigBytes, BigIBytes. // -// ParseBigBytes("42MB") -> 42000000, nil -// ParseBigBytes("42mib") -> 44040192, nil +// ParseBigBytes("42 MB") -> 42000000, nil +// ParseBigBytes("42 mib") -> 44040192, nil func ParseBigBytes(s string) (*big.Int, error) { lastDigit := 0 + hasComma := false for _, r := range s { - if !(unicode.IsDigit(r) || r == '.') { + if !(unicode.IsDigit(r) || r == '.' || r == ',') { break } + if r == ',' { + hasComma = true + } lastDigit++ } + num := s[:lastDigit] + if hasComma { + num = strings.Replace(num, ",", "", -1) + } + val := &big.Rat{} - _, err := fmt.Sscanf(s[:lastDigit], "%f", val) + _, err := fmt.Sscanf(num, "%f", val) if err != nil { return nil, err } diff --git a/vendor/github.com/dustin/go-humanize/bytes.go b/vendor/github.com/dustin/go-humanize/bytes.go index dacbb9c1e..0b498f488 100644 --- a/vendor/github.com/dustin/go-humanize/bytes.go +++ b/vendor/github.com/dustin/go-humanize/bytes.go @@ -84,7 +84,7 @@ func humanateBytes(s uint64, base float64, sizes []string) string { // // See also: ParseBytes. // -// Bytes(82854982) -> 83MB +// Bytes(82854982) -> 83 MB func Bytes(s uint64) string { sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} return humanateBytes(s, 1000, sizes) @@ -94,7 +94,7 @@ func Bytes(s uint64) string { // // See also: ParseBytes. // -// IBytes(82854982) -> 79MiB +// IBytes(82854982) -> 79 MiB func IBytes(s uint64) string { sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} return humanateBytes(s, 1024, sizes) @@ -105,18 +105,27 @@ func IBytes(s uint64) string { // // See Also: Bytes, IBytes. // -// ParseBytes("42MB") -> 42000000, nil -// ParseBytes("42mib") -> 44040192, nil +// ParseBytes("42 MB") -> 42000000, nil +// ParseBytes("42 mib") -> 44040192, nil func ParseBytes(s string) (uint64, error) { lastDigit := 0 + hasComma := false for _, r := range s { - if !(unicode.IsDigit(r) || r == '.') { + if !(unicode.IsDigit(r) || r == '.' || r == ',') { break } + if r == ',' { + hasComma = true + } lastDigit++ } - f, err := strconv.ParseFloat(s[:lastDigit], 64) + num := s[:lastDigit] + if hasComma { + num = strings.Replace(num, ",", "", -1) + } + + f, err := strconv.ParseFloat(num, 64) if err != nil { return 0, err } diff --git a/vendor/github.com/dustin/go-humanize/comma.go b/vendor/github.com/dustin/go-humanize/comma.go index 74f446b67..eb285cb95 100644 --- a/vendor/github.com/dustin/go-humanize/comma.go +++ b/vendor/github.com/dustin/go-humanize/comma.go @@ -2,6 +2,7 @@ package humanize import ( "bytes" + "math" "math/big" "strconv" "strings" @@ -13,6 +14,12 @@ import ( // e.g. Comma(834142) -> 834,142 func Comma(v int64) string { sign := "" + + // minin64 can't be negated to a usable value, so it has to be special cased. + if v == math.MinInt64 { + return "-9,223,372,036,854,775,808" + } + if v < 0 { sign = "-" v = 0 - v diff --git a/vendor/github.com/dustin/go-humanize/humanize.go b/vendor/github.com/dustin/go-humanize/humanize.go index a69540a06..a2c2da31e 100644 --- a/vendor/github.com/dustin/go-humanize/humanize.go +++ b/vendor/github.com/dustin/go-humanize/humanize.go @@ -2,7 +2,7 @@ Package humanize converts boring ugly numbers to human-friendly strings and back. Durations can be turned into strings such as "3 days ago", numbers -representing sizes like 82854982 into useful strings like, "83MB" or -"79MiB" (whichever you prefer). +representing sizes like 82854982 into useful strings like, "83 MB" or +"79 MiB" (whichever you prefer). */ package humanize diff --git a/vendor/github.com/dustin/go-humanize/number.go b/vendor/github.com/dustin/go-humanize/number.go index 32141348c..dec618659 100644 --- a/vendor/github.com/dustin/go-humanize/number.go +++ b/vendor/github.com/dustin/go-humanize/number.go @@ -160,7 +160,7 @@ func FormatFloat(format string, n float64) string { intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) // generate integer part string - intStr := strconv.Itoa(int(intf)) + intStr := strconv.FormatInt(int64(intf), 10) // add thousand separator if required if len(thousandStr) > 0 { diff --git a/vendor/github.com/dustin/go-humanize/si.go b/vendor/github.com/dustin/go-humanize/si.go index 9cce4e8d3..b24e48169 100644 --- a/vendor/github.com/dustin/go-humanize/si.go +++ b/vendor/github.com/dustin/go-humanize/si.go @@ -68,7 +68,7 @@ func ComputeSI(input float64) (float64, string) { value := mag / math.Pow(10, exponent) // Handle special case where value is exactly 1000.0 - // Should return 1M instead of 1000k + // Should return 1 M instead of 1000 k if value == 1000.0 { exponent += 3 value = mag / math.Pow(10, exponent) @@ -86,8 +86,8 @@ func ComputeSI(input float64) (float64, string) { // // See also: ComputeSI, ParseSI. // -// e.g. SI(1000000, B) -> 1MB -// e.g. SI(2.2345e-12, "F") -> 2.2345pF +// e.g. SI(1000000, "B") -> 1 MB +// e.g. SI(2.2345e-12, "F") -> 2.2345 pF func SI(input float64, unit string) string { value, prefix := ComputeSI(input) return Ftoa(value) + " " + prefix + unit @@ -99,7 +99,7 @@ var errInvalid = errors.New("invalid input") // // See also: SI, ComputeSI. // -// e.g. ParseSI(2.2345pF) -> (2.2345e-12, "F", nil) +// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil) func ParseSI(input string) (float64, string, error) { found := riParseRegex.FindStringSubmatch(input) if len(found) != 4 { diff --git a/vendor/github.com/dustin/go-humanize/times.go b/vendor/github.com/dustin/go-humanize/times.go index 49906b372..b311f11c8 100644 --- a/vendor/github.com/dustin/go-humanize/times.go +++ b/vendor/github.com/dustin/go-humanize/times.go @@ -9,9 +9,7 @@ import ( // Seconds-based time units const ( - Minute = 60 - Hour = 60 * Minute - Day = 24 * Hour + Day = 24 * time.Hour Week = 7 * Day Month = 30 * Day Year = 12 * Month @@ -25,18 +23,35 @@ func Time(then time.Time) string { return RelTime(then, time.Now(), "ago", "from now") } -var magnitudes = []struct { - d int64 - format string - divby int64 -}{ - {1, "now", 1}, - {2, "1 second %s", 1}, - {Minute, "%d seconds %s", 1}, - {2 * Minute, "1 minute %s", 1}, - {Hour, "%d minutes %s", Minute}, - {2 * Hour, "1 hour %s", 1}, - {Day, "%d hours %s", Hour}, +// A RelTimeMagnitude struct contains a relative time point at which +// the relative format of time will switch to a new format string. A +// slice of these in ascending order by their "D" field is passed to +// CustomRelTime to format durations. +// +// The Format field is a string that may contain a "%s" which will be +// replaced with the appropriate signed label (e.g. "ago" or "from +// now") and a "%d" that will be replaced by the quantity. +// +// The DivBy field is the amount of time the time difference must be +// divided by in order to display correctly. +// +// e.g. if D is 2*time.Minute and you want to display "%d minutes %s" +// DivBy should be time.Minute so whatever the duration is will be +// expressed in minutes. +type RelTimeMagnitude struct { + D time.Duration + Format string + DivBy time.Duration +} + +var defaultMagnitudes = []RelTimeMagnitude{ + {time.Second, "now", time.Second}, + {2 * time.Second, "1 second %s", 1}, + {time.Minute, "%d seconds %s", time.Second}, + {2 * time.Minute, "1 minute %s", 1}, + {time.Hour, "%d minutes %s", time.Minute}, + {2 * time.Hour, "1 hour %s", 1}, + {Day, "%d hours %s", time.Hour}, {2 * Day, "1 day %s", 1}, {Week, "%d days %s", Day}, {2 * Week, "1 week %s", 1}, @@ -57,34 +72,46 @@ var magnitudes = []struct { // // RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier" func RelTime(a, b time.Time, albl, blbl string) string { + return CustomRelTime(a, b, albl, blbl, defaultMagnitudes) +} + +// CustomRelTime formats a time into a relative string. +// +// It takes two times two labels and a table of relative time formats. +// In addition to the generic time delta string (e.g. 5 minutes), the +// labels are used applied so that the label corresponding to the +// smaller time is applied. +func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string { lbl := albl - diff := b.Unix() - a.Unix() + diff := b.Sub(a) - after := a.After(b) - if after { + if a.After(b) { lbl = blbl - diff = a.Unix() - b.Unix() + diff = a.Sub(b) } n := sort.Search(len(magnitudes), func(i int) bool { - return magnitudes[i].d > diff + return magnitudes[i].D >= diff }) + if n >= len(magnitudes) { + n = len(magnitudes) - 1 + } mag := magnitudes[n] args := []interface{}{} escaped := false - for _, ch := range mag.format { + for _, ch := range mag.Format { if escaped { switch ch { case 's': args = append(args, lbl) case 'd': - args = append(args, diff/mag.divby) + args = append(args, diff/mag.DivBy) } escaped = false } else { escaped = ch == '%' } } - return fmt.Sprintf(mag.format, args...) + return fmt.Sprintf(mag.Format, args...) } diff --git a/vendor/vendor.json b/vendor/vendor.json index 567818cd6..781804675 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -35,9 +35,10 @@ "revisionTime": "2016-07-05T13:30:06-07:00" }, { + "checksumSHA1": "rhLUtXvcmouYuBwOq9X/nYKzvNg=", "path": "github.com/dustin/go-humanize", - "revision": "fef948f2d241bd1fd0631108ecc2c9553bae60bf", - "revisionTime": "2016-06-23T09:40:21+08:00" + "revision": "259d2a102b871d17f30e3cd9881a642961a1e486", + "revisionTime": "2017-02-28T07:34:54Z" }, { "checksumSHA1": "y2Kh4iPlgCPXSGTCcFpzePYdzzg=",