diff --git a/src/HuaJiBot.NET.Plugin.Calendar/CalendarExtensions.cs b/src/HuaJiBot.NET.Plugin.Calendar/CalendarExtensions.cs index a6295ec..3d5ddf7 100644 --- a/src/HuaJiBot.NET.Plugin.Calendar/CalendarExtensions.cs +++ b/src/HuaJiBot.NET.Plugin.Calendar/CalendarExtensions.cs @@ -1,5 +1,6 @@ using System.Text; using System.Text.RegularExpressions; +using HuaJiBot.NET.Utils; using Ical.Net; using Ical.Net.CalendarComponents; using Ical.Net.DataTypes; @@ -8,11 +9,42 @@ namespace HuaJiBot.NET.Plugin.Calendar; internal static class CalendarExtensions { - public class Period(Ical.Net.DataTypes.Period p) + /// + /// Wraps an ical.net and converts its times to + /// . The is required to + /// correctly compute because ical.net 5.1.1 does not populate + /// Period.EndTime when using the single-argument GetOccurrences overload. + /// + public class Period(Ical.Net.DataTypes.Period p, CalendarEvent? calendarEvent = null) { public DateTimeOffset StartTime { get; } = p.StartTime.ToLocalNetworkTime(); - public DateTimeOffset EndTime { get; } = (p.EndTime ?? p.StartTime).ToLocalNetworkTime(); + // ical.net 5.1.1 does not populate Period.EndTime when using GetOccurrences(startArg). + // Compute the end time using the event duration (End - Start) applied to the occurrence's + // start time. This correctly handles both non-recurring and recurring events. + public DateTimeOffset EndTime { get; } = ComputeEndTime(p, calendarEvent); + + private static DateTimeOffset ComputeEndTime( + Ical.Net.DataTypes.Period p, + CalendarEvent? calendarEvent + ) + { + if (p.EndTime is not null) + return p.EndTime.ToLocalNetworkTime(); + if (calendarEvent?.Start is not null) + { + // Compute occurrence end = occurrence start + event duration + // Using duration instead of ev.End directly ensures recurring events + // get the correct end date for each occurrence. + // When End is null, duration is zero, treating the event as instantaneous. + var duration = + (calendarEvent.End?.AsUtc ?? calendarEvent.Start.AsUtc) + - calendarEvent.Start.AsUtc; + var endUtc = p.StartTime.AsUtc.Add(duration); + return new DateTimeOffset(endUtc).ToOffset(NetworkTime.LocalTimeZoneOffset); + } + return p.StartTime.ToLocalNetworkTime(); + } } /// @@ -74,7 +106,7 @@ orderby tuple.Period.StartTime ascending //按照开始时间排序 occurrence.Source switch { CalendarEvent calendarEvent => ( - Period: new Period(occurrence.Period), + Period: new Period(occurrence.Period, calendarEvent), calendarEvent ), _ => throw new ArgumentOutOfRangeException( diff --git a/src/HuaJiBot.NET.Plugin.Calendar/ReminderTask.cs b/src/HuaJiBot.NET.Plugin.Calendar/ReminderTask.cs index 2723074..460a44d 100644 --- a/src/HuaJiBot.NET.Plugin.Calendar/ReminderTask.cs +++ b/src/HuaJiBot.NET.Plugin.Calendar/ReminderTask.cs @@ -173,14 +173,14 @@ private void InvokeCheck() ev => { Service.Log( - $"[日程] {RemindBeforeEndMinutes} 分钟后开始日程:{ev.Summary}({ev.Start?.ToLocalNetworkTime()})" + $"[日程] {RemindBeforeEndMinutes} 分钟后结束日程:{ev.Summary}({eventEndTime})" ); ForEachMatchedGroup( ev, send => send( $""" - 日程提醒({ev.End?.ToLocalNetworkTime()}): + 日程提醒({eventEndTime}): {ev.Summary} {ev.Location} 预计于 {RemindBeforeEndMinutes} 分钟后结束 """