profile
viewpoint
stuart nelson stuartnelson3 @soundcloud berlin

prometheus-junkyard/promdash 271

Prometheus Dashboard Builder

stuartnelson3/blog 9

mein Blog

stuartnelson3/alertmanager-routingtree 1

routingtree visualization for alert manager

stuartnelson3/am-ui 1

This project has been merged into https://github.com/prometheus/alertmanager

stuartnelson3/dwmstatus-rs 1

dwmstatus bar written in rust

stuartnelson3/elm-bootstrap 1

Bootstrap a new elm project that auto-recompiles and lets you use custom CSS

mikefrey/JetpackHell.js 0

Escape Hell using a Jetpack and Sword

stuartnelson3/alertmanager 0

Prometheus Alertmanager

stuartnelson3/audite 0

Portable mp3 player for Ruby

startedaerospike/aerospike-client-ruby

started time in 19 hours

PR opened prometheus/alertmanager

cli: avoid nil dereference in silence update

A trivial fix to avoid nil pointer dereference. It can occur when amtool fails to get silences in amtool silence update.

Before:

$ amtool silence update --alertmanager.url=INVALID-URL --duration=1h 94fbd24a-79fc-40d9-8a11-026c83ab9015
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1793415]

goroutine 1 [running]:
github.com/prometheus/alertmanager/cli.(*silenceUpdateCmd).update(0xc000322a80, 0x1b15d40, 0xc000322ae0, 0xc0000c6120, 0xc000322ae0, 0xc000588ed0)
	/Users/koki/go/src/github.com/prometheus/alertmanager/cli/silence_update.go:67 +0x3d5
github.com/prometheus/alertmanager/cli.execWithTimeout.func1(0xc0000c6120, 0x0, 0x0)
	/Users/koki/go/src/github.com/prometheus/alertmanager/cli/utils.go:165 +0xa1
github.com/prometheus/alertmanager/vendor/gopkg.in/alecthomas/kingpin%2ev2.(*actionMixin).applyActions(0xc0000cc618, 0xc0000c6120, 0x0, 0x0)
	/Users/koki/go/src/github.com/prometheus/alertmanager/vendor/gopkg.in/alecthomas/kingpin.v2/actions.go:28 +0x6d
github.com/prometheus/alertmanager/vendor/gopkg.in/alecthomas/kingpin%2ev2.(*Application).applyActions(0xc0000c2000, 0xc0000c6120, 0x0, 0x0)
	/Users/koki/go/src/github.com/prometheus/alertmanager/vendor/gopkg.in/alecthomas/kingpin.v2/app.go:557 +0xdc
github.com/prometheus/alertmanager/vendor/gopkg.in/alecthomas/kingpin%2ev2.(*Application).execute(0xc0000c2000, 0xc0000c6120, 0xc00058bd60, 0x2, 0x2, 0x0, 0x0, 0x0, 0x0)
	/Users/koki/go/src/github.com/prometheus/alertmanager/vendor/gopkg.in/alecthomas/kingpin.v2/app.go:390 +0x8f
github.com/prometheus/alertmanager/vendor/gopkg.in/alecthomas/kingpin%2ev2.(*Application).Parse(0xc0000c2000, 0xc0000321f0, 0x5, 0x5, 0x5, 0x0, 0x0, 0x10)
	/Users/koki/go/src/github.com/prometheus/alertmanager/vendor/gopkg.in/alecthomas/kingpin.v2/app.go:222 +0x1fe
github.com/prometheus/alertmanager/cli.Execute()
	/Users/koki/go/src/github.com/prometheus/alertmanager/cli/root.go:122 +0x8a9
main.main()
	/Users/koki/go/src/github.com/prometheus/alertmanager/cmd/amtool/main.go:19 +0x20

After

./amtool silence update --alertmanager.url=INVALID-URL --duration=1h 94fbd24a-79fc-40d9-8a11-026c83ab9015
amtool: error: Get "http://localhost:9093/INVALID-URL/api/v2/silence/94fbd24a-79fc-40d9-8a11-026c83ab9015": dial tcp [::1]:9093: connect: connection refused```
+1 -1

0 comment

1 changed file

pr created time in 21 hours

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {

I've thought more about this, I think we should leave this as is. This makes sense with the semantics of a time interval and modifying it would complicate the function by possibly returning only a single error case.

benridley

comment created time in a day

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {

No problem, I felt this was clunky as well initially but I couldn't see a better way :) Cheers

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++/* TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++var months = map[string]int{+	"january":   1,

Thanks, I'll add tests for this.

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {

sorry I did speak too fast :)

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {

We need the return false

But if we break we will not need the if

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 route:       team: frontend ``` +## `<mute_time_interval>`++A `mute_time_interval` specifies a named interval of time that may be referenced+in the routing tree to mute particular routes for particular times of the day.++```yaml+name: <string>+time_intervals:+  [ - <time_interval> ... ]++```+## `<time_interval>`+A `time_interval` contains the actual definition for an interval of time. The syntax+supports the following fields:++```yaml+- times:+  [ - <time_range> ...]+  weekdays:+  [ - <weekday_range> ...]+  days_of_month:+  [ - <days_of_month_range> ...]+  months:+  [ - <month_range> ...]+  years:+  [ - <year_range> ...]+```++All these fields are optional and if left unspecified allow any value to match the interval.+Some fields support ranges and negative indices, and are detailed below:++`times`: A list of time-ranges. They are inclusive of the starting time and exclusive
`times_range`: Ranges inclusive of the starting time and exclusive
benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 route:       team: frontend ``` +## `<mute_time_interval>`++A `mute_time_interval` specifies a named interval of time that may be referenced+in the routing tree to mute particular routes for particular times of the day.++```yaml+name: <string>+time_intervals:+  [ - <time_interval> ... ]+

extra white line not needed in the exemple

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 receivers: # A list of inhibition rules. inhibit_rules:   [ - <inhibit_rule> ... ]++# A list of mute time intervals for muting routes

full stop

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error { 	if len(c.Route.Match) > 0 || len(c.Route.MatchRE) > 0 { 		return fmt.Errorf("root route must not have any matchers") 	}+	if len(c.Route.MuteTimes) > 0 {+		return fmt.Errorf("root route cannot have any mute times")

we should be consistent and pick a unique name

mute times mute times interval ?

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {

How do we avoid this? We need to loop over all the days/months/years etc to check if at least one of them matches, and return false if it doesn't.

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 func resolveFilepaths(baseDir string, cfg *Config) { 	} } +// A MuteTimeInterval represents a named set of time intervals for which a route should be muted.+type MuteTimeInterval struct {+	Name          string                      `yaml:"name" json:"name"`+	TimeIntervals []timeinterval.TimeInterval `yaml:"time_intervals"`+}++// UnmarshalYAML implements the yaml.Unmarshaler interface for MuteTimeInterval

full stop

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 func resolveFilepaths(baseDir string, cfg *Config) { 	} } +// A MuteTimeInterval represents a named set of time intervals for which a route should be muted.
// MuteTimeInterval represents a named set of time intervals for which a route should be muted.
benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 import ( 	"time"  	"github.com/pkg/errors"+	"github.com/prometheus/alertmanager/timeinterval"

Can we split this internal import from the others?

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 func run() int { 			integrationsNum += len(integrations) 		} +		// Build the map of time interval names to mute time definitions

full stop

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 func run() int { 			integrationsNum += len(integrations) 		} +		// Build the map of time interval names to mute time definitions+		muteTimes := make(map[string][]timeinterval.TimeInterval)

Can we pre-allocate the size?

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 func run() int { 			integrationsNum += len(integrations) 		} +		// Build the map of time interval names to mute time definitions+		muteTimes := make(map[string][]timeinterval.TimeInterval)+		for _, ti := range conf.MuteTimeIntervals {+			muteTimes[ti.Name] = ti.TimeIntervals

Should we check for duplicate names?

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {

While this makes sense due to the semantics of the time interval (where not specifying a restriction means all possible times are included), I think an empty time interval is probably a symptom of an error. I'll add a check for this, thanks.

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"reflect"+	"testing"+	"time"++	"gopkg.in/yaml.v2"+)++var timeIntervalTestCases = []struct {+	validTimeStrings   []string+	invalidTimeStrings []string+	timeInterval       TimeInterval+}{+	{+		timeInterval: TimeInterval{},+		validTimeStrings: []string{+			"02 Jan 06 15:04 MST",+			"03 Jan 07 10:04 MST",+			"04 Jan 06 09:04 MST",+		},+		invalidTimeStrings: []string{},+	},+	{+		// 9am to 5pm, monday to friday+		timeInterval: TimeInterval{+			Times:    []TimeRange{{StartMinute: 540, EndMinute: 1020}},+			Weekdays: []WeekdayRange{{InclusiveRange{Begin: 1, End: 5}}},+		},+		validTimeStrings: []string{+			"04 May 20 15:04 MST",+			"05 May 20 10:04 MST",+			"09 Jun 20 09:04 MST",+		},+		invalidTimeStrings: []string{+			"03 May 20 15:04 MST",+			"04 May 20 08:59 MST",+			"05 May 20 05:00 MST",+		},+	},+	{+		// Easter 2020+		timeInterval: TimeInterval{+			DaysOfMonth: []DayOfMonthRange{{InclusiveRange{Begin: 4, End: 6}}},+			Months:      []MonthRange{{InclusiveRange{Begin: 4, End: 4}}},+			Years:       []YearRange{{InclusiveRange{Begin: 2020, End: 2020}}},+		},+		validTimeStrings: []string{+			"04 Apr 20 15:04 MST",+			"05 Apr 20 00:00 MST",+			"06 Apr 20 23:05 MST",+		},+		invalidTimeStrings: []string{+			"03 May 18 15:04 MST",+			"03 Apr 20 23:59 MST",+			"04 Jun 20 23:59 MST",+			"06 Apr 19 23:59 MST",+			"07 Apr 20 00:00 MST",+		},+	},+	{+		// Check negative days of month, last 3 days of each month+		timeInterval: TimeInterval{+			DaysOfMonth: []DayOfMonthRange{{InclusiveRange{Begin: -3, End: -1}}},+		},+		validTimeStrings: []string{+			"31 Jan 20 15:04 MST",+			"30 Jan 20 15:04 MST",+			"29 Jan 20 15:04 MST",+			"30 Jun 20 00:00 MST",+			"29 Feb 20 23:05 MST",+		},+		invalidTimeStrings: []string{+			"03 May 18 15:04 MST",+			"27 Jan 20 15:04 MST",+			"03 Apr 20 23:59 MST",+			"04 Jun 20 23:59 MST",+			"06 Apr 19 23:59 MST",+			"07 Apr 20 00:00 MST",+			"01 Mar 20 00:00 MST",+		},+	},+	{+		// Check out of bound days are clamped to month boundaries+		timeInterval: TimeInterval{+			Months:      []MonthRange{{InclusiveRange{Begin: 6, End: 6}}},+			DaysOfMonth: []DayOfMonthRange{{InclusiveRange{Begin: -31, End: 31}}},+		},+		validTimeStrings: []string{+			"30 Jun 20 00:00 MST",+			"01 Jun 20 00:00 MST",+		},+		invalidTimeStrings: []string{+			"31 May 20 00:00 MST",+			"1 Jul 20 00:00 MST",+		},+	},+}++var timeStringTestCases = []struct {+	timeString  string+	TimeRange   TimeRange+	expectError bool+}{+	{+		timeString:  "{'start_time': '00:00', 'end_time': '24:00'}",+		TimeRange:   TimeRange{StartMinute: 0, EndMinute: 1440},+		expectError: false,+	},+	{+		timeString:  "{'start_time': '01:35', 'end_time': '17:39'}",+		TimeRange:   TimeRange{StartMinute: 95, EndMinute: 1059},+		expectError: false,+	},+	{+		timeString:  "{'start_time': '09:35', 'end_time': '09:39'}",+		TimeRange:   TimeRange{StartMinute: 575, EndMinute: 579},+		expectError: false,+	},+	{+		// Error: Begin and End times are the same+		timeString:  "{'start_time': '17:31', 'end_time': '17:31'}",+		TimeRange:   TimeRange{},+		expectError: true,+	},+	{+		// Error: End time out of range+		timeString:  "{'start_time': '12:30', 'end_time': '24:01'}",+		TimeRange:   TimeRange{},+		expectError: true,+	},+	{+		// Error: Start time greater than End time+		timeString:  "{'start_time': '09:30', 'end_time': '07:41'}",+		TimeRange:   TimeRange{},+		expectError: true,+	},+	{+		// Error: Start time out of range and greater than End time+		timeString:  "{'start_time': '24:00', 'end_time': '17:41'}",+		TimeRange:   TimeRange{},+		expectError: true,+	},+	{+		// Error: No range specified+		timeString:  "{'start_time': '14:03'}",+		TimeRange:   TimeRange{},+		expectError: true,+	},+}++var dayOfWeekStringTestCases = []struct {+	dowString   string+	ranges      []WeekdayRange+	expectError bool+}{+	{+		dowString:   "['monday:friday', 'saturday']",+		ranges:      []WeekdayRange{{InclusiveRange{Begin: 1, End: 5}}, {InclusiveRange{Begin: 6, End: 6}}},

There are no negative tests here:

[5:1]

[-1]

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Months != nil {+		in := false+		for _, validMonths := range tp.Months {+			if t.Month() >= time.Month(validMonths.Begin) && t.Month() <= time.Month(validMonths.End) {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Weekdays != nil {+		in := false+		for _, validDays := range tp.Weekdays {+			if t.Weekday() >= time.Weekday(validDays.Begin) && t.Weekday() <= time.Weekday(validDays.End) {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Years != nil {+		in := false+		for _, validYears := range tp.Years {+			if t.Year() >= validYears.Begin && t.Year() <= validYears.End {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	return true+}++// Converts a string of the form "HH:MM" into the number of minutes elapsed in the day+func parseTime(in string) (mins int, err error) {+	if !validTimeRE.MatchString(in) {+		return 0, fmt.Errorf("couldn't parse timestamp %s, invalid format", in)+	}+	timestampComponents := strings.Split(in, ":")+	if len(timestampComponents) != 2 {+		return 0, fmt.Errorf("invalid timestamp format: %s", in)+	}+	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 {+		return 0, fmt.Errorf("timestamp %s out of range", in)+	}+	// Timestamps are stored as minutes elapsed in the day, so multiply hours by 60+	mins = timeStampHours*60 + timeStampMinutes+	return mins, nil+}++// Converts a range that can be represented as strings (e.g. monday:wednesday) into an equivalent integer-represented range+func stringableRangeFromString(in string, r stringableRange) (err error) {+	in = strings.ToLower(in)+	if strings.ContainsRune(in, ':') {+		components := strings.Split(in, ":")+		if len(components) != 2 {+			return fmt.Errorf("couldn't parse range %s, invalid format", in)+		}+		start, err := r.memberFromString(components[0])+		if err != nil {+			return err+		}+		End, err := r.memberFromString(components[1])+		if err != nil {+			return err+		}+		r.setBegin(start)+		r.setEnd(End)+		return nil+	}+	val, err := r.memberFromString(in)+	if err != nil {+		return err+	}+	r.setBegin(val)

Do we set them individualy somewhere? Can we do with one setter function ? Do we need those extra setters, can't we set EndDate directly?

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Months != nil {+		in := false+		for _, validMonths := range tp.Months {+			if t.Month() >= time.Month(validMonths.Begin) && t.Month() <= time.Month(validMonths.End) {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Weekdays != nil {+		in := false+		for _, validDays := range tp.Weekdays {+			if t.Weekday() >= time.Weekday(validDays.Begin) && t.Weekday() <= time.Weekday(validDays.End) {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Years != nil {+		in := false+		for _, validYears := range tp.Years {+			if t.Year() >= validYears.Begin && t.Year() <= validYears.End {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	return true+}++// Converts a string of the form "HH:MM" into the number of minutes elapsed in the day+func parseTime(in string) (mins int, err error) {+	if !validTimeRE.MatchString(in) {+		return 0, fmt.Errorf("couldn't parse timestamp %s, invalid format", in)+	}+	timestampComponents := strings.Split(in, ":")+	if len(timestampComponents) != 2 {+		return 0, fmt.Errorf("invalid timestamp format: %s", in)+	}+	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 {+		return 0, fmt.Errorf("timestamp %s out of range", in)+	}+	// Timestamps are stored as minutes elapsed in the day, so multiply hours by 60+	mins = timeStampHours*60 + timeStampMinutes+	return mins, nil+}++// Converts a range that can be represented as strings (e.g. monday:wednesday) into an equivalent integer-represented range

full stop

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Months != nil {+		in := false+		for _, validMonths := range tp.Months {+			if t.Month() >= time.Month(validMonths.Begin) && t.Month() <= time.Month(validMonths.End) {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Weekdays != nil {+		in := false+		for _, validDays := range tp.Weekdays {+			if t.Weekday() >= time.Weekday(validDays.Begin) && t.Weekday() <= time.Weekday(validDays.End) {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Years != nil {+		in := false+		for _, validYears := range tp.Years {+			if t.Year() >= validYears.Begin && t.Year() <= validYears.End {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	return true+}++// Converts a string of the form "HH:MM" into the number of minutes elapsed in the day+func parseTime(in string) (mins int, err error) {+	if !validTimeRE.MatchString(in) {+		return 0, fmt.Errorf("couldn't parse timestamp %s, invalid format", in)+	}+	timestampComponents := strings.Split(in, ":")+	if len(timestampComponents) != 2 {+		return 0, fmt.Errorf("invalid timestamp format: %s", in)+	}+	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 {+		return 0, fmt.Errorf("timestamp %s out of range", in)+	}+	// Timestamps are stored as minutes elapsed in the day, so multiply hours by 60

full stop

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Months != nil {+		in := false+		for _, validMonths := range tp.Months {+			if t.Month() >= time.Month(validMonths.Begin) && t.Month() <= time.Month(validMonths.End) {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Weekdays != nil {+		in := false+		for _, validDays := range tp.Weekdays {+			if t.Weekday() >= time.Weekday(validDays.Begin) && t.Weekday() <= time.Weekday(validDays.End) {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Years != nil {+		in := false+		for _, validYears := range tp.Years {+			if t.Year() >= validYears.Begin && t.Year() <= validYears.End {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	return true+}++// Converts a string of the form "HH:MM" into the number of minutes elapsed in the day

full stop

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Months != nil {+		in := false+		for _, validMonths := range tp.Months {+			if t.Month() >= time.Month(validMonths.Begin) && t.Month() <= time.Month(validMonths.End) {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Weekdays != nil {+		in := false+		for _, validDays := range tp.Weekdays {+			if t.Weekday() >= time.Weekday(validDays.Begin) && t.Weekday() <= time.Weekday(validDays.End) {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Years != nil {+		in := false+		for _, validYears := range tp.Years {+			if t.Year() >= validYears.Begin && t.Year() <= validYears.End {+				in = true+				break+			}+		}+		if !in {

We do not need this thanks to break, we do not need in at all

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {

We do not need this thanks to break, we do not need in at all

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {+	if tp.Times != nil {+		in := false+		for _, validMinutes := range tp.Times {+			if (t.Hour()*60+t.Minute()) >= validMinutes.StartMinute && (t.Hour()*60+t.Minute()) < validMinutes.EndMinute {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.DaysOfMonth != nil {+		in := false+		for _, validDates := range tp.DaysOfMonth {+			var begin, end int+			daysInMonth := daysInMonth(t)+			if validDates.Begin < 0 {+				begin = daysInMonth + validDates.Begin + 1+			} else {+				begin = validDates.Begin+			}+			if validDates.End < 0 {+				end = daysInMonth + validDates.End + 1+			} else {+				end = validDates.End+			}+			// Skip clamping if the beginning date is after the end of the month+			if begin > daysInMonth {+				continue+			}+			// Clamp to the boundaries of the month to prevent crossing into other months+			begin = clamp(begin, -1*daysInMonth, daysInMonth)+			end = clamp(end, -1*daysInMonth, daysInMonth)+			if t.Day() >= begin && t.Day() <= end {+				in = true+				break+			}+		}+		if !in {+			return false+		}+	}+	if tp.Months != nil {+		in := false+		for _, validMonths := range tp.Months {+			if t.Month() >= time.Month(validMonths.Begin) && t.Month() <= time.Month(validMonths.End) {+				in = true+				break+			}+		}+		if !in {

We do not need this thanks to break, we do not need in at all

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++// TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++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,+}++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",+}++// UnmarshalYAML implements the Unmarshaller interface for WeekdayRange.+func (r *WeekdayRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return errors.New("start day cannot be before end day")+	}+	if r.Begin < 0 || r.Begin > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	if r.End < 0 || r.End > 6 {+		return fmt.Errorf("%s is not a valid day of the week: out of range", str)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for DayOfMonthRange.+func (r *DayOfMonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	// 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.+	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)+	}+	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)+	}+	// 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+	// but we can prevent cases where its always invalid (using 28 day minimum length)+	checkBegin := r.Begin+	checkEnd := r.End+	if r.Begin < 0 {+		checkBegin = 28 + r.Begin+	}+	if r.End < 0 {+		checkEnd = 28 + r.End+	}+	if checkBegin > checkEnd {+		return fmt.Errorf("end day %d is always before start day %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for MonthRange.+func (r *MonthRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	if err := stringableRangeFromString(str, r); err != nil {+		return err+	}+	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)+	}+	return nil+}++// UnmarshalYAML implements the Unmarshaller interface for YearRange.+func (r *YearRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var str string+	if err := unmarshal(&str); err != nil {+		return err+	}+	err := stringableRangeFromString(str, r)+	if r.Begin > r.End {+		return fmt.Errorf("end year %d is before start year %d", r.End, r.Begin)+	}+	return err+}++// UnmarshalYAML implements the Unmarshaller interface for TimeRanges.+func (tr *TimeRange) UnmarshalYAML(unmarshal func(interface{}) error) error {+	var y yamlTimeRange+	if err := unmarshal(&y); err != nil {+		return err+	}+	if y.EndTime == "" || y.StartTime == "" {+		return errors.New("both start and end times must be provided")+	}+	start, err := parseTime(y.StartTime)+	if err != nil {+		return err+	}+	end, err := parseTime(y.EndTime)+	if err != nil {+		return err+	}+	if start >= end {+		return errors.New("start time cannot be equal or greater than end time")+	}+	tr.StartMinute, tr.EndMinute = start, end+	return nil+}++// MarshalYAML implements the yaml.Marshaler interface for WeekdayRange+func (r WeekdayRange) MarshalYAML() (interface{}, error) {+	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 {+		return interface{}(beginStr), nil+	}+	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)+	return interface{}(rangeStr), nil+}++//MarshalYAML implements the yaml.Marshaler interface for TimeRange+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+}++//MarshalYAML implements the yaml.Marshaler interface for InclusiveRange+func (ir InclusiveRange) MarshalYAML() (interface{}, error) {+	if ir.Begin == ir.End {+		return strconv.Itoa(ir.Begin), nil+	}+	out := fmt.Sprintf("%d:%d", ir.Begin, ir.End)+	return interface{}(out), nil+}++// TimeLayout specifies the layout to be used in time.Parse() calls for time intervals+const TimeLayout = "15:04"++var validTime string = "^((([01][0-9])|(2[0-3])):[0-5][0-9])$|(^24:00$)"+var validTimeRE *regexp.Regexp = regexp.MustCompile(validTime)++// 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+}++// ContainsTime returns true if the TimeInterval contains the given time, otherwise returns false+func (tp TimeInterval) ContainsTime(t time.Time) bool {

Do we check somewhere for empty time intervals? WIth an empty time interval, this would always return true

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

+// 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.++package timeinterval++import (+	"errors"+	"fmt"+	"regexp"+	"strconv"+	"strings"+	"time"+)++// TimeInterval describes intervals of time. ContainsTime will tell you if a golang time is contained+// within the interval.+type TimeInterval struct {+	Times       []TimeRange       `yaml:"times,omitempty"`+	Weekdays    []WeekdayRange    `yaml:"weekdays,flow,omitempty"`+	DaysOfMonth []DayOfMonthRange `yaml:"days_of_month,flow,omitempty"`+	Months      []MonthRange      `yaml:"months,flow,omitempty"`+	Years       []YearRange       `yaml:"years,flow,omitempty"`+}++/* TimeRange represents a range of minutes within a 1440 minute day, exclusive of the End minute. A day consists of 1440 minutes.+   For example, 5:00PM to End of the day would Begin at 1020 and End at 1440. */+type TimeRange struct {+	StartMinute int+	EndMinute   int+}++// InclusiveRange is used to hold the Beginning and End values of many time interval components+type InclusiveRange struct {+	Begin int+	End   int+}++// A WeekdayRange is an inclusive range between [0, 6] where 0 = Sunday+type WeekdayRange struct {+	InclusiveRange+}++// 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+type DayOfMonthRange struct {+	InclusiveRange+}++// A MonthRange is an inclusive range between [1, 12] where 1 = January+type MonthRange struct {+	InclusiveRange+}++// A YearRange is a positive inclusive range+type YearRange struct {+	InclusiveRange+}++type yamlTimeRange struct {+	StartTime string `yaml:"start_time"`+	EndTime   string `yaml:"end_time"`+}++// A range with a Beginning and End that can be represented as strings+type stringableRange interface {+	setBegin(int)+	setEnd(int)+	// Try to map a member of the range into an integer.+	memberFromString(string) (int, error)+}++func (ir *InclusiveRange) setBegin(n int) {+	ir.Begin = n+}++func (ir *InclusiveRange) setEnd(n int) {+	ir.End = n+}++func (ir *InclusiveRange) memberFromString(in string) (out int, err error) {+	out, err = strconv.Atoi(in)+	if err != nil {+		return -1, err+	}+	return out, nil+}++func (r *WeekdayRange) memberFromString(in string) (out int, err error) {+	out, ok := daysOfWeek[in]+	if !ok {+		return -1, fmt.Errorf("%s is not a valid weekday", in)+	}+	return out, nil+}++func (r *MonthRange) memberFromString(in string) (out int, err error) {+	out, ok := months[in]+	if !ok {+		out, err = strconv.Atoi(in)+		if err != nil {+			return -1, fmt.Errorf("%s is not a valid month", in)+		}+	}+	return out, nil+}++var daysOfWeek = map[string]int{+	"sunday":    0,+	"monday":    1,+	"tuesday":   2,+	"wednesday": 3,+	"thursday":  4,+	"friday":    5,+	"saturday":  6,+}+var daysOfWeekInv = map[int]string{+	0: "sunday",+	1: "monday",+	2: "tuesday",+	3: "wednesday",+	4: "thursday",+	5: "friday",+	6: "saturday",+}++var months = map[string]int{+	"january":   1,

Ok, then this is not tested

benridley

comment created time in 2 days

Pull request review commentprometheus/alertmanager

Add time-based muting to routing tree

 func (n SetNotifiesStage) Exec(ctx context.Context, l log.Logger, alerts ...*typ  	return ctx, alerts, n.nflog.Log(n.recv, gkey, firing, resolved) }++type TimeMuteStage struct {+	muteTimes map[string][]timeinterval.TimeInterval+}++func NewTimeMuteStage(mt map[string][]timeinterval.TimeInterval) *TimeMuteStage {+	return &TimeMuteStage{mt}+}++// Exec implements the stage interface for TimeMuteStage+// TimeMuteStage is responsible for muting alerts whose route is not in an active time+func (mts TimeMuteStage) Exec(ctx context.Context, l log.Logger, alerts ...*types.Alert) (context.Context, []*types.Alert, error) {+	muteTimeNames, ok := MuteTimeNames(ctx)+	if !ok {+		return ctx, alerts, nil+	}+	now, ok := Now(ctx)+	if !ok {+		return ctx, alerts, errors.New("missing now timestamp")+	}++	muted := false+	for _, mtName := range muteTimeNames {+		mt, ok := mts.muteTimes[mtName]+		if !ok {+			return ctx, alerts, errors.Errorf("mute time %s doesn't exist in config", mtName)+		}+		for _, ti := range mt {+			if ti.ContainsTime(now) {+				muted = true

Thanks, good pickup. I'll fix this.

benridley

comment created time in 2 days

more