按时区(BST / GMT,CET / CEST等)查找下一个夏令时

时间:2019-10-21 15:26:12

标签: go timezone dst

根据英国夏令时(BST)/夏令时(DST)(https://www.gov.uk/when-do-the-clocks-change)的规则,时钟:

  • 在3月的最后一个星期日(BST开始)的凌晨1点前进1小时,
  • 在10月的最后一个星期日的凌晨2点回到1小时(格林威治标准时间GMT =协调世界时)。

在2019年,这种当地时间发生在3月31日和10月27日之间,但每年的日期略有变化。

类似的DST规则适用于中欧时间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在每月的星期日发生循环?
  • 有没有一种方法可以避免基于BST / GMT或CET / CEST和月份的硬编码map来找出下一个时钟更改?

代码:

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 个答案:

答案 0 :(得分:1)

视您的情况而定,改为呼唤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

答案 1 :(得分:0)

Go已通过IANA TZDB获得了完整的time.LoadLocation支持。将Europe/London用于英国,将Europe/Copenhagen用于丹麦的CET / CEST,等等。请参见the list here

您不应该自己重新实现时区逻辑。正如汤姆·斯科特(Tom Scott)在The Problem with Time & Timezones中恰当地打趣-“那就是疯狂。”