diff --git a/VisualCard.Calendar/Parsers/VCalendarConstants.cs b/VisualCard.Calendar/Parsers/VCalendarConstants.cs index b124034..a25f37c 100644 --- a/VisualCard.Calendar/Parsers/VCalendarConstants.cs +++ b/VisualCard.Calendar/Parsers/VCalendarConstants.cs @@ -105,5 +105,7 @@ internal static class VCalendarConstants internal const string _percentCompletionSpecifier = "PERCENT-COMPLETION"; internal const string _freeBusySpecifier = "FREEBUSY"; internal const string _recurIdSpecifier = "RECURRENCE-ID"; + internal const string _repeatSpecifier = "REPEAT"; + internal const string _durationSpecifier = "DURATION"; } } diff --git a/VisualCard.Calendar/Parsers/VCalendarParser.cs b/VisualCard.Calendar/Parsers/VCalendarParser.cs index a504613..4adba17 100644 --- a/VisualCard.Calendar/Parsers/VCalendarParser.cs +++ b/VisualCard.Calendar/Parsers/VCalendarParser.cs @@ -377,7 +377,16 @@ private void ValidateAlarm(CalendarAlarm alarmInfo) if (!ValidateComponent(ref expectedMailAlarmFields, out string[] actualMailAlarmFields, alarmInfo)) throw new InvalidDataException($"The following keys [{string.Join(", ", expectedMailAlarmFields)}] are required in the mail alarm representation. Got [{string.Join(", ", actualMailAlarmFields)}]."); break; - } + } + + // Check to see if there is a repeat property + int repeat = (int)alarmInfo.GetInteger(CalendarIntegersEnum.Repeat); + string[] expectedRepeatedAlarmFields = [VCalendarConstants._durationSpecifier]; + if (repeat >= 1) + { + if (!ValidateComponent(ref expectedRepeatedAlarmFields, out string[] actualRepeatedAlarmFields, alarmInfo)) + throw new InvalidDataException($"The following keys [{string.Join(", ", expectedRepeatedAlarmFields)}] are required in the repeated alarm representation. Got [{string.Join(", ", actualRepeatedAlarmFields)}]."); + } } private Parts.Calendar GetCalendarInheritedInstance(string type) diff --git a/VisualCard.Calendar/Parsers/VCalendarParserTools.cs b/VisualCard.Calendar/Parsers/VCalendarParserTools.cs index d0c781c..eb1f4bc 100644 --- a/VisualCard.Calendar/Parsers/VCalendarParserTools.cs +++ b/VisualCard.Calendar/Parsers/VCalendarParserTools.cs @@ -65,6 +65,7 @@ internal static bool IntegerSupported(CalendarIntegersEnum integersEnum, Type co CalendarIntegersEnum.Priority => TypeMatch(componentType, typeof(CalendarEvent), typeof(CalendarTodo)), CalendarIntegersEnum.Sequence => TypeMatch(componentType, typeof(CalendarEvent), typeof(CalendarTodo), typeof(CalendarJournal)), CalendarIntegersEnum.PercentComplete => TypeMatch(componentType, typeof(CalendarTodo)), + CalendarIntegersEnum.Repeat => TypeMatch(componentType, typeof(CalendarAlarm)), _ => throw new InvalidOperationException("Invalid integer enumeration type to get supported value"), }; @@ -83,6 +84,7 @@ internal static bool EnumArrayTypeSupported(CalendarPartsArrayEnum partsArrayEnu CalendarPartsArrayEnum.DateCreatedAlt => TypeMatch(componentType, typeof(CalendarEvent), typeof(CalendarTodo)), CalendarPartsArrayEnum.DateStart => TypeMatch(componentType, typeof(CalendarEvent), typeof(CalendarTodo), typeof(CalendarFreeBusy), typeof(CalendarStandard), typeof(CalendarDaylight)), CalendarPartsArrayEnum.DateEnd => TypeMatch(componentType, typeof(CalendarEvent), typeof(CalendarFreeBusy)), + CalendarPartsArrayEnum.Duration => calendarVersion.Major == 2 && TypeMatch(componentType, typeof(CalendarEvent), typeof(CalendarTodo), typeof(CalendarAlarm)), CalendarPartsArrayEnum.DateCompleted => TypeMatch(componentType, typeof(CalendarTodo)), CalendarPartsArrayEnum.DueDate => TypeMatch(componentType, typeof(CalendarTodo)), CalendarPartsArrayEnum.DateStamp => calendarVersion.Major == 2 && TypeMatch(componentType, typeof(CalendarEvent), typeof(CalendarTodo), typeof(CalendarJournal), typeof(CalendarFreeBusy)), @@ -134,6 +136,7 @@ internal static string GetPrefixFromIntegersEnum(CalendarIntegersEnum integersEn CalendarIntegersEnum.Priority => VCalendarConstants._prioritySpecifier, CalendarIntegersEnum.Sequence => VCalendarConstants._sequenceSpecifier, CalendarIntegersEnum.PercentComplete => VCalendarConstants._percentCompletionSpecifier, + CalendarIntegersEnum.Repeat => VCalendarConstants._repeatSpecifier, _ => throw new NotImplementedException($"Integer enumeration {integersEnum} is not implemented.") }; @@ -152,6 +155,7 @@ internal static string GetPrefixFromPartsArrayEnum(CalendarPartsArrayEnum partsA CalendarPartsArrayEnum.DateCreatedAlt => VCalendarConstants._created1Specifier, CalendarPartsArrayEnum.DateStart => VCalendarConstants._dateStartSpecifier, CalendarPartsArrayEnum.DateEnd => VCalendarConstants._dateEndSpecifier, + CalendarPartsArrayEnum.Duration => VCalendarConstants._durationSpecifier, CalendarPartsArrayEnum.DateCompleted => VCalendarConstants._dateCompletedSpecifier, CalendarPartsArrayEnum.DueDate => VCalendarConstants._dueDateSpecifier, CalendarPartsArrayEnum.DateStamp => VCalendarConstants._dateStampSpecifier, @@ -200,6 +204,8 @@ internal static (CalendarPartsArrayEnum, PartCardinality) GetPartsArrayEnumFromT return (CalendarPartsArrayEnum.DateStart, PartCardinality.ShouldBeOne); else if (partsArrayType == typeof(DateEndInfo)) return (CalendarPartsArrayEnum.DateEnd, PartCardinality.MayBeOne); + else if (partsArrayType == typeof(DurationInfo)) + return (CalendarPartsArrayEnum.Duration, PartCardinality.MayBeOne); else if (partsArrayType == typeof(DateCompletedInfo)) return (CalendarPartsArrayEnum.DateCompleted, PartCardinality.MayBeOne); else if (partsArrayType == typeof(DueDateInfo)) @@ -245,6 +251,7 @@ internal static (PartType type, object enumeration, Type? enumType, Func (PartType.PartsArray, CalendarPartsArrayEnum.DateCreatedAlt, typeof(DateCreatedInfo), DateCreatedInfo.FromStringVcalendarStatic, "", "", "date-time", []), VCalendarConstants._dateStartSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.DateStart, typeof(DateStartInfo), DateStartInfo.FromStringVcalendarStatic, "", "", "date-time", []), VCalendarConstants._dateEndSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.DateEnd, typeof(DateEndInfo), DateEndInfo.FromStringVcalendarStatic, "", "", "date-time", []), + VCalendarConstants._durationSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.Duration, typeof(DurationInfo), DurationInfo.FromStringVcalendarStatic, "", "", "duration", []), VCalendarConstants._dateCompletedSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.DateCompleted, typeof(DateCompletedInfo), DateCompletedInfo.FromStringVcalendarStatic, "", "", "date-time", []), VCalendarConstants._dueDateSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.DueDate, typeof(DueDateInfo), DueDateInfo.FromStringVcalendarStatic, "", "", "date-time", []), VCalendarConstants._dateStampSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.DateStamp, typeof(DateStampInfo), DateStampInfo.FromStringVcalendarStatic, "", "", "date-time", []), @@ -281,6 +288,7 @@ internal static (PartType type, object enumeration, Type? enumType, Func (PartType.Integers, CalendarIntegersEnum.Priority, null, null, "", "", "integer", []), VCalendarConstants._sequenceSpecifier => (PartType.Integers, CalendarIntegersEnum.Sequence, null, null, "", "", "integer", []), VCalendarConstants._percentCompletionSpecifier => (PartType.Integers, CalendarIntegersEnum.PercentComplete, null, null, "", "", "integer", []), + VCalendarConstants._repeatSpecifier => (PartType.Integers, CalendarIntegersEnum.Repeat, null, null, "", "", "integer", []), // Extensions are allowed VCalendarConstants._xSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.NonstandardNames, typeof(XNameInfo), XNameInfo.FromStringVcalendarStatic, "", "", "", []), diff --git a/VisualCard.Calendar/Parts/Enums/CalendarIntegersEnum.cs b/VisualCard.Calendar/Parts/Enums/CalendarIntegersEnum.cs index 4b19966..ebb8c30 100644 --- a/VisualCard.Calendar/Parts/Enums/CalendarIntegersEnum.cs +++ b/VisualCard.Calendar/Parts/Enums/CalendarIntegersEnum.cs @@ -36,5 +36,9 @@ public enum CalendarIntegersEnum /// To-do percent completion /// PercentComplete, + /// + /// Alarm repetition rate + /// + Repeat, } } diff --git a/VisualCard.Calendar/Parts/Enums/CalendarPartsArrayEnum.cs b/VisualCard.Calendar/Parts/Enums/CalendarPartsArrayEnum.cs index 504f01c..a0f7f33 100644 --- a/VisualCard.Calendar/Parts/Enums/CalendarPartsArrayEnum.cs +++ b/VisualCard.Calendar/Parts/Enums/CalendarPartsArrayEnum.cs @@ -133,6 +133,10 @@ public enum CalendarPartsArrayEnum /// FreeBusy, /// + /// Duration (event, todo, or alarm) + /// + Duration, + /// /// The calendar's extended IANA options (usually starts with SOMETHING:Value1;Value2...) (event, todo, journal, free/busy, or alarm) /// IanaNames = int.MaxValue - 2, diff --git a/VisualCard.Calendar/Parts/Implementations/DurationInfo.cs b/VisualCard.Calendar/Parts/Implementations/DurationInfo.cs new file mode 100644 index 0000000..0a31828 --- /dev/null +++ b/VisualCard.Calendar/Parts/Implementations/DurationInfo.cs @@ -0,0 +1,125 @@ +// +// VisualCard Copyright (C) 2021-2024 Aptivi +// +// This file is part of VisualCard +// +// VisualCard is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// VisualCard is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY, without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text.RegularExpressions; +using VisualCard.Parsers; + +namespace VisualCard.Calendar.Parts.Implementations +{ + /// + /// Duration info + /// + [DebuggerDisplay("Duration = {Duration}")] + public class DurationInfo : BaseCalendarPartInfo, IEquatable + { + /// + /// Duration in a string + /// + public string? Duration { get; } + + /// + /// Duration in a time span + /// + public TimeSpan DurationSpan => + VcardCommonTools.GetDurationSpan(Duration ?? "").span; + + /// + /// Duration in a date/time representation + /// + public DateTimeOffset DurationResult => + VcardCommonTools.GetDurationSpan(Duration ?? "").result; + + internal static BaseCalendarPartInfo FromStringVcalendarStatic(string value, string[] finalArgs, string[] elementTypes, string valueType, Version cardVersion) => + new DurationInfo().FromStringVcalendarInternal(value, finalArgs, elementTypes, valueType, cardVersion); + + internal override string ToStringVcalendarInternal(Version cardVersion) => + Duration ?? ""; + + internal override BaseCalendarPartInfo FromStringVcalendarInternal(string value, string[] finalArgs, string[] elementTypes, string valueType, Version cardVersion) + { + // Populate the fields + string duration = Regex.Unescape(value); + + // Add the fetched information + DurationInfo _time = new(finalArgs, elementTypes, valueType, duration); + return _time; + } + + /// + public override bool Equals(object obj) => + Equals((DurationInfo)obj); + + /// + /// Checks to see if both the parts are equal + /// + /// The target instance to check to see if they equal + /// True if all the part elements are equal. Otherwise, false. + public bool Equals(DurationInfo other) => + Equals(this, other); + + /// + /// Checks to see if both the parts are equal + /// + /// The source instance to check to see if they equal + /// The target instance to check to see if they equal + /// True if all the part elements are equal. Otherwise, false. + public bool Equals(DurationInfo source, DurationInfo target) + { + // We can't perform this operation on null. + if (source is null || target is null) + return false; + + // Check all the properties + return + source.Duration == target.Duration + ; + } + + /// + public override int GetHashCode() + { + int hashCode = -101114941; + hashCode = hashCode * -1521134295 + base.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Duration); + return hashCode; + } + + /// + public static bool operator ==(DurationInfo left, DurationInfo right) => + left.Equals(right); + + /// + public static bool operator !=(DurationInfo left, DurationInfo right) => + !(left == right); + + internal override bool EqualsInternal(BaseCalendarPartInfo source, BaseCalendarPartInfo target) => + (DurationInfo)source == (DurationInfo)target; + + internal DurationInfo() { } + + internal DurationInfo(string[] arguments, string[] elementTypes, string valueType, string duration) : + base(arguments, elementTypes, valueType) + { + Duration = duration; + } + } +}