vault/helper/timeutil/timeutil.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

168 lines
4.6 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package timeutil
import (
"errors"
"fmt"
"strconv"
"strings"
"testing"
"time"
)
func StartOfPreviousMonth(t time.Time) time.Time {
year, month, _ := t.Date()
return time.Date(year, month, 1, 0, 0, 0, 0, t.Location()).AddDate(0, -1, 0)
}
func StartOfMonth(t time.Time) time.Time {
year, month, _ := t.Date()
return time.Date(year, month, 1, 0, 0, 0, 0, t.Location())
}
func StartOfNextMonth(t time.Time) time.Time {
year, month, _ := t.Date()
return time.Date(year, month, 1, 0, 0, 0, 0, t.Location()).AddDate(0, 1, 0)
}
// IsMonthStart checks if :t: is the start of the month
func IsMonthStart(t time.Time) bool {
return t.Equal(StartOfMonth(t))
}
func EndOfMonth(t time.Time) time.Time {
year, month, _ := t.Date()
if month == time.December {
return time.Date(year, time.December, 31, 23, 59, 59, 0, t.Location())
} else {
eom := time.Date(year, month+1, 1, 23, 59, 59, 0, t.Location())
return eom.AddDate(0, 0, -1)
}
}
// IsPreviousMonth checks if :t: is in the month directly before :toCompare:
func IsPreviousMonth(t, toCompare time.Time) bool {
thisMonthStart := StartOfMonth(toCompare)
previousMonthStart := StartOfMonth(thisMonthStart.AddDate(0, 0, -1))
if t.Equal(previousMonthStart) {
return true
}
return t.After(previousMonthStart) && t.Before(thisMonthStart)
}
// IsCurrentMonth checks if :t: is in the current month, as defined by :compare:
// generally, pass in time.Now().UTC() as :compare:
func IsCurrentMonth(t, compare time.Time) bool {
thisMonthStart := StartOfMonth(compare)
queryMonthStart := StartOfMonth(t)
return queryMonthStart.Equal(thisMonthStart)
}
// GetMostRecentContinuousMonths finds the start time of the most
// recent set of continguous months.
//
// For example, if the most recent start time is Aug 15, then that range is just 1 month
// If the recent start times are Aug 1 and July 1 and June 15, then that range is
// three months and we return June 15.
//
// note: return slice will be nil if :startTimes: is nil
// :startTimes: must be sorted in decreasing order (see unit test for examples)
func GetMostRecentContiguousMonths(startTimes []time.Time) []time.Time {
if len(startTimes) < 2 {
// no processing needed if 0 or 1 months worth of logs
return startTimes
}
out := []time.Time{startTimes[0]}
if !IsMonthStart(out[0]) {
// there is less than one contiguous month (most recent start time is after the start of this month)
return out
}
i := 1
for ; i < len(startTimes); i++ {
if !IsMonthStart(startTimes[i]) || !IsPreviousMonth(startTimes[i], startTimes[i-1]) {
break
}
out = append(out, startTimes[i])
}
// handle mid-month log starts
if i < len(startTimes) {
if IsPreviousMonth(StartOfMonth(startTimes[i]), startTimes[i-1]) {
// the earliest part of the segment is mid-month, but still valid for this segment
out = append(out, startTimes[i])
}
}
return out
}
func InRange(t, start, end time.Time) bool {
return (t.Equal(start) || t.After(start)) &&
(t.Equal(end) || t.Before(end))
}
// ParseTimeFromPath returns a UTC time from a path of the form '<timestamp>/',
// where <timestamp> is a Unix timestamp
func ParseTimeFromPath(path string) (time.Time, error) {
elems := strings.Split(path, "/")
if len(elems) == 1 {
// :path: is a directory that must have children
return time.Time{}, errors.New("Invalid path provided")
}
unixSeconds, err := strconv.ParseInt(elems[0], 10, 64)
if err != nil {
return time.Time{}, fmt.Errorf("could not convert time from path segment %q. error: %w", elems[0], err)
}
return time.Unix(unixSeconds, 0).UTC(), nil
}
// Compute the N-month period before the given date.
// For example, if it is currently April 2020, then 12 months is April 2019 through March 2020.
func MonthsPreviousTo(months int, now time.Time) time.Time {
firstOfMonth := StartOfMonth(now.UTC())
return firstOfMonth.AddDate(0, -months, 0)
}
// Skip this test if too close to the end of a month!
func SkipAtEndOfMonth(t *testing.T) {
t.Helper()
thisMonth := StartOfMonth(time.Now().UTC())
endOfMonth := EndOfMonth(thisMonth)
if endOfMonth.Sub(time.Now()) < 10*time.Minute {
t.Skip("too close to end of month")
}
}
// This interface allows unit tests to substitute in a simulated Clock.
type Clock interface {
Now() time.Time
NewTicker(time.Duration) *time.Ticker
NewTimer(time.Duration) *time.Timer
}
type DefaultClock struct{}
var _ Clock = (*DefaultClock)(nil)
func (_ DefaultClock) Now() time.Time {
return time.Now()
}
func (_ DefaultClock) NewTicker(d time.Duration) *time.Ticker {
return time.NewTicker(d)
}
func (_ DefaultClock) NewTimer(d time.Duration) *time.Timer {
return time.NewTimer(d)
}