grokify/mogo

View on GitHub
time/timeutil/times.go

Summary

Maintainability
A
3 hrs
Test Coverage
package timeutil

import (
    "errors"
    "sort"
    "time"
)

var (
    ErrEmptyTimeSlice   = errors.New("empty time slice")
    ErrOutOfBounds      = errors.New("out of bounds")
    ErrOutOfBoundsLower = errors.New("out of bounds lower")
    ErrOutOfBoundsUpper = errors.New("out of bounds upper")
)

// Times is a slice of `time.Time` that supports a number of functions and can be used for sorting,
// e.g. `sort.Sort(times)` or `sort.Sort(sort.Reverse(times))`.
type Times []time.Time

func (ts Times) Len() int           { return len(ts) }
func (ts Times) Less(i, j int) bool { return ts[i].Before(ts[j]) }
func (ts Times) Swap(i, j int)      { ts[i], ts[j] = ts[j], ts[i] }
func (ts Times) Sort()              { sort.Sort(ts) }
func (ts Times) SortReverse()       { sort.Sort(sort.Reverse(ts)) }

func (ts Times) Dedupe() Times {
    newTimeSlice := Times{}
    seenMap := map[time.Time]int{}
    for _, dt := range ts {
        if _, ok := seenMap[dt]; !ok {
            newTimeSlice = append(newTimeSlice, dt)
            seenMap[dt] = 1
        }
    }
    return newTimeSlice
}

func (ts Times) Deltas() Durations {
    var durs Durations
    if len(ts) < 2 {
        return durs
    }
    for i := 1; i < len(ts); i++ {
        durs = append(durs, ts[i].Sub(ts[i-1]))
    }
    return durs
}

func (ts Times) Duplicate() Times {
    var newTS Times
    newTS = append(newTS, ts...)
    return newTS
}

func (ts Times) Equal(compare Times) bool {
    if ts.Len() != compare.Len() {
        return false
    }
    for i, dt := range compare {
        if !dt.Equal(ts[i]) {
            return false
        }
    }
    return true
}

func (ts Times) Format(format string) []string {
    var formatted []string
    for _, dt := range ts {
        formatted = append(formatted, dt.Format(format))
    }
    return formatted
}

func (ts Times) IsSorted(asc bool) bool {
    deltas := ts.Deltas()
    if len(deltas) == 0 {
        return true
    }
    if asc {
        for delta := range deltas {
            if delta < 0 {
                return false
            }
        }
        return true
    }
    for delta := range deltas {
        if delta > 0 {
            return false
        }
    }
    return true
}

func (ts Times) Max() *time.Time {
    if len(ts) == 0 {
        return nil
    }
    var max time.Time
    for i, t := range ts {
        if i == 0 {
            max = t
        } else if t.Sub(max) > 0 {
            max = t
        }
    }
    return &max
}

func (ts Times) Min() *time.Time {
    if len(ts) == 0 {
        return nil
    }
    var min time.Time
    for i, t := range ts {
        if i == 0 {
            min = t
        } else if t.Sub(min) < 0 {
            min = t
        }
    }
    return &min
}

// RangeLower returns the TimeSlice time value for the range
// lower than or equal to the supplied time.
func (ts Times) RangeLower(t time.Time, inclusive bool) (time.Time, error) {
    if len(ts) == 0 {
        return t, ErrEmptyTimeSlice
    }
    sortedTS := ts.Dedupe()
    sort.Sort(sortedTS)

    if sortedTS[0].After(t) {
        return t, ErrOutOfBoundsLower
    }

    curRangeLower := sortedTS[0]
    for _, nextRangeLower := range sortedTS {
        if t.Before(nextRangeLower) {
            return curRangeLower, nil
        } else if inclusive && t.Equal(nextRangeLower) {
            return nextRangeLower, nil
        } else {
            curRangeLower = nextRangeLower
        }
    }
    return sortedTS[len(sortedTS)-1], nil
}

// RangeUpper returns the Times time value for the range
// lower than or equal to the supplied time. The time `t` must
// be less than or equal to the upper range.
func (ts Times) RangeUpper(t time.Time, inclusive bool) (time.Time, error) {
    if len(ts) == 0 {
        return t, ErrEmptyTimeSlice
    }
    sortedTS := ts.Dedupe()
    sort.Sort(sortedTS)

    if sortedTS[len(sortedTS)-1].Before(t) {
        return t, ErrOutOfBoundsUpper
    }
    curRangeUpper := sortedTS[len(sortedTS)-1]
    for i := range sortedTS {
        // check times in reverse order
        nextRangeUpper := sortedTS[len(sortedTS)-1-i]
        if t.After(nextRangeUpper) {
            return curRangeUpper, nil
        } else if inclusive && t.Equal(nextRangeUpper) {
            return nextRangeUpper, nil
        } else {
            curRangeUpper = nextRangeUpper
        }
    }
    return sortedTS[0], nil
}

func ParseTimes(format string, times []string) (Times, error) {
    var ts Times
    for _, raw := range times {
        if dt, err := time.Parse(format, raw); err != nil {
            return ts, err
        } else {
            ts = append(ts, dt)
        }
    }
    return ts, nil
}