add humanize package
This commit is contained in:
parent
4114e5fcb0
commit
4199b81b5f
|
@ -6,6 +6,7 @@ import (
|
|||
_ "git.nspix.com/golang/kos/pkg/request"
|
||||
_ "git.nspix.com/golang/kos/util/bs"
|
||||
_ "git.nspix.com/golang/kos/util/fetch"
|
||||
_ "git.nspix.com/golang/kos/util/humanize"
|
||||
_ "git.nspix.com/golang/kos/util/random"
|
||||
_ "git.nspix.com/golang/kos/util/reflection"
|
||||
"sync"
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"git.nspix.com/golang/kos/util/bs"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Nanosecond Duration = 1
|
||||
Microsecond = 1000 * Nanosecond
|
||||
Millisecond = 1000 * Microsecond
|
||||
Second = 1000 * Millisecond
|
||||
Minute = 60 * Second
|
||||
Hour = 60 * Minute
|
||||
)
|
||||
|
||||
type Duration int64
|
||||
|
||||
// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the
|
||||
// tail of buf, omitting trailing zeros. It omits the decimal
|
||||
// point too when the fraction is 0. It returns the index where the
|
||||
// output bytes begin and the value v/10**prec.
|
||||
func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
|
||||
// Omit trailing zeros up to and including decimal point.
|
||||
w := len(buf)
|
||||
print := false
|
||||
for i := 0; i < prec; i++ {
|
||||
digit := v % 10
|
||||
print = print || digit != 0
|
||||
if print {
|
||||
w--
|
||||
buf[w] = byte(digit) + '0'
|
||||
}
|
||||
v /= 10
|
||||
}
|
||||
if print {
|
||||
w--
|
||||
buf[w] = '.'
|
||||
}
|
||||
return w, v
|
||||
}
|
||||
|
||||
// fmtInt formats v into the tail of buf.
|
||||
// It returns the index where the output begins.
|
||||
func fmtInt(buf []byte, v uint64) int {
|
||||
w := len(buf)
|
||||
if v == 0 {
|
||||
w--
|
||||
buf[w] = '0'
|
||||
} else {
|
||||
for v > 0 {
|
||||
w--
|
||||
buf[w] = byte(v%10) + '0'
|
||||
v /= 10
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (d Duration) String() string {
|
||||
// Largest time is 2540400h10m10.000000000s
|
||||
var buf [32]byte
|
||||
w := len(buf)
|
||||
|
||||
u := uint64(d)
|
||||
neg := d < 0
|
||||
if neg {
|
||||
u = -u
|
||||
}
|
||||
|
||||
if u < uint64(time.Second) {
|
||||
// Special case: if duration is smaller than a second,
|
||||
// use smaller units, like 1.2ms
|
||||
var prec int
|
||||
w--
|
||||
buf[w] = 's'
|
||||
w--
|
||||
switch {
|
||||
case u == 0:
|
||||
return "0s"
|
||||
case u < uint64(time.Microsecond):
|
||||
// print nanoseconds
|
||||
prec = 0
|
||||
buf[w] = 'n'
|
||||
case u < uint64(time.Millisecond):
|
||||
// print microseconds
|
||||
prec = 3
|
||||
// U+00B5 'µ' micro sign == 0xC2 0xB5
|
||||
w-- // Need room for two bytes.
|
||||
copy(buf[w:], "µ")
|
||||
default:
|
||||
// print milliseconds
|
||||
prec = 6
|
||||
buf[w] = 'm'
|
||||
}
|
||||
w, u = fmtFrac(buf[:w], u, prec)
|
||||
w = fmtInt(buf[:w], u)
|
||||
} else {
|
||||
w--
|
||||
buf[w] = 's'
|
||||
|
||||
w, u = fmtFrac(buf[:w], u, 9)
|
||||
|
||||
// u is now integer seconds
|
||||
w = fmtInt(buf[:w], u%60)
|
||||
u /= 60
|
||||
|
||||
// u is now integer minutes
|
||||
if u > 0 {
|
||||
w--
|
||||
buf[w] = 'm'
|
||||
w = fmtInt(buf[:w], u%60)
|
||||
u /= 60
|
||||
|
||||
// u is now integer hours
|
||||
// Stop at hours because days can be different lengths.
|
||||
if u > 0 {
|
||||
w--
|
||||
buf[w] = 'h'
|
||||
w = fmtInt(buf[:w], u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if neg {
|
||||
w--
|
||||
buf[w] = '-'
|
||||
}
|
||||
|
||||
return string(buf[w:])
|
||||
}
|
||||
|
||||
func (d *Duration) UnmarshalJSON(b []byte) (err error) {
|
||||
var n time.Duration
|
||||
b = bytes.TrimFunc(b, func(r rune) bool {
|
||||
if r == '"' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if n, err = time.ParseDuration(bs.BytesToString(b)); err == nil {
|
||||
*d = Duration(n)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d Duration) MarshalJSON() ([]byte, error) {
|
||||
return bs.StringToBytes(`"` + d.String() + `"`), nil
|
||||
}
|
||||
|
||||
func (d Duration) Duration() time.Duration {
|
||||
return time.Duration(d)
|
||||
}
|
|
@ -0,0 +1,184 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var (
|
||||
renderFloatPrecisionMultipliers = [...]float64{
|
||||
1,
|
||||
10,
|
||||
100,
|
||||
1000,
|
||||
10000,
|
||||
100000,
|
||||
1000000,
|
||||
10000000,
|
||||
100000000,
|
||||
1000000000,
|
||||
}
|
||||
|
||||
renderFloatPrecisionRounders = [...]float64{
|
||||
0.5,
|
||||
0.05,
|
||||
0.005,
|
||||
0.0005,
|
||||
0.00005,
|
||||
0.000005,
|
||||
0.0000005,
|
||||
0.00000005,
|
||||
0.000000005,
|
||||
0.0000000005,
|
||||
}
|
||||
)
|
||||
|
||||
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
|
||||
// * thousands separator
|
||||
// * decimal separator
|
||||
// * decimal precision
|
||||
//
|
||||
// Usage: s := RenderFloat(format, n)
|
||||
// The format parameter tells how to render the number n.
|
||||
//
|
||||
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
|
||||
//
|
||||
// Examples of format strings, given n = 12345.6789:
|
||||
// "#,###.##" => "12,345.67"
|
||||
// "#,###." => "12,345"
|
||||
// "#,###" => "12345,678"
|
||||
// "#\u202F###,##" => "12 345,68"
|
||||
// "#.###,###### => 12.345,678900
|
||||
// "" (aka default format) => 12,345.67
|
||||
//
|
||||
// The highest precision allowed is 9 digits after the decimal symbol.
|
||||
// There is also a version for integer number, FormatInteger(),
|
||||
// which is convenient for calls within template.
|
||||
func FormatFloat(format string, n float64) string {
|
||||
// Special cases:
|
||||
// NaN = "NaN"
|
||||
// +Inf = "+Infinity"
|
||||
// -Inf = "-Infinity"
|
||||
if math.IsNaN(n) {
|
||||
return "NaN"
|
||||
}
|
||||
if n > math.MaxFloat64 {
|
||||
return "Infinity"
|
||||
}
|
||||
if n < (0.0 - math.MaxFloat64) {
|
||||
return "-Infinity"
|
||||
}
|
||||
|
||||
// default format
|
||||
precision := 2
|
||||
decimalStr := "."
|
||||
thousandStr := ","
|
||||
positiveStr := ""
|
||||
negativeStr := "-"
|
||||
|
||||
if len(format) > 0 {
|
||||
format := []rune(format)
|
||||
|
||||
// If there is an explicit format directive,
|
||||
// then default values are these:
|
||||
precision = 9
|
||||
thousandStr = ""
|
||||
|
||||
// collect indices of meaningful formatting directives
|
||||
formatIndx := []int{}
|
||||
for i, char := range format {
|
||||
if char != '#' && char != '0' {
|
||||
formatIndx = append(formatIndx, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(formatIndx) > 0 {
|
||||
// Directive at index 0:
|
||||
// Must be a '+'
|
||||
// Raise an error if not the case
|
||||
// index: 0123456789
|
||||
// +0.000,000
|
||||
// +000,000.0
|
||||
// +0000.00
|
||||
// +0000
|
||||
if formatIndx[0] == 0 {
|
||||
if format[formatIndx[0]] != '+' {
|
||||
panic("RenderFloat(): invalid positive sign directive")
|
||||
}
|
||||
positiveStr = "+"
|
||||
formatIndx = formatIndx[1:]
|
||||
}
|
||||
|
||||
// Two directives:
|
||||
// First is thousands separator
|
||||
// Raise an error if not followed by 3-digit
|
||||
// 0123456789
|
||||
// 0.000,000
|
||||
// 000,000.00
|
||||
if len(formatIndx) == 2 {
|
||||
if (formatIndx[1] - formatIndx[0]) != 4 {
|
||||
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||||
}
|
||||
thousandStr = string(format[formatIndx[0]])
|
||||
formatIndx = formatIndx[1:]
|
||||
}
|
||||
|
||||
// One directive:
|
||||
// Directive is decimal separator
|
||||
// The number of digit-specifier following the separator indicates wanted precision
|
||||
// 0123456789
|
||||
// 0.00
|
||||
// 000,0000
|
||||
if len(formatIndx) == 1 {
|
||||
decimalStr = string(format[formatIndx[0]])
|
||||
precision = len(format) - formatIndx[0] - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate sign part
|
||||
var signStr string
|
||||
if n >= 0.000000001 {
|
||||
signStr = positiveStr
|
||||
} else if n <= -0.000000001 {
|
||||
signStr = negativeStr
|
||||
n = -n
|
||||
} else {
|
||||
signStr = ""
|
||||
n = 0.0
|
||||
}
|
||||
|
||||
// split number into integer and fractional parts
|
||||
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
|
||||
|
||||
// generate integer part string
|
||||
intStr := strconv.FormatInt(int64(intf), 10)
|
||||
|
||||
// add thousand separator if required
|
||||
if len(thousandStr) > 0 {
|
||||
for i := len(intStr); i > 3; {
|
||||
i -= 3
|
||||
intStr = intStr[:i] + thousandStr + intStr[i:]
|
||||
}
|
||||
}
|
||||
|
||||
// no fractional part, we can leave now
|
||||
if precision == 0 {
|
||||
return signStr + intStr
|
||||
}
|
||||
|
||||
// generate fractional part
|
||||
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
|
||||
// may need padding
|
||||
if len(fracStr) < precision {
|
||||
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
|
||||
}
|
||||
|
||||
return signStr + intStr + decimalStr + fracStr
|
||||
}
|
||||
|
||||
// FormatInteger produces a formatted number as string.
|
||||
// See FormatFloat.
|
||||
func FormatInteger(format string, n int) string {
|
||||
return FormatFloat(format, float64(n))
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"git.nspix.com/golang/kos/util/bs"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type Size uint64
|
||||
|
||||
// IEC Sizes.
|
||||
// kibis of bits
|
||||
const (
|
||||
Byte = 1 << (iota * 10)
|
||||
KiByte
|
||||
MiByte
|
||||
GiByte
|
||||
TiByte
|
||||
PiByte
|
||||
EiByte
|
||||
)
|
||||
|
||||
// SI Sizes.
|
||||
const (
|
||||
IByte = 1
|
||||
KByte = IByte * 1000
|
||||
MByte = KByte * 1000
|
||||
GByte = MByte * 1000
|
||||
TByte = GByte * 1000
|
||||
PByte = TByte * 1000
|
||||
EByte = PByte * 1000
|
||||
)
|
||||
|
||||
var bytesSizeTable = map[string]uint64{
|
||||
"b": Byte,
|
||||
"kib": KiByte,
|
||||
"kb": KByte,
|
||||
"mib": MiByte,
|
||||
"mb": MByte,
|
||||
"gib": GiByte,
|
||||
"gb": GByte,
|
||||
"tib": TiByte,
|
||||
"tb": TByte,
|
||||
"pib": PiByte,
|
||||
"pb": PByte,
|
||||
"eib": EiByte,
|
||||
"eb": EByte,
|
||||
// Without suffix
|
||||
"": Byte,
|
||||
"ki": KiByte,
|
||||
"k": KByte,
|
||||
"mi": MiByte,
|
||||
"m": MByte,
|
||||
"gi": GiByte,
|
||||
"g": GByte,
|
||||
"ti": TiByte,
|
||||
"t": TByte,
|
||||
"pi": PiByte,
|
||||
"p": PByte,
|
||||
"ei": EiByte,
|
||||
"e": EByte,
|
||||
}
|
||||
|
||||
func logn(n, b float64) float64 {
|
||||
return math.Log(n) / math.Log(b)
|
||||
}
|
||||
|
||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||
if s < 10 {
|
||||
return fmt.Sprintf("%d B", s)
|
||||
}
|
||||
e := math.Floor(logn(float64(s), base))
|
||||
suffix := sizes[int(e)]
|
||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||
f := "%.0f %s"
|
||||
if val < 10 {
|
||||
f = "%.1f %s"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(f, val, suffix)
|
||||
}
|
||||
|
||||
// Bytes produces a human readable representation of an SI size.
|
||||
//
|
||||
// See also: ParseBytes.
|
||||
//
|
||||
// Bytes(82854982) -> 83 MB
|
||||
func Bytes(s uint64) string {
|
||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||
return humanateBytes(s, 1000, sizes)
|
||||
}
|
||||
|
||||
// IBytes produces a human readable representation of an IEC size.
|
||||
//
|
||||
// See also: ParseBytes.
|
||||
//
|
||||
// IBytes(82854982) -> 79 MiB
|
||||
func IBytes(s uint64) string {
|
||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||
return humanateBytes(s, 1024, sizes)
|
||||
}
|
||||
|
||||
// ParseBytes parses a string representation of bytes into the number
|
||||
// of bytes it represents.
|
||||
//
|
||||
// See Also: Bytes, IBytes.
|
||||
//
|
||||
// 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 == '.' || r == ',') {
|
||||
break
|
||||
}
|
||||
if r == ',' {
|
||||
hasComma = true
|
||||
}
|
||||
lastDigit++
|
||||
}
|
||||
|
||||
num := s[:lastDigit]
|
||||
if hasComma {
|
||||
num = strings.Replace(num, ",", "", -1)
|
||||
}
|
||||
|
||||
f, err := strconv.ParseFloat(num, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||
if m, ok := bytesSizeTable[extra]; ok {
|
||||
f *= float64(m)
|
||||
if f >= math.MaxUint64 {
|
||||
return 0, fmt.Errorf("too large: %v", s)
|
||||
}
|
||||
return uint64(f), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||
}
|
||||
|
||||
func (b *Size) UnmarshalJSON(buf []byte) error {
|
||||
var (
|
||||
n uint64
|
||||
err error
|
||||
)
|
||||
buf = bytes.TrimFunc(buf, func(r rune) bool {
|
||||
if r == '"' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
if n, err = ParseBytes(bs.BytesToString(buf)); err == nil {
|
||||
*b = Size(n)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (b Size) MarshalJSON() ([]byte, error) {
|
||||
s := `"` + IBytes(uint64(b)) + `"`
|
||||
return bs.StringToBytes(s), nil
|
||||
}
|
||||
|
||||
func (b Size) String() string {
|
||||
return IBytes(uint64(b))
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"git.nspix.com/golang/kos/util/bs"
|
||||
"math"
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Day = 24 * time.Hour
|
||||
Week = 7 * Day
|
||||
Month = 30 * Day
|
||||
Year = 12 * Month
|
||||
LongTime = 37 * Year
|
||||
)
|
||||
|
||||
// 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},
|
||||
{Month, "%d weeks %s", Week},
|
||||
{2 * Month, "1 month %s", 1},
|
||||
{Year, "%d months %s", Month},
|
||||
{18 * Month, "1 year %s", 1},
|
||||
{2 * Year, "2 years %s", 1},
|
||||
{LongTime, "%d years %s", Year},
|
||||
{math.MaxInt64, "a long while %s", 1},
|
||||
}
|
||||
|
||||
// RelTime formats a time into a relative string.
|
||||
//
|
||||
// It takes two times and two labels. 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.
|
||||
//
|
||||
// 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.Sub(a)
|
||||
|
||||
if a.After(b) {
|
||||
lbl = blbl
|
||||
diff = a.Sub(b)
|
||||
}
|
||||
|
||||
n := sort.Search(len(magnitudes), func(i int) bool {
|
||||
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 {
|
||||
if escaped {
|
||||
switch ch {
|
||||
case 's':
|
||||
args = append(args, lbl)
|
||||
case 'd':
|
||||
args = append(args, diff/mag.DivBy)
|
||||
}
|
||||
escaped = false
|
||||
} else {
|
||||
escaped = ch == '%'
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf(mag.Format, args...)
|
||||
}
|
||||
|
||||
type Time struct {
|
||||
tm time.Time
|
||||
}
|
||||
|
||||
func Now() Time {
|
||||
return Time{tm: time.Now()}
|
||||
}
|
||||
|
||||
func WrapTime(t time.Time) Time {
|
||||
return Time{tm: t}
|
||||
}
|
||||
|
||||
func (t Time) Add(d Duration) Time {
|
||||
t.tm = t.tm.Add(d.Duration())
|
||||
return t
|
||||
}
|
||||
|
||||
func (t Time) AddDuration(d time.Duration) Time {
|
||||
t.tm = t.tm.Add(d)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t Time) After(u Time) bool {
|
||||
return t.tm.After(u.tm)
|
||||
}
|
||||
|
||||
func (t Time) AfterTime(u time.Time) bool {
|
||||
return t.tm.After(u)
|
||||
}
|
||||
|
||||
func (t Time) Sub(u Time) Duration {
|
||||
return Duration(t.tm.Sub(u.tm))
|
||||
}
|
||||
|
||||
func (t Time) SubTime(u time.Time) Duration {
|
||||
return Duration(t.tm.Sub(u))
|
||||
}
|
||||
|
||||
func (t Time) Time() time.Time {
|
||||
return t.tm
|
||||
}
|
||||
|
||||
func (t Time) String() string {
|
||||
return t.tm.Format(time.DateTime)
|
||||
}
|
||||
|
||||
func (t Time) MarshalJSON() ([]byte, error) {
|
||||
s := `"` + t.tm.Format(time.DateTime) + `"`
|
||||
return bs.StringToBytes(s), nil
|
||||
}
|
||||
|
||||
func (t Time) Ago() string {
|
||||
return RelTime(t.tm, time.Now(), "ago", "from now")
|
||||
}
|
||||
|
||||
func (t *Time) UnmarshalJSON(buf []byte) (err error) {
|
||||
buf = bytes.TrimFunc(buf, func(r rune) bool {
|
||||
if r == '"' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
t.tm, err = time.ParseInLocation(time.DateTime, bs.BytesToString(buf), time.Local)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package humanize
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type test struct {
|
||||
Time Time
|
||||
}
|
||||
|
||||
func TestNow(t *testing.T) {
|
||||
tm := Now().Add(-1 * Hour * 223)
|
||||
t.Log(tm.Ago())
|
||||
ts := &test{Time: Now()}
|
||||
buf, err := json.Marshal(ts)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(string(buf))
|
||||
vv := &test{}
|
||||
if err = json.Unmarshal(buf, vv); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Log(vv.Time)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package reflect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
type hack struct {
|
||||
Duration time.Duration
|
||||
}
|
||||
h := &hack{}
|
||||
Set(h, "Duration", "5s")
|
||||
t.Log(h.Duration)
|
||||
}
|
Loading…
Reference in New Issue