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
3 changes: 3 additions & 0 deletions Ical.Net.Tests/AlarmTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
// Licensed under the MIT license.
//

#nullable enable
using Ical.Net.CalendarComponents;
using Ical.Net.DataTypes;
using NUnit.Framework;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Ical.Net.Serialization.DataTypes;

namespace Ical.Net.Tests;

Expand Down
41 changes: 41 additions & 0 deletions Ical.Net.Tests/DeserializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -612,4 +612,45 @@ public void CalendarWithMissingProdIdOrVersion_ShouldLeavePropertiesInvalid()
Assert.That(deserialized, Does.Not.Contain("PRODID:").And.Not.Contains("VERSION:"));
});
}

[Test, Category("DurationSerializer")]
[TestCase("PT1H", 0, 0, 1, 0, 0)]
[TestCase("PT1H30M", 0, 0, 1, 30, 0)]
[TestCase("PT1H30M45S", 0, 0, 1, 30, 45)]
[TestCase("P1D", 0, 1, 0, 0, 0)]
[TestCase("-P1D", 0, -1, 0, 0, 0)]
[TestCase("P1W", 1, 0, 0, 0, 0)]
[TestCase("-P1W", -1, 0, 0, 0, 0)]
[TestCase("P0W", null, null, null, null, null)]
[TestCase("-P0W")]
[TestCase("-P1D", 0, -1, 0, 0, 0)]
[TestCase("-P1W", -1, 0, 0, 0, 0)]
[TestCase("PT0S", 0, 0, 0, 0, 0)]
[TestCase("-PT0S", 0, 0, 0, 0, 0)]
[TestCase("-PT1H", 0, 0, -1, 0, 0)]
[TestCase("-PT1H30M", 0, 0, -1, -30, 0)]
[TestCase("-P1D", 0, -1, 0, 0, 0)]
[TestCase("PT125H199M199S", 0, 0, 125, 199, 199)]
[TestCase("-PT125H199M199S", 0, 0, -125, -199, -199)]
[TestCase("-P0DT0H30M0S", 0, 0, 0, -30, 0)]
[TestCase("-P1DT1H", 0, -1, -1, 0, 0)]
[TestCase("PT1000H", 0, 0, 1000, 0, 0)]
[TestCase("-PT1000H", null, null, -1000, null, null)]
public void DurationSerializer_ShouldReturn_ExpectedDuration(string text, int? weeks = null, int? days = null, int? hours = null, int? minutes = null, int? seconds = null)
{
var s = new DurationSerializer();
Assert.That((Duration?) s.Deserialize(new StringReader(text)), Is.EqualTo(new Duration(weeks, days, hours, minutes, seconds)));
}

[Test, Category("DurationSerializer")]
[TestCase("")]
[TestCase("Invalid")]
public void Duration_InvalidArguments_ShouldThrow(string text)
{
Assert.Multiple(() =>
{
Assert.That(new DurationSerializer().Deserialize(new StringReader(text)), Is.Null);
Assert.That(Duration.Parse(text), Is.Null);
});
}
}
12 changes: 8 additions & 4 deletions Ical.Net.Tests/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -348,17 +349,20 @@ public void AttendeesSerialized()
}
}

//todo test event:
//-GeographicLocation
//-Alarm

[Test]
public void ZeroDuration_Test()
{
var result = new DurationSerializer().SerializeToString(Duration.Zero);
Assert.That("P0D".Equals(result, StringComparison.Ordinal), Is.True);
}

[Test]
public void Duration_FromWeeks()
{
var weeks = Duration.FromWeeks(4).Weeks;
Assert.That(weeks, Is.EqualTo(4));
}

[Test]
public void DurationIsStable_Tests()
{
Expand Down
14 changes: 10 additions & 4 deletions Ical.Net/DataTypes/Duration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ public struct Duration
/// <exception cref="ArgumentException">Thrown if not all non-null arguments have the same sign.</exception>
public Duration(int? weeks = null, int? days = null, int? hours = null, int? minutes = null, int? seconds = null)
{
weeks = NullIfZero(weeks);
days = NullIfZero(days);
hours = NullIfZero(hours);
minutes = NullIfZero(minutes);
seconds = NullIfZero(seconds);

var sign = GetSign(weeks) ?? GetSign(days) ?? GetSign(hours) ?? GetSign(minutes) ?? GetSign(seconds) ?? 1;
if (
((GetSign(weeks) ?? sign) != sign)
Expand Down Expand Up @@ -119,8 +125,8 @@ public static Duration FromSeconds(int seconds) =>
/// Parses the specified value according to RFC 5545.
/// </summary>
/// <exception cref="System.FormatException">Thrown if the value is not a valid duration.</exception>
public static Duration Parse(string value) =>
(Duration) new DurationSerializer().Deserialize(new StringReader(value))!; // throws if null
public static Duration? Parse(string value) =>
(Duration?) new DurationSerializer().Deserialize(new StringReader(value))!;

/// <summary>
/// Creates an instance that represents the given time span as exact value, that is, time-only.
Expand Down Expand Up @@ -200,7 +206,7 @@ internal bool IsEmpty
/// <summary>
/// Returns a negated copy of the given instance.
/// </summary>
public static Duration operator-(Duration d) =>
public static Duration operator -(Duration d) =>
new Duration(-d.Weeks, -d.Days, -d.Hours, -d.Minutes, -d.Seconds);

/// <inheritdoc/>
Expand All @@ -215,5 +221,5 @@ internal bool IsEmpty
< 0 => -1
};

private static int? NullIfZero(int v) => (v == 0) ? null : v;
private static int? NullIfZero(int? v) => (v == 0) ? null : v;
}
18 changes: 13 additions & 5 deletions Ical.Net/Serialization/DataTypes/DurationSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@
new Regex(@"^(?<sign>\+|-)?P(((?<week>\d+)W)|(?<main>((?<day>\d+)D)?(?<time>T((?<hour>\d+)H)?((?<minute>\d+)M)?((?<second>\d+)S)?)?))$",
RegexOptions.Compiled | RegexOptions.IgnoreCase, RegexDefaults.Timeout);

/// <summary>
/// Deserializes a string into a <see cref="Duration"/> object.
/// </summary>
/// <param name="tr"></param>
/// <returns>A <see cref="Duration"/> for a valid input pattern, or <see langword="null"/> otherwise.</returns>
/// <exception cref="FormatException">Cannot create a <see cref="Duration"/> from the input pattern values.</exception>
public override object? Deserialize(TextReader tr)
{
var value = tr.ReadToEnd();
Expand All @@ -68,8 +74,10 @@

if (!match.Success)
{
// This happens for EXDATE values, which never have a duration,
// and also RDATE values, where the optional duration is missing.
// This happens for
// * EXDATE values, which never have a duration,
// * RDATE values, where the optional duration is missing
// * TRIGGER values that are invalid
return null;
}

Expand All @@ -92,17 +100,17 @@
days = GetGroupInt("day");
if (match.Groups["time"].Success)
{
hours = GetGroupInt("hour");
hours = GetGroupInt("hour");
minutes = GetGroupInt("minute");
seconds = GetGroupInt("second");
}
}

return new Duration(sign * weeks, sign * days, sign * hours, sign * minutes, sign * seconds);
}
catch
catch (Exception ex)

Check warning on line 111 in Ical.Net/Serialization/DataTypes/DurationSerializer.cs

View check run for this annotation

Codecov / codecov/patch

Ical.Net/Serialization/DataTypes/DurationSerializer.cs#L111

Added line #L111 was not covered by tests
{
throw new FormatException();
throw new FormatException(ex.Message, ex);

Check warning on line 113 in Ical.Net/Serialization/DataTypes/DurationSerializer.cs

View check run for this annotation

Codecov / codecov/patch

Ical.Net/Serialization/DataTypes/DurationSerializer.cs#L113

Added line #L113 was not covered by tests
}
}
}
Loading