From 76d12ecf3f9ef0a08dd4aa7b67721d1fc36d3eb4 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 27 Mar 2026 12:34:58 +0000
Subject: [PATCH 1/2] Initial plan
From 98f561cf1658e2a5ddf5c5509577e94c4a6b8415 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 27 Mar 2026 12:59:03 +0000
Subject: [PATCH 2/2] Fix schedule reminder end time by computing duration from
CalendarEvent when Period.EndTime is null (ical.net 5.1.1 bug)
Agent-Logs-Url: https://github.com/nbtca/HuaJiBot.NET/sessions/98314277-209b-4b98-8be4-d16c908c5ad5
Co-authored-by: wen-templari <52404670+wen-templari@users.noreply.github.com>
---
.../CalendarExtensions.cs | 38 +++++++++++++++++--
.../ReminderTask.cs | 4 +-
2 files changed, 37 insertions(+), 5 deletions(-)
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} 分钟后结束
"""