arran4 / golang-ical

A ICS / ICal parser and serialiser for Golang.
Apache License 2.0
307 stars 73 forks source link

IsInEvent functionality #70

Open Mereep opened 1 year ago

Mereep commented 1 year ago

Hi, I found this repo yesterday when brainstorming (yes, and GPT-Storming) for a general way to represent a allowance to unlock a door.

Given a valid iCal format, I now need a way to check if now falls into the calender event (true false). Is there an easy way to do so (I didn't see a command in the lib)? I do not have any prior experience with the format yet, so I cannot judge on the complexity and edgy cases for this.

idk if it helps, but this is what ChatGPT came up with (untested yet): Edit: code built into my program, tested for intervals and simple dates (UTC and local time)

func (cal *ICalCalendar) IsIn(unixTimestamp uint64)  (res Result[bool]) {
    defer func() {
        if r := recover(); r != nil {
            res = ResultError[bool](
                fmt.Errorf("panic when checking for date in ical calendar: %v", r),
                ERROR_ICAL_ISIN_CHECK)
        }
    }()
    givenTime := time.Unix(int64(unixTimestamp), 0)
    errors := []error{}
    found := false
    for _, event := range cal.inst.Events() {
        start, err := event.GetStartAt()
        if err != nil {
            errors = append(errors, err)
            continue
        }

        end, err := event.GetEndAt()
        if err != nil {
            errors = append(errors, err)
            continue
        }

        duration := end.Sub(start)

        // Check non-recurring event
        if (givenTime.After(start) || givenTime.Unix() == start.Unix()) && (givenTime.Before(end) || givenTime.Unix() == end.Unix()) {
            found = true
            break
        }

        // Check recurring event
        PropRRule := event.GetProperty("RRULE")
        if PropRRule != nil {
            startStr := event.GetProperty("DTSTART").Value
            rruleStr := PropRRule.Value
            r, err := rrule.StrToRRule(fmt.Sprintf("DTSTART:%s\nRRULE:%s", startStr, rruleStr))
            if err != nil {
                errors = append(errors, err)
                continue
            }

            instances := r.Between(start, start.Add(1*time.Hour*24*365), true) // Limit search to 1 year ahead
            for _, instance := range instances {
                instanceEnd := instance.Add(duration)
                if (givenTime.After(instance) || givenTime.Unix() == instance.Unix()) && (givenTime.Before(instanceEnd) || givenTime.Unix() == instanceEnd.Unix()) {
                    found = true
                    break
                }
            }
        }
    }

    if len(errors) > 0 {
        return ResultWarning[bool](
            found,
            errors,
            ERROR_ICAL_CHECK,
        )
    }

    return ResultOk(found)
}

it should be able to handle interval events also. Any feedback on that?

arran4 commented 3 weeks ago

Hey.

The code generated doesn't really have much to do with the code in the project. However I'm going to pretend that this is a request for an "IsIn" function.

arran4 commented 3 weeks ago

I have attempted to resolve this issue in the PR: https://github.com/arran4/golang-ical/pull/110

However it's not working just as https://github.com/arran4/golang-ical/issues/99 's @brackendawson predicted. It will require: https://github.com/arran4/golang-ical/pull/107 to be resolved. And some effort will be required to merge it as I have moved some functionality around that was also modified. Hopefully I remember!

I am not sure if you were serious about needing this functionality as it was chatgpt based. But I thought I would give it a go. It's not entirely within scope of the library but it is helpful as it's not easy to do this. Please let me know. If no response I will probably merge it in and then wait for bugs. I will give this until end of November before revisiting it myself.

Mereep commented 3 weeks ago

Hi,

nice to see that some work is done here 👍

Some background: The request actually came from a real requirement. So to say I was serious at the time. I was working on a (Micro Controller) based project, which had to deal with Calendar Entryies: Basically, the requirement was to check if a moment in time falls into one or more registered calendars for access control. So I searched for well known calendar formats for dealing with calendar representations, leading me here. However, I noticed that, while I could parse calendars, the key functionality for my use case was missing (the bespoke isDuring / isIn). So I came up with a some simple solution for my project and thought this would be a nice general addition to the library, hence I reached out.

Unfortunately, now I had to move the micro processor part of the project to a C++ code base as I could not achieve some further functionality using TinyGo at the time due to missing support for some micro codes on my architecture. I had to roll a more simplistic calendar format on my own there, for which I implemented the functionality.

However, for what I saw in func TestIsDuring(t *testing.T) {... in commit (#https://github.com/arran4/golang-ical/pull/110/commits/a55b8bfaecd4dbd16274a9c42a288d7da4d48ce7#diff-6d954679945b7bbcf24799bf249475110f4672b088e44b574dbfd349b2fc365bR204) , which seems to check particular intervals? I can just slightly remember that the complicated thing were those recurring rules, which spawn sub events until the end of time (like every other monday between 12 and 13 oclock).

I hope I find a moment in time next week, so I can try this. Cannot promise however.

arran4 commented 3 weeks ago

Thanks. I will follow up if I haven't heard back the following weekend.

Yep. I did eventually get around to it!

Thanks, the background helps a bit. I find that I need real world use cases and real users to engage with to build something mostly for imagination reasons I think.

You indirectly raise a point about testing with TinyGo. That's something I should setup in my CI tests. But once I find a user who is doing that incase issues are encountered.

No it doesn't support RRules at all. It was something I was ignoring as this was really supposed to be a serializer / deserializer. However I had to implement ParseDuration here, which shows even in terms of a parser there is a lot more work to do here for completion! It felt that checking if time was in a simple event would be doable. But it seems it's only 80% useful since I haven't included re-occurring events. I will have to add that to my issues at some stage or wait for someone else to do so.

Thanks for raising this even if it doesn't match your use case @Mereep and thanks for verifying / testing it.

arran4 commented 1 week ago

Okay I had ti fix a few bugs, which ended up taking a couple hours. The code is more expansive than thought, and it's touching upon timezone issues in https://github.com/arran4/golang-ical/pull/107 -- I might have to v2 this solution as it breaks not only some of the interface but expectations to do with formatting. The change can be scaled back to just solve this issue. (I ended up implementing duration which led to a series of other issues that needed to be fixed.)