Improve duration humanization. (#4071)

master
Bala FA 8 years ago committed by Harshavardhana
parent 64c1c0f37d
commit cf1fc45142
  1. 88
      cmd/humanized-duration.go
  2. 75
      cmd/humanized-duration_test.go
  3. 67
      cmd/update-notifier.go
  4. 26
      cmd/update-notifier_test.go
  5. 26
      vendor/github.com/dustin/go-humanize/README.markdown
  6. 21
      vendor/github.com/dustin/go-humanize/bigbytes.go
  7. 21
      vendor/github.com/dustin/go-humanize/bytes.go
  8. 7
      vendor/github.com/dustin/go-humanize/comma.go
  9. 4
      vendor/github.com/dustin/go-humanize/humanize.go
  10. 2
      vendor/github.com/dustin/go-humanize/number.go
  11. 8
      vendor/github.com/dustin/go-humanize/si.go
  12. 73
      vendor/github.com/dustin/go-humanize/times.go
  13. 5
      vendor/vendor.json

@ -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
}

@ -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())
}
}

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import (
"time" "time"
"github.com/cheggaaa/pb" "github.com/cheggaaa/pb"
humanize "github.com/dustin/go-humanize"
"github.com/fatih/color" "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 // Calculate length without color coding, due to ANSI color
// characters padded to actual string the final length is wrong // characters padded to actual string the final length is wrong
// than the original string length. // than the original string length.
hTime := timeDurationToHumanizedDuration(newerThan) newerThanStr := humanize.Time(UTCNow().Add(newerThan))
line1Str := fmt.Sprintf(" Minio is %s old ", hTime.StringShort())
line1Str := fmt.Sprintf(" You are running an older version of Minio released %s ", newerThanStr)
line2Str := fmt.Sprintf(" Update: %s ", updateString) line2Str := fmt.Sprintf(" Update: %s ", updateString)
line1Length := len(line1Str) line1Length := len(line1Str)
line2Length := len(line2Str) line2Length := len(line2Str)
// Populate lines with color coding. // 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)) line2InColor := fmt.Sprintf(" Update: %s ", cyan(updateString))
// calculate the rectangular box size. // calculate the rectangular box size.
maxContentWidth := int(math.Max(float64(line1Length), float64(line2Length))) 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 // termWidth is set to a default one to use when we are
// not able to calculate terminal width via OS syscalls // not able to calculate terminal width via OS syscalls
termWidth := 25 termWidth := 25
if width, err := pb.GetTerminalWidth(); err == nil { if width, err := pb.GetTerminalWidth(); err == nil {
termWidth = width termWidth = width
} }
var message string // Box cannot be printed if terminal width is small than maxContentWidth
switch { if maxContentWidth > termWidth {
case len(line2Str) > termWidth: return "\n" + line1InColor + "\n" + line2InColor + "\n" + "\n"
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)
// construct the final message. topLeftChar := "┏"
message = "\n" + top + "\n" + topRightChar := "┓"
sideBar + line1InColor + spacePaddingLine1 + sideBar + "\n" + bottomLeftChar := "┗"
sideBar + line2InColor + spacePaddingLine2 + sideBar + "\n" + bottomRightChar := "┛"
bottom + "\n" 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 return message
} }

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,18 +17,34 @@
package cmd package cmd
import ( import (
"runtime"
"strings" "strings"
"testing" "testing"
"time" "time"
"github.com/fatih/color"
) )
// Tests update notifier string builder. // Tests update notifier string builder.
func TestUpdateNotifier(t *testing.T) { func TestUpdateNotifier(t *testing.T) {
colorUpdateMsg := colorizeUpdateMessage(minioReleaseURL, time.Duration(72*time.Hour)) plainMsg := "You are running an older version of Minio released "
if !strings.Contains(colorUpdateMsg, "minutes") { colorMsg := plainMsg
t.Fatal("Duration string not found in colorized update message", colorUpdateMsg) 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) t.Fatal("Update message not found in colorized update message", minioReleaseURL)
} }
} }

@ -3,7 +3,7 @@
Just a few functions for helping humanize times and sizes. Just a few functions for helping humanize times and sizes.
`go get` it as `github.com/dustin/go-humanize`, import it as `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 See [godoc](https://godoc.org/github.com/dustin/go-humanize) for
complete documentation. complete documentation.
@ -11,12 +11,12 @@ complete documentation.
## Sizes ## Sizes
This lets you take numbers like `82854982` and convert them to useful 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: Example:
```go ```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 ## Times
@ -27,11 +27,11 @@ For example, `12 seconds ago` or `3 days from now`.
Example: Example:
```go ```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 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 ## Ordinals
@ -48,12 +48,12 @@ to label ordinals.
Example: Example:
```go ```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 ## Commas
Want to shove commas into numbers? Be my guest. Want to shove commas into numbers? Be my guest.
0 -> 0 0 -> 0
100 -> 100 100 -> 100
@ -64,7 +64,7 @@ Want to shove commas into numbers? Be my guest.
Example: Example:
```go ```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 ## Ftoa
@ -72,10 +72,10 @@ fmt.Printf("You owe $%s.\n", humanize.Comma(6582491))
Nicer float64 formatter that removes trailing zeros. Nicer float64 formatter that removes trailing zeros.
```go ```go
fmt.Printf("%f", 2.24) // 2.240000 fmt.Printf("%f", 2.24) // 2.240000
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24 fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
fmt.Printf("%f", 2.0) // 2.000000 fmt.Printf("%f", 2.0) // 2.000000
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2 fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
``` ```
## SI notation ## SI notation
@ -85,7 +85,7 @@ Format numbers with [SI notation][sinotation].
Example: Example:
```go ```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 [odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion

@ -113,7 +113,7 @@ func humanateBigBytes(s, base *big.Int, sizes []string) string {
// //
// See also: ParseBigBytes. // See also: ParseBigBytes.
// //
// BigBytes(82854982) -> 83MB // BigBytes(82854982) -> 83 MB
func BigBytes(s *big.Int) string { func BigBytes(s *big.Int) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
return humanateBigBytes(s, bigSIExp, sizes) return humanateBigBytes(s, bigSIExp, sizes)
@ -123,7 +123,7 @@ func BigBytes(s *big.Int) string {
// //
// See also: ParseBigBytes. // See also: ParseBigBytes.
// //
// BigIBytes(82854982) -> 79MiB // BigIBytes(82854982) -> 79 MiB
func BigIBytes(s *big.Int) string { func BigIBytes(s *big.Int) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
return humanateBigBytes(s, bigIECExp, sizes) return humanateBigBytes(s, bigIECExp, sizes)
@ -134,19 +134,28 @@ func BigIBytes(s *big.Int) string {
// //
// See also: BigBytes, BigIBytes. // See also: BigBytes, BigIBytes.
// //
// ParseBigBytes("42MB") -> 42000000, nil // ParseBigBytes("42 MB") -> 42000000, nil
// ParseBigBytes("42mib") -> 44040192, nil // ParseBigBytes("42 mib") -> 44040192, nil
func ParseBigBytes(s string) (*big.Int, error) { func ParseBigBytes(s string) (*big.Int, error) {
lastDigit := 0 lastDigit := 0
hasComma := false
for _, r := range s { for _, r := range s {
if !(unicode.IsDigit(r) || r == '.') { if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break break
} }
if r == ',' {
hasComma = true
}
lastDigit++ lastDigit++
} }
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
val := &big.Rat{} val := &big.Rat{}
_, err := fmt.Sscanf(s[:lastDigit], "%f", val) _, err := fmt.Sscanf(num, "%f", val)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -84,7 +84,7 @@ func humanateBytes(s uint64, base float64, sizes []string) string {
// //
// See also: ParseBytes. // See also: ParseBytes.
// //
// Bytes(82854982) -> 83MB // Bytes(82854982) -> 83 MB
func Bytes(s uint64) string { func Bytes(s uint64) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
return humanateBytes(s, 1000, sizes) return humanateBytes(s, 1000, sizes)
@ -94,7 +94,7 @@ func Bytes(s uint64) string {
// //
// See also: ParseBytes. // See also: ParseBytes.
// //
// IBytes(82854982) -> 79MiB // IBytes(82854982) -> 79 MiB
func IBytes(s uint64) string { func IBytes(s uint64) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
return humanateBytes(s, 1024, sizes) return humanateBytes(s, 1024, sizes)
@ -105,18 +105,27 @@ func IBytes(s uint64) string {
// //
// See Also: Bytes, IBytes. // See Also: Bytes, IBytes.
// //
// ParseBytes("42MB") -> 42000000, nil // ParseBytes("42 MB") -> 42000000, nil
// ParseBytes("42mib") -> 44040192, nil // ParseBytes("42 mib") -> 44040192, nil
func ParseBytes(s string) (uint64, error) { func ParseBytes(s string) (uint64, error) {
lastDigit := 0 lastDigit := 0
hasComma := false
for _, r := range s { for _, r := range s {
if !(unicode.IsDigit(r) || r == '.') { if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break break
} }
if r == ',' {
hasComma = true
}
lastDigit++ 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 { if err != nil {
return 0, err return 0, err
} }

@ -2,6 +2,7 @@ package humanize
import ( import (
"bytes" "bytes"
"math"
"math/big" "math/big"
"strconv" "strconv"
"strings" "strings"
@ -13,6 +14,12 @@ import (
// e.g. Comma(834142) -> 834,142 // e.g. Comma(834142) -> 834,142
func Comma(v int64) string { func Comma(v int64) string {
sign := "" 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 { if v < 0 {
sign = "-" sign = "-"
v = 0 - v v = 0 - v

@ -2,7 +2,7 @@
Package humanize converts boring ugly numbers to human-friendly strings and back. Package humanize converts boring ugly numbers to human-friendly strings and back.
Durations can be turned into strings such as "3 days ago", numbers Durations can be turned into strings such as "3 days ago", numbers
representing sizes like 82854982 into useful strings like, "83MB" or representing sizes like 82854982 into useful strings like, "83 MB" or
"79MiB" (whichever you prefer). "79 MiB" (whichever you prefer).
*/ */
package humanize package humanize

@ -160,7 +160,7 @@ func FormatFloat(format string, n float64) string {
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
// generate integer part string // generate integer part string
intStr := strconv.Itoa(int(intf)) intStr := strconv.FormatInt(int64(intf), 10)
// add thousand separator if required // add thousand separator if required
if len(thousandStr) > 0 { if len(thousandStr) > 0 {

@ -68,7 +68,7 @@ func ComputeSI(input float64) (float64, string) {
value := mag / math.Pow(10, exponent) value := mag / math.Pow(10, exponent)
// Handle special case where value is exactly 1000.0 // 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 { if value == 1000.0 {
exponent += 3 exponent += 3
value = mag / math.Pow(10, exponent) value = mag / math.Pow(10, exponent)
@ -86,8 +86,8 @@ func ComputeSI(input float64) (float64, string) {
// //
// See also: ComputeSI, ParseSI. // See also: ComputeSI, ParseSI.
// //
// e.g. SI(1000000, B) -> 1MB // e.g. SI(1000000, "B") -> 1 MB
// e.g. SI(2.2345e-12, "F") -> 2.2345pF // e.g. SI(2.2345e-12, "F") -> 2.2345 pF
func SI(input float64, unit string) string { func SI(input float64, unit string) string {
value, prefix := ComputeSI(input) value, prefix := ComputeSI(input)
return Ftoa(value) + " " + prefix + unit return Ftoa(value) + " " + prefix + unit
@ -99,7 +99,7 @@ var errInvalid = errors.New("invalid input")
// //
// See also: SI, ComputeSI. // 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) { func ParseSI(input string) (float64, string, error) {
found := riParseRegex.FindStringSubmatch(input) found := riParseRegex.FindStringSubmatch(input)
if len(found) != 4 { if len(found) != 4 {

@ -9,9 +9,7 @@ import (
// Seconds-based time units // Seconds-based time units
const ( const (
Minute = 60 Day = 24 * time.Hour
Hour = 60 * Minute
Day = 24 * Hour
Week = 7 * Day Week = 7 * Day
Month = 30 * Day Month = 30 * Day
Year = 12 * Month Year = 12 * Month
@ -25,18 +23,35 @@ func Time(then time.Time) string {
return RelTime(then, time.Now(), "ago", "from now") return RelTime(then, time.Now(), "ago", "from now")
} }
var magnitudes = []struct { // A RelTimeMagnitude struct contains a relative time point at which
d int64 // the relative format of time will switch to a new format string. A
format string // slice of these in ascending order by their "D" field is passed to
divby int64 // CustomRelTime to format durations.
}{ //
{1, "now", 1}, // The Format field is a string that may contain a "%s" which will be
{2, "1 second %s", 1}, // replaced with the appropriate signed label (e.g. "ago" or "from
{Minute, "%d seconds %s", 1}, // now") and a "%d" that will be replaced by the quantity.
{2 * Minute, "1 minute %s", 1}, //
{Hour, "%d minutes %s", Minute}, // The DivBy field is the amount of time the time difference must be
{2 * Hour, "1 hour %s", 1}, // divided by in order to display correctly.
{Day, "%d hours %s", Hour}, //
// 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}, {2 * Day, "1 day %s", 1},
{Week, "%d days %s", Day}, {Week, "%d days %s", Day},
{2 * Week, "1 week %s", 1}, {2 * Week, "1 week %s", 1},
@ -57,34 +72,46 @@ var magnitudes = []struct {
// //
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier" // RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
func RelTime(a, b time.Time, albl, blbl string) string { 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 lbl := albl
diff := b.Unix() - a.Unix() diff := b.Sub(a)
after := a.After(b) if a.After(b) {
if after {
lbl = blbl lbl = blbl
diff = a.Unix() - b.Unix() diff = a.Sub(b)
} }
n := sort.Search(len(magnitudes), func(i int) bool { 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] mag := magnitudes[n]
args := []interface{}{} args := []interface{}{}
escaped := false escaped := false
for _, ch := range mag.format { for _, ch := range mag.Format {
if escaped { if escaped {
switch ch { switch ch {
case 's': case 's':
args = append(args, lbl) args = append(args, lbl)
case 'd': case 'd':
args = append(args, diff/mag.divby) args = append(args, diff/mag.DivBy)
} }
escaped = false escaped = false
} else { } else {
escaped = ch == '%' escaped = ch == '%'
} }
} }
return fmt.Sprintf(mag.format, args...) return fmt.Sprintf(mag.Format, args...)
} }

@ -35,9 +35,10 @@
"revisionTime": "2016-07-05T13:30:06-07:00" "revisionTime": "2016-07-05T13:30:06-07:00"
}, },
{ {
"checksumSHA1": "rhLUtXvcmouYuBwOq9X/nYKzvNg=",
"path": "github.com/dustin/go-humanize", "path": "github.com/dustin/go-humanize",
"revision": "fef948f2d241bd1fd0631108ecc2c9553bae60bf", "revision": "259d2a102b871d17f30e3cd9881a642961a1e486",
"revisionTime": "2016-06-23T09:40:21+08:00" "revisionTime": "2017-02-28T07:34:54Z"
}, },
{ {
"checksumSHA1": "y2Kh4iPlgCPXSGTCcFpzePYdzzg=", "checksumSHA1": "y2Kh4iPlgCPXSGTCcFpzePYdzzg=",

Loading…
Cancel
Save