Skip to content

Commit c387516

Browse files
committed
CalendarEvent: Don't set DURATION resp. DTEND from each other. Only calculate the duration on the fly when needed. This way we don't overwrite the information, which of both was originally set and can adjust calculations accordingly (in the future). See #574.
1 parent 587e8bb commit c387516

File tree

4 files changed

+65
-53
lines changed

4 files changed

+65
-53
lines changed

Ical.Net.Tests/DeserializationTests.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Ical.Net.CalendarComponents;
1+
using Ical.Net.CalendarComponents;
22
using Ical.Net.DataTypes;
33
using Ical.Net.Serialization;
44
using Ical.Net.Serialization.DataTypes;
@@ -505,5 +505,25 @@ public void Property1()
505505
Assert.AreEqual("2." + i, props[i].Value);
506506
}
507507
}
508+
509+
510+
[Test]
511+
[TestCase(true)]
512+
[TestCase(false)]
513+
public void KeepApartDtEndAndDuration_Tests(bool useDtEnd)
514+
{
515+
var calStr = $@"BEGIN:VCALENDAR
516+
BEGIN:VEVENT
517+
DTSTART:20070406T230000Z
518+
{(useDtEnd ? "DTEND:20070407T010000Z" : "DURATION:PT1H")}
519+
END:VEVENT
520+
END:VCALENDAR
521+
";
522+
523+
var calendar = Calendar.Load(calStr);
524+
525+
Assert.AreEqual(useDtEnd, calendar.Events.Single().DtEnd != null);
526+
Assert.AreEqual(!useDtEnd, calendar.Events.Single().Duration != default);
527+
}
508528
}
509529
}

Ical.Net.Tests/SymmetricSerializationTests.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,17 @@ public class SymmetricSerializationTests
1919
private static readonly DateTime _later = _nowTime.AddHours(1);
2020
private static CalendarSerializer GetNewSerializer() => new CalendarSerializer();
2121
private static string SerializeToString(Calendar c) => GetNewSerializer().SerializeToString(c);
22-
private static CalendarEvent GetSimpleEvent() => new CalendarEvent {DtStart = new CalDateTime(_nowTime), DtEnd = new CalDateTime(_later)};
22+
private static CalendarEvent GetSimpleEvent(bool useDtEnd = true)
23+
{
24+
var evt = new CalendarEvent { DtStart = new CalDateTime(_nowTime) };
25+
if (useDtEnd)
26+
evt.DtEnd = new CalDateTime(_later);
27+
else
28+
evt.Duration = _later - _nowTime;
29+
30+
return evt;
31+
}
32+
2333
private static Calendar UnserializeCalendar(string s) => Calendar.Load(s);
2434

2535
[Test, TestCaseSource(nameof(Event_TestCases))]
@@ -38,24 +48,33 @@ public void Event_Tests(Calendar iCalendar)
3848
}
3949

4050
public static IEnumerable<ITestCaseData> Event_TestCases()
51+
{
52+
return Event_TestCasesInt(true).Concat(Event_TestCasesInt(false));
53+
}
54+
55+
private static IEnumerable<ITestCaseData> Event_TestCasesInt(bool useDtEnd)
4156
{
4257
var rrule = new RecurrencePattern(FrequencyType.Daily, 1) { Count = 5 };
4358
var e = new CalendarEvent
4459
{
4560
DtStart = new CalDateTime(_nowTime),
46-
DtEnd = new CalDateTime(_later),
4761
RecurrenceRules = new List<RecurrencePattern> { rrule },
4862
};
4963

64+
if (useDtEnd)
65+
e.DtEnd = new CalDateTime(_later);
66+
else
67+
e.Duration = _later - _nowTime;
68+
5069
var calendar = new Calendar();
5170
calendar.Events.Add(e);
52-
yield return new TestCaseData(calendar).SetName("readme.md example");
71+
yield return new TestCaseData(calendar).SetName($"readme.md example with {(useDtEnd ? "DTEND" : "DURATION")}");
5372

54-
e = GetSimpleEvent();
73+
e = GetSimpleEvent(useDtEnd);
5574
e.Description = "This is an event description that is really rather long. Hopefully the line breaks work now, and it's serialized properly.";
5675
calendar = new Calendar();
5776
calendar.Events.Add(e);
58-
yield return new TestCaseData(calendar).SetName("Description serialization isn't working properly. Issue #60");
77+
yield return new TestCaseData(calendar).SetName($"Description serialization isn't working properly. Issue #60 {(useDtEnd ? "DTEND" : "DURATION")}");
5978
}
6079

6180
[Test]

Ical.Net/CalendarComponents/CalendarEvent.cs

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,6 @@ public class CalendarEvent : RecurringComponent, IAlarmContainer, IComparable<Ca
2626
{
2727
internal const string ComponentName = "VEVENT";
2828

29-
/// <summary>
30-
/// The start date/time of the event.
31-
/// <note>
32-
/// If the duration has not been set, but
33-
/// the start/end time of the event is available,
34-
/// the duration is automatically determined.
35-
/// Likewise, if the end date/time has not been
36-
/// set, but a start and duration are available,
37-
/// the end date/time will be extrapolated.
38-
/// </note>
39-
/// </summary>
40-
public override IDateTime DtStart
41-
{
42-
get => base.DtStart;
43-
set
44-
{
45-
base.DtStart = value;
46-
ExtrapolateTimes(2);
47-
}
48-
}
49-
5029
/// <summary>
5130
/// The end date/time of the event.
5231
/// <note>
@@ -66,7 +45,6 @@ public virtual IDateTime DtEnd
6645
if (!Equals(DtEnd, value))
6746
{
6847
Properties.Set("DTEND", value);
69-
ExtrapolateTimes(0);
7048
}
7149
}
7250
}
@@ -100,11 +78,24 @@ public virtual TimeSpan Duration
10078
if (!Equals(Duration, value))
10179
{
10280
Properties.Set("DURATION", value);
103-
ExtrapolateTimes(1);
10481
}
10582
}
10683
}
10784

85+
/// <summary>
86+
/// Calculates and returns the effective duration of this event.
87+
/// </summary>
88+
/// <remarks>
89+
/// If the 'DURATION' property iis set, this method will return it.
90+
/// Otherwise, if DTSTART and DTEND are set, it will calculate the duration from those.
91+
/// Otherwise it will return `default(TimeSpan)`.
92+
/// </remarks>
93+
/// <returns>The effective duration of this event.</returns>
94+
public virtual TimeSpan GetEffectiveDuration()
95+
=> (Properties.ContainsKey("DURATION")) ? Duration
96+
: ((DtEnd != null) && (DtStart != null)) ? DtEnd.Subtract(DtStart)
97+
: default(TimeSpan);
98+
10899
/// <summary>
109100
/// An alias to the DtEnd field (i.e. end date/time).
110101
/// </summary>
@@ -257,26 +248,6 @@ protected override void OnDeserializing(StreamingContext context)
257248
protected override void OnDeserialized(StreamingContext context)
258249
{
259250
base.OnDeserialized(context);
260-
261-
ExtrapolateTimes(-1);
262-
}
263-
264-
private void ExtrapolateTimes(int source)
265-
{
266-
/*
267-
* Source values, a fix introduced to prevent stack overflow exceptions from occuring.
268-
* -1 = Anybody, stack overflow could maybe still occur in this case?
269-
* 0 = End
270-
* 1 = Duration
271-
*/
272-
if (DtEnd == null && DtStart != null && Duration != default(TimeSpan) && source != 0)
273-
{
274-
DtEnd = DtStart.Add(Duration);
275-
}
276-
else if (Duration == default(TimeSpan) && DtStart != null && DtEnd != null && source != 1)
277-
{
278-
Duration = DtEnd.Subtract(DtStart);
279-
}
280251
}
281252

282253
protected bool Equals(CalendarEvent other)
@@ -289,6 +260,7 @@ protected bool Equals(CalendarEvent other)
289260
&& string.Equals(Summary, other.Summary, StringComparison.OrdinalIgnoreCase)
290261
&& string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase)
291262
&& Equals(DtEnd, other.DtEnd)
263+
&& Equals(Duration, other.Duration)
292264
&& string.Equals(Location, other.Location, StringComparison.OrdinalIgnoreCase)
293265
&& resourcesSet.SetEquals(other.Resources)
294266
&& string.Equals(Status, other.Status, StringComparison.Ordinal)
@@ -348,6 +320,7 @@ public override int GetHashCode()
348320
var hashCode = DtStart?.GetHashCode() ?? 0;
349321
hashCode = (hashCode * 397) ^ (Summary?.GetHashCode() ?? 0);
350322
hashCode = (hashCode * 397) ^ (Description?.GetHashCode() ?? 0);
323+
hashCode = (hashCode * 397) ^ Duration.GetHashCode();
351324
hashCode = (hashCode * 397) ^ (DtEnd?.GetHashCode() ?? 0);
352325
hashCode = (hashCode * 397) ^ (Location?.GetHashCode() ?? 0);
353326
hashCode = (hashCode * 397) ^ Status?.GetHashCode() ?? 0;

Ical.Net/Evaluation/EventEvaluator.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,19 @@ public override HashSet<Period> Evaluate(IDateTime referenceTime, DateTime perio
4040

4141
foreach (var period in Periods)
4242
{
43-
period.Duration = CalendarEvent.Duration;
43+
period.Duration = CalendarEvent.GetEffectiveDuration();
4444
period.EndTime = period.Duration == default
4545
? period.StartTime
46-
: period.StartTime.Add(CalendarEvent.Duration);
46+
: period.StartTime.Add(CalendarEvent.GetEffectiveDuration());
4747
}
4848

4949
// Ensure each period has a duration
5050
foreach (var period in Periods.Where(p => p.EndTime == null))
5151
{
52-
period.Duration = CalendarEvent.Duration;
52+
period.Duration = CalendarEvent.GetEffectiveDuration();
5353
period.EndTime = period.Duration == default
5454
? period.StartTime
55-
: period.StartTime.Add(CalendarEvent.Duration);
55+
: period.StartTime.Add(CalendarEvent.GetEffectiveDuration());
5656
}
5757

5858
return Periods;

0 commit comments

Comments
 (0)