diff --git a/README.md b/README.md
index 90409b86..5d2a436e 100644
--- a/README.md
+++ b/README.md
@@ -1459,15 +1459,15 @@ To do that, you'll have to use the maintenance configuration:
| `maintenance.enabled` | Whether the maintenance period is enabled | `true` |
| `maintenance.start` | Time at which the maintenance window starts in `hh:mm` format (e.g. `23:00`) | Required `""` |
| `maintenance.duration` | Duration of the maintenance window (e.g. `1h`, `30m`) | Required `""` |
+| `maintenance.timezone` | Timezone of the maintenance window format (e.g. `Europe/Amsterdam`).
See [List of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for more info | `UTC` |
| `maintenance.every` | Days on which the maintenance period applies (e.g. `[Monday, Thursday]`).
If left empty, the maintenance window applies every day | `[]` |
-> 📝 The maintenance configuration uses UTC
-
Here's an example:
```yaml
maintenance:
start: 23:00
duration: 1h
+ timezone: "Europe/Amsterdam"
every: [Monday, Thursday]
```
Note that you can also specify each day on separate lines:
@@ -1475,6 +1475,7 @@ Note that you can also specify each day on separate lines:
maintenance:
start: 23:00
duration: 1h
+ timezone: "Europe/Amsterdam"
every:
- Monday
- Thursday
diff --git a/config/maintenance/maintenance.go b/config/maintenance/maintenance.go
index dc66fefe..291f0d11 100644
--- a/config/maintenance/maintenance.go
+++ b/config/maintenance/maintenance.go
@@ -12,6 +12,7 @@ var (
errInvalidMaintenanceStartFormat = errors.New("invalid maintenance start format: must be hh:mm, between 00:00 and 23:59 inclusively (e.g. 23:00)")
errInvalidMaintenanceDuration = errors.New("invalid maintenance duration: must be bigger than 0 (e.g. 30m)")
errInvalidDayName = fmt.Errorf("invalid value specified for 'on'. supported values are %s", longDayNames)
+ errInvalidTimezone = errors.New("invalid timezone specified or format not supported. Use IANA timezone format (e.g. America/Sao_Paulo)")
longDayNames = []string{
"Sunday",
@@ -27,17 +28,19 @@ var (
// Config allows for the configuration of a maintenance period.
// During this maintenance period, no alerts will be sent.
//
-// Uses UTC.
+// Uses UTC by default.
type Config struct {
Enabled *bool `yaml:"enabled"` // Whether the maintenance period is enabled. Enabled by default if nil.
Start string `yaml:"start"` // Time at which the maintenance period starts (e.g. 23:00)
Duration time.Duration `yaml:"duration"` // Duration of the maintenance period (e.g. 4h)
+ Timezone string `yaml:"timezone"` // Timezone in string format which the maintenance period is configured (e.g. America/Sao_Paulo)
// Every is a list of days of the week during which maintenance period applies.
// See longDayNames for list of valid values.
// Every day if empty.
Every []string `yaml:"every"`
+ TimezoneLocation *time.Location // Timezone in location format which the maintenance period is configured
durationToStartFromMidnight time.Duration
}
@@ -85,6 +88,15 @@ func (c *Config) ValidateAndSetDefaults() error {
if c.Duration <= 0 || c.Duration > 24*time.Hour {
return errInvalidMaintenanceDuration
}
+ if c.Timezone != "" {
+ c.TimezoneLocation, err = time.LoadLocation(c.Timezone)
+ if err != nil {
+ return fmt.Errorf("%w: %w", errInvalidTimezone, err)
+ }
+ } else {
+ c.Timezone = "UTC"
+ c.TimezoneLocation = time.UTC
+ }
return nil
}
@@ -93,7 +105,10 @@ func (c Config) IsUnderMaintenance() bool {
if !c.IsEnabled() {
return false
}
- now := time.Now().UTC()
+ now := time.Now()
+ if c.TimezoneLocation != nil {
+ now = now.In(c.TimezoneLocation)
+ }
var dayWhereMaintenancePeriodWouldStart time.Time
if now.Hour() >= int(c.durationToStartFromMidnight.Hours()) {
dayWhereMaintenancePeriodWouldStart = now.Truncate(24 * time.Hour)
diff --git a/config/maintenance/maintenance_test.go b/config/maintenance/maintenance_test.go
index 76ef9eb2..edbdad37 100644
--- a/config/maintenance/maintenance_test.go
+++ b/config/maintenance/maintenance_test.go
@@ -90,6 +90,15 @@ func TestConfig_ValidateAndSetDefaults(t *testing.T) {
},
expectedError: errInvalidMaintenanceDuration,
},
+ {
+ name: "invalid-timezone",
+ cfg: &Config{
+ Start: "23:00",
+ Duration: time.Hour,
+ Timezone: "invalid-timezone",
+ },
+ expectedError: errInvalidTimezone,
+ },
{
name: "every-day-at-2300",
cfg: &Config{
@@ -126,6 +135,33 @@ func TestConfig_ValidateAndSetDefaults(t *testing.T) {
},
expectedError: nil,
},
+ {
+ name: "timezone-amsterdam",
+ cfg: &Config{
+ Start: "23:00",
+ Duration: time.Hour,
+ Timezone: "Europe/Amsterdam",
+ },
+ expectedError: nil,
+ },
+ {
+ name: "timezone-cet",
+ cfg: &Config{
+ Start: "23:00",
+ Duration: time.Hour,
+ Timezone: "CET",
+ },
+ expectedError: nil,
+ },
+ {
+ name: "timezone-etc-plus-5",
+ cfg: &Config{
+ Start: "23:00",
+ Duration: time.Hour,
+ Timezone: "Etc/GMT+5",
+ },
+ expectedError: nil,
+ },
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
@@ -220,7 +256,25 @@ func TestConfig_IsUnderMaintenance(t *testing.T) {
expected: true,
},
{
- name: "under-maintenance-starting-4h-ago-for-3h",
+ name: "under-maintenance-amsterdam-timezone-starting-now-for-2h",
+ cfg: &Config{
+ Start: fmt.Sprintf("%02d:00", now.Hour()),
+ Duration: 2 * time.Hour,
+ Timezone: "Europe/Amsterdam",
+ },
+ expected: true,
+ },
+ {
+ name: "under-maintenance-utc-timezone-starting-now-for-2h",
+ cfg: &Config{
+ Start: fmt.Sprintf("%02d:00", now.Hour()),
+ Duration: 2 * time.Hour,
+ Timezone: "UTC",
+ },
+ expected: true,
+ },
+ {
+ name: "not-under-maintenance-starting-4h-ago-for-3h",
cfg: &Config{
Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-4)),
Duration: 3 * time.Hour,
@@ -228,7 +282,7 @@ func TestConfig_IsUnderMaintenance(t *testing.T) {
expected: false,
},
{
- name: "under-maintenance-starting-5h-ago-for-1h",
+ name: "not-under-maintenance-starting-5h-ago-for-1h",
cfg: &Config{
Start: fmt.Sprintf("%02d:00", normalizeHour(now.Hour()-5)),
Duration: time.Hour,
@@ -253,6 +307,16 @@ func TestConfig_IsUnderMaintenance(t *testing.T) {
},
expected: false,
},
+ {
+ name: "not-under-maintenance-los-angeles-timezone-starting-now-for-2h-today",
+ cfg: &Config{
+ Start: fmt.Sprintf("%02d:00", now.Hour()),
+ Duration: 2 * time.Hour,
+ Timezone: "America/Los_Angeles",
+ Every: []string{now.Weekday().String()},
+ },
+ expected: false,
+ },
}
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {