go - 按时区查找下一个夏令时日期(BST/GMT、CET/CEST 等)

标签 go timezone dst

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

  • 三月最后一个星期日(英国夏令时间开始)凌晨 1 点向前推进 1 小时,
  • 回溯 10 月最后一个星期日凌晨 2 点 1 小时(格林威治标准时间 GMT = 协调世界时 UTC 的开始时间)。

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 时间戳应该是相同的。

我已经根据提供 BST/GMT 日期的 time.UTC 编写了以下代码,但我想知道是否有更简单/更通用的方法来使用任意 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())
    })
}

最佳答案

Go 已经满了 IANA TZDB支持通过time.LoadLocation 。英国使用 Europe/London,丹麦的 CET/CEST 使用 Europe/Copenhagen,等等。请参阅 the list here .

您不应该自己重新实现时区逻辑。正如汤姆·斯科特在The Problem with Time & Timezones中恰本地调侃的那样- “那是疯狂。”

关于go - 按时区查找下一个夏令时日期(BST/GMT、CET/CEST 等),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58489477/

相关文章:

node.js - Sails autoCreatedAt 和 autoUpdatedAt 插入比我当前时区向前 5 小时的日期时间

Javascript 日期问题返回比设置日期-时区少 1 天

java - 如何在 Java 中手动设置夏令时 (DST) 轮类日期

c# - 每 24 小时触发一次计时器回调 - DST 是否正确处理?

sql - TDD 构造器 Golang

go - 为什么共享 int 变量在 go 例程中递增时显示原子行为?

.net - IANA 到 Windows 时区的映射

go - 如何隐藏日志

testing - 是否可以编译带有特定标志的 Go 程序以进行覆盖率分析?

php - php 的 date_default_timezone_set 是否调整为夏令时?