diff --git a/docs/content/filtering.md b/docs/content/filtering.md index b69e494fe..acab891eb 100644 --- a/docs/content/filtering.md +++ b/docs/content/filtering.md @@ -429,6 +429,13 @@ seconds or with a suffix of: For example `--max-age 2d` means no files older than 2 days will be transferred. +This can also be an absolute time in one of these formats + +- RFC3339 - eg "2006-01-02T15:04:05Z07:00" +- ISO8601 Date and time, local timezone - "2006-01-02T15:04:05" +- ISO8601 Date and time, local timezone - "2006-01-02 15:04:05" +- ISO8601 Date - "2006-01-02" (YYYY-MM-DD) + ### `--min-age` - Don't transfer any file younger than this ### This option controls the minimum age of files to transfer. Give in diff --git a/fs/parseduration.go b/fs/parseduration.go index e79b323ee..cd3f192ac 100644 --- a/fs/parseduration.go +++ b/fs/parseduration.go @@ -48,20 +48,10 @@ var ageSuffixes = []struct { {Suffix: "", Multiplier: time.Second}, } -// ParseDuration parses a duration string. Accept ms|s|m|h|d|w|M|y suffixes. Defaults to second if not provided -func ParseDuration(age string) (time.Duration, error) { +// parse the age as suffixed ages +func parseDurationSuffixes(age string) (time.Duration, error) { var period float64 - if age == "off" { - return time.Duration(DurationOff), nil - } - - // Attempt to parse as a time.Duration first - d, err := time.ParseDuration(age) - if err == nil { - return d, nil - } - for _, ageSuffix := range ageSuffixes { if strings.HasSuffix(age, ageSuffix.Suffix) { numberString := age[:len(age)-len(ageSuffix.Suffix)] @@ -78,6 +68,51 @@ func ParseDuration(age string) (time.Duration, error) { return time.Duration(period), nil } +// time formats to try parsing ages as - in order +var timeFormats = []string{ + time.RFC3339, + "2006-01-02T15:04:05", + "2006-01-02 15:04:05", + "2006-01-02", +} + +// parse the age as time before the epoch in various date formats +func parseDurationDates(age string, epoch time.Time) (t time.Duration, err error) { + var instant time.Time + for _, timeFormat := range timeFormats { + instant, err = time.Parse(timeFormat, age) + if err == nil { + return epoch.Sub(instant), nil + } + } + return t, err +} + +// ParseDuration parses a duration string. Accept ms|s|m|h|d|w|M|y suffixes. Defaults to second if not provided +func ParseDuration(age string) (d time.Duration, err error) { + if age == "off" { + return time.Duration(DurationOff), nil + } + + // Attempt to parse as a time.Duration first + d, err = time.ParseDuration(age) + if err == nil { + return d, nil + } + + d, err = parseDurationSuffixes(age) + if err == nil { + return d, nil + } + + d, err = parseDurationDates(age, time.Now()) + if err == nil { + return d, nil + } + + return d, err +} + // ReadableString parses d into a human readable duration. // Based on https://github.com/hako/durafmt func (d Duration) ReadableString() string { diff --git a/fs/parseduration_test.go b/fs/parseduration_test.go index 49ff164c9..35441600c 100644 --- a/fs/parseduration_test.go +++ b/fs/parseduration_test.go @@ -2,6 +2,7 @@ package fs import ( "fmt" + "strings" "testing" "time" @@ -36,6 +37,10 @@ func TestParseDuration(t *testing.T) { {"1x", 0, true}, {"off", time.Duration(DurationOff), false}, {"1h2m3s", time.Hour + 2*time.Minute + 3*time.Second, false}, + {"2001-02-03", time.Since(time.Date(2001, 2, 3, 0, 0, 0, 0, time.Local)), false}, + {"2001-02-03 10:11:12", time.Since(time.Date(2001, 2, 3, 10, 11, 12, 0, time.Local)), false}, + {"2001-02-03T10:11:12", time.Since(time.Date(2001, 2, 3, 10, 11, 12, 0, time.Local)), false}, + {"2001-02-03T10:11:12.123Z", time.Since(time.Date(2001, 2, 3, 10, 11, 12, 123, time.UTC)), false}, } { duration, err := ParseDuration(test.in) if test.err { @@ -43,7 +48,12 @@ func TestParseDuration(t *testing.T) { } else { require.NoError(t, err) } - assert.Equal(t, test.want, duration) + if strings.HasPrefix(test.in, "2001-") { + ok := duration > test.want-time.Second && duration < test.want+time.Second + assert.True(t, ok, test.in) + } else { + assert.Equal(t, test.want, duration) + } } }