github.com/divyam234/rclone@v1.64.1/fs/parseduration.go (about) 1 package fs 2 3 import ( 4 "fmt" 5 "math" 6 "strconv" 7 "strings" 8 "time" 9 ) 10 11 // Duration is a time.Duration with some more parsing options 12 type Duration time.Duration 13 14 // DurationOff is the default value for flags which can be turned off 15 const DurationOff = Duration((1 << 63) - 1) 16 17 // Turn Duration into a string 18 func (d Duration) String() string { 19 if d == DurationOff { 20 return "off" 21 } 22 for i := len(ageSuffixes) - 2; i >= 0; i-- { 23 ageSuffix := &ageSuffixes[i] 24 if math.Abs(float64(d)) >= float64(ageSuffix.Multiplier) { 25 timeUnits := float64(d) / float64(ageSuffix.Multiplier) 26 return strconv.FormatFloat(timeUnits, 'f', -1, 64) + ageSuffix.Suffix 27 } 28 } 29 return time.Duration(d).String() 30 } 31 32 // IsSet returns if the duration is != DurationOff 33 func (d Duration) IsSet() bool { 34 return d != DurationOff 35 } 36 37 // We use time conventions 38 var ageSuffixes = []struct { 39 Suffix string 40 Multiplier time.Duration 41 }{ 42 {Suffix: "d", Multiplier: time.Hour * 24}, 43 {Suffix: "w", Multiplier: time.Hour * 24 * 7}, 44 {Suffix: "M", Multiplier: time.Hour * 24 * 30}, 45 {Suffix: "y", Multiplier: time.Hour * 24 * 365}, 46 47 // Default to second 48 {Suffix: "", Multiplier: time.Second}, 49 } 50 51 // parse the age as suffixed ages 52 func parseDurationSuffixes(age string) (time.Duration, error) { 53 var period float64 54 55 for _, ageSuffix := range ageSuffixes { 56 if strings.HasSuffix(age, ageSuffix.Suffix) { 57 numberString := age[:len(age)-len(ageSuffix.Suffix)] 58 var err error 59 period, err = strconv.ParseFloat(numberString, 64) 60 if err != nil { 61 return time.Duration(0), err 62 } 63 period *= float64(ageSuffix.Multiplier) 64 break 65 } 66 } 67 68 return time.Duration(period), nil 69 } 70 71 // time formats to try parsing ages as - in order 72 var timeFormats = []string{ 73 time.RFC3339, 74 "2006-01-02T15:04:05", 75 "2006-01-02 15:04:05", 76 "2006-01-02", 77 } 78 79 // parse the date as time in various date formats 80 func parseTimeDates(date string) (t time.Time, err error) { 81 var instant time.Time 82 for _, timeFormat := range timeFormats { 83 instant, err = time.ParseInLocation(timeFormat, date, time.Local) 84 if err == nil { 85 return instant, nil 86 } 87 } 88 return t, err 89 } 90 91 // parse the age as time before the epoch in various date formats 92 func parseDurationDates(age string, epoch time.Time) (d time.Duration, err error) { 93 instant, err := parseTimeDates(age) 94 if err != nil { 95 return d, err 96 } 97 98 return epoch.Sub(instant), nil 99 } 100 101 // parseDurationFromNow parses a duration string. Allows ParseDuration to match the time 102 // package and easier testing within the fs package. 103 func parseDurationFromNow(age string, getNow func() time.Time) (d time.Duration, err error) { 104 if age == "off" { 105 return time.Duration(DurationOff), nil 106 } 107 108 // Attempt to parse as a time.Duration first 109 d, err = time.ParseDuration(age) 110 if err == nil { 111 return d, nil 112 } 113 114 d, err = parseDurationSuffixes(age) 115 if err == nil { 116 return d, nil 117 } 118 119 d, err = parseDurationDates(age, getNow()) 120 if err == nil { 121 return d, nil 122 } 123 124 return d, err 125 } 126 127 // ParseDuration parses a duration string. Accept ms|s|m|h|d|w|M|y suffixes. Defaults to second if not provided 128 func ParseDuration(age string) (time.Duration, error) { 129 return parseDurationFromNow(age, timeNowFunc) 130 } 131 132 // ReadableString parses d into a human-readable duration with units. 133 // Examples: "3s", "1d2h23m20s", "292y24w3d23h47m16s". 134 func (d Duration) ReadableString() string { 135 return d.readableString(0) 136 } 137 138 // ShortReadableString parses d into a human-readable duration with units. 139 // This method returns it in short format, including the 3 most significant 140 // units only, sacrificing precision if necessary. E.g. returns "292y24w3d" 141 // instead of "292y24w3d23h47m16s", and "3d23h47m" instead of "3d23h47m16s". 142 func (d Duration) ShortReadableString() string { 143 return d.readableString(3) 144 } 145 146 // readableString parses d into a human-readable duration with units. 147 // Parameter maxNumberOfUnits limits number of significant units to include, 148 // sacrificing precision. E.g. with argument 3 it returns "292y24w3d" instead 149 // of "292y24w3d23h47m16s", and "3d23h47m" instead of "3d23h47m16s". Zero or 150 // negative argument means include all. 151 // Based on https://github.com/hako/durafmt 152 func (d Duration) readableString(maxNumberOfUnits int) string { 153 switch d { 154 case DurationOff: 155 return "off" 156 case 0: 157 return "0s" 158 } 159 160 readableString := "" 161 162 // Check for minus durations. 163 if d < 0 { 164 readableString += "-" 165 } 166 167 duration := time.Duration(math.Abs(float64(d))) 168 169 // Convert duration. 170 seconds := int64(duration.Seconds()) % 60 171 minutes := int64(duration.Minutes()) % 60 172 hours := int64(duration.Hours()) % 24 173 days := int64(duration/(24*time.Hour)) % 365 % 7 174 175 // Edge case between 364 and 365 days. 176 // We need to calculate weeks from what is left from years 177 leftYearDays := int64(duration/(24*time.Hour)) % 365 178 weeks := leftYearDays / 7 179 if leftYearDays >= 364 && leftYearDays < 365 { 180 weeks = 52 181 } 182 183 years := int64(duration/(24*time.Hour)) / 365 184 milliseconds := int64(duration/time.Millisecond) - 185 (seconds * 1000) - (minutes * 60000) - (hours * 3600000) - 186 (days * 86400000) - (weeks * 604800000) - (years * 31536000000) 187 188 // Create a map of the converted duration time. 189 durationMap := map[string]int64{ 190 "ms": milliseconds, 191 "s": seconds, 192 "m": minutes, 193 "h": hours, 194 "d": days, 195 "w": weeks, 196 "y": years, 197 } 198 199 // Construct duration string. 200 numberOfUnits := 0 201 for _, u := range [...]string{"y", "w", "d", "h", "m", "s", "ms"} { 202 v := durationMap[u] 203 strval := strconv.FormatInt(v, 10) 204 if v == 0 { 205 continue 206 } 207 readableString += strval + u 208 numberOfUnits++ 209 if maxNumberOfUnits > 0 && numberOfUnits >= maxNumberOfUnits { 210 break 211 } 212 } 213 214 return readableString 215 } 216 217 // Set a Duration 218 func (d *Duration) Set(s string) error { 219 duration, err := ParseDuration(s) 220 if err != nil { 221 return err 222 } 223 *d = Duration(duration) 224 return nil 225 } 226 227 // Type of the value 228 func (d Duration) Type() string { 229 return "Duration" 230 } 231 232 // UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON 233 func (d *Duration) UnmarshalJSON(in []byte) error { 234 return UnmarshalJSONFlag(in, d, func(i int64) error { 235 *d = Duration(i) 236 return nil 237 }) 238 } 239 240 // Scan implements the fmt.Scanner interface 241 func (d *Duration) Scan(s fmt.ScanState, ch rune) error { 242 token, err := s.Token(true, func(rune) bool { return true }) 243 if err != nil { 244 return err 245 } 246 return d.Set(string(token)) 247 }