teambition / rrule-go

Go library for working with recurrence rules for calendar dates.
MIT License
319 stars 57 forks source link

rrrule#All() generates wrong recurrences around DST #59

Closed yhabteab closed 1 year ago

yhabteab commented 1 year ago

Describe the bug teambition/rrule-go version: v1.8.2

rrule#All() is generating duplicate events for the same date time during DST changes.

To Reproduce Code to reproduce the behavior:

 rule, _ := rrule.NewRRule(rrule.ROption{
    Freq: rrule.HOURLY,
    Dtstart: time.Date(2023, time.March, 26, 1, 30, 0, 0, time.Local),
    Until: time.Date(2023, time.March, 26, 5, 0, 0, 0, time.Local),
})

recurrences := rule.All()
// Produces the following result.
[]time.Time{
    time.Date(2023, time.March, 26, 1, 30, 0, 0, time.Local),

       // Duplicate events here
    time.Date(2023, time.March, 26, 3, 30, 0, 0, time.Local),
    time.Date(2023, time.March, 26, 3, 30, 0, 0, time.Local),

    time.Date(2023, time.March, 26, 4, 30, 0, 0, time.Local),
}

Expected behavior Don't duplicate events.

Screenshots If applicable, add screenshots to help explain your problem.

Additional context Add any other context about the problem here.

zensh commented 1 year ago

It works well on my computer:

package main

import (
    "fmt"
    "time"

    "github.com/teambition/rrule-go"
)

func main() {
    rule, _ := rrule.NewRRule(rrule.ROption{
        Freq:    rrule.HOURLY,
        Dtstart: time.Date(2023, time.March, 26, 1, 30, 0, 0, time.Local),
        Until:   time.Date(2023, time.March, 26, 5, 0, 0, 0, time.Local),
    })
    recurrences := rule.All()
    fmt.Println(recurrences)
    // [2023-03-26 01:30:00 +0800 CST 2023-03-26 02:30:00 +0800 CST 2023-03-26 03:30:00 +0800 CST 2023-03-26 04:30:00 +0800 CST]
}
yhabteab commented 1 year ago

It works well on my computer:

From the outputs you provided, there is no DST changes on your computer. Otherwise, the 02:30 event wouldn't have appeared there. For comparison on my end (Mac Ventura), here's what it looks like. I just used the exact same program.

[
   2023-03-26 01:30:00 +0100 CET // Before 2 am, so no DST change
   2023-03-26 03:30:00 +0200 CEST // This is supposed to be 02:30, but there is no such time due to the daylight saving time change at 02:00.
   2023-03-26 03:30:00 +0200 CEST // The same event as the previous one
  2023-03-26 04:30:00 +0200 CEST
]
zensh commented 1 year ago

Some underlying code used UTC, Maybe you should do:

    rule, _ := rrule.NewRRule(rrule.ROption{
        Freq:    rrule.HOURLY,
        Dtstart: time.Date(2023, time.March, 26, 1, 30, 0, 0, time.UTC),
        Until:   time.Date(2023, time.March, 26, 5, 0, 0, 0, time.UTC),
    })

And then convert the results to CEST time