Skip to content

Commit cb89eda

Browse files
authored
Merge pull request #17 from nbtca/copilot/fix-schedule-reminder-end-time-parsing
Fix end-time reminder firing at event start instead of end (ical.net Period.EndTime null bug)
2 parents 6703acc + 98f561c commit cb89eda

2 files changed

Lines changed: 37 additions & 5 deletions

File tree

src/HuaJiBot.NET.Plugin.Calendar/CalendarExtensions.cs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Text;
22
using System.Text.RegularExpressions;
3+
using HuaJiBot.NET.Utils;
34
using Ical.Net;
45
using Ical.Net.CalendarComponents;
56
using Ical.Net.DataTypes;
@@ -8,11 +9,42 @@ namespace HuaJiBot.NET.Plugin.Calendar;
89

910
internal static class CalendarExtensions
1011
{
11-
public class Period(Ical.Net.DataTypes.Period p)
12+
/// <summary>
13+
/// Wraps an ical.net <see cref="Ical.Net.DataTypes.Period"/> and converts its times to
14+
/// <see cref="DateTimeOffset"/>. The <paramref name="calendarEvent"/> is required to
15+
/// correctly compute <see cref="EndTime"/> because ical.net 5.1.1 does not populate
16+
/// <c>Period.EndTime</c> when using the single-argument <c>GetOccurrences</c> overload.
17+
/// </summary>
18+
public class Period(Ical.Net.DataTypes.Period p, CalendarEvent? calendarEvent = null)
1219
{
1320
public DateTimeOffset StartTime { get; } = p.StartTime.ToLocalNetworkTime();
1421

15-
public DateTimeOffset EndTime { get; } = (p.EndTime ?? p.StartTime).ToLocalNetworkTime();
22+
// ical.net 5.1.1 does not populate Period.EndTime when using GetOccurrences(startArg).
23+
// Compute the end time using the event duration (End - Start) applied to the occurrence's
24+
// start time. This correctly handles both non-recurring and recurring events.
25+
public DateTimeOffset EndTime { get; } = ComputeEndTime(p, calendarEvent);
26+
27+
private static DateTimeOffset ComputeEndTime(
28+
Ical.Net.DataTypes.Period p,
29+
CalendarEvent? calendarEvent
30+
)
31+
{
32+
if (p.EndTime is not null)
33+
return p.EndTime.ToLocalNetworkTime();
34+
if (calendarEvent?.Start is not null)
35+
{
36+
// Compute occurrence end = occurrence start + event duration
37+
// Using duration instead of ev.End directly ensures recurring events
38+
// get the correct end date for each occurrence.
39+
// When End is null, duration is zero, treating the event as instantaneous.
40+
var duration =
41+
(calendarEvent.End?.AsUtc ?? calendarEvent.Start.AsUtc)
42+
- calendarEvent.Start.AsUtc;
43+
var endUtc = p.StartTime.AsUtc.Add(duration);
44+
return new DateTimeOffset(endUtc).ToOffset(NetworkTime.LocalTimeZoneOffset);
45+
}
46+
return p.StartTime.ToLocalNetworkTime();
47+
}
1648
}
1749

1850
/// <summary>
@@ -74,7 +106,7 @@ orderby tuple.Period.StartTime ascending //按照开始时间排序
74106
occurrence.Source switch
75107
{
76108
CalendarEvent calendarEvent => (
77-
Period: new Period(occurrence.Period),
109+
Period: new Period(occurrence.Period, calendarEvent),
78110
calendarEvent
79111
),
80112
_ => throw new ArgumentOutOfRangeException(

src/HuaJiBot.NET.Plugin.Calendar/ReminderTask.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,14 +173,14 @@ private void InvokeCheck()
173173
ev =>
174174
{
175175
Service.Log(
176-
$"[日程] {RemindBeforeEndMinutes} 分钟后开始日程{ev.Summary}({ev.Start?.ToLocalNetworkTime()})"
176+
$"[日程] {RemindBeforeEndMinutes} 分钟后结束日程{ev.Summary}({eventEndTime})"
177177
);
178178
ForEachMatchedGroup(
179179
ev,
180180
send =>
181181
send(
182182
$"""
183-
日程提醒({ev.End?.ToLocalNetworkTime()}):
183+
日程提醒({eventEndTime}):
184184
{ev.Summary} {ev.Location}
185185
预计于 {RemindBeforeEndMinutes} 分钟后结束
186186
"""

0 commit comments

Comments
 (0)