Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions src/HuaJiBot.NET.Plugin.Calendar/CalendarExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -8,11 +9,42 @@ namespace HuaJiBot.NET.Plugin.Calendar;

internal static class CalendarExtensions
{
public class Period(Ical.Net.DataTypes.Period p)
/// <summary>
/// Wraps an ical.net <see cref="Ical.Net.DataTypes.Period"/> and converts its times to
/// <see cref="DateTimeOffset"/>. The <paramref name="calendarEvent"/> is required to
/// correctly compute <see cref="EndTime"/> because ical.net 5.1.1 does not populate
/// <c>Period.EndTime</c> when using the single-argument <c>GetOccurrences</c> overload.
/// </summary>
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();
}
}

/// <summary>
Expand Down Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions src/HuaJiBot.NET.Plugin.Calendar/ReminderTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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} 分钟后结束
"""
Expand Down
Loading