Найти следующие даты перехода на летнее время по часовому поясу (BST / GMT, CET / CEST и т. Д.) - PullRequest
1 голос
/ 21 октября 2019

Согласно правилам британского летнего времени (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())
    })
}

Ответы [ 2 ]

1 голос
/ 22 октября 2019

В зависимости от вашей ситуации может оказаться целесообразным вместо этого вызвать zdump и проанализировать ответ.

Например, чтобы перечислить переходы перехода на летнее время для часового пояса Тихого океана / Окленда:

zdump "Pacific/Auckland" -c 2020,2022 -V

Результаты показывают NZDT (дневное время) или NZST (стандартное время) и флаг 'isdst':

Pacific/Auckland  Sat Apr  4 13:59:59 2020 UT = Sun Apr  5 02:59:59 2020 NZDT isdst=1 gmtoff=46800
Pacific/Auckland  Sat Apr  4 14:00:00 2020 UT = Sun Apr  5 02:00:00 2020 NZST isdst=0 gmtoff=43200
Pacific/Auckland  Sat Sep 26 13:59:59 2020 UT = Sun Sep 27 01:59:59 2020 NZST isdst=0 gmtoff=43200
Pacific/Auckland  Sat Sep 26 14:00:00 2020 UT = Sun Sep 27 03:00:00 2020 NZDT isdst=1 gmtoff=46800
Pacific/Auckland  Sat Apr  3 13:59:59 2021 UT = Sun Apr  4 02:59:59 2021 NZDT isdst=1 gmtoff=46800
0 голосов
/ 21 октября 2019

Go уже имеет полную поддержку IANA TZDB через time.LoadLocation. Используйте Europe/London для Великобритании, Europe/Copenhagen для CET / CEST в Дании и т. Д. См. список здесь .

Вы не должны заново реализовывать логику часового пояса самостоятельно. Как Том Скотт удачно шутит в Проблема со временем и часовыми поясами - «Так лежит безумие».

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...