Skip to content

Commit

Permalink
add - doc - Added daylight info for vCalendar 1.0
Browse files Browse the repository at this point in the history
---

We've added vCalendar 1.0's DAYLIGHT property.

---

Type: add
Breaking: False
Doc Required: True
Backport Required: False
Part: 1/1
  • Loading branch information
AptiviCEO committed Sep 23, 2024
1 parent 800eb28 commit 420782f
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 6 deletions.
3 changes: 3 additions & 0 deletions VisualCard.Calendar/Parsers/VCalendarConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ internal static class VCalendarConstants
internal const string _valueArgumentSpecifier = "VALUE=";
internal const string _encodingArgumentSpecifier = "ENCODING=";

// Available in vCalendar 1.0
internal const string _daylightSpecifier = "DAYLIGHT";

// Available in vCalendar 2.0
internal const string _dateStampSpecifier = "DTSTAMP";
internal const string _calScaleSpecifier = "CALSCALE";
Expand Down
8 changes: 7 additions & 1 deletion VisualCard.Calendar/Parsers/VCalendarParserTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
using System.Linq;
using VisualCard.Calendar.Parts.Implementations.TimeZone;
using VisualCard.Calendar.Parts.Implementations.Todo;
using VisualCard.Calendar.Parts.Implementations.Legacy;

namespace VisualCard.Calendar.Parsers
{
Expand Down Expand Up @@ -83,7 +84,8 @@ internal static bool EnumArrayTypeSupported(CalendarPartsArrayEnum partsArrayEnu
CalendarPartsArrayEnum.TimeZoneName => calendarVersion.Major == 2 && TypeMatch(componentType, typeof(CalendarTimeZone)),
CalendarPartsArrayEnum.TimeZoneOffsetFrom => calendarVersion.Major == 2 && TypeMatch(componentType, typeof(CalendarStandard), typeof(CalendarDaylight)),
CalendarPartsArrayEnum.TimeZoneOffsetTo => calendarVersion.Major == 2 && TypeMatch(componentType, typeof(CalendarStandard), typeof(CalendarDaylight)),
CalendarPartsArrayEnum.RecDate => calendarVersion.Major == 2 && TypeMatch(componentType, typeof(CalendarEvent), typeof(CalendarTodo), typeof(CalendarJournal), typeof(CalendarStandard), typeof(CalendarDaylight)),
CalendarPartsArrayEnum.RecDate => calendarVersion.Major == 2 && TypeMatch(componentType, typeof(Parts.Calendar)),
CalendarPartsArrayEnum.Daylight => calendarVersion.Major == 1 && TypeMatch(componentType, typeof(CalendarEvent), typeof(CalendarTodo), typeof(CalendarJournal), typeof(CalendarStandard), typeof(CalendarDaylight)),
CalendarPartsArrayEnum.NonstandardNames => true,
_ =>
throw new InvalidOperationException("Invalid parts array enumeration type to get supported value"),
Expand Down Expand Up @@ -142,6 +144,7 @@ internal static string GetPrefixFromPartsArrayEnum(CalendarPartsArrayEnum partsA
CalendarPartsArrayEnum.TimeZoneOffsetFrom => VCalendarConstants._tzOffsetFromSpecifier,
CalendarPartsArrayEnum.TimeZoneOffsetTo => VCalendarConstants._tzOffsetToSpecifier,
CalendarPartsArrayEnum.RecDate => VCalendarConstants._recDateSpecifier,
CalendarPartsArrayEnum.Daylight => VCalendarConstants._daylightSpecifier,
CalendarPartsArrayEnum.NonstandardNames => VCalendarConstants._xSpecifier,
_ =>
throw new NotImplementedException($"Array enumeration {partsArrayEnum} is not implemented.")
Expand Down Expand Up @@ -187,6 +190,8 @@ internal static (CalendarPartsArrayEnum, PartCardinality) GetPartsArrayEnumFromT
return (CalendarPartsArrayEnum.TimeZoneOffsetTo, PartCardinality.MayBeOne);
else if (partsArrayType == typeof(RecDateInfo))
return (CalendarPartsArrayEnum.RecDate, PartCardinality.Any);
else if (partsArrayType == typeof(DaylightInfo))
return (CalendarPartsArrayEnum.Daylight, PartCardinality.MayBeOne);
else if (partsArrayType == typeof(XNameInfo))
return (CalendarPartsArrayEnum.NonstandardNames, PartCardinality.Any);
throw new NotImplementedException($"Type {partsArrayType.Name} doesn't represent any part array.");
Expand All @@ -213,6 +218,7 @@ internal static (PartType type, object enumeration, Type? enumType, Func<string,
VCalendarConstants._tzOffsetFromSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.TimeZoneOffsetFrom, typeof(TimeZoneOffsetFromInfo), TimeZoneOffsetFromInfo.FromStringVcalendarStatic, "", "", []),
VCalendarConstants._tzOffsetToSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.TimeZoneOffsetTo, typeof(TimeZoneOffsetToInfo), TimeZoneOffsetToInfo.FromStringVcalendarStatic, "", "", []),
VCalendarConstants._recDateSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.RecDate, typeof(RecDateInfo), RecDateInfo.FromStringVcalendarStatic, "", "", []),
VCalendarConstants._daylightSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.Daylight, typeof(DaylightInfo), DaylightInfo.FromStringVcalendarStatic, "", "", []),
VCalendarConstants._xSpecifier => (PartType.PartsArray, CalendarPartsArrayEnum.NonstandardNames, typeof(XNameInfo), XNameInfo.FromStringVcalendarStatic, "", "", []),
VCalendarConstants._productIdSpecifier => (PartType.Strings, CalendarStringsEnum.ProductId, null, null, "", "", []),
VCalendarConstants._calScaleSpecifier => (PartType.Strings, CalendarStringsEnum.CalScale, null, null, "", "", []),
Expand Down
4 changes: 4 additions & 0 deletions VisualCard.Calendar/Parts/Enums/CalendarPartsArrayEnum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,9 @@ public enum CalendarPartsArrayEnum
/// Time zone offset to
/// </summary>
TimeZoneOffsetTo,
/// <summary>
/// Daylight saving time info (vCalendar 1.0)
/// </summary>
Daylight,
}
}
173 changes: 173 additions & 0 deletions VisualCard.Calendar/Parts/Implementations/Legacy/DaylightInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
//
// 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 <https://www.gnu.org/licenses/>.
//

using System;
using System.Collections.Generic;
using System.Diagnostics;
using VisualCard.Parsers;

namespace VisualCard.Calendar.Parts.Implementations.Legacy
{
/// <summary>
/// Daylight information (entire calendar)
/// </summary>
[DebuggerDisplay("Daylight = {Flag}")]
public class DaylightInfo : BaseCalendarPartInfo, IEquatable<DaylightInfo>
{
/// <summary>
/// Whether it's enabled or not
/// </summary>
public bool Flag { get; }

/// <summary>
/// UTC offset of the daylight saving time
/// </summary>
public TimeSpan UtcOffset { get; }

/// <summary>
/// Start date and time of the daylight saving time
/// </summary>
public DateTime DaylightStart { get; }

/// <summary>
/// End date and time of the daylight saving time
/// </summary>
public DateTime DaylightEnd { get; }

/// <summary>
/// Standard time designation
/// </summary>
public string? StandardDesignation { get; }

/// <summary>
/// Daylight saving time designation
/// </summary>
public string? DaylightDesignation { get; }

internal static BaseCalendarPartInfo FromStringVcalendarStatic(string value, string[] finalArgs, string[] elementTypes, string valueType, Version calendarVersion) =>
new DaylightInfo().FromStringVcalendarInternal(value, finalArgs, elementTypes, valueType, calendarVersion);

internal override string ToStringVcalendarInternal(Version calendarVersion)
{
if (!Flag)
return "FALSE";
string posixUtc = VcardParserTools.SaveUtcOffset(UtcOffset);
string posixStart = VcardParserTools.SavePosixDate(DaylightStart);
string posixEnd = VcardParserTools.SavePosixDate(DaylightEnd);
return $"TRUE;{posixUtc};{posixStart};{posixEnd};{StandardDesignation};{DaylightDesignation}";
}

internal override BaseCalendarPartInfo FromStringVcalendarInternal(string value, string[] finalArgs, string[] elementTypes, string valueType, Version calendarVersion)
{
// Get the values
string[] split = value.Split(';');
if (split.Length == 1 && split[0] == "FALSE")
return new DaylightInfo(finalArgs, elementTypes, valueType, false, new(), new(), new(), "", "");
if (split.Length != 6)
throw new ArgumentException($"When splitting daylight information, the split value is {split.Length} instead of 6.");
string unprocessedUtc = split[1];
string unprocessedStart = split[2];
string unprocessedEnd = split[3];
string standard = split[4];
string daylight = split[5];

// Process the UTC offset and start/end dates
TimeSpan utcOffset = VcardParserTools.ParseUtcOffset(unprocessedUtc);
DateTime startDate = VcardParserTools.ParsePosixDate(unprocessedStart);
DateTime endDate = VcardParserTools.ParsePosixDate(unprocessedEnd);

// Populate the fields
DaylightInfo _geo = new(finalArgs, elementTypes, valueType, true, utcOffset, startDate, endDate, standard, daylight);
return _geo;
}

/// <inheritdoc/>
public override bool Equals(object obj) =>
Equals((DaylightInfo)obj);

/// <summary>
/// Checks to see if both the parts are equal
/// </summary>
/// <param name="other">The target <see cref="DaylightInfo"/> instance to check to see if they equal</param>
/// <returns>True if all the part elements are equal. Otherwise, false.</returns>
public bool Equals(DaylightInfo other) =>
Equals(this, other);

/// <summary>
/// Checks to see if both the parts are equal
/// </summary>
/// <param name="source">The source <see cref="DaylightInfo"/> instance to check to see if they equal</param>
/// <param name="target">The target <see cref="DaylightInfo"/> instance to check to see if they equal</param>
/// <returns>True if all the part elements are equal. Otherwise, false.</returns>
public bool Equals(DaylightInfo source, DaylightInfo target)
{
// We can't perform this operation on null.
if (source is null || target is null)
return false;

// Check all the properties
return
source.Flag == target.Flag &&
source.UtcOffset == target.UtcOffset &&
source.DaylightStart == target.DaylightStart &&
source.DaylightEnd == target.DaylightEnd &&
source.StandardDesignation == target.StandardDesignation &&
source.DaylightDesignation == target.DaylightDesignation
;
}

/// <inheritdoc/>
public override int GetHashCode()
{
int hashCode = 269991333;
hashCode = hashCode * -1521134295 + base.GetHashCode();
hashCode = hashCode * -1521134295 + Flag.GetHashCode();
hashCode = hashCode * -1521134295 + UtcOffset.GetHashCode();
hashCode = hashCode * -1521134295 + DaylightStart.GetHashCode();
hashCode = hashCode * -1521134295 + DaylightEnd.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<string?>.Default.GetHashCode(StandardDesignation);
hashCode = hashCode * -1521134295 + EqualityComparer<string?>.Default.GetHashCode(DaylightDesignation);
return hashCode;
}

/// <inheritdoc/>
public static bool operator ==(DaylightInfo left, DaylightInfo right) =>
left.Equals(right);

/// <inheritdoc/>
public static bool operator !=(DaylightInfo left, DaylightInfo right) =>
!(left == right);

internal override bool EqualsInternal(BaseCalendarPartInfo source, BaseCalendarPartInfo target) =>
(DaylightInfo)source == (DaylightInfo)target;

internal DaylightInfo() { }

internal DaylightInfo(string[] arguments, string[] elementTypes, string valueType, bool flag, TimeSpan utcOffset, DateTime daylightStart, DateTime daylightEnd, string standardDesignation, string daylightDesignation) :
base(arguments, elementTypes, valueType)
{
Flag = flag;
UtcOffset = utcOffset;
DaylightStart = daylightStart;
DaylightEnd = daylightEnd;
StandardDesignation = standardDesignation;
DaylightDesignation = daylightDesignation;
}
}
}
4 changes: 4 additions & 0 deletions VisualCard.ShowCalendars/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
"vCalendar 2.0 test - event with time zone": {
"commandName": "Project",
"commandLineArgs": "TestFiles/vcalendarEventTz.ics"
},
"vCalendar 1.0 test": {
"commandName": "Project",
"commandLineArgs": "TestFiles/vevent1.vcs"
}
}
}
18 changes: 18 additions & 0 deletions VisualCard.ShowCalendars/TestFiles/vevent1.vcs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
BEGIN:VCALENDAR
VERSION:1.0
DAYLIGHT:TRUE;-09;19960407T115959;19961027T100000;PST;PDT
BEGIN:VEVENT
CATEGORIES:MEETING
STATUS:NEEDS ACTION
DTSTART:19960401T073000Z
DTEND:19960401T083000Z
SUMMARY:Steve's Proposal Review
DESCRIPTION:Steve and John to review newest proposal material
CLASS:PRIVATE
END:VEVENT
BEGIN:VTODO
SUMMARY:John to pay for lunch
DUE:19960401T083000Z
STATUS:NEEDS ACTION
END:VTODO
END:VCALENDAR
11 changes: 6 additions & 5 deletions VisualCard/Parsers/VcardParserTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -443,25 +443,26 @@ internal static string SavePosixDate(DateTime posixDateRepresentation, bool date
internal static TimeSpan ParseUtcOffset(string utcOffsetRepresentation)
{
// Check for sanity
if (utcOffsetRepresentation.Length != 5 && utcOffsetRepresentation.Length != 7)
if (utcOffsetRepresentation.Length != 3 && utcOffsetRepresentation.Length != 5 && utcOffsetRepresentation.Length != 7)
throw new ArgumentException($"UTC offset representation [{utcOffsetRepresentation}] is invalid.");
bool hasMinutes = utcOffsetRepresentation.Length >= 5;
bool hasSeconds = utcOffsetRepresentation.Length == 7;

// Now, this representation might be a POSIX offset that follows the vCard specification, but check it,
// because it might be either <+/->HHmmss or <+/->HHmm.
// because it might be either <+/->HHmmss, <+/->HHmm, or <+/->HH.
string designatorStr = utcOffsetRepresentation.Substring(0, 1);
string hourStr = utcOffsetRepresentation.Substring(1, 2);
string minuteStr = utcOffsetRepresentation.Substring(3, 2);
string minuteStr = hasMinutes ? utcOffsetRepresentation.Substring(3, 2) : "";
string secondStr = hasSeconds ? utcOffsetRepresentation.Substring(5, 2) : "";
if (designatorStr != "+" && designatorStr != "-")
throw new ArgumentException($"Designator {designatorStr} is invalid.");
if (hourStr == "00" && minuteStr == "00" && (!hasSeconds || (hasSeconds && secondStr == "00")))
if (hourStr == "00" && (!hasMinutes || (hasMinutes && minuteStr == "00")) && (!hasSeconds || (hasSeconds && secondStr == "00")))
{
if (designatorStr == "-")
throw new ArgumentException($"Can't specify negative zero offset.");
return new();
}
if (TimeSpan.TryParseExact($"{hourStr}:{minuteStr}:{(hasSeconds ? secondStr : "00")}", "hh\\:mm\\:ss", CultureInfo.InvariantCulture, out TimeSpan offset))
if (TimeSpan.TryParseExact($"{hourStr}:{(hasMinutes ? minuteStr : "00")}:{(hasSeconds ? secondStr : "00")}", "hh\\:mm\\:ss", CultureInfo.InvariantCulture, out TimeSpan offset))
return designatorStr == "-" ? -offset : offset;
throw new ArgumentException($"Can't parse offset {utcOffsetRepresentation}");
}
Expand Down

0 comments on commit 420782f

Please sign in to comment.