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  }