Согласно правилам британского летнего времени (BST) / летнего времени (DST) (https://www.gov.uk/when-do-the-clocks-change) часы:
- вперед на 1 час в 1:00 в последнее воскресеньев марте (начало BST)
- возвращение на 1 час в 2 часа ночи в последнее воскресенье октября (начало по Гринвичу, среднее время по Гринвичу = всемирное время с UTC).
In2019 это гражданское изменение местного времени происходит 31 марта и 27 октября, но дни слегка меняются каждый год.
Аналогичное правило перехода на летнее время применяется к центральноевропейскому времени CET (зима)> CEST (лето), проверяющему последнее воскресеньемарта / октября (https://www.timeanddate.com/time/change/denmark/copenhagen). Комбинация этих правил BST / GMT и CET / CEST влияет, например, на все страны вокруг Северного моря. Независимо от BST / GMT или CET / CEST, отметка времени UTC должна бытьТо же самое.
Я написал следующий код на основе time.UTC
, предоставляющий даты для BST / GMT, но мне интересно, есть ли более простой / более общий способ использовать произвольный time.Location
, который можетЭто применимо к CET / CEST и (в идеале) к любому правилу DST.
- Проверка документов (здесь https://golang.org/pkg/time/#FixedZone) кажется, что
time.FixedZone
должен иметь дело с инвариантными часовыми поясами, но что там за offset
? Как я могу сделать следующие функцииинвариант от часового пояса? - Есть ли способ избежать цикла
for
в воскресенье (я) месяца? - Есть ли способ избежать жестко закодированного
map
на основе BST / GMT или CET / CEST и месяца, чтобы узнать следующее изменение часов?
Код:
func beginOfMonth(year, month int, loc *time.Location) time.Time {
return time.Date(year, time.Month(month), 1, 0, 0, 0, 0, loc)
}
// https://www.gov.uk/when-do-the-clocks-change
func lastUTCSunday(year, month int) time.Time {
beginOfMonth := beginOfMonth(year, month, time.UTC)
// we can find max 5 sundays in a month
sundays := make([]time.Time, 5)
for d := 1; d <= 31; d++ {
currDay := beginOfMonth.Add(time.Duration(24*d) * time.Hour)
if currDay.Weekday().String() == "Sunday" {
sundays = append(sundays, currDay)
}
if currDay.Month() != beginOfMonth.Month() {
break
}
}
// check if last date is same month
if sundays[len(sundays)-1].Month() == beginOfMonth.Month() {
// the 5th sunday
return sundays[len(sundays)-1]
}
// if not like before, then we only have 4 Sundays
return sundays[len(sundays)-2]
}
// https://www.gov.uk/when-do-the-clocks-change
func MarchClockSwitchTime(year int) time.Time {
lastSunday := lastUTCSunday(year, int(time.March)) // month: 3
return time.Date(
year, lastSunday.Month(), lastSunday.Day(),
1, 0, 0, 0, // 1:00 AM
lastSunday.Location(),
)
}
// https://www.gov.uk/when-do-the-clocks-change
func OctoberClockSwitchTime(year int) time.Time {
lastSunday := lastUTCSunday(year, int(time.October)) // month: 10
return time.Date(
year, lastSunday.Month(), lastSunday.Day(),
2, 0, 0, 0, // 2:00 AM
lastSunday.Location(),
)
}
Я также написалнекоторые тесты, использующие GoConvey, должны проверять эти странные правила перехода на летнее время (DST) по воскресеньям, но они работают только на 2019, 2020 годы. Было бы хорошо найти способ сделать этот код более общим.
func TestLastSunday(t *testing.T) {
Convey("Should find the last UTC Sunday of each month\n\n", t, func() {
for year := 2019; year <= 2020; year++ {
for month := 1; month <= 12; month++ {
lastUtcSunday := lastUTCSunday(year, month)
So(lastUtcSunday.Month(), ShouldEqual, time.Month(month))
So(lastUtcSunday.Weekday().String(), ShouldEqual, "Sunday")
So(lastUtcSunday.Year(), ShouldEqual, year)
So(lastUtcSunday.Day(), ShouldBeGreaterThanOrEqualTo, 28-7)
}
}
})
}
// https://www.gov.uk/when-do-the-clocks-change
func TestClockChange(t *testing.T) {
Convey("Should find the last UTC Sunday for the March switch\n\n", t, func() {
switch2019 := MarchClockSwitchTime(2019)
So(switch2019.Month(), ShouldEqual, time.March)
So(switch2019.Weekday().String(), ShouldEqual, "Sunday")
So(switch2019.Day(), ShouldEqual, 31)
So(switch2019.Location().String(), ShouldEqual, "UTC")
So(switch2019.Location().String(), ShouldEqual, time.UTC.String())
switch2020 := MarchClockSwitchTime(2020)
So(switch2020.Month(), ShouldEqual, time.March)
So(switch2020.Weekday().String(), ShouldEqual, "Sunday")
So(switch2020.Day(), ShouldEqual, 29)
So(switch2020.Location().String(), ShouldEqual, "UTC")
So(switch2020.Location().String(), ShouldEqual, time.UTC.String())
})
Convey("Should find the last UTC Sunday for the October switch\n\n", t, func() {
switch2019 := OctoberClockSwitchTime(2019)
So(switch2019.Month(), ShouldEqual, time.October)
So(switch2019.Weekday().String(), ShouldEqual, "Sunday")
So(switch2019.Day(), ShouldEqual, 27)
So(switch2019.Location().String(), ShouldEqual, "UTC")
So(switch2019.Location().String(), ShouldEqual, time.UTC.String())
switch2020 := OctoberClockSwitchTime(2020)
So(switch2020.Month(), ShouldEqual, time.October)
So(switch2020.Weekday().String(), ShouldEqual, "Sunday")
So(switch2020.Day(), ShouldEqual, 25)
So(switch2020.Location().String(), ShouldEqual, "UTC")
So(switch2020.Location().String(), ShouldEqual, time.UTC.String())
})
}