2020-10-13 01:30:47 +00:00
// Copyright 2020 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
2020-10-13 00:04:16 +00:00
package timeinterval
2020-09-22 07:13:10 +00:00
import (
2021-01-24 09:37:46 +00:00
"encoding/json"
2020-09-22 07:13:10 +00:00
"errors"
"fmt"
2022-09-22 12:45:17 +00:00
"os"
2020-09-22 07:13:10 +00:00
"regexp"
2022-09-22 12:45:17 +00:00
"runtime"
2020-09-22 07:13:10 +00:00
"strconv"
"strings"
"time"
2021-01-24 09:37:46 +00:00
"gopkg.in/yaml.v2"
2020-09-22 07:13:10 +00:00
)
2023-10-13 13:15:05 +00:00
// Intervener determines whether a given time and active route time interval should mute outgoing notifications.
// It implements the TimeMuter interface.
type Intervener struct {
intervals map [ string ] [ ] TimeInterval
}
2024-04-30 11:47:00 +00:00
// Mutes implements the TimeMuter interface.
func ( i * Intervener ) Mutes ( names [ ] string , now time . Time ) ( bool , [ ] string , error ) {
var in [ ] string
2023-10-13 13:15:05 +00:00
for _ , name := range names {
interval , ok := i . intervals [ name ]
if ! ok {
2024-04-30 11:47:00 +00:00
return false , nil , fmt . Errorf ( "time interval %s doesn't exist in config" , name )
2023-10-13 13:15:05 +00:00
}
for _ , ti := range interval {
if ti . ContainsTime ( now . UTC ( ) ) {
2024-04-30 11:47:00 +00:00
in = append ( in , name )
2023-10-13 13:15:05 +00:00
}
}
}
2024-04-30 11:47:00 +00:00
return len ( in ) > 0 , in , nil
2023-10-13 13:15:05 +00:00
}
func NewIntervener ( ti map [ string ] [ ] TimeInterval ) * Intervener {
return & Intervener {
intervals : ti ,
}
}
2020-09-22 07:13:10 +00:00
// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained
// within the interval.
type TimeInterval struct {
2021-01-24 09:37:46 +00:00
Times [ ] TimeRange ` yaml:"times,omitempty" json:"times,omitempty" `
Weekdays [ ] WeekdayRange ` yaml:"weekdays,flow,omitempty" json:"weekdays,omitempty" `
DaysOfMonth [ ] DayOfMonthRange ` yaml:"days_of_month,flow,omitempty" json:"days_of_month,omitempty" `
Months [ ] MonthRange ` yaml:"months,flow,omitempty" json:"months,omitempty" `
Years [ ] YearRange ` yaml:"years,flow,omitempty" json:"years,omitempty" `
2022-09-22 12:45:17 +00:00
Location * Location ` yaml:"location,flow,omitempty" json:"location,omitempty" `
2020-09-22 07:13:10 +00:00
}
2020-11-23 21:36:42 +00:00
// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.
2020-11-24 04:05:37 +00:00
// For example, 4:00PM to End of the day would Begin at 1020 and End at 1440.
2020-10-06 10:07:08 +00:00
type TimeRange struct {
StartMinute int
EndMinute int
2020-09-22 07:13:10 +00:00
}
2020-11-24 01:45:40 +00:00
// InclusiveRange is used to hold the Beginning and End values of many time interval components.
2020-10-06 10:07:08 +00:00
type InclusiveRange struct {
Begin int
End int
2020-09-22 07:13:10 +00:00
}
2020-11-24 01:45:40 +00:00
// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday.
2020-10-06 10:07:08 +00:00
type WeekdayRange struct {
InclusiveRange
2020-09-22 07:13:10 +00:00
}
2020-11-24 01:45:40 +00:00
// A DayOfMonthRange is an inclusive range that may have negative Beginning/End values that represent distance from the End of the month Beginning at -1.
2020-10-06 10:07:08 +00:00
type DayOfMonthRange struct {
InclusiveRange
2020-09-22 07:13:10 +00:00
}
2020-11-24 01:45:40 +00:00
// A MonthRange is an inclusive range between [1, 12] where 1 = January.
2020-10-06 10:07:08 +00:00
type MonthRange struct {
InclusiveRange
2020-09-22 07:13:10 +00:00
}
2020-11-24 01:45:40 +00:00
// A YearRange is a positive inclusive range.
2020-10-06 10:07:08 +00:00
type YearRange struct {
InclusiveRange
2020-09-22 07:13:10 +00:00
}
2022-09-22 12:45:17 +00:00
// A Location is a container for a time.Location, used for custom unmarshalling/validation logic.
type Location struct {
* time . Location
}
2020-09-22 07:13:10 +00:00
type yamlTimeRange struct {
2021-01-24 09:37:46 +00:00
StartTime string ` yaml:"start_time" json:"start_time" `
EndTime string ` yaml:"end_time" json:"end_time" `
2020-09-22 07:13:10 +00:00
}
2020-11-24 01:45:40 +00:00
// A range with a Beginning and End that can be represented as strings.
2020-09-22 07:13:10 +00:00
type stringableRange interface {
setBegin ( int )
setEnd ( int )
// Try to map a member of the range into an integer.
memberFromString ( string ) ( int , error )
}
2020-10-06 10:07:08 +00:00
func ( ir * InclusiveRange ) setBegin ( n int ) {
ir . Begin = n
2020-09-22 07:13:10 +00:00
}
2020-10-06 10:07:08 +00:00
func ( ir * InclusiveRange ) setEnd ( n int ) {
ir . End = n
2020-09-22 07:13:10 +00:00
}
2020-10-06 10:07:08 +00:00
func ( ir * InclusiveRange ) memberFromString ( in string ) ( out int , err error ) {
2020-09-22 07:13:10 +00:00
out , err = strconv . Atoi ( in )
if err != nil {
return - 1 , err
}
return out , nil
}
2020-10-06 10:07:08 +00:00
func ( r * WeekdayRange ) memberFromString ( in string ) ( out int , err error ) {
2020-09-22 07:13:10 +00:00
out , ok := daysOfWeek [ in ]
if ! ok {
return - 1 , fmt . Errorf ( "%s is not a valid weekday" , in )
}
return out , nil
}
2020-10-06 10:07:08 +00:00
func ( r * MonthRange ) memberFromString ( in string ) ( out int , err error ) {
2020-09-22 07:13:10 +00:00
out , ok := months [ in ]
if ! ok {
2020-10-06 10:07:08 +00:00
out , err = strconv . Atoi ( in )
if err != nil {
return - 1 , fmt . Errorf ( "%s is not a valid month" , in )
}
2020-09-22 07:13:10 +00:00
}
return out , nil
}
var daysOfWeek = map [ string ] int {
"sunday" : 0 ,
"monday" : 1 ,
"tuesday" : 2 ,
"wednesday" : 3 ,
"thursday" : 4 ,
"friday" : 5 ,
"saturday" : 6 ,
}
2022-03-25 16:59:51 +00:00
2020-10-06 10:07:08 +00:00
var daysOfWeekInv = map [ int ] string {
0 : "sunday" ,
1 : "monday" ,
2 : "tuesday" ,
3 : "wednesday" ,
4 : "thursday" ,
5 : "friday" ,
6 : "saturday" ,
}
2020-09-22 07:13:10 +00:00
var months = map [ string ] int {
"january" : 1 ,
"february" : 2 ,
"march" : 3 ,
"april" : 4 ,
"may" : 5 ,
"june" : 6 ,
"july" : 7 ,
"august" : 8 ,
"september" : 9 ,
"october" : 10 ,
"november" : 11 ,
"december" : 12 ,
}
2020-10-06 10:07:08 +00:00
var monthsInv = map [ int ] string {
1 : "january" ,
2 : "february" ,
3 : "march" ,
4 : "april" ,
5 : "may" ,
6 : "june" ,
7 : "july" ,
8 : "august" ,
9 : "september" ,
10 : "october" ,
11 : "november" ,
12 : "december" ,
}
2022-09-22 12:45:17 +00:00
// UnmarshalYAML implements the Unmarshaller interface for Location.
func ( tz * Location ) UnmarshalYAML ( unmarshal func ( interface { } ) error ) error {
var str string
if err := unmarshal ( & str ) ; err != nil {
return err
}
loc , err := time . LoadLocation ( str )
if err != nil {
if runtime . GOOS == "windows" {
if zoneinfo := os . Getenv ( "ZONEINFO" ) ; zoneinfo != "" {
return fmt . Errorf ( "%w (ZONEINFO=%q)" , err , zoneinfo )
}
return fmt . Errorf ( "%w (on Windows platforms, you may have to pass the time zone database using the ZONEINFO environment variable, see https://pkg.go.dev/time#LoadLocation for details)" , err )
}
return err
}
* tz = Location { loc }
return nil
}
// UnmarshalJSON implements the json.Unmarshaler interface for Location.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func ( tz * Location ) UnmarshalJSON ( in [ ] byte ) error {
return yaml . Unmarshal ( in , tz )
}
2020-10-06 10:07:08 +00:00
// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.
func ( r * WeekdayRange ) UnmarshalYAML ( unmarshal func ( interface { } ) error ) error {
2020-09-22 07:13:10 +00:00
var str string
if err := unmarshal ( & str ) ; err != nil {
return err
}
2020-11-24 03:12:13 +00:00
if err := stringableRangeFromString ( str , r ) ; err != nil {
return err
}
2020-10-06 10:07:08 +00:00
if r . Begin > r . End {
2020-10-14 10:29:50 +00:00
return errors . New ( "start day cannot be before end day" )
2020-09-22 07:13:10 +00:00
}
2020-10-06 10:07:08 +00:00
if r . Begin < 0 || r . Begin > 6 {
2020-09-22 07:13:10 +00:00
return fmt . Errorf ( "%s is not a valid day of the week: out of range" , str )
}
2020-10-06 10:07:08 +00:00
if r . End < 0 || r . End > 6 {
2020-09-22 07:13:10 +00:00
return fmt . Errorf ( "%s is not a valid day of the week: out of range" , str )
}
2020-11-24 03:12:13 +00:00
return nil
2020-09-22 07:13:10 +00:00
}
2021-01-24 09:37:46 +00:00
// UnmarshalJSON implements the json.Unmarshaler interface for WeekdayRange.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func ( r * WeekdayRange ) UnmarshalJSON ( in [ ] byte ) error {
return yaml . Unmarshal ( in , r )
}
2020-10-06 10:07:08 +00:00
// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.
func ( r * DayOfMonthRange ) UnmarshalYAML ( unmarshal func ( interface { } ) error ) error {
2020-09-22 07:13:10 +00:00
var str string
if err := unmarshal ( & str ) ; err != nil {
return err
}
2020-11-24 03:12:13 +00:00
if err := stringableRangeFromString ( str , r ) ; err != nil {
return err
}
2020-11-15 05:56:46 +00:00
// Check beginning <= end accounting for negatives day of month indices as well.
// Months != 31 days can't be addressed here and are clamped, but at least we can catch blatant errors.
2020-10-06 10:07:08 +00:00
if r . Begin == 0 || r . Begin < - 31 || r . Begin > 31 {
return fmt . Errorf ( "%d is not a valid day of the month: out of range" , r . Begin )
2020-09-22 07:13:10 +00:00
}
2020-10-06 10:07:08 +00:00
if r . End == 0 || r . End < - 31 || r . End > 31 {
return fmt . Errorf ( "%d is not a valid day of the month: out of range" , r . End )
2020-09-22 07:13:10 +00:00
}
2020-11-15 05:56:46 +00:00
// Restricting here prevents errors where begin > end in longer months but not shorter months.
if r . Begin < 0 && r . End > 0 {
return fmt . Errorf ( "end day must be negative if start day is negative" )
}
// Check begin <= end. We can't know this for sure when using negative indices
2020-11-24 01:45:40 +00:00
// but we can prevent cases where its always invalid (using 28 day minimum length).
2020-11-15 05:56:46 +00:00
checkBegin := r . Begin
checkEnd := r . End
2020-10-06 10:07:08 +00:00
if r . Begin < 0 {
2020-11-15 05:56:46 +00:00
checkBegin = 28 + r . Begin
2020-09-22 07:13:10 +00:00
}
2020-10-06 10:07:08 +00:00
if r . End < 0 {
2020-11-15 05:56:46 +00:00
checkEnd = 28 + r . End
2020-09-22 07:13:10 +00:00
}
2020-11-15 05:56:46 +00:00
if checkBegin > checkEnd {
return fmt . Errorf ( "end day %d is always before start day %d" , r . End , r . Begin )
2020-09-22 07:13:10 +00:00
}
2020-11-24 03:12:13 +00:00
return nil
2020-09-22 07:13:10 +00:00
}
2021-01-24 09:37:46 +00:00
// UnmarshalJSON implements the json.Unmarshaler interface for DayOfMonthRange.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func ( r * DayOfMonthRange ) UnmarshalJSON ( in [ ] byte ) error {
return yaml . Unmarshal ( in , r )
}
2020-10-06 10:07:08 +00:00
// UnmarshalYAML implements the Unmarshaller interface for MonthRange.
func ( r * MonthRange ) UnmarshalYAML ( unmarshal func ( interface { } ) error ) error {
2020-09-22 07:13:10 +00:00
var str string
if err := unmarshal ( & str ) ; err != nil {
return err
}
2020-11-15 05:56:46 +00:00
if err := stringableRangeFromString ( str , r ) ; err != nil {
return err
2020-10-06 10:07:08 +00:00
}
2020-11-15 05:56:46 +00:00
if r . Begin > r . End {
begin := monthsInv [ r . Begin ]
end := monthsInv [ r . End ]
return fmt . Errorf ( "end month %s is before start month %s" , end , begin )
2020-10-06 10:07:08 +00:00
}
2020-11-15 05:56:46 +00:00
return nil
2020-10-06 10:07:08 +00:00
}
2021-01-24 09:37:46 +00:00
// UnmarshalJSON implements the json.Unmarshaler interface for MonthRange.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func ( r * MonthRange ) UnmarshalJSON ( in [ ] byte ) error {
return yaml . Unmarshal ( in , r )
}
2020-10-06 10:07:08 +00:00
// UnmarshalYAML implements the Unmarshaller interface for YearRange.
func ( r * YearRange ) UnmarshalYAML ( unmarshal func ( interface { } ) error ) error {
2020-09-22 07:13:10 +00:00
var str string
if err := unmarshal ( & str ) ; err != nil {
return err
}
2020-11-24 03:12:13 +00:00
if err := stringableRangeFromString ( str , r ) ; err != nil {
return err
}
2020-10-06 10:07:08 +00:00
if r . Begin > r . End {
2020-11-15 05:56:46 +00:00
return fmt . Errorf ( "end year %d is before start year %d" , r . End , r . Begin )
2020-09-22 07:13:10 +00:00
}
2020-11-24 03:12:13 +00:00
return nil
2020-09-22 07:13:10 +00:00
}
2021-01-24 09:37:46 +00:00
// UnmarshalJSON implements the json.Unmarshaler interface for YearRange.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func ( r * YearRange ) UnmarshalJSON ( in [ ] byte ) error {
return yaml . Unmarshal ( in , r )
}
2020-10-06 10:07:08 +00:00
// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.
func ( tr * TimeRange ) UnmarshalYAML ( unmarshal func ( interface { } ) error ) error {
2020-09-22 07:13:10 +00:00
var y yamlTimeRange
if err := unmarshal ( & y ) ; err != nil {
return err
}
if y . EndTime == "" || y . StartTime == "" {
2020-10-14 10:29:50 +00:00
return errors . New ( "both start and end times must be provided" )
2020-09-22 07:13:10 +00:00
}
start , err := parseTime ( y . StartTime )
if err != nil {
2020-11-15 05:56:46 +00:00
return err
2020-09-22 07:13:10 +00:00
}
2020-11-15 05:56:46 +00:00
end , err := parseTime ( y . EndTime )
2020-09-22 07:13:10 +00:00
if err != nil {
return err
}
2020-11-15 05:56:46 +00:00
if start >= end {
2020-10-14 10:29:50 +00:00
return errors . New ( "start time cannot be equal or greater than end time" )
2020-09-22 07:13:10 +00:00
}
2020-11-15 05:56:46 +00:00
tr . StartMinute , tr . EndMinute = start , end
2020-09-22 07:13:10 +00:00
return nil
}
2021-01-24 09:37:46 +00:00
// UnmarshalJSON implements the json.Unmarshaler interface for Timerange.
// It delegates to the YAML unmarshaller as it can parse JSON and has validation logic.
func ( tr * TimeRange ) UnmarshalJSON ( in [ ] byte ) error {
return yaml . Unmarshal ( in , tr )
}
2020-11-24 01:45:40 +00:00
// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange.
2020-11-15 05:56:46 +00:00
func ( r WeekdayRange ) MarshalYAML ( ) ( interface { } , error ) {
2021-01-24 09:37:46 +00:00
bytes , err := r . MarshalText ( )
return string ( bytes ) , err
}
// MarshalText implements the econding.TextMarshaler interface for WeekdayRange.
2022-03-25 16:59:51 +00:00
// It converts the range into a colon-separated string, or a single weekday if possible.
2024-03-21 11:26:46 +00:00
// E.g. "monday:friday" or "saturday".
2021-01-24 09:37:46 +00:00
func ( r WeekdayRange ) MarshalText ( ) ( [ ] byte , error ) {
2020-11-15 05:56:46 +00:00
beginStr , ok := daysOfWeekInv [ r . Begin ]
if ! ok {
return nil , fmt . Errorf ( "unable to convert %d into weekday string" , r . Begin )
}
if r . Begin == r . End {
2021-01-24 09:37:46 +00:00
return [ ] byte ( beginStr ) , nil
2020-11-15 05:56:46 +00:00
}
endStr , ok := daysOfWeekInv [ r . End ]
if ! ok {
return nil , fmt . Errorf ( "unable to convert %d into weekday string" , r . End )
}
rangeStr := fmt . Sprintf ( "%s:%s" , beginStr , endStr )
2021-01-24 09:37:46 +00:00
return [ ] byte ( rangeStr ) , nil
2020-11-15 05:56:46 +00:00
}
2021-01-24 09:37:46 +00:00
// MarshalYAML implements the yaml.Marshaler interface for TimeRange.
2020-10-06 10:07:08 +00:00
func ( tr TimeRange ) MarshalYAML ( ) ( out interface { } , err error ) {
startHr := tr . StartMinute / 60
endHr := tr . EndMinute / 60
startMin := tr . StartMinute % 60
endMin := tr . EndMinute % 60
startStr := fmt . Sprintf ( "%02d:%02d" , startHr , startMin )
endStr := fmt . Sprintf ( "%02d:%02d" , endHr , endMin )
yTr := yamlTimeRange { startStr , endStr }
return interface { } ( yTr ) , err
}
2021-01-24 09:37:46 +00:00
// MarshalJSON implements the json.Marshaler interface for TimeRange.
func ( tr TimeRange ) MarshalJSON ( ) ( out [ ] byte , err error ) {
startHr := tr . StartMinute / 60
endHr := tr . EndMinute / 60
startMin := tr . StartMinute % 60
endMin := tr . EndMinute % 60
startStr := fmt . Sprintf ( "%02d:%02d" , startHr , startMin )
endStr := fmt . Sprintf ( "%02d:%02d" , endHr , endMin )
yTr := yamlTimeRange { startStr , endStr }
return json . Marshal ( yTr )
}
2022-09-22 12:45:17 +00:00
// MarshalText implements the econding.TextMarshaler interface for Location.
// It marshals a Location back into a string that represents a time.Location.
func ( tz Location ) MarshalText ( ) ( [ ] byte , error ) {
if tz . Location == nil {
return nil , fmt . Errorf ( "unable to convert nil location into string" )
}
return [ ] byte ( tz . Location . String ( ) ) , nil
}
2022-09-30 11:33:28 +00:00
// MarshalYAML implements the yaml.Marshaler interface for Location.
2022-09-22 12:45:17 +00:00
func ( tz Location ) MarshalYAML ( ) ( interface { } , error ) {
bytes , err := tz . MarshalText ( )
return string ( bytes ) , err
}
2022-09-30 11:33:28 +00:00
// MarshalJSON implements the json.Marshaler interface for Location.
2022-09-22 12:45:17 +00:00
func ( tz Location ) MarshalJSON ( ) ( out [ ] byte , err error ) {
return json . Marshal ( tz . String ( ) )
}
2021-01-24 09:37:46 +00:00
// MarshalText implements the encoding.TextMarshaler interface for InclusiveRange.
// It converts the struct into a colon-separated string, or a single element if
2024-03-21 11:26:46 +00:00
// appropriate. E.g. "monday:friday" or "monday".
2021-01-24 09:37:46 +00:00
func ( ir InclusiveRange ) MarshalText ( ) ( [ ] byte , error ) {
2020-10-06 10:07:08 +00:00
if ir . Begin == ir . End {
2021-01-24 09:37:46 +00:00
return [ ] byte ( strconv . Itoa ( ir . Begin ) ) , nil
2020-10-06 10:07:08 +00:00
}
out := fmt . Sprintf ( "%d:%d" , ir . Begin , ir . End )
2021-01-24 09:37:46 +00:00
return [ ] byte ( out ) , nil
}
2022-03-25 16:59:51 +00:00
// MarshalYAML implements the yaml.Marshaler interface for InclusiveRange.
2021-01-24 09:37:46 +00:00
func ( ir InclusiveRange ) MarshalYAML ( ) ( interface { } , error ) {
bytes , err := ir . MarshalText ( )
return string ( bytes ) , err
2020-10-06 10:07:08 +00:00
}
2022-03-25 16:59:51 +00:00
var (
validTime = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"
validTimeRE = regexp . MustCompile ( validTime )
)
2020-09-22 07:13:10 +00:00
// Given a time, determines the number of days in the month that time occurs in.
func daysInMonth ( t time . Time ) int {
monthStart := time . Date ( t . Year ( ) , t . Month ( ) , 1 , 0 , 0 , 0 , 0 , t . Location ( ) )
monthEnd := monthStart . AddDate ( 0 , 1 , 0 )
diff := monthEnd . Sub ( monthStart )
return int ( diff . Hours ( ) / 24 )
}
func clamp ( n , min , max int ) int {
if n <= min {
return min
}
if n >= max {
return max
}
return n
}
2020-11-24 01:45:40 +00:00
// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false.
2020-09-22 07:13:10 +00:00
func ( tp TimeInterval ) ContainsTime ( t time . Time ) bool {
2022-09-22 12:45:17 +00:00
if tp . Location != nil {
t = t . In ( tp . Location . Location )
}
2020-09-22 07:13:10 +00:00
if tp . Times != nil {
in := false
for _ , validMinutes := range tp . Times {
2020-10-06 10:07:08 +00:00
if ( t . Hour ( ) * 60 + t . Minute ( ) ) >= validMinutes . StartMinute && ( t . Hour ( ) * 60 + t . Minute ( ) ) < validMinutes . EndMinute {
2020-09-22 07:13:10 +00:00
in = true
break
}
}
if ! in {
return false
}
}
if tp . DaysOfMonth != nil {
in := false
for _ , validDates := range tp . DaysOfMonth {
2020-10-13 00:28:10 +00:00
var begin , end int
2020-09-22 07:13:10 +00:00
daysInMonth := daysInMonth ( t )
2020-10-06 10:07:08 +00:00
if validDates . Begin < 0 {
2020-10-13 00:28:10 +00:00
begin = daysInMonth + validDates . Begin + 1
2020-09-22 07:13:10 +00:00
} else {
2020-10-13 00:28:10 +00:00
begin = validDates . Begin
2020-09-22 07:13:10 +00:00
}
2020-10-06 10:07:08 +00:00
if validDates . End < 0 {
2020-10-13 00:28:10 +00:00
end = daysInMonth + validDates . End + 1
2020-09-22 07:13:10 +00:00
} else {
2020-10-13 00:28:10 +00:00
end = validDates . End
2020-09-22 07:13:10 +00:00
}
2020-11-24 01:45:40 +00:00
// Skip clamping if the beginning date is after the end of the month.
2020-11-17 00:26:29 +00:00
if begin > daysInMonth {
continue
}
2020-11-24 01:45:40 +00:00
// Clamp to the boundaries of the month to prevent crossing into other months.
2020-10-13 00:28:10 +00:00
begin = clamp ( begin , - 1 * daysInMonth , daysInMonth )
end = clamp ( end , - 1 * daysInMonth , daysInMonth )
if t . Day ( ) >= begin && t . Day ( ) <= end {
2020-09-22 07:13:10 +00:00
in = true
break
}
}
if ! in {
return false
}
}
if tp . Months != nil {
in := false
for _ , validMonths := range tp . Months {
2020-10-06 10:07:08 +00:00
if t . Month ( ) >= time . Month ( validMonths . Begin ) && t . Month ( ) <= time . Month ( validMonths . End ) {
2020-09-22 07:13:10 +00:00
in = true
break
}
}
if ! in {
return false
}
}
if tp . Weekdays != nil {
in := false
for _ , validDays := range tp . Weekdays {
2020-10-06 10:07:08 +00:00
if t . Weekday ( ) >= time . Weekday ( validDays . Begin ) && t . Weekday ( ) <= time . Weekday ( validDays . End ) {
2020-09-22 07:13:10 +00:00
in = true
break
}
}
if ! in {
return false
}
}
if tp . Years != nil {
in := false
for _ , validYears := range tp . Years {
2020-10-06 10:07:08 +00:00
if t . Year ( ) >= validYears . Begin && t . Year ( ) <= validYears . End {
2020-09-22 07:13:10 +00:00
in = true
break
}
}
if ! in {
return false
}
}
return true
}
2020-11-24 01:45:40 +00:00
// Converts a string of the form "HH:MM" into the number of minutes elapsed in the day.
2020-09-22 07:13:10 +00:00
func parseTime ( in string ) ( mins int , err error ) {
if ! validTimeRE . MatchString ( in ) {
2020-10-14 10:29:50 +00:00
return 0 , fmt . Errorf ( "couldn't parse timestamp %s, invalid format" , in )
2020-09-22 07:13:10 +00:00
}
timestampComponents := strings . Split ( in , ":" )
if len ( timestampComponents ) != 2 {
2020-10-14 10:29:50 +00:00
return 0 , fmt . Errorf ( "invalid timestamp format: %s" , in )
2020-09-22 07:13:10 +00:00
}
timeStampHours , err := strconv . Atoi ( timestampComponents [ 0 ] )
if err != nil {
return 0 , err
}
timeStampMinutes , err := strconv . Atoi ( timestampComponents [ 1 ] )
if err != nil {
return 0 , err
}
if timeStampHours < 0 || timeStampHours > 24 || timeStampMinutes < 0 || timeStampMinutes > 60 {
2020-10-14 10:29:50 +00:00
return 0 , fmt . Errorf ( "timestamp %s out of range" , in )
2020-09-22 07:13:10 +00:00
}
2020-11-24 04:05:37 +00:00
// Timestamps are stored as minutes elapsed in the day, so multiply hours by 60.
2020-09-22 07:13:10 +00:00
mins = timeStampHours * 60 + timeStampMinutes
return mins , nil
}
2020-11-24 01:45:40 +00:00
// Converts a range that can be represented as strings (e.g. monday:wednesday) into an equivalent integer-represented range.
2020-09-22 07:13:10 +00:00
func stringableRangeFromString ( in string , r stringableRange ) ( err error ) {
in = strings . ToLower ( in )
if strings . ContainsRune ( in , ':' ) {
components := strings . Split ( in , ":" )
if len ( components ) != 2 {
2020-10-14 10:29:50 +00:00
return fmt . Errorf ( "couldn't parse range %s, invalid format" , in )
2020-09-22 07:13:10 +00:00
}
start , err := r . memberFromString ( components [ 0 ] )
if err != nil {
return err
}
2020-10-06 10:07:08 +00:00
End , err := r . memberFromString ( components [ 1 ] )
2020-09-22 07:13:10 +00:00
if err != nil {
return err
}
r . setBegin ( start )
2020-10-06 10:07:08 +00:00
r . setEnd ( End )
2020-09-22 07:13:10 +00:00
return nil
}
val , err := r . memberFromString ( in )
if err != nil {
return err
}
r . setBegin ( val )
r . setEnd ( val )
return nil
}