base-golang/pkg/duration_fmt/fmt.go

341 lines
8.7 KiB
Go
Raw Permalink Normal View History

2024-07-23 10:23:43 +08:00
package duration_fmt
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
)
var (
//units, _ = DefaultUnitsCoder.Decode("year,week,day,hour,minute,second,millisecond,microsecond")
units, _ = DefaultUnitsCoder.Decode("年,星期,天,小时,分钟,秒,毫秒,微秒")
unitsShort = []string{"y", "w", "d", "h", "m", "s", "ms", "µs"}
)
// Durafmt holds the parsed duration and the original input duration.
type Durafmt struct {
duration time.Duration
input string // Used as reference.
limitN int // Non-zero to limit only first N elements to output.
limitUnit string // Non-empty to limit max unit
}
// LimitToUnit sets the output format, you will not have unit bigger than the UNIT specified. UNIT = "" means no restriction.
func (d *Durafmt) LimitToUnit(unit string) *Durafmt {
d.limitUnit = unit
return d
}
// LimitFirstN sets the output format, outputing only first N elements. n == 0 means no limit.
func (d *Durafmt) LimitFirstN(n int) *Durafmt {
d.limitN = n
return d
}
func (d *Durafmt) Duration() time.Duration {
return d.duration
}
// Truncate sets precision
func (d *Durafmt) Truncate(unit time.Duration) *Durafmt {
d.duration = d.duration.Truncate(unit)
return d
}
// Parse creates a new *Durafmt struct, returns error if input is invalid.
func Parse(dinput time.Duration) *Durafmt {
input := dinput.String()
return &Durafmt{dinput, input, 0, ""}
}
// ParseShort creates a new *Durafmt struct, short form, returns error if input is invalid.
// It's shortcut for `Parse(dur).LimitFirstN(1)`
func ParseShort(dinput time.Duration) *Durafmt {
input := dinput.String()
return &Durafmt{dinput, input, 1, ""}
}
// ParseString creates a new *Durafmt struct from a string.
// returns an error if input is invalid.
func ParseString(input string) (*Durafmt, error) {
if input == "0" || input == "-0" {
return nil, errors.New("durafmt: missing unit in duration " + input)
}
duration, err := time.ParseDuration(input)
if err != nil {
return nil, err
}
return &Durafmt{duration, input, 0, ""}, nil
}
// ParseStringShort creates a new *Durafmt struct from a string, short form
// returns an error if input is invalid.
// It's shortcut for `ParseString(durStr)` and then calling `LimitFirstN(1)`
func ParseStringShort(input string) (*Durafmt, error) {
if input == "0" || input == "-0" {
return nil, errors.New("durafmt: missing unit in duration " + input)
}
duration, err := time.ParseDuration(input)
if err != nil {
return nil, err
}
return &Durafmt{duration, input, 1, ""}, nil
}
// String parses d *Durafmt into a human readable duration with default units.
func (d *Durafmt) String() string {
return d.Format(units)
}
// Format parses d *Durafmt into a human readable duration with units.
func (d *Durafmt) Format(units Units) string {
var duration string
// Check for minus durations.
if string(d.input[0]) == "-" {
duration += "-"
d.duration = -d.duration
}
var microseconds int64
var milliseconds int64
var seconds int64
var minutes int64
var hours int64
var days int64
var weeks int64
var years int64
var shouldConvert = false
remainingSecondsToConvert := int64(d.duration / time.Microsecond)
// Convert duration.
if d.limitUnit == "" {
shouldConvert = true
}
if d.limitUnit == "years" || shouldConvert {
years = remainingSecondsToConvert / (365 * 24 * 3600 * 1000000)
remainingSecondsToConvert -= years * 365 * 24 * 3600 * 1000000
shouldConvert = true
}
if d.limitUnit == "weeks" || shouldConvert {
weeks = remainingSecondsToConvert / (7 * 24 * 3600 * 1000000)
remainingSecondsToConvert -= weeks * 7 * 24 * 3600 * 1000000
shouldConvert = true
}
if d.limitUnit == "days" || shouldConvert {
days = remainingSecondsToConvert / (24 * 3600 * 1000000)
remainingSecondsToConvert -= days * 24 * 3600 * 1000000
shouldConvert = true
}
if d.limitUnit == "hours" || shouldConvert {
hours = remainingSecondsToConvert / (3600 * 1000000)
remainingSecondsToConvert -= hours * 3600 * 1000000
shouldConvert = true
}
if d.limitUnit == "minutes" || shouldConvert {
minutes = remainingSecondsToConvert / (60 * 1000000)
remainingSecondsToConvert -= minutes * 60 * 1000000
shouldConvert = true
}
if d.limitUnit == "seconds" || shouldConvert {
seconds = remainingSecondsToConvert / 1000000
remainingSecondsToConvert -= seconds * 1000000
shouldConvert = true
}
if d.limitUnit == "milliseconds" || shouldConvert {
milliseconds = remainingSecondsToConvert / 1000
remainingSecondsToConvert -= milliseconds * 1000
}
microseconds = remainingSecondsToConvert
// Create a map of the converted duration time.
durationMap := []int64{
microseconds,
milliseconds,
seconds,
minutes,
hours,
days,
weeks,
years,
}
// Construct duration string.
for i, u := range units.Units() {
v := durationMap[7-i]
strval := strconv.FormatInt(v, 10)
switch {
// add to the duration string if v > 1.
case v > 1:
duration += strval + " " + u.Plural + " "
// remove the plural 's', if v is 1.
case v == 1:
duration += strval + " " + u.Singular + " "
// omit any value with 0s or 0.
case d.duration.String() == "0" || d.duration.String() == "0s":
pattern := fmt.Sprintf("^-?0%s$", unitsShort[i])
isMatch, err := regexp.MatchString(pattern, d.input)
if err != nil {
return ""
}
if isMatch {
duration += strval + " " + u.Plural
}
// omit any value with 0.
case v == 0:
continue
}
}
// trim any remaining spaces.
duration = strings.TrimSpace(duration)
// if more than 2 spaces present return the first 2 strings
// if short version is requested
if d.limitN > 0 {
parts := strings.Split(duration, " ")
if len(parts) > d.limitN*2 {
duration = strings.Join(parts[:d.limitN*2], " ")
}
}
return duration
}
func (d *Durafmt) InternationalString() string {
var duration string
// Check for minus durations.
if string(d.input[0]) == "-" {
duration += "-"
d.duration = -d.duration
}
var microseconds int64
var milliseconds int64
var seconds int64
var minutes int64
var hours int64
var days int64
var weeks int64
var years int64
var shouldConvert = false
remainingSecondsToConvert := int64(d.duration / time.Microsecond)
// Convert duration.
if d.limitUnit == "" {
shouldConvert = true
}
if d.limitUnit == "years" || shouldConvert {
years = remainingSecondsToConvert / (365 * 24 * 3600 * 1000000)
remainingSecondsToConvert -= years * 365 * 24 * 3600 * 1000000
shouldConvert = true
}
if d.limitUnit == "weeks" || shouldConvert {
weeks = remainingSecondsToConvert / (7 * 24 * 3600 * 1000000)
remainingSecondsToConvert -= weeks * 7 * 24 * 3600 * 1000000
shouldConvert = true
}
if d.limitUnit == "days" || shouldConvert {
days = remainingSecondsToConvert / (24 * 3600 * 1000000)
remainingSecondsToConvert -= days * 24 * 3600 * 1000000
shouldConvert = true
}
if d.limitUnit == "hours" || shouldConvert {
hours = remainingSecondsToConvert / (3600 * 1000000)
remainingSecondsToConvert -= hours * 3600 * 1000000
shouldConvert = true
}
if d.limitUnit == "minutes" || shouldConvert {
minutes = remainingSecondsToConvert / (60 * 1000000)
remainingSecondsToConvert -= minutes * 60 * 1000000
shouldConvert = true
}
if d.limitUnit == "seconds" || shouldConvert {
seconds = remainingSecondsToConvert / 1000000
remainingSecondsToConvert -= seconds * 1000000
shouldConvert = true
}
if d.limitUnit == "milliseconds" || shouldConvert {
milliseconds = remainingSecondsToConvert / 1000
remainingSecondsToConvert -= milliseconds * 1000
}
microseconds = remainingSecondsToConvert
// Create a map of the converted duration time.
durationMap := map[string]int64{
"µs": microseconds,
"ms": milliseconds,
"s": seconds,
"m": minutes,
"h": hours,
"d": days,
"w": weeks,
"y": years,
}
// Construct duration string.
for i := range units.Units() {
u := unitsShort[i]
v := durationMap[u]
strval := strconv.FormatInt(v, 10)
switch {
// add to the duration string if v > 0.
case v > 0:
duration += strval + " " + u + " "
// omit any value with 0.
case d.duration.String() == "0":
pattern := fmt.Sprintf("^-?0%s$", unitsShort[i])
isMatch, err := regexp.MatchString(pattern, d.input)
if err != nil {
return ""
}
if isMatch {
duration += strval + " " + u
}
// omit any value with 0.
case v == 0:
continue
}
}
// trim any remaining spaces.
duration = strings.TrimSpace(duration)
// if more than 2 spaces present return the first 2 strings
// if short version is requested
if d.limitN > 0 {
parts := strings.Split(duration, " ")
if len(parts) > d.limitN*2 {
duration = strings.Join(parts[:d.limitN*2], " ")
}
}
return duration
}
func (d *Durafmt) TrimSpace() string {
return strings.Replace(d.String(), " ", "", -1)
}