teambition / rrule-go

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

rruleset.After(time.Now()) with count 1 returns zero time value #44

Closed brumhard closed 3 years ago

brumhard commented 3 years ago

Hi,

I tested the following code today:

package main

import (
    "log"
    "time"

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

func main() {
    rrule, err := rrule.StrToRRuleSet("RRULE:FREQ=SECONDLY;INTERVAL=10;COUNT=1")
    if err != nil {
        log.Fatal(err)
    }

    log.Print(rrule.After(time.Now(), true))
}

It returns a zero time value. From my perspective I would expect it to return the current time, since if I input a schedule with a count of one I would expect it to have exactly one recurrence (if the dtstart is not in the past, which is not specified here). Of course if you specify inc=false I agree it should probably return the zero value.

Actually this is what I get from other rrule libraries like shown here http://jakubroztocil.github.io/rrule/.

My proposed fix would be the following:

diff --git a/util.go b/util.go
index e01d3e9..b1cc735 100644
--- a/util.go
+++ b/util.go
@@ -172,6 +172,9 @@ func after(next Next, dt time.Time, inc bool) time.Time {
                if !ok {
                        return time.Time{}
                }
+
+               v = v.Truncate(time.Second)
+               dt = dt.Truncate(time.Second)
                if inc && !v.Before(dt) || !inc && v.After(dt) {
                        return v
                }

I truncate to seconds since seconds are the highest precision that the rrule standard defines and it does not have any value to also include the milliseconds imo.

If you agree I'd be glad to open a PR. Of course this could be kind of a breaking change and it's probably an edge case.

zensh commented 3 years ago

You should set DTStart to get consistent results:

    rrule, err := rrule.StrToRRuleSet("RRULE:FREQ=SECONDLY;INTERVAL=10;COUNT=1")
    if err != nil {
        log.Fatal(err)
    }
    now := time.Now().UTC().Truncate(time.Second)
    rrule.DTStart(now)
    log.Print(now)
    log.Print(rrule.After(now, true))