diff --git a/.editorconfig b/.editorconfig index 21943c337..52000afa8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,10 +1,265 @@ -root = true +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true -[*] -charset = utf-8-bom -end_of_line = crlf +[*.{cs,xml,csproj,sln}] + +#Core editorconfig formatting - indentation + +#use soft tabs (spaces) for indentation indent_style = space +indent_size = 4 +tab_width = 4 + +#Formatting - new line options + trim_trailing_whitespace = true +insert_final_newline = true +#require members of anonymous types to be on separate lines +csharp_new_line_before_members_in_anonymous_types = true [*.cs] -indent_size = 4 \ No newline at end of file + +#place catch statements on a new line +csharp_new_line_before_catch = true +#place else statements on a new line +csharp_new_line_before_else = true +#require members of object initializers to be on the same line +csharp_new_line_before_members_in_object_initializers = false +#require braces to be on a new line for object_collection_array_initializers, accessors, types, control_blocks, methods, lambdas, and properties (also known as "Allman" style) +csharp_new_line_before_open_brace = object_collection_array_initializers, accessors, types, control_blocks, methods, lambdas, properties + +#Formatting - organize using options + +#sort System.* using directives alphabetically, and place them before other usings +dotnet_sort_system_directives_first = true + +#Formatting - spacing options + +#require a space between a cast and the value +csharp_space_after_cast = true +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_after_colon_in_inheritance_clause = true +#require a space after a keyword in a control flow statement such as a for loop +csharp_space_after_keywords_in_control_flow_statements = true +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_before_colon_in_inheritance_clause = true +#remove space within empty argument list parentheses +csharp_space_between_method_call_empty_parameter_list_parentheses = false +#remove space between method call name and opening parenthesis +csharp_space_between_method_call_name_and_opening_parenthesis = false +#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call +csharp_space_between_method_call_parameter_list_parentheses = false +#remove space within empty parameter list parentheses for a method declaration +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. +csharp_space_between_method_declaration_parameter_list_parentheses = false +#use space after cast and colon and comma and dot and semicolon +csharp_space_after_cast = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_colon_for_base_or_interface_in_type_declaration = true +csharp_space_before_comma = false +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +# use space between brackets +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false +# use space before brackets +csharp_space_before_open_square_brackets = false + +#Formatting - wrapping options + +#leave code block on single line +csharp_preserve_single_line_blocks = true +#leave statements and member declarations on the same line +csharp_preserve_single_line_statements = true + +#Style - Code block preferences + +#prefer no curly braces if allowed +csharp_prefer_braces = false:suggestion + +#Style - expression bodied member options + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:suggestion +csharp_style_expression_bodied_properties = true:suggestion +csharp_style_expression_bodied_properties = when_on_single_line:suggestion +#prefer block bodies for constructors +csharp_style_expression_bodied_constructors = false:suggestion +#prefer block bodies for accessors +csharp_style_expression_bodied_accessors = false:suggestion + + +#Style - expression level options + +#prefer out variables to be declared inline in the argument list of a method call when possible +csharp_style_inlined_variable_declaration = true:suggestion +#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_member_access = true:suggestion + +#Style - Expression-level preferences + +#prefer objects to be initialized using object initializers when possible +dotnet_style_object_initializer = true:suggestion + +#Style - implicit and explicit types + +#prefer var over explicit type in all cases, unless overridden by another code style rule +csharp_style_var_elsewhere = true:suggestion +#prefer var is used to declare variables with built-in system types such as int +csharp_style_var_for_built_in_types = true:suggestion +#prefer var when the type is already mentioned on the right-hand side of a declaration expression +csharp_style_var_when_type_is_apparent = true:suggestion + +#Style - language keyword and framework type options + +#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +#Style - modifier options + +#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +#Style - Modifier preferences + +#when this rule is set to a list of modifiers, prefer the specified ordering. +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +#Style - Pattern matching + +#prefer pattern matching instead of is expression with type casts +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +#Style - qualification options + +#prefer events not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_event = false:suggestion +#prefer fields not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_field = false:suggestion +#prefer methods not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_method = false:suggestion +#prefer properties not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_property = false:suggestion + +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_style_namespace_declarations = file_scoped:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_new_line_before_finally = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_parentheses = false +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_prefer_static_local_function = true:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion + +[*.{cs,vb}] +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = warning +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_readonly_field = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Copyright file header +file_header_template = \nCopyright ical.net project maintainers and contributors.\nLicensed under the MIT license.\n diff --git a/.gitattributes b/.gitattributes index 0a2cec4b8..a1ffca9ec 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,45 @@ -# Auto detect text files and perform LF normalization -# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ +# Set the default behavior, in case people don't have core.autocrlf set. * text=auto -*.cs diff=csharp +# Explicitly declare text files to always be normalized and converted +# to native line endings on checkout: +*.csproj text +*.nuspec text +*.sln eol=crlf +*.msg text +*.txt text +*.yml text +*.cs text diff=csharp +*.md text diff=markdown +*.editorconfig text +*.json text +*.xml text +*.bash text eol=lf +*.sh text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf +*.css text +*.scss text diff=css +*.htm text diff=html +*.html text diff=html +*.sql text +*.js text +*.ts text +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf +*.{cmd,[cC][mM][dD]} text eol=crlf +*.{bat,[bB][aA][tT]} text eol=crlf + +# Documents +*.docx diff=astextplain +*.xlsx binary +*.pdf diff=astextplain +*.csv text + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.gif binary +*.jpg binary +*.cdr binary +*.psd binary diff --git a/Ical.Net.Benchmarks/ApplicationWorkflows.cs b/Ical.Net.Benchmarks/ApplicationWorkflows.cs index e3b82b049..4358a6627 100644 --- a/Ical.Net.Benchmarks/ApplicationWorkflows.cs +++ b/Ical.Net.Benchmarks/ApplicationWorkflows.cs @@ -1,74 +1,78 @@ -using BenchmarkDotNet.Attributes; -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.IO; using System.Linq; +using BenchmarkDotNet.Attributes; +using Ical.Net.DataTypes; + +namespace Ical.Net.Benchmarks; -namespace Ical.Net.Benchmarks +public class ApplicationWorkflows { - public class ApplicationWorkflows - { - private static readonly TimeSpan _oneYear = TimeSpan.FromDays(365); - private static readonly DateTime _searchStart = DateTime.Now.Subtract(_oneYear); - private static readonly DateTime _searchEnd = DateTime.Now.Add(_oneYear); - private static readonly List _manyCalendars = GetIcalStrings(); + private static readonly TimeSpan _oneYear = TimeSpan.FromDays(365); + private static readonly DateTime _searchStart = DateTime.Now.Subtract(_oneYear); + private static readonly DateTime _searchEnd = DateTime.Now.Add(_oneYear); + private static readonly List _manyCalendars = GetIcalStrings(); - private static List GetIcalStrings() - { - var testProjectDirectory = Runner.FindParentFolder("Ical.Net.Tests", Directory.GetCurrentDirectory()); - var topLevelIcsPath = Path.GetFullPath(Path.Combine(testProjectDirectory, "Calendars")); - return Directory.EnumerateFiles(topLevelIcsPath, "*.ics", SearchOption.AllDirectories) - .Select(File.ReadAllText) - .Distinct(StringComparer.OrdinalIgnoreCase) - .Where(s => !s.Contains("InternetExplorer") && !s.Contains("SECONDLY")) - .OrderByDescending(s => s.Length) - .Take(10) - .ToList(); - } + private static List GetIcalStrings() + { + var testProjectDirectory = Runner.FindParentFolder("Ical.Net.Tests", Directory.GetCurrentDirectory()); + var topLevelIcsPath = Path.GetFullPath(Path.Combine(testProjectDirectory, "Calendars")); + return Directory.EnumerateFiles(topLevelIcsPath, "*.ics", SearchOption.AllDirectories) + .Select(File.ReadAllText) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Where(s => !s.Contains("InternetExplorer") && !s.Contains("SECONDLY")) + .OrderByDescending(s => s.Length) + .Take(10) + .ToList(); + } - [Benchmark] - public List SingleThreaded() - { - return _manyCalendars - .SelectMany(Calendar.Load) - .SelectMany(c => c.Events) - .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) - .ToList(); - } + [Benchmark] + public List SingleThreaded() + { + return _manyCalendars + .SelectMany(Calendar.Load) + .SelectMany(c => c.Events) + .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) + .ToList(); + } - [Benchmark] - public List ParallelUponDeserialize() - { - return _manyCalendars - .AsParallel() - .SelectMany(Calendar.Load) - .SelectMany(c => c.Events) - .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) - .ToList(); - } + [Benchmark] + public List ParallelUponDeserialize() + { + return _manyCalendars + .AsParallel() + .SelectMany(Calendar.Load) + .SelectMany(c => c.Events) + .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) + .ToList(); + } - [Benchmark] - public List ParallelUponGetOccurrences() - { - return _manyCalendars - .SelectMany(Calendar.Load) - .SelectMany(c => c.Events) - .AsParallel() - .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) - .ToList(); - } + [Benchmark] + public List ParallelUponGetOccurrences() + { + return _manyCalendars + .SelectMany(Calendar.Load) + .SelectMany(c => c.Events) + .AsParallel() + .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) + .ToList(); + } - [Benchmark] - public List ParallelDeserializeSequentialGatherEventsParallelGetOccurrences() - { - return _manyCalendars - .AsParallel() - .SelectMany(Calendar.Load) - .AsSequential() - .SelectMany(c => c.Events) - .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) - .ToList(); - } + [Benchmark] + public List ParallelDeserializeSequentialGatherEventsParallelGetOccurrences() + { + return _manyCalendars + .AsParallel() + .SelectMany(Calendar.Load) + .AsSequential() + .SelectMany(c => c.Events) + .SelectMany(e => e.GetOccurrences(_searchStart, _searchEnd)) + .ToList(); } } \ No newline at end of file diff --git a/Ical.Net.Benchmarks/CalDateTimePerfTests.cs b/Ical.Net.Benchmarks/CalDateTimePerfTests.cs index 4377f8112..eabb0d384 100644 --- a/Ical.Net.Benchmarks/CalDateTimePerfTests.cs +++ b/Ical.Net.Benchmarks/CalDateTimePerfTests.cs @@ -1,30 +1,34 @@ -using BenchmarkDotNet.Attributes; -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; +using BenchmarkDotNet.Attributes; +using Ical.Net.DataTypes; + +namespace Ical.Net.Benchmarks; -namespace Ical.Net.Benchmarks +public class CalDateTimePerfTests { - public class CalDateTimePerfTests - { - private const string _aTzid = "Australia/Sydney"; - private const string _bTzid = "America/New_York"; + private const string _aTzid = "Australia/Sydney"; + private const string _bTzid = "America/New_York"; - [Benchmark] - public IDateTime EmptyTzid() => new CalDateTime(DateTime.Now); + [Benchmark] + public IDateTime EmptyTzid() => new CalDateTime(DateTime.Now); - [Benchmark] - public IDateTime SpecifiedTzid() => new CalDateTime(DateTime.Now, _aTzid); + [Benchmark] + public IDateTime SpecifiedTzid() => new CalDateTime(DateTime.Now, _aTzid); - [Benchmark] - public IDateTime UtcDateTime() => new CalDateTime(DateTime.UtcNow); + [Benchmark] + public IDateTime UtcDateTime() => new CalDateTime(DateTime.UtcNow); - [Benchmark] - public IDateTime EmptyTzidToTzid() => EmptyTzid().ToTimeZone(_bTzid); + [Benchmark] + public IDateTime EmptyTzidToTzid() => EmptyTzid().ToTimeZone(_bTzid); - [Benchmark] - public IDateTime SpecifiedTzidToDifferentTzid() => SpecifiedTzid().ToTimeZone(_bTzid); + [Benchmark] + public IDateTime SpecifiedTzidToDifferentTzid() => SpecifiedTzid().ToTimeZone(_bTzid); - [Benchmark] - public IDateTime UtcToDifferentTzid() => UtcDateTime().ToTimeZone(_bTzid); - } + [Benchmark] + public IDateTime UtcToDifferentTzid() => UtcDateTime().ToTimeZone(_bTzid); } \ No newline at end of file diff --git a/Ical.Net.Benchmarks/OccurencePerfTests.cs b/Ical.Net.Benchmarks/OccurencePerfTests.cs index c397fe3f2..02096158c 100644 --- a/Ical.Net.Benchmarks/OccurencePerfTests.cs +++ b/Ical.Net.Benchmarks/OccurencePerfTests.cs @@ -1,140 +1,144 @@ -using BenchmarkDotNet.Attributes; -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using BenchmarkDotNet.Attributes; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; -namespace Ical.Net.Benchmarks +namespace Ical.Net.Benchmarks; + +public class OccurencePerfTests { - public class OccurencePerfTests + [Benchmark] + public void MultipleEventsWithUntilOccurrencesSearchingByWholeCalendar() + { + var calendar = GetFourCalendarEventsWithUntilRule(); + var searchStart = calendar.Events.First().DtStart.AddYears(-1); + var searchEnd = calendar.Events.Last().DtStart.AddYears(1); + _ = calendar.GetOccurrences(searchStart, searchEnd); + } + + [Benchmark] + public void MultipleEventsWithUntilOccurrences() { - [Benchmark] - public void MultipleEventsWithUntilOccurrencesSearchingByWholeCalendar() - { - var calendar = GetFourCalendarEventsWithUntilRule(); - var searchStart = calendar.Events.First().DtStart.AddYears(-1); - var searchEnd = calendar.Events.Last().DtStart.AddYears(1); - _ = calendar.GetOccurrences(searchStart, searchEnd); - } - - [Benchmark] - public void MultipleEventsWithUntilOccurrences() - { - var calendar = GetFourCalendarEventsWithUntilRule(); - var searchStart = calendar.Events.First().DtStart.AddYears(-1); - var searchEnd = calendar.Events.Last().DtStart.AddYears(1); - _ = calendar.Events - .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) - .ToList(); - } - - [Benchmark] - public void MultipleEventsWithUntilOccurrencesEventsAsParallel() - { - var calendar = GetFourCalendarEventsWithUntilRule(); - var searchStart = calendar.Events.First().DtStart.AddYears(-1); - var searchEnd = calendar.Events.Last().DtStart.AddYears(1).AddDays(10); - _ = calendar.Events - .AsParallel() - .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) - .ToList(); - } - - private static Calendar GetFourCalendarEventsWithUntilRule() - { - const string tzid = "America/New_York"; - const int limit = 4; - - var startTime = DateTime.Now.AddDays(-1); - var interval = TimeSpan.FromDays(1); - - var events = Enumerable - .Range(0, limit) - .Select(n => + var calendar = GetFourCalendarEventsWithUntilRule(); + var searchStart = calendar.Events.First().DtStart.AddYears(-1); + var searchEnd = calendar.Events.Last().DtStart.AddYears(1); + _ = calendar.Events + .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) + .ToList(); + } + + [Benchmark] + public void MultipleEventsWithUntilOccurrencesEventsAsParallel() + { + var calendar = GetFourCalendarEventsWithUntilRule(); + var searchStart = calendar.Events.First().DtStart.AddYears(-1); + var searchEnd = calendar.Events.Last().DtStart.AddYears(1).AddDays(10); + _ = calendar.Events + .AsParallel() + .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) + .ToList(); + } + + private static Calendar GetFourCalendarEventsWithUntilRule() + { + const string tzid = "America/New_York"; + const int limit = 4; + + var startTime = DateTime.Now.AddDays(-1); + var interval = TimeSpan.FromDays(1); + + var events = Enumerable + .Range(0, limit) + .Select(n => + { + var rrule = new RecurrencePattern(FrequencyType.Daily, 1) { - var rrule = new RecurrencePattern(FrequencyType.Daily, 1) - { - Until = startTime.AddDays(10), - }; - - var e = new CalendarEvent - { - Start = new CalDateTime(startTime.AddMinutes(5), tzid), - End = new CalDateTime(startTime.AddMinutes(10), tzid), - RecurrenceRules = new List { rrule }, - }; - startTime += interval; - return e; - }); - - var c = new Calendar(); - c.Events.AddRange(events); - return c; - } - - [Benchmark] - public void MultipleEventsWithCountOccurrencesSearchingByWholeCalendar() - { - var calendar = GetFourCalendarEventsWithCountRule(); - var searchStart = calendar.Events.First().DtStart.AddYears(-1); - var searchEnd = calendar.Events.Last().DtStart.AddYears(1); - _ = calendar.GetOccurrences(searchStart, searchEnd); - } - - [Benchmark] - public void MultipleEventsWithCountOccurrences() - { - var calendar = GetFourCalendarEventsWithCountRule(); - var searchStart = calendar.Events.First().DtStart.AddYears(-1); - var searchEnd = calendar.Events.Last().DtStart.AddYears(1); - _ = calendar.Events - .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) - .ToList(); - } - - [Benchmark] - public void MultipleEventsWithCountOccurrencesEventsAsParallel() - { - var calendar = GetFourCalendarEventsWithCountRule(); - var searchStart = calendar.Events.First().DtStart.AddYears(-1); - var searchEnd = calendar.Events.Last().DtStart.AddYears(1).AddDays(10); - _ = calendar.Events - .AsParallel() - .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) - .ToList(); - } - - private static Calendar GetFourCalendarEventsWithCountRule() - { - const string tzid = "America/New_York"; - const int limit = 4; - - var startTime = DateTime.Now.AddDays(-1); - var interval = TimeSpan.FromDays(1); - - var events = Enumerable - .Range(0, limit) - .Select(n => + Until = startTime.AddDays(10), + }; + + var e = new CalendarEvent + { + Start = new CalDateTime(startTime.AddMinutes(5), tzid), + End = new CalDateTime(startTime.AddMinutes(10), tzid), + RecurrenceRules = new List { rrule }, + }; + startTime += interval; + return e; + }); + + var c = new Calendar(); + c.Events.AddRange(events); + return c; + } + + [Benchmark] + public void MultipleEventsWithCountOccurrencesSearchingByWholeCalendar() + { + var calendar = GetFourCalendarEventsWithCountRule(); + var searchStart = calendar.Events.First().DtStart.AddYears(-1); + var searchEnd = calendar.Events.Last().DtStart.AddYears(1); + _ = calendar.GetOccurrences(searchStart, searchEnd); + } + + [Benchmark] + public void MultipleEventsWithCountOccurrences() + { + var calendar = GetFourCalendarEventsWithCountRule(); + var searchStart = calendar.Events.First().DtStart.AddYears(-1); + var searchEnd = calendar.Events.Last().DtStart.AddYears(1); + _ = calendar.Events + .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) + .ToList(); + } + + [Benchmark] + public void MultipleEventsWithCountOccurrencesEventsAsParallel() + { + var calendar = GetFourCalendarEventsWithCountRule(); + var searchStart = calendar.Events.First().DtStart.AddYears(-1); + var searchEnd = calendar.Events.Last().DtStart.AddYears(1).AddDays(10); + _ = calendar.Events + .AsParallel() + .SelectMany(e => e.GetOccurrences(searchStart, searchEnd)) + .ToList(); + } + + private static Calendar GetFourCalendarEventsWithCountRule() + { + const string tzid = "America/New_York"; + const int limit = 4; + + var startTime = DateTime.Now.AddDays(-1); + var interval = TimeSpan.FromDays(1); + + var events = Enumerable + .Range(0, limit) + .Select(n => + { + var rrule = new RecurrencePattern(FrequencyType.Daily, 1) + { + Count = 100, + }; + + var e = new CalendarEvent { - var rrule = new RecurrencePattern(FrequencyType.Daily, 1) - { - Count = 100, - }; - - var e = new CalendarEvent - { - Start = new CalDateTime(startTime.AddMinutes(5), tzid), - End = new CalDateTime(startTime.AddMinutes(10), tzid), - RecurrenceRules = new List { rrule }, - }; - startTime += interval; - return e; - }); - - var c = new Calendar(); - c.Events.AddRange(events); - return c; - } + Start = new CalDateTime(startTime.AddMinutes(5), tzid), + End = new CalDateTime(startTime.AddMinutes(10), tzid), + RecurrenceRules = new List { rrule }, + }; + startTime += interval; + return e; + }); + + var c = new Calendar(); + c.Events.AddRange(events); + return c; } -} +} \ No newline at end of file diff --git a/Ical.Net.Benchmarks/Runner.cs b/Ical.Net.Benchmarks/Runner.cs index f557dc59a..8383e2c1f 100644 --- a/Ical.Net.Benchmarks/Runner.cs +++ b/Ical.Net.Benchmarks/Runner.cs @@ -1,15 +1,20 @@ -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Running; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.IO; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Running; -namespace Ical.Net.Benchmarks +namespace Ical.Net.Benchmarks; + +public class Runner { - public class Runner + private static void Main(string[] args) { - private static void Main(string[] args) - { #if DEBUG - BenchmarkSwitcher.FromAssembly(typeof(ApplicationWorkflows).Assembly).Run(args, new DebugInProcessConfig()); + BenchmarkSwitcher.FromAssembly(typeof(ApplicationWorkflows).Assembly).Run(args, new DebugInProcessConfig()); #else #region * ApplicationWorkflows results * /* @@ -159,41 +164,40 @@ .NET SDK 8.0.403 #endregion BenchmarkRunner.Run(BenchmarkConverter.TypeToBenchmarks(typeof(ThroughputTests))); #endif - } + } - /// - /// Searches the parent directories of for - /// the first directory with name of . - /// - /// The name of the directory to search. - /// The path where the search starts. - /// The full path of the found folder. - /// - /// The does not exist, or the - /// was not found in the parent directories. - /// - /// The caller does not have the required permission. - /// The specified path, file name, or both exceed the system-defined maximum length. - public static string FindParentFolder(string directoryName, string startPath) + /// + /// Searches the parent directories of for + /// the first directory with name of . + /// + /// The name of the directory to search. + /// The path where the search starts. + /// The full path of the found folder. + /// + /// The does not exist, or the + /// was not found in the parent directories. + /// + /// The caller does not have the required permission. + /// The specified path, file name, or both exceed the system-defined maximum length. + public static string FindParentFolder(string directoryName, string startPath) + { + if (!Directory.Exists(startPath)) { - if (!Directory.Exists(startPath)) - { - throw new DirectoryNotFoundException($"Start path '{startPath}' not found."); - } - - var currentPath = startPath; - var rootReached = false; + throw new DirectoryNotFoundException($"Start path '{startPath}' not found."); + } - while (!rootReached && !Directory.Exists(Path.Combine(currentPath, directoryName))) - { - currentPath = Directory.GetParent(currentPath)?.FullName; - rootReached = currentPath == null; - currentPath ??= Directory.GetDirectoryRoot(startPath); - } + var currentPath = startPath; + var rootReached = false; - var resultPath = Path.Combine(currentPath, directoryName); - if (!Directory.Exists(resultPath)) throw new DirectoryNotFoundException($"Folder '{directoryName}' not found in parent directories."); - return resultPath; + while (!rootReached && !Directory.Exists(Path.Combine(currentPath, directoryName))) + { + currentPath = Directory.GetParent(currentPath)?.FullName; + rootReached = currentPath == null; + currentPath ??= Directory.GetDirectoryRoot(startPath); } + + var resultPath = Path.Combine(currentPath, directoryName); + if (!Directory.Exists(resultPath)) throw new DirectoryNotFoundException($"Folder '{directoryName}' not found in parent directories."); + return resultPath; } -} +} \ No newline at end of file diff --git a/Ical.Net.Benchmarks/SerializationPerfTests.cs b/Ical.Net.Benchmarks/SerializationPerfTests.cs index 62c08fb94..552be24b3 100644 --- a/Ical.Net.Benchmarks/SerializationPerfTests.cs +++ b/Ical.Net.Benchmarks/SerializationPerfTests.cs @@ -1,95 +1,98 @@ -using BenchmarkDotNet.Attributes; -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Serialization; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using BenchmarkDotNet.Attributes; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Serialization; -namespace Ical.Net.Benchmarks +namespace Ical.Net.Benchmarks; + +public class SerializationPerfTests { - public class SerializationPerfTests - { - private const string SampleEvent = """ - BEGIN:VCALENDAR - PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN - VERSION:2.0 - METHOD:PUBLISH - X-CALSTART:20090621T000000 - X-CALEND:20090622T000000 - X-WR-RELCALID:{0000002E-6380-7FD2-FED7-97EAE70D6611} - X-WR-CALNAME:Parse Error Calendar - BEGIN:VEVENT - ATTENDEE;CN=some.attendee@event.com;RSVP=TRUE:mailto:some.attendee@event.co - m - ATTENDEE;CN=event@calendardemo.net;RSVP=TRUE:mailto:event@calendardemo.net - ATTENDEE;CN="4th Floor Meeting Room";CUTYPE=RESOURCE;ROLE=NON-PARTICIPANT;R - SVP=TRUE:mailto:4th.floor.meeting.room@somewhere.com - CLASS:PUBLIC - CREATED:20090621T201527Z - DESCRIPTION:\n - DTEND;VALUE=DATE:20090622 - DTSTAMP:20090621T201612Z - DTSTART;VALUE=DATE:20090621 - LAST-MODIFIED:20090621T201618Z - LOCATION:The Exceptionally Long Named Meeting Room Whose Name Wraps Over Se - veral Lines When Exported From Leading Calendar and Office Software App - lication Microsoft Office 2007 - ORGANIZER;CN="Event Organizer":mailto:some.attendee@somewhere.com - PRIORITY:5 - SEQUENCE:0 - SUMMARY;LANGUAGE=en-gb:Example Calendar Export that Blows Up DDay.iCal - TRANSP:TRANSPARENT - UID:040000008200E00074C5B7101A82E00800000000900AD080B5F2C901000000000000000 - 010000000B29680BF9E5DC246B5EDDE228038E71F - X-ALT-DESC;FMTTYPE=text/html:\n\n\n\n\n\n\n\n\n

\n\n\n - X-MICROSOFT-CDO-BUSYSTATUS:FREE - X-MICROSOFT-CDO-IMPORTANCE:1 - X-MICROSOFT-DISALLOW-COUNTER:FALSE - X-MS-OLK-ALLOWEXTERNCHECK:TRUE - X-MS-OLK-APPTSEQTIME:20090621T201612Z - X-MS-OLK-AUTOFILLLOCATION:TRUE - X-MS-OLK-CONFTYPE:0 - BEGIN:VALARM - TRIGGER:-PT1080M - ACTION:DISPLAY - DESCRIPTION:Reminder - END:VALARM - END:VEVENT - END:VCALENDAR - """; + private const string SampleEvent = """ + BEGIN:VCALENDAR + PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN + VERSION:2.0 + METHOD:PUBLISH + X-CALSTART:20090621T000000 + X-CALEND:20090622T000000 + X-WR-RELCALID:{0000002E-6380-7FD2-FED7-97EAE70D6611} + X-WR-CALNAME:Parse Error Calendar + BEGIN:VEVENT + ATTENDEE;CN=some.attendee@event.com;RSVP=TRUE:mailto:some.attendee@event.co + m + ATTENDEE;CN=event@calendardemo.net;RSVP=TRUE:mailto:event@calendardemo.net + ATTENDEE;CN="4th Floor Meeting Room";CUTYPE=RESOURCE;ROLE=NON-PARTICIPANT;R + SVP=TRUE:mailto:4th.floor.meeting.room@somewhere.com + CLASS:PUBLIC + CREATED:20090621T201527Z + DESCRIPTION:\n + DTEND;VALUE=DATE:20090622 + DTSTAMP:20090621T201612Z + DTSTART;VALUE=DATE:20090621 + LAST-MODIFIED:20090621T201618Z + LOCATION:The Exceptionally Long Named Meeting Room Whose Name Wraps Over Se + veral Lines When Exported From Leading Calendar and Office Software App + lication Microsoft Office 2007 + ORGANIZER;CN="Event Organizer":mailto:some.attendee@somewhere.com + PRIORITY:5 + SEQUENCE:0 + SUMMARY;LANGUAGE=en-gb:Example Calendar Export that Blows Up DDay.iCal + TRANSP:TRANSPARENT + UID:040000008200E00074C5B7101A82E00800000000900AD080B5F2C901000000000000000 + 010000000B29680BF9E5DC246B5EDDE228038E71F + X-ALT-DESC;FMTTYPE=text/html:\n\n\n\n\n\n\n\n\n

\n\n\n + X-MICROSOFT-CDO-BUSYSTATUS:FREE + X-MICROSOFT-CDO-IMPORTANCE:1 + X-MICROSOFT-DISALLOW-COUNTER:FALSE + X-MS-OLK-ALLOWEXTERNCHECK:TRUE + X-MS-OLK-APPTSEQTIME:20090621T201612Z + X-MS-OLK-AUTOFILLLOCATION:TRUE + X-MS-OLK-CONFTYPE:0 + BEGIN:VALARM + TRIGGER:-PT1080M + ACTION:DISPLAY + DESCRIPTION:Reminder + END:VALARM + END:VEVENT + END:VCALENDAR + """; - [Benchmark] - public void Deserialize() => Calendar.Load(SampleEvent).Events.First(); + [Benchmark] + public void Deserialize() => Calendar.Load(SampleEvent).Events.First(); - [Benchmark] - public void BenchmarkSerializeCalendar() => new CalendarSerializer().SerializeToString(CreateSimpleCalendar()); + [Benchmark] + public void BenchmarkSerializeCalendar() => new CalendarSerializer().SerializeToString(CreateSimpleCalendar()); - private static Calendar CreateSimpleCalendar() - { - const string timeZoneId = "America/New_York"; + private static Calendar CreateSimpleCalendar() + { + const string timeZoneId = "America/New_York"; - var simpleCalendar = new Calendar(); - var calendarEvent = new CalendarEvent + var simpleCalendar = new Calendar(); + var calendarEvent = new CalendarEvent + { + Start = new CalDateTime(DateTime.Now, timeZoneId), + End = new CalDateTime(DateTime.Now + TimeSpan.FromHours(1), timeZoneId), + RecurrenceRules = new List { - Start = new CalDateTime(DateTime.Now, timeZoneId), - End = new CalDateTime(DateTime.Now + TimeSpan.FromHours(1), timeZoneId), - RecurrenceRules = new List + new RecurrencePattern(FrequencyType.Daily, 1) { - new RecurrencePattern(FrequencyType.Daily, 1) - { - Count = 100, - } + Count = 100, } - }; + } + }; - simpleCalendar.Events.Add(calendarEvent); - return simpleCalendar; - } + simpleCalendar.Events.Add(calendarEvent); + return simpleCalendar; } -} - +} \ No newline at end of file diff --git a/Ical.Net.Benchmarks/ThroughputTests.cs b/Ical.Net.Benchmarks/ThroughputTests.cs index f5a8a833f..c1d557cee 100644 --- a/Ical.Net.Benchmarks/ThroughputTests.cs +++ b/Ical.Net.Benchmarks/ThroughputTests.cs @@ -1,15 +1,20 @@ -using BenchmarkDotNet.Attributes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Linq; +using BenchmarkDotNet.Attributes; + +namespace Ical.Net.Benchmarks; -namespace Ical.Net.Benchmarks +public class ThroughputTests { - public class ThroughputTests + [Benchmark] + public void DeserializeAndComputeUntilOccurrences() { - [Benchmark] - public void DeserializeAndComputeUntilOccurrences() - { - const string e = @"BEGIN:VCALENDAR + const string e = @"BEGIN:VCALENDAR PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN VERSION:2.0 METHOD:PUBLISH @@ -61,17 +66,17 @@ rsion 08.00.0681.000"">\n\n\n\n\n\n

This is some HTML formatted text.

\n\n\n"; - - var start = DateTime.Now; - var end = start.AddHours(1); - var @event = new CalendarEvent - { - Start = new CalDateTime(start), - End = new CalDateTime(end), - Description = "This is a description", - }; - var property = new CalendarProperty("X-ALT-DESC", formatted); - @event.AddProperty(property); - var calendar = new Calendar(); - calendar.Events.Add(@event); - - var serialized = new CalendarSerializer().SerializeToString(calendar); - Assert.That(serialized.Contains("X-ALT-DESC;"), Is.True); - } - - [Test] - public void PropertySetValueMustAllowNull() + [Test] + [Ignore("Calendar properties aren't being properly serialized")] + public void PropertySerialization_Tests() + { + const string formatted = + @"FMTTYPE=text/html:\n\n\n\n\n\n\n\n\n

This is some HTML formatted text.

\n\n\n"; + + var start = DateTime.Now; + var end = start.AddHours(1); + var @event = new CalendarEvent { - var property = new CalendarProperty(); - Assert.DoesNotThrow(() => property.SetValue(null)); - } + Start = new CalDateTime(start), + End = new CalDateTime(end), + Description = "This is a description", + }; + var property = new CalendarProperty("X-ALT-DESC", formatted); + @event.AddProperty(property); + var calendar = new Calendar(); + calendar.Events.Add(@event); + + var serialized = new CalendarSerializer().SerializeToString(calendar); + Assert.That(serialized.Contains("X-ALT-DESC;"), Is.True); + } + + [Test] + public void PropertySetValueMustAllowNull() + { + var property = new CalendarProperty(); + Assert.DoesNotThrow(() => property.SetValue(null)); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/CollectionHelpersTests.cs b/Ical.Net.Tests/CollectionHelpersTests.cs index e60da6bf9..d152e8629 100644 --- a/Ical.Net.Tests/CollectionHelpersTests.cs +++ b/Ical.Net.Tests/CollectionHelpersTests.cs @@ -1,36 +1,40 @@ -using Ical.Net.DataTypes; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using Ical.Net.DataTypes; +using NUnit.Framework; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +internal class CollectionHelpersTests { - internal class CollectionHelpersTests - { - private static readonly DateTime _now = DateTime.UtcNow; - private static readonly DateTime _later = _now.AddHours(1); - private static readonly string _uid = Guid.NewGuid().ToString(); + private static readonly DateTime _now = DateTime.UtcNow; + private static readonly DateTime _later = _now.AddHours(1); + private static readonly string _uid = Guid.NewGuid().ToString(); - private static List GetSimpleRecurrenceList() - => new List { new RecurrencePattern(FrequencyType.Daily, 1) { Count = 5 } }; - private static List GetExceptionDates() - => new List { new PeriodList { new Period(new CalDateTime(_now.AddDays(1).Date)) } }; + private static List GetSimpleRecurrenceList() + => new List { new RecurrencePattern(FrequencyType.Daily, 1) { Count = 5 } }; + private static List GetExceptionDates() + => new List { new PeriodList { new Period(new CalDateTime(_now.AddDays(1).Date)) } }; - [Test] - public void ExDateTests() + [Test] + public void ExDateTests() + { + Assert.Multiple(() => { - Assert.Multiple(() => - { - Assert.That(GetExceptionDates(), Is.EqualTo(GetExceptionDates())); - Assert.That(GetExceptionDates(), Is.Not.Null); - Assert.That(GetExceptionDates(), Is.Not.EqualTo(null)); - }); + Assert.That(GetExceptionDates(), Is.EqualTo(GetExceptionDates())); + Assert.That(GetExceptionDates(), Is.Not.Null); + Assert.That(GetExceptionDates(), Is.Not.EqualTo(null)); + }); - var changedPeriod = GetExceptionDates(); - changedPeriod.First().First().StartTime = new CalDateTime(_now.AddHours(-1)); + var changedPeriod = GetExceptionDates(); + changedPeriod.First().First().StartTime = new CalDateTime(_now.AddHours(-1)); - Assert.That(changedPeriod, Is.Not.EqualTo(GetExceptionDates())); - } + Assert.That(changedPeriod, Is.Not.EqualTo(GetExceptionDates())); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/ComponentTest.cs b/Ical.Net.Tests/ComponentTest.cs index 65f4f2872..eef17a8be 100644 --- a/Ical.Net.Tests/ComponentTest.cs +++ b/Ical.Net.Tests/ComponentTest.cs @@ -1,46 +1,50 @@ +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System; using Ical.Net.CalendarComponents; using Ical.Net.DataTypes; using NUnit.Framework; -using System; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +public class ComponentTest { - public class ComponentTest + [Test, Category("Components")] + public void UniqueComponent1() + { + var iCal = new Calendar(); + var evt = iCal.Create(); + + Assert.That(evt.Uid, Is.Not.Null); + Assert.That(evt.Created, Is.Null); // We don't want this to be set automatically + Assert.That(evt.DtStamp, Is.Not.Null); + } + + [Test, Category("Components")] + public void ChangeCalDateTimeValue() { - [Test, Category("Components")] - public void UniqueComponent1() + var e = new CalendarEvent { - var iCal = new Calendar(); - var evt = iCal.Create(); + Start = new CalDateTime(2017, 11, 22, 11, 00, 01), + End = new CalDateTime(2017, 11, 22, 11, 30, 01), + }; + + var firstStartAsUtc = e.Start.AsUtc; + var firstEndAsUtc = e.End.AsUtc; + + e.Start.Value = new DateTime(2017, 11, 22, 11, 30, 01); + e.End.Value = new DateTime(2017, 11, 22, 12, 00, 01); - Assert.That(evt.Uid, Is.Not.Null); - Assert.That(evt.Created, Is.Null); // We don't want this to be set automatically - Assert.That(evt.DtStamp, Is.Not.Null); - } + var secondStartAsUtc = e.Start.AsUtc; + var secondEndAsUtc = e.End.AsUtc; - [Test, Category("Components")] - public void ChangeCalDateTimeValue() + Assert.Multiple(() => { - var e = new CalendarEvent - { - Start = new CalDateTime(2017, 11, 22, 11, 00, 01), - End = new CalDateTime(2017, 11, 22, 11, 30, 01), - }; - - var firstStartAsUtc = e.Start.AsUtc; - var firstEndAsUtc = e.End.AsUtc; - - e.Start.Value = new DateTime(2017, 11, 22, 11, 30, 01); - e.End.Value = new DateTime(2017, 11, 22, 12, 00, 01); - - var secondStartAsUtc = e.Start.AsUtc; - var secondEndAsUtc = e.End.AsUtc; - - Assert.Multiple(() => - { - Assert.That(secondStartAsUtc, Is.Not.EqualTo(firstStartAsUtc)); - Assert.That(secondEndAsUtc, Is.Not.EqualTo(firstEndAsUtc)); - }); - } + Assert.That(secondStartAsUtc, Is.Not.EqualTo(firstStartAsUtc)); + Assert.That(secondEndAsUtc, Is.Not.EqualTo(firstEndAsUtc)); + }); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/ConcurrentDeserializationTests.cs b/Ical.Net.Tests/ConcurrentDeserializationTests.cs index 442e6a7bf..e839a618a 100644 --- a/Ical.Net.Tests/ConcurrentDeserializationTests.cs +++ b/Ical.Net.Tests/ConcurrentDeserializationTests.cs @@ -1,27 +1,31 @@ -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Collections.Generic; using System.Linq; +using NUnit.Framework; + +namespace Ical.Net.Tests; -namespace Ical.Net.Tests +public class ConcurrentDeserializationTests { - public class ConcurrentDeserializationTests + [Test] + public void ConcurrentDeserialization_Test() { - [Test] - public void ConcurrentDeserialization_Test() + // https://github.com/rianjs/ical.net/issues/40 + var calendars = new List { - // https://github.com/rianjs/ical.net/issues/40 - var calendars = new List - { - IcsFiles.DailyCount2, - IcsFiles.DailyInterval2, - IcsFiles.DailyByDay1, - IcsFiles.RecurrenceDates1, - IcsFiles.DailyByHourMinute1, - }; + IcsFiles.DailyCount2, + IcsFiles.DailyInterval2, + IcsFiles.DailyByDay1, + IcsFiles.RecurrenceDates1, + IcsFiles.DailyByHourMinute1, + }; - var deserializedCalendars = calendars.AsParallel().SelectMany(CalendarCollection.Load); - var materialized = deserializedCalendars.ToList(); - Assert.That(materialized, Has.Count.EqualTo(5)); - } + var deserializedCalendars = calendars.AsParallel().SelectMany(CalendarCollection.Load); + var materialized = deserializedCalendars.ToList(); + Assert.That(materialized, Has.Count.EqualTo(5)); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/CopyComponentTests.cs b/Ical.Net.Tests/CopyComponentTests.cs index 0fbeb512c..225b3bb66 100644 --- a/Ical.Net.Tests/CopyComponentTests.cs +++ b/Ical.Net.Tests/CopyComponentTests.cs @@ -1,198 +1,202 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Serialization; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Serialization; +using NUnit.Framework; + +namespace Ical.Net.Tests; -namespace Ical.Net.Tests +/// +/// Tests for deep copying of ICal components. +/// +[TestFixture] +public class CopyComponentTests { - /// - /// Tests for deep copying of ICal components. - /// - [TestFixture] - public class CopyComponentTests + [Test, TestCaseSource(nameof(CopyCalendarTest_TestCases)), Category("Copy tests")] + public void CopyCalendarTest(string calendarString) + { + var iCal1 = Calendar.Load(calendarString); + var iCal2 = iCal1.Copy(); + SerializationTests.CompareCalendars(iCal1, iCal2); + } + + public static IEnumerable CopyCalendarTest_TestCases() + { + yield return new TestCaseData(IcsFiles.Attachment3).SetName("Attachment3"); + yield return new TestCaseData(IcsFiles.Bug2148092).SetName("Bug2148092"); + yield return new TestCaseData(IcsFiles.CaseInsensitive1).SetName("CaseInsensitive1"); + yield return new TestCaseData(IcsFiles.CaseInsensitive2).SetName("CaseInsensitive2"); + yield return new TestCaseData(IcsFiles.CaseInsensitive3).SetName("CaseInsensitive3"); + yield return new TestCaseData(IcsFiles.Categories1).SetName("Categories1"); + yield return new TestCaseData(IcsFiles.Duration1).SetName("Duration1"); + yield return new TestCaseData(IcsFiles.Encoding1).SetName("Encoding1"); + yield return new TestCaseData(IcsFiles.Event1).SetName("Event1"); + yield return new TestCaseData(IcsFiles.Event2).SetName("Event2"); + yield return new TestCaseData(IcsFiles.Event3).SetName("Event3"); + yield return new TestCaseData(IcsFiles.Event4).SetName("Event4"); + yield return new TestCaseData(IcsFiles.GeographicLocation1).SetName("GeographicLocation1"); + yield return new TestCaseData(IcsFiles.Language1).SetName("Language1"); + yield return new TestCaseData(IcsFiles.Language2).SetName("Language2"); + yield return new TestCaseData(IcsFiles.Language3).SetName("Language3"); + yield return new TestCaseData(IcsFiles.TimeZone1).SetName("TimeZone1"); + yield return new TestCaseData(IcsFiles.TimeZone2).SetName("TimeZone2"); + yield return new TestCaseData(IcsFiles.TimeZone3).SetName("TimeZone3"); + yield return new TestCaseData(IcsFiles.XProperty1).SetName("XProperty1"); + yield return new TestCaseData(IcsFiles.XProperty2).SetName("XProperty2"); + } + + private static readonly DateTime _now = DateTime.Now; + private static readonly DateTime _later = _now.AddHours(1); + + private static CalendarEvent GetSimpleEvent() => new CalendarEvent { - [Test, TestCaseSource(nameof(CopyCalendarTest_TestCases)), Category("Copy tests")] - public void CopyCalendarTest(string calendarString) + DtStart = new CalDateTime(_now), + DtEnd = new CalDateTime(_later), + Duration = TimeSpan.FromHours(1), + }; + + private static string SerializeEvent(CalendarEvent e) => new CalendarSerializer().SerializeToString(new Calendar { Events = { e } }); + + [Test] + public void CopyCalendarEventTest() + { + var orig = GetSimpleEvent(); + orig.Uid = "Hello"; + orig.Summary = "Original summary"; + orig.Resources = new[] { "A", "B" }; + orig.GeographicLocation = new GeographicLocation(48.210033, 16.363449); + orig.Transparency = TransparencyType.Opaque; + orig.Attachments.Add(new Attachment("https://original.org/")); + var copy = orig.Copy(); + + copy.Uid = "Goodbye"; + copy.Summary = "Copy summary"; + + var resourcesCopyFromOrig = new List(copy.Resources); + copy.Resources = new[] { "C", "D" }; + copy.Attachments[0].Uri = new Uri("https://copy.org/"); + const string uidPattern = "UID:"; + var serializedOrig = SerializeEvent(orig); + var serializedCopy = SerializeEvent(copy); + + Assert.Multiple(() => { - var iCal1 = Calendar.Load(calendarString); - var iCal2 = iCal1.Copy(); - SerializationTests.CompareCalendars(iCal1, iCal2); - } + // Should be a deep copy and changes only apply to the copy instance + Assert.That(copy.Uid, Is.Not.EqualTo(orig.Uid)); + Assert.That(copy.Summary, Is.Not.EqualTo(orig.Summary)); + Assert.That(copy.Attachments[0].Uri, Is.Not.EqualTo(orig.Attachments[0].Uri)); + Assert.That(copy.Resources[0], Is.Not.EqualTo(orig.Resources[0])); + + Assert.That(resourcesCopyFromOrig, Is.EquivalentTo(orig.Resources)); + Assert.That(copy.GeographicLocation, Is.EqualTo(orig.GeographicLocation)); + Assert.That(copy.Transparency, Is.EqualTo(orig.Transparency)); + + Assert.That(Regex.Matches(serializedOrig, uidPattern, RegexOptions.Compiled, TimeSpan.FromSeconds(100)), Has.Count.EqualTo(1)); + Assert.That(Regex.Matches(serializedCopy, uidPattern, RegexOptions.Compiled, TimeSpan.FromSeconds(100)), Has.Count.EqualTo(1)); + }); + } - public static IEnumerable CopyCalendarTest_TestCases() + [Test] + public void CopyFreeBusyTest() + { + var orig = new FreeBusy { - yield return new TestCaseData(IcsFiles.Attachment3).SetName("Attachment3"); - yield return new TestCaseData(IcsFiles.Bug2148092).SetName("Bug2148092"); - yield return new TestCaseData(IcsFiles.CaseInsensitive1).SetName("CaseInsensitive1"); - yield return new TestCaseData(IcsFiles.CaseInsensitive2).SetName("CaseInsensitive2"); - yield return new TestCaseData(IcsFiles.CaseInsensitive3).SetName("CaseInsensitive3"); - yield return new TestCaseData(IcsFiles.Categories1).SetName("Categories1"); - yield return new TestCaseData(IcsFiles.Duration1).SetName("Duration1"); - yield return new TestCaseData(IcsFiles.Encoding1).SetName("Encoding1"); - yield return new TestCaseData(IcsFiles.Event1).SetName("Event1"); - yield return new TestCaseData(IcsFiles.Event2).SetName("Event2"); - yield return new TestCaseData(IcsFiles.Event3).SetName("Event3"); - yield return new TestCaseData(IcsFiles.Event4).SetName("Event4"); - yield return new TestCaseData(IcsFiles.GeographicLocation1).SetName("GeographicLocation1"); - yield return new TestCaseData(IcsFiles.Language1).SetName("Language1"); - yield return new TestCaseData(IcsFiles.Language2).SetName("Language2"); - yield return new TestCaseData(IcsFiles.Language3).SetName("Language3"); - yield return new TestCaseData(IcsFiles.TimeZone1).SetName("TimeZone1"); - yield return new TestCaseData(IcsFiles.TimeZone2).SetName("TimeZone2"); - yield return new TestCaseData(IcsFiles.TimeZone3).SetName("TimeZone3"); - yield return new TestCaseData(IcsFiles.XProperty1).SetName("XProperty1"); - yield return new TestCaseData(IcsFiles.XProperty2).SetName("XProperty2"); - } - - private static readonly DateTime _now = DateTime.Now; - private static readonly DateTime _later = _now.AddHours(1); - - private static CalendarEvent GetSimpleEvent() => new CalendarEvent + Start = new CalDateTime(_now), + End = new CalDateTime(_later), + Entries = { new FreeBusyEntry { Language = "English", StartTime = new CalDateTime(2024, 10, 1), Duration = TimeSpan.FromDays(1), Status = FreeBusyStatus.Busy } } + }; + + var copy = orig.Copy(); + + Assert.Multiple(() => + { + // Start/DtStart and End/DtEnd are the same + Assert.That(copy.Start, Is.EqualTo(orig.DtStart)); + Assert.That(copy.End, Is.EqualTo(orig.DtEnd)); + Assert.That(copy.Entries[0].Language, Is.EqualTo(orig.Entries[0].Language)); + Assert.That(copy.Entries[0].StartTime, Is.EqualTo(orig.Entries[0].StartTime)); + Assert.That(copy.Entries[0].Duration, Is.EqualTo(orig.Entries[0].Duration)); + Assert.That(copy.Entries[0].Status, Is.EqualTo(orig.Entries[0].Status)); + }); + } + + [Test] + public void CopyAlarmTest() + { + var orig = new Alarm { - DtStart = new CalDateTime(_now), - DtEnd = new CalDateTime(_later), - Duration = TimeSpan.FromHours(1), + Action = AlarmAction.Display, + Trigger = new Trigger(TimeSpan.FromMinutes(15)), + Description = "Test Alarm" }; - private static string SerializeEvent(CalendarEvent e) => new CalendarSerializer().SerializeToString(new Calendar { Events = { e } }); + var copy = orig.Copy(); - [Test] - public void CopyCalendarEventTest() + Assert.Multiple(() => { - var orig = GetSimpleEvent(); - orig.Uid = "Hello"; - orig.Summary = "Original summary"; - orig.Resources = new[] { "A", "B" }; - orig.GeographicLocation = new GeographicLocation(48.210033, 16.363449); - orig.Transparency = TransparencyType.Opaque; - orig.Attachments.Add(new Attachment("https://original.org/")); - var copy = orig.Copy(); - - copy.Uid = "Goodbye"; - copy.Summary = "Copy summary"; - - var resourcesCopyFromOrig = new List(copy.Resources); - copy.Resources = new[] { "C", "D" }; - copy.Attachments[0].Uri = new Uri("https://copy.org/"); - const string uidPattern = "UID:"; - var serializedOrig = SerializeEvent(orig); - var serializedCopy = SerializeEvent(copy); - - Assert.Multiple(() => - { - // Should be a deep copy and changes only apply to the copy instance - Assert.That(copy.Uid, Is.Not.EqualTo(orig.Uid)); - Assert.That(copy.Summary, Is.Not.EqualTo(orig.Summary)); - Assert.That(copy.Attachments[0].Uri, Is.Not.EqualTo(orig.Attachments[0].Uri)); - Assert.That(copy.Resources[0], Is.Not.EqualTo(orig.Resources[0])); - - Assert.That(resourcesCopyFromOrig, Is.EquivalentTo(orig.Resources)); - Assert.That(copy.GeographicLocation, Is.EqualTo(orig.GeographicLocation)); - Assert.That(copy.Transparency, Is.EqualTo(orig.Transparency)); - - Assert.That(Regex.Matches(serializedOrig, uidPattern, RegexOptions.Compiled, TimeSpan.FromSeconds(100)), Has.Count.EqualTo(1)); - Assert.That(Regex.Matches(serializedCopy, uidPattern, RegexOptions.Compiled, TimeSpan.FromSeconds(100)), Has.Count.EqualTo(1)); - }); - } - - [Test] - public void CopyFreeBusyTest() + Assert.That(copy.Action, Is.EqualTo(orig.Action)); + Assert.That(copy.Trigger, Is.EqualTo(orig.Trigger)); + Assert.That(copy.Description, Is.EqualTo(orig.Description)); + }); + } + + [Test] + public void CopyTodoTest() + { + var orig = new Todo { - var orig = new FreeBusy - { - Start = new CalDateTime(_now), - End = new CalDateTime(_later), - Entries = { new FreeBusyEntry { Language = "English", StartTime = new CalDateTime(2024, 10, 1), Duration = TimeSpan.FromDays(1), Status = FreeBusyStatus.Busy}} - }; - - var copy = orig.Copy(); - - Assert.Multiple(() => - { - // Start/DtStart and End/DtEnd are the same - Assert.That(copy.Start, Is.EqualTo(orig.DtStart)); - Assert.That(copy.End, Is.EqualTo(orig.DtEnd)); - Assert.That(copy.Entries[0].Language, Is.EqualTo(orig.Entries[0].Language)); - Assert.That(copy.Entries[0].StartTime, Is.EqualTo(orig.Entries[0].StartTime)); - Assert.That(copy.Entries[0].Duration, Is.EqualTo(orig.Entries[0].Duration)); - Assert.That(copy.Entries[0].Status, Is.EqualTo(orig.Entries[0].Status)); - }); - } - - [Test] - public void CopyAlarmTest() + Summary = "Test Todo", + Description = "This is a test todo", + Due = new CalDateTime(DateTime.Now.AddDays(10)), + Priority = 1, + Contacts = new[] { "John", "Paul" }, + Status = "NeedsAction" + }; + + var copy = orig.Copy(); + + Assert.Multiple(() => { - var orig = new Alarm - { - Action = AlarmAction.Display, - Trigger = new Trigger(TimeSpan.FromMinutes(15)), - Description = "Test Alarm" - }; - - var copy = orig.Copy(); - - Assert.Multiple(() => - { - Assert.That(copy.Action, Is.EqualTo(orig.Action)); - Assert.That(copy.Trigger, Is.EqualTo(orig.Trigger)); - Assert.That(copy.Description, Is.EqualTo(orig.Description)); - }); - } - - [Test] - public void CopyTodoTest() + Assert.That(copy.Summary, Is.EqualTo(orig.Summary)); + Assert.That(copy.Description, Is.EqualTo(orig.Description)); + Assert.That(copy.Due, Is.EqualTo(orig.Due)); + Assert.That(copy.Priority, Is.EqualTo(orig.Priority)); + Assert.That(copy.Contacts, Is.EquivalentTo(orig.Contacts)); + Assert.That(copy.Status, Is.EqualTo(orig.Status)); + }); + } + + [Test] + public void CopyJournalTest() + { + var orig = new Journal { - var orig = new Todo - { - Summary = "Test Todo", - Description = "This is a test todo", - Due = new CalDateTime(DateTime.Now.AddDays(10)), - Priority = 1, - Contacts = new[] { "John", "Paul" }, - Status = "NeedsAction" - }; - - var copy = orig.Copy(); - - Assert.Multiple(() => - { - Assert.That(copy.Summary, Is.EqualTo(orig.Summary)); - Assert.That(copy.Description, Is.EqualTo(orig.Description)); - Assert.That(copy.Due, Is.EqualTo(orig.Due)); - Assert.That(copy.Priority, Is.EqualTo(orig.Priority)); - Assert.That(copy.Contacts, Is.EquivalentTo(orig.Contacts)); - Assert.That(copy.Status, Is.EqualTo(orig.Status)); - }); - } - - [Test] - public void CopyJournalTest() + Summary = "Test Journal", + Description = "This is a test journal", + DtStart = new CalDateTime(DateTime.Now), + Categories = new List { "Category1", "Category2" }, + Priority = 1, + Status = "Draft" + }; + + var copy = orig.Copy(); + + Assert.Multiple(() => { - var orig = new Journal - { - Summary = "Test Journal", - Description = "This is a test journal", - DtStart = new CalDateTime(DateTime.Now), - Categories = new List { "Category1", "Category2" }, - Priority = 1, - Status = "Draft" - }; - - var copy = orig.Copy(); - - Assert.Multiple(() => - { - Assert.That(copy.Summary, Is.EqualTo(orig.Summary)); - Assert.That(copy.Description, Is.EqualTo(orig.Description)); - Assert.That(copy.DtStart, Is.EqualTo(orig.DtStart)); - Assert.That(copy.Categories, Is.EquivalentTo(orig.Categories)); - Assert.That(copy.Priority, Is.EqualTo(orig.Priority)); - Assert.That(copy.Status, Is.EqualTo(orig.Status)); - }); - } + Assert.That(copy.Summary, Is.EqualTo(orig.Summary)); + Assert.That(copy.Description, Is.EqualTo(orig.Description)); + Assert.That(copy.DtStart, Is.EqualTo(orig.DtStart)); + Assert.That(copy.Categories, Is.EquivalentTo(orig.Categories)); + Assert.That(copy.Priority, Is.EqualTo(orig.Priority)); + Assert.That(copy.Status, Is.EqualTo(orig.Status)); + }); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/DataTypeTest.cs b/Ical.Net.Tests/DataTypeTest.cs index d0190f8f7..c24e2f4b0 100644 --- a/Ical.Net.Tests/DataTypeTest.cs +++ b/Ical.Net.Tests/DataTypeTest.cs @@ -1,22 +1,26 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using Ical.Net.DataTypes; using NUnit.Framework; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +[TestFixture] +public class DataTypeTest { - [TestFixture] - public class DataTypeTest + [Test, Category("DataType")] + public void OrganizerConstructorMustAcceptNull() { - [Test, Category("DataType")] - public void OrganizerConstructorMustAcceptNull() - { - Assert.DoesNotThrow(() => { var o = new Organizer(null); }); - } + Assert.DoesNotThrow(() => { var o = new Organizer(null); }); + } - [Test, Category("DataType")] - public void AttachmentConstructorMustAcceptNull() - { - Assert.DoesNotThrow(() => { var o = new Attachment((byte[])null); }); - Assert.DoesNotThrow(() => { var o = new Attachment((string)null); }); - } + [Test, Category("DataType")] + public void AttachmentConstructorMustAcceptNull() + { + Assert.DoesNotThrow(() => { var o = new Attachment((byte[]) null); }); + Assert.DoesNotThrow(() => { var o = new Attachment((string) null); }); } } \ No newline at end of file diff --git a/Ical.Net.Tests/DateTimeSerializerTests.cs b/Ical.Net.Tests/DateTimeSerializerTests.cs index 445c05a69..888eea6a5 100644 --- a/Ical.Net.Tests/DateTimeSerializerTests.cs +++ b/Ical.Net.Tests/DateTimeSerializerTests.cs @@ -1,23 +1,27 @@ +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System; using Ical.Net.DataTypes; using Ical.Net.Serialization.DataTypes; using NUnit.Framework; -using System; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +[TestFixture] +public class DateTimeSerializerTests { - [TestFixture] - public class DateTimeSerializerTests + [Test, Category("Deserialization")] + public void TZIDPropertyShouldBeAppliedForLocalTimezones() { - [Test, Category("Deserialization")] - public void TZIDPropertyShouldBeAppliedForLocalTimezones() - { - // see http://www.ietf.org/rfc/rfc2445.txt p.36 - var result = new DateTimeSerializer() - .SerializeToString( + // see http://www.ietf.org/rfc/rfc2445.txt p.36 + var result = new DateTimeSerializer() + .SerializeToString( new CalDateTime(new DateTime(1997, 7, 14, 13, 30, 0, DateTimeKind.Local), "US-Eastern")); - // TZID is applied elsewhere - just make sure this doesn't have 'Z' appended. - Assert.That(result, Is.EqualTo("19970714T133000")); - } + // TZID is applied elsewhere - just make sure this doesn't have 'Z' appended. + Assert.That(result, Is.EqualTo("19970714T133000")); } } \ No newline at end of file diff --git a/Ical.Net.Tests/DeserializationTests.cs b/Ical.Net.Tests/DeserializationTests.cs index e32d6fb02..57ca53aa0 100644 --- a/Ical.Net.Tests/DeserializationTests.cs +++ b/Ical.Net.Tests/DeserializationTests.cs @@ -1,268 +1,273 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Serialization; -using Ical.Net.Serialization.DataTypes; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Text; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Serialization; +using Ical.Net.Serialization.DataTypes; +using NUnit.Framework; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +[TestFixture, Category("Deserialization")] +public class DeserializationTests { - [TestFixture, Category("Deserialization")] - public class DeserializationTests + [Test] + public void Attendee1() { - [Test] - public void Attendee1() + var iCal = Calendar.Load(IcsFiles.Attendee1); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); + + var evt = iCal.Events.First(); + // Ensure there are 2 attendees + Assert.That(evt.Attendees, Has.Count.EqualTo(2)); + + var attendee1 = evt.Attendees[0]; + var attendee2 = evt.Attendees[1]; + + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.Attendee1); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); + // Values + Assert.That(attendee1.Value, Is.EqualTo(new Uri("mailto:joecool@example.com"))); + Assert.That(attendee2.Value, Is.EqualTo(new Uri("mailto:ildoit@example.com"))); + + // MEMBERS + Assert.That(attendee1.Members, Has.Count.EqualTo(1)); + Assert.That(attendee2.Members.Count, Is.EqualTo(0)); + }); + Assert.Multiple(() => + { + Assert.That(attendee1.Members[0], Is.EqualTo(new Uri("mailto:DEV-GROUP@example.com").ToString())); + + // DELEGATED-FROM + Assert.That(attendee1.DelegatedFrom.Count, Is.EqualTo(0)); + Assert.That(attendee2.DelegatedFrom, Has.Count.EqualTo(1)); + Assert.That(attendee2.DelegatedFrom[0], Is.EqualTo(new Uri("mailto:immud@example.com").ToString())); + }); + Assert.Multiple(() => + { + // DELEGATED-TO + Assert.That(attendee1.DelegatedTo.Count, Is.EqualTo(0)); + Assert.That(attendee2.DelegatedTo.Count, Is.EqualTo(0)); + }); + } - var evt = iCal.Events.First(); - // Ensure there are 2 attendees - Assert.That(evt.Attendees, Has.Count.EqualTo(2)); + /// + /// Tests that multiple parameters of the + /// same name are correctly aggregated into + /// a single list. + /// + [Test] + public void Attendee2() + { + var iCal = Calendar.Load(IcsFiles.Attendee2); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); - var attendee1 = evt.Attendees[0]; - var attendee2 = evt.Attendees[1]; + var evt = iCal.Events.First(); + // Ensure there is 1 attendee + Assert.That(evt.Attendees, Has.Count.EqualTo(1)); - Assert.Multiple(() => - { - // Values - Assert.That(attendee1.Value, Is.EqualTo(new Uri("mailto:joecool@example.com"))); - Assert.That(attendee2.Value, Is.EqualTo(new Uri("mailto:ildoit@example.com"))); + var attendee1 = evt.Attendees; - // MEMBERS - Assert.That(attendee1.Members, Has.Count.EqualTo(1)); - Assert.That(attendee2.Members.Count, Is.EqualTo(0)); - }); - Assert.Multiple(() => - { - Assert.That(attendee1.Members[0], Is.EqualTo(new Uri("mailto:DEV-GROUP@example.com").ToString())); + // Values + Assert.That(attendee1[0].Value, Is.EqualTo(new Uri("mailto:joecool@example.com"))); - // DELEGATED-FROM - Assert.That(attendee1.DelegatedFrom.Count, Is.EqualTo(0)); - Assert.That(attendee2.DelegatedFrom, Has.Count.EqualTo(1)); - Assert.That(attendee2.DelegatedFrom[0], Is.EqualTo(new Uri("mailto:immud@example.com").ToString())); - }); - Assert.Multiple(() => - { - // DELEGATED-TO - Assert.That(attendee1.DelegatedTo.Count, Is.EqualTo(0)); - Assert.That(attendee2.DelegatedTo.Count, Is.EqualTo(0)); - }); - } + Assert.Multiple(() => + { + // MEMBERS + Assert.That(attendee1[0].Members, Has.Count.EqualTo(3)); + Assert.That(attendee1[0].Members[0], Is.EqualTo(new Uri("mailto:DEV-GROUP@example.com").ToString())); + Assert.That(attendee1[0].Members[1], Is.EqualTo(new Uri("mailto:ANOTHER-GROUP@example.com").ToString())); + Assert.That(attendee1[0].Members[2], Is.EqualTo(new Uri("mailto:THIRD-GROUP@example.com").ToString())); + }); + } - /// - /// Tests that multiple parameters of the - /// same name are correctly aggregated into - /// a single list. - /// - [Test] - public void Attendee2() + /// + /// Tests that Lotus Notes-style properties are properly handled. + /// https://sourceforge.net/tracker/?func=detail&aid=2033495&group_id=187422&atid=921236 + /// Sourceforge bug #2033495 + /// + [Test] + public void Bug2033495() + { + var iCal = Calendar.Load(IcsFiles.Bug2033495); + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.Attendee2); Assert.That(iCal.Events, Has.Count.EqualTo(1)); + Assert.That(iCal.Properties["X-LOTUS-CHILD_UID"].Value, Is.EqualTo("XXX")); + }); + } - var evt = iCal.Events.First(); - // Ensure there is 1 attendee - Assert.That(evt.Attendees, Has.Count.EqualTo(1)); - - var attendee1 = evt.Attendees; - - // Values - Assert.That(attendee1[0].Value, Is.EqualTo(new Uri("mailto:joecool@example.com"))); + /// + /// Tests bug #2938007 - involving the HasTime property in IDateTime values. + /// See https://sourceforge.net/tracker/?func=detail&aid=2938007&group_id=187422&atid=921236 + /// + [Test] + public void Bug2938007() + { + var iCal = Calendar.Load(IcsFiles.Bug2938007); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); - Assert.Multiple(() => - { - // MEMBERS - Assert.That(attendee1[0].Members, Has.Count.EqualTo(3)); - Assert.That(attendee1[0].Members[0], Is.EqualTo(new Uri("mailto:DEV-GROUP@example.com").ToString())); - Assert.That(attendee1[0].Members[1], Is.EqualTo(new Uri("mailto:ANOTHER-GROUP@example.com").ToString())); - Assert.That(attendee1[0].Members[2], Is.EqualTo(new Uri("mailto:THIRD-GROUP@example.com").ToString())); - }); - } + var evt = iCal.Events.First(); + Assert.Multiple(() => + { + Assert.That(evt.Start.HasTime, Is.EqualTo(true)); + Assert.That(evt.End.HasTime, Is.EqualTo(true)); + }); - /// - /// Tests that Lotus Notes-style properties are properly handled. - /// https://sourceforge.net/tracker/?func=detail&aid=2033495&group_id=187422&atid=921236 - /// Sourceforge bug #2033495 - /// - [Test] - public void Bug2033495() + foreach (var o in evt.GetOccurrences(new CalDateTime(2010, 1, 17, 0, 0, 0), new CalDateTime(2010, 2, 1, 0, 0, 0))) { - var iCal = Calendar.Load(IcsFiles.Bug2033495); Assert.Multiple(() => { - Assert.That(iCal.Events, Has.Count.EqualTo(1)); - Assert.That(iCal.Properties["X-LOTUS-CHILD_UID"].Value, Is.EqualTo("XXX")); + Assert.That(o.Period.StartTime.HasTime, Is.EqualTo(true)); + Assert.That(o.Period.EndTime.HasTime, Is.EqualTo(true)); }); } + } - /// - /// Tests bug #2938007 - involving the HasTime property in IDateTime values. - /// See https://sourceforge.net/tracker/?func=detail&aid=2938007&group_id=187422&atid=921236 - /// - [Test] - public void Bug2938007() - { - var iCal = Calendar.Load(IcsFiles.Bug2938007); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); - - var evt = iCal.Events.First(); - Assert.Multiple(() => - { - Assert.That(evt.Start.HasTime, Is.EqualTo(true)); - Assert.That(evt.End.HasTime, Is.EqualTo(true)); - }); + /// + /// Tests bug #3177278 - Serialize closes stream + /// See https://sourceforge.net/tracker/?func=detail&aid=3177278&group_id=187422&atid=921236 + /// + [Test] + public void Bug3177278() + { + var calendar = new Calendar(); + var serializer = new CalendarSerializer(); - foreach (var o in evt.GetOccurrences(new CalDateTime(2010, 1, 17, 0, 0, 0), new CalDateTime(2010, 2, 1, 0, 0, 0))) - { - Assert.Multiple(() => - { - Assert.That(o.Period.StartTime.HasTime, Is.EqualTo(true)); - Assert.That(o.Period.EndTime.HasTime, Is.EqualTo(true)); - }); - } - } + var ms = new MemoryStream(); + serializer.Serialize(calendar, ms, Encoding.UTF8); - /// - /// Tests bug #3177278 - Serialize closes stream - /// See https://sourceforge.net/tracker/?func=detail&aid=3177278&group_id=187422&atid=921236 - /// - [Test] - public void Bug3177278() - { - var calendar = new Calendar(); - var serializer = new CalendarSerializer(); + Assert.That(ms.CanWrite, Is.True); + } - var ms = new MemoryStream(); - serializer.Serialize(calendar, ms, Encoding.UTF8); + /// + /// Tests that a mixed-case VERSION property is loaded properly + /// + [Test] + public void CaseInsensitive4() + { + var iCal = Calendar.Load(IcsFiles.CaseInsensitive4); + Assert.That(iCal.Version, Is.EqualTo("2.5")); + } - Assert.That(ms.CanWrite, Is.True); - } + [Test] + public void Categories1_2() + { + var iCal = Calendar.Load(IcsFiles.Categories1); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); - /// - /// Tests that a mixed-case VERSION property is loaded properly - /// - [Test] - public void CaseInsensitive4() + var items = new List(); + items.AddRange(new[] { - var iCal = Calendar.Load(IcsFiles.CaseInsensitive4); - Assert.That(iCal.Version, Is.EqualTo("2.5")); - } + "One", "Two", "Three", + "Four", "Five", "Six", + "Seven", "A string of text with nothing less than a comma, semicolon; and a newline\n." + }); - [Test] - public void Categories1_2() + var found = new Dictionary(); + foreach (var s in evt.Categories.Where(s => items.Contains(s))) { - var iCal = Calendar.Load(IcsFiles.Categories1); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); - - var items = new List(); - items.AddRange(new[] - { - "One", "Two", "Three", - "Four", "Five", "Six", - "Seven", "A string of text with nothing less than a comma, semicolon; and a newline\n." - }); - - var found = new Dictionary(); - foreach (var s in evt.Categories.Where(s => items.Contains(s))) - { - found[s] = true; - } - - foreach (string item in items) - { - Assert.That(found.ContainsKey(item), Is.True, "Event should contain CATEGORY '" + item + "', but it was not found."); - } + found[s] = true; } - [Test] - public void EmptyLines1() + foreach (string item in items) { - var iCal = Calendar.Load(IcsFiles.EmptyLines1); - Assert.That(iCal.Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); + Assert.That(found.ContainsKey(item), Is.True, "Event should contain CATEGORY '" + item + "', but it was not found."); } + } - [Test] - public void EmptyLines2() - { - var calendars = CalendarCollection.Load(IcsFiles.EmptyLines2); - Assert.That(calendars, Has.Count.EqualTo(2)); - Assert.Multiple(() => - { - Assert.That(calendars[0].Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); - Assert.That(calendars[1].Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); - }); - } + [Test] + public void EmptyLines1() + { + var iCal = Calendar.Load(IcsFiles.EmptyLines1); + Assert.That(iCal.Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); + } - /// - /// Verifies that blank lines between components are allowed - /// (as occurs with some applications/parsers - i.e. KOrganizer) - /// - [Test] - public void EmptyLines3() + [Test] + public void EmptyLines2() + { + var calendars = CalendarCollection.Load(IcsFiles.EmptyLines2); + Assert.That(calendars, Has.Count.EqualTo(2)); + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.EmptyLines3); - Assert.That(iCal.Todos, Has.Count.EqualTo(1), "iCalendar should have 1 todo"); - } + Assert.That(calendars[0].Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); + Assert.That(calendars[1].Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); + }); + } - /// - /// Similar to PARSE4 and PARSE5 tests. - /// - [Test] - public void EmptyLines4() - { - var iCal = Calendar.Load(IcsFiles.EmptyLines4); - Assert.That(iCal.Events, Has.Count.EqualTo(28)); - } + /// + /// Verifies that blank lines between components are allowed + /// (as occurs with some applications/parsers - i.e. KOrganizer) + /// + [Test] + public void EmptyLines3() + { + var iCal = Calendar.Load(IcsFiles.EmptyLines3); + Assert.That(iCal.Todos, Has.Count.EqualTo(1), "iCalendar should have 1 todo"); + } - [Test] - public void Encoding2() - { - var iCal = Calendar.Load(IcsFiles.Encoding2); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); - - Assert.That( - evt.Attachments[0].ToString(), - Is.EqualTo("This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large.\r\n" + - "This is a test to try out base64 encoding without being too large."), - "Attached value does not match."); - } + /// + /// Similar to PARSE4 and PARSE5 tests. + /// + [Test] + public void EmptyLines4() + { + var iCal = Calendar.Load(IcsFiles.EmptyLines4); + Assert.That(iCal.Events, Has.Count.EqualTo(28)); + } - [Test] - public void Encoding3() - { - var iCal = Calendar.Load(IcsFiles.Encoding3); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); + [Test] + public void Encoding2() + { + var iCal = Calendar.Load(IcsFiles.Encoding2); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); + + Assert.That( + evt.Attachments[0].ToString(), + Is.EqualTo("This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large."), + "Attached value does not match."); + } - Assert.Multiple(() => - { - Assert.That(evt.Uid, Is.EqualTo("uuid1153170430406"), "UID should be 'uuid1153170430406'; it is " + evt.Uid); - Assert.That(evt.Sequence, Is.EqualTo(1), "SEQUENCE should be 1; it is " + evt.Sequence); - }); - } + [Test] + public void Encoding3() + { + var iCal = Calendar.Load(IcsFiles.Encoding3); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); - [Test] - public void Event8() + Assert.Multiple(() => { - var sr = @"BEGIN:VCALENDAR + Assert.That(evt.Uid, Is.EqualTo("uuid1153170430406"), "UID should be 'uuid1153170430406'; it is " + evt.Uid); + Assert.That(evt.Sequence, Is.EqualTo(1), "SEQUENCE should be 1; it is " + evt.Sequence); + }); + } + + [Test] + public void Event8() + { + var sr = @"BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Computer\, Inc//iCal 1.0//EN CALSCALE:GREGORIAN @@ -292,260 +297,259 @@ public void Event8() END:VEVENT END:VCALENDAR "; - var iCal = Calendar.Load(sr); - Assert.That(iCal.Events.Count == 2, Is.True, "There should be 2 events in the parsed calendar"); - Assert.That(iCal.Events["fd940618-45e2-4d19-b118-37fd7a8e3906"], Is.Not.Null, "Event fd940618-45e2-4d19-b118-37fd7a8e3906 should exist in the calendar"); - Assert.That(iCal.Events["ebfbd3e3-cc1e-4a64-98eb-ced2598b3908"], Is.Not.Null, "Event ebfbd3e3-cc1e-4a64-98eb-ced2598b3908 should exist in the calendar"); - } + var iCal = Calendar.Load(sr); + Assert.That(iCal.Events.Count == 2, Is.True, "There should be 2 events in the parsed calendar"); + Assert.That(iCal.Events["fd940618-45e2-4d19-b118-37fd7a8e3906"], Is.Not.Null, "Event fd940618-45e2-4d19-b118-37fd7a8e3906 should exist in the calendar"); + Assert.That(iCal.Events["ebfbd3e3-cc1e-4a64-98eb-ced2598b3908"], Is.Not.Null, "Event ebfbd3e3-cc1e-4a64-98eb-ced2598b3908 should exist in the calendar"); + } + + [Test] + public void GeographicLocation1_2() + { + var iCal = Calendar.Load(IcsFiles.GeographicLocation1); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); - [Test] - public void GeographicLocation1_2() + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.GeographicLocation1); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); + Assert.That(evt.GeographicLocation.Latitude, Is.EqualTo(37.386013), "Latitude should be 37.386013; it is not."); + Assert.That(evt.GeographicLocation.Longitude, Is.EqualTo(-122.082932), "Longitude should be -122.082932; it is not."); + }); + } - Assert.Multiple(() => - { - Assert.That(evt.GeographicLocation.Latitude, Is.EqualTo(37.386013), "Latitude should be 37.386013; it is not."); - Assert.That(evt.GeographicLocation.Longitude, Is.EqualTo(-122.082932), "Longitude should be -122.082932; it is not."); - }); - } + [Test] + public void Google1() + { + var tzId = "Europe/Berlin"; + var iCal = Calendar.Load(IcsFiles.Google1); + var evt = iCal.Events["594oeajmftl3r9qlkb476rpr3c@google.com"]; + Assert.That(evt, Is.Not.Null); - [Test] - public void Google1() - { - var tzId = "Europe/Berlin"; - var iCal = Calendar.Load(IcsFiles.Google1); - var evt = iCal.Events["594oeajmftl3r9qlkb476rpr3c@google.com"]; - Assert.That(evt, Is.Not.Null); + IDateTime dtStart = new CalDateTime(2006, 12, 18, tzId); + IDateTime dtEnd = new CalDateTime(2006, 12, 23, tzId); + var occurrences = iCal.GetOccurrences(dtStart, dtEnd).OrderBy(o => o.Period.StartTime).ToList(); - IDateTime dtStart = new CalDateTime(2006, 12, 18, tzId); - IDateTime dtEnd = new CalDateTime(2006, 12, 23, tzId); - var occurrences = iCal.GetOccurrences(dtStart, dtEnd).OrderBy(o => o.Period.StartTime).ToList(); + var dateTimes = new[] + { + new CalDateTime(2006, 12, 18, 7, 0, 0, tzId), + new CalDateTime(2006, 12, 19, 7, 0, 0, tzId), + new CalDateTime(2006, 12, 20, 7, 0, 0, tzId), + new CalDateTime(2006, 12, 21, 7, 0, 0, tzId), + new CalDateTime(2006, 12, 22, 7, 0, 0, tzId) + }; - var dateTimes = new[] - { - new CalDateTime(2006, 12, 18, 7, 0, 0, tzId), - new CalDateTime(2006, 12, 19, 7, 0, 0, tzId), - new CalDateTime(2006, 12, 20, 7, 0, 0, tzId), - new CalDateTime(2006, 12, 21, 7, 0, 0, tzId), - new CalDateTime(2006, 12, 22, 7, 0, 0, tzId) - }; + for (var i = 0; i < dateTimes.Length; i++) + Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dateTimes[i]), "Event should occur at " + dateTimes[i]); - for (var i = 0; i < dateTimes.Length; i++) - Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dateTimes[i]), "Event should occur at " + dateTimes[i]); + Assert.That(occurrences, Has.Count.EqualTo(dateTimes.Length), "There should be exactly " + dateTimes.Length + " occurrences; there were " + occurrences.Count); + } - Assert.That(occurrences, Has.Count.EqualTo(dateTimes.Length), "There should be exactly " + dateTimes.Length + " occurrences; there were " + occurrences.Count); - } + /// + /// Tests that valid RDATE properties are parsed correctly. + /// + [Test] + public void RecurrenceDates1() + { + var iCal = Calendar.Load(IcsFiles.RecurrenceDates1); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); + Assert.That(iCal.Events.First().RecurrenceDates, Has.Count.EqualTo(3)); - /// - /// Tests that valid RDATE properties are parsed correctly. - /// - [Test] - public void RecurrenceDates1() + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.RecurrenceDates1); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); - Assert.That(iCal.Events.First().RecurrenceDates, Has.Count.EqualTo(3)); + Assert.That(iCal.Events.First().RecurrenceDates[0][0].StartTime, Is.EqualTo((CalDateTime) new DateTime(1997, 7, 14, 12, 30, 0, DateTimeKind.Utc))); + Assert.That(iCal.Events.First().RecurrenceDates[1][0].StartTime, Is.EqualTo((CalDateTime) new DateTime(1996, 4, 3, 2, 0, 0, DateTimeKind.Utc))); + Assert.That(iCal.Events.First().RecurrenceDates[1][0].EndTime, Is.EqualTo((CalDateTime) new DateTime(1996, 4, 3, 4, 0, 0, DateTimeKind.Utc))); + Assert.That(iCal.Events.First().RecurrenceDates[2][0].StartTime, Is.EqualTo(new CalDateTime(1997, 1, 1))); + Assert.That(iCal.Events.First().RecurrenceDates[2][1].StartTime, Is.EqualTo(new CalDateTime(1997, 1, 20))); + Assert.That(iCal.Events.First().RecurrenceDates[2][2].StartTime, Is.EqualTo(new CalDateTime(1997, 2, 17))); + Assert.That(iCal.Events.First().RecurrenceDates[2][3].StartTime, Is.EqualTo(new CalDateTime(1997, 4, 21))); + Assert.That(iCal.Events.First().RecurrenceDates[2][4].StartTime, Is.EqualTo(new CalDateTime(1997, 5, 26))); + Assert.That(iCal.Events.First().RecurrenceDates[2][5].StartTime, Is.EqualTo(new CalDateTime(1997, 7, 4))); + Assert.That(iCal.Events.First().RecurrenceDates[2][6].StartTime, Is.EqualTo(new CalDateTime(1997, 9, 1))); + Assert.That(iCal.Events.First().RecurrenceDates[2][7].StartTime, Is.EqualTo(new CalDateTime(1997, 10, 14))); + Assert.That(iCal.Events.First().RecurrenceDates[2][8].StartTime, Is.EqualTo(new CalDateTime(1997, 11, 28))); + Assert.That(iCal.Events.First().RecurrenceDates[2][9].StartTime, Is.EqualTo(new CalDateTime(1997, 11, 29))); + Assert.That(iCal.Events.First().RecurrenceDates[2][10].StartTime, Is.EqualTo(new CalDateTime(1997, 12, 25))); + }); + } - Assert.Multiple(() => - { - Assert.That(iCal.Events.First().RecurrenceDates[0][0].StartTime, Is.EqualTo((CalDateTime)new DateTime(1997, 7, 14, 12, 30, 0, DateTimeKind.Utc))); - Assert.That(iCal.Events.First().RecurrenceDates[1][0].StartTime, Is.EqualTo((CalDateTime)new DateTime(1996, 4, 3, 2, 0, 0, DateTimeKind.Utc))); - Assert.That(iCal.Events.First().RecurrenceDates[1][0].EndTime, Is.EqualTo((CalDateTime)new DateTime(1996, 4, 3, 4, 0, 0, DateTimeKind.Utc))); - Assert.That(iCal.Events.First().RecurrenceDates[2][0].StartTime, Is.EqualTo(new CalDateTime(1997, 1, 1))); - Assert.That(iCal.Events.First().RecurrenceDates[2][1].StartTime, Is.EqualTo(new CalDateTime(1997, 1, 20))); - Assert.That(iCal.Events.First().RecurrenceDates[2][2].StartTime, Is.EqualTo(new CalDateTime(1997, 2, 17))); - Assert.That(iCal.Events.First().RecurrenceDates[2][3].StartTime, Is.EqualTo(new CalDateTime(1997, 4, 21))); - Assert.That(iCal.Events.First().RecurrenceDates[2][4].StartTime, Is.EqualTo(new CalDateTime(1997, 5, 26))); - Assert.That(iCal.Events.First().RecurrenceDates[2][5].StartTime, Is.EqualTo(new CalDateTime(1997, 7, 4))); - Assert.That(iCal.Events.First().RecurrenceDates[2][6].StartTime, Is.EqualTo(new CalDateTime(1997, 9, 1))); - Assert.That(iCal.Events.First().RecurrenceDates[2][7].StartTime, Is.EqualTo(new CalDateTime(1997, 10, 14))); - Assert.That(iCal.Events.First().RecurrenceDates[2][8].StartTime, Is.EqualTo(new CalDateTime(1997, 11, 28))); - Assert.That(iCal.Events.First().RecurrenceDates[2][9].StartTime, Is.EqualTo(new CalDateTime(1997, 11, 29))); - Assert.That(iCal.Events.First().RecurrenceDates[2][10].StartTime, Is.EqualTo(new CalDateTime(1997, 12, 25))); - }); - } + /// + /// Tests that valid REQUEST-STATUS properties are parsed correctly. + /// + [Test] + public void RequestStatus1() + { + var iCal = Calendar.Load(IcsFiles.RequestStatus1); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); + Assert.That(iCal.Events.First().RequestStatuses, Has.Count.EqualTo(4)); - /// - /// Tests that valid REQUEST-STATUS properties are parsed correctly. - /// - [Test] - public void RequestStatus1() + var rs = iCal.Events.First().RequestStatuses[0]; + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.RequestStatus1); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); - Assert.That(iCal.Events.First().RequestStatuses, Has.Count.EqualTo(4)); + Assert.That(rs.StatusCode.Primary, Is.EqualTo(2)); + Assert.That(rs.StatusCode.Secondary, Is.EqualTo(0)); + Assert.That(rs.Description, Is.EqualTo("Success")); + }); + Assert.That(rs.ExtraData, Is.Null); + + rs = iCal.Events.First().RequestStatuses[1]; + Assert.Multiple(() => + { + Assert.That(rs.StatusCode.Primary, Is.EqualTo(3)); + Assert.That(rs.StatusCode.Secondary, Is.EqualTo(1)); + Assert.That(rs.Description, Is.EqualTo("Invalid property value")); + Assert.That(rs.ExtraData, Is.EqualTo("DTSTART:96-Apr-01")); + }); + + rs = iCal.Events.First().RequestStatuses[2]; + Assert.Multiple(() => + { + Assert.That(rs.StatusCode.Primary, Is.EqualTo(2)); + Assert.That(rs.StatusCode.Secondary, Is.EqualTo(8)); + Assert.That(rs.Description, Is.EqualTo(" Success, repeating event ignored. Scheduled as a single event.")); + Assert.That(rs.ExtraData, Is.EqualTo("RRULE:FREQ=WEEKLY;INTERVAL=2")); + }); + + rs = iCal.Events.First().RequestStatuses[3]; + Assert.Multiple(() => + { + Assert.That(rs.StatusCode.Primary, Is.EqualTo(4)); + Assert.That(rs.StatusCode.Secondary, Is.EqualTo(1)); + Assert.That(rs.Description, Is.EqualTo("Event conflict. Date/time is busy.")); + }); + Assert.That(rs.ExtraData, Is.Null); + } - var rs = iCal.Events.First().RequestStatuses[0]; - Assert.Multiple(() => - { - Assert.That(rs.StatusCode.Primary, Is.EqualTo(2)); - Assert.That(rs.StatusCode.Secondary, Is.EqualTo(0)); - Assert.That(rs.Description, Is.EqualTo("Success")); - }); - Assert.That(rs.ExtraData, Is.Null); + /// + /// Tests that string escaping works with Text elements. + /// + [Test] + public void String2() + { + var serializer = new StringSerializer(); + var value = @"test\with\;characters"; + var unescaped = (string) serializer.Deserialize(new StringReader(value)); - rs = iCal.Events.First().RequestStatuses[1]; - Assert.Multiple(() => - { - Assert.That(rs.StatusCode.Primary, Is.EqualTo(3)); - Assert.That(rs.StatusCode.Secondary, Is.EqualTo(1)); - Assert.That(rs.Description, Is.EqualTo("Invalid property value")); - Assert.That(rs.ExtraData, Is.EqualTo("DTSTART:96-Apr-01")); - }); + Assert.That(unescaped, Is.EqualTo(@"test\with;characters"), "String unescaping was incorrect."); - rs = iCal.Events.First().RequestStatuses[2]; - Assert.Multiple(() => - { - Assert.That(rs.StatusCode.Primary, Is.EqualTo(2)); - Assert.That(rs.StatusCode.Secondary, Is.EqualTo(8)); - Assert.That(rs.Description, Is.EqualTo(" Success, repeating event ignored. Scheduled as a single event.")); - Assert.That(rs.ExtraData, Is.EqualTo("RRULE:FREQ=WEEKLY;INTERVAL=2")); - }); + value = @"C:\Path\To\My\New\Information"; + unescaped = (string) serializer.Deserialize(new StringReader(value)); + Assert.That(unescaped, Is.EqualTo("C:\\Path\\To\\My\new\\Information"), "String unescaping was incorrect."); - rs = iCal.Events.First().RequestStatuses[3]; - Assert.Multiple(() => - { - Assert.That(rs.StatusCode.Primary, Is.EqualTo(4)); - Assert.That(rs.StatusCode.Secondary, Is.EqualTo(1)); - Assert.That(rs.Description, Is.EqualTo("Event conflict. Date/time is busy.")); - }); - Assert.That(rs.ExtraData, Is.Null); - } - - /// - /// Tests that string escaping works with Text elements. - /// - [Test] - public void String2() - { - var serializer = new StringSerializer(); - var value = @"test\with\;characters"; - var unescaped = (string)serializer.Deserialize(new StringReader(value)); + value = @"\""This\r\nis\Na\, test\""\;\\;,"; + unescaped = (string) serializer.Deserialize(new StringReader(value)); - Assert.That(unescaped, Is.EqualTo(@"test\with;characters"), "String unescaping was incorrect."); + Assert.That(unescaped, Is.EqualTo("\"This\\r\nis\na, test\";\\;,"), "String unescaping was incorrect."); + } - value = @"C:\Path\To\My\New\Information"; - unescaped = (string)serializer.Deserialize(new StringReader(value)); - Assert.That(unescaped, Is.EqualTo("C:\\Path\\To\\My\new\\Information"), "String unescaping was incorrect."); + [Test] + public void Transparency2() + { + var iCal = Calendar.Load(IcsFiles.Transparency2); - value = @"\""This\r\nis\Na\, test\""\;\\;,"; - unescaped = (string)serializer.Deserialize(new StringReader(value)); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); + var evt = iCal.Events.First(); - Assert.That(unescaped, Is.EqualTo("\"This\\r\nis\na, test\";\\;,"), "String unescaping was incorrect."); - } + Assert.That(evt.Transparency, Is.EqualTo(TransparencyType.Transparent)); + } - [Test] - public void Transparency2() - { - var iCal = Calendar.Load(IcsFiles.Transparency2); + /// + /// Tests that DateTime values that are out-of-range are still parsed correctly + /// and set to the closest representable date/time in .NET. + /// + [Test] + public void DateTime1() + { + var iCal = Calendar.Load(IcsFiles.DateTime1); + Assert.That(iCal.Events, Has.Count.EqualTo(6)); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); - var evt = iCal.Events.First(); + var evt = iCal.Events["nc2o66s0u36iesitl2l0b8inn8@google.com"]; + Assert.That(evt, Is.Not.Null); - Assert.That(evt.Transparency, Is.EqualTo(TransparencyType.Transparent)); - } + // The "Created" date is out-of-bounds. It should be coerced to the + // closest representable date/time. + Assert.That(evt.Created.Value, Is.EqualTo(DateTime.MinValue)); + } - /// - /// Tests that DateTime values that are out-of-range are still parsed correctly - /// and set to the closest representable date/time in .NET. - /// - [Test] - public void DateTime1() - { - var iCal = Calendar.Load(IcsFiles.DateTime1); - Assert.That(iCal.Events, Has.Count.EqualTo(6)); + [Test] + public void Language4() + { + var iCal = Calendar.Load(IcsFiles.Language4); + Assert.That(iCal, Is.Not.Null); + } - var evt = iCal.Events["nc2o66s0u36iesitl2l0b8inn8@google.com"]; - Assert.That(evt, Is.Not.Null); + [Test] + public void Outlook2007_LineFolds1() + { + var iCal = Calendar.Load(IcsFiles.Outlook2007LineFolds); + var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)); + Assert.That(events, Has.Count.EqualTo(1)); + } - // The "Created" date is out-of-bounds. It should be coerced to the - // closest representable date/time. - Assert.That(evt.Created.Value, Is.EqualTo(DateTime.MinValue)); - } + [Test] + public void Outlook2007_LineFolds2() + { + var longName = "The Exceptionally Long Named Meeting Room Whose Name Wraps Over Several Lines When Exported From Leading Calendar and Office Software Application Microsoft Office 2007"; + var iCal = Calendar.Load(IcsFiles.Outlook2007LineFolds); + var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)).OrderBy(o => o.Period.StartTime).ToList(); + Assert.That(((CalendarEvent) events[0].Source).Location, Is.EqualTo(longName)); + } - [Test] - public void Language4() - { - var iCal = Calendar.Load(IcsFiles.Language4); - Assert.That(iCal, Is.Not.Null); - } + /// + /// Tests that multiple parameters are allowed in iCalObjects + /// + [Test] + public void Parameter1() + { + var iCal = Calendar.Load(IcsFiles.Parameter1); - [Test] - public void Outlook2007_LineFolds1() + var evt = iCal.Events.First(); + IList parms = evt.Properties["DTSTART"].Parameters.AllOf("VALUE").ToList(); + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.Outlook2007LineFolds); - var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)); - Assert.That(events, Has.Count.EqualTo(1)); - } + Assert.That(parms, Has.Count.EqualTo(2)); + Assert.That(parms[0].Values.First(), Is.EqualTo("DATE")); + Assert.That(parms[1].Values.First(), Is.EqualTo("OTHER")); + }); + } - [Test] - public void Outlook2007_LineFolds2() - { - var longName = "The Exceptionally Long Named Meeting Room Whose Name Wraps Over Several Lines When Exported From Leading Calendar and Office Software Application Microsoft Office 2007"; - var iCal = Calendar.Load(IcsFiles.Outlook2007LineFolds); - var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)).OrderBy(o => o.Period.StartTime).ToList(); - Assert.That(((CalendarEvent)events[0].Source).Location, Is.EqualTo(longName)); - } + /// + /// Tests that empty parameters are allowed in iCalObjects + /// + [Test] + public void Parameter2() + { + var iCal = Calendar.Load(IcsFiles.Parameter2); + Assert.That(iCal.Events, Has.Count.EqualTo(2)); + } - /// - /// Tests that multiple parameters are allowed in iCalObjects - /// - [Test] - public void Parameter1() + /// + /// Tests a calendar that should fail to properly parse. + /// + [Test] + public void Parse1() + { + Assert.That(() => { - var iCal = Calendar.Load(IcsFiles.Parameter1); - - var evt = iCal.Events.First(); - IList parms = evt.Properties["DTSTART"].Parameters.AllOf("VALUE").ToList(); - Assert.Multiple(() => - { - Assert.That(parms, Has.Count.EqualTo(2)); - Assert.That(parms[0].Values.First(), Is.EqualTo("DATE")); - Assert.That(parms[1].Values.First(), Is.EqualTo("OTHER")); - }); - } + var content = IcsFiles.Parse1; + var iCal = Calendar.Load(content); + }, Throws.Exception.TypeOf()); + } - /// - /// Tests that empty parameters are allowed in iCalObjects - /// - [Test] - public void Parameter2() - { - var iCal = Calendar.Load(IcsFiles.Parameter2); - Assert.That(iCal.Events, Has.Count.EqualTo(2)); - } + /// + /// Tests that multiple properties are allowed in iCalObjects + /// + [Test] + public void Property1() + { + var iCal = Calendar.Load(IcsFiles.Property1); - /// - /// Tests a calendar that should fail to properly parse. - /// - [Test] - public void Parse1() - { - Assert.That(() => - { - var content = IcsFiles.Parse1; - var iCal = Calendar.Load(content); - }, Throws.Exception.TypeOf()); - } + IList props = iCal.Properties.AllOf("VERSION").ToList(); + Assert.That(props, Has.Count.EqualTo(2)); - /// - /// Tests that multiple properties are allowed in iCalObjects - /// - [Test] - public void Property1() + for (var i = 0; i < props.Count; i++) { - var iCal = Calendar.Load(IcsFiles.Property1); - - IList props = iCal.Properties.AllOf("VERSION").ToList(); - Assert.That(props, Has.Count.EqualTo(2)); - - for (var i = 0; i < props.Count; i++) - { - Assert.That(props[i].Value, Is.EqualTo("2." + i)); - } + Assert.That(props[i].Value, Is.EqualTo("2." + i)); } } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/DocumentationExamples.cs b/Ical.Net.Tests/DocumentationExamples.cs index 43ad6db72..a7aecc6e5 100644 --- a/Ical.Net.Tests/DocumentationExamples.cs +++ b/Ical.Net.Tests/DocumentationExamples.cs @@ -1,128 +1,132 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using NUnit.Framework; + +namespace Ical.Net.Tests; -namespace Ical.Net.Tests +public class DocumentationExamples { - public class DocumentationExamples + [Test] + public void Daily_Test() { - [Test] - public void Daily_Test() + // The first instance of an event taking place on July 1, 2016 between 07:00 and 08:00. + // We want it to recur through the end of July. + var vEvent = new CalendarEvent { - // The first instance of an event taking place on July 1, 2016 between 07:00 and 08:00. - // We want it to recur through the end of July. - var vEvent = new CalendarEvent - { - DtStart = new CalDateTime(DateTime.Parse("2016-07-01T07:00")), - DtEnd = new CalDateTime(DateTime.Parse("2016-07-01T08:00")), - }; - - //Recur daily through the end of the day, July 31, 2016 - var recurrenceRule = new RecurrencePattern(FrequencyType.Daily, 1) - { - Until = DateTime.Parse("2016-07-31T23:59:59") - }; - - vEvent.RecurrenceRules = new List {recurrenceRule}; - var calendar = new Calendar(); - calendar.Events.Add(vEvent); - - - // Count the occurrences between July 20, and Aug 5 -- there should be 12: - // July 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 - var searchStart = DateTime.Parse("2016-07-20"); - var searchEnd = DateTime.Parse("2016-08-05"); - var occurrences = calendar.GetOccurrences(searchStart, searchEnd); - Assert.That(occurrences, Has.Count.EqualTo(12)); - } + DtStart = new CalDateTime(DateTime.Parse("2016-07-01T07:00")), + DtEnd = new CalDateTime(DateTime.Parse("2016-07-01T08:00")), + }; - [Test] - public void EveryOtherTuesdayUntilTheEndOfTheYear_Test() + //Recur daily through the end of the day, July 31, 2016 + var recurrenceRule = new RecurrencePattern(FrequencyType.Daily, 1) { - // An event taking place between 07:00 and 08:00, beginning July 5 (a Tuesday) - var vEvent = new CalendarEvent - { - DtStart = new CalDateTime(DateTime.Parse("2016-07-05T07:00")), - DtEnd = new CalDateTime(DateTime.Parse("2016-07-05T08:00")), - }; - - // Recurring every other Tuesday until Dec 31 - var rrule = new RecurrencePattern(FrequencyType.Weekly, 2) - { - Until = DateTime.Parse("2016-12-31T11:59:59") - }; - vEvent.RecurrenceRules = new List { rrule }; - - // Count every other Tuesday between July 1 and Dec 31. - // The first Tuesday is July 5. There should be 13 in total - var searchStart = DateTime.Parse("2010-01-01"); - var searchEnd = DateTime.Parse("2016-12-31"); - var tuesdays = vEvent.GetOccurrences(searchStart, searchEnd); - - Assert.That(tuesdays, Has.Count.EqualTo(13)); - } + Until = DateTime.Parse("2016-07-31T23:59:59") + }; - [Test] - public void FourthThursdayOfNovember_Tests() + vEvent.RecurrenceRules = new List { recurrenceRule }; + var calendar = new Calendar(); + calendar.Events.Add(vEvent); + + + // Count the occurrences between July 20, and Aug 5 -- there should be 12: + // July 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 + var searchStart = DateTime.Parse("2016-07-20"); + var searchEnd = DateTime.Parse("2016-08-05"); + var occurrences = calendar.GetOccurrences(searchStart, searchEnd); + Assert.That(occurrences, Has.Count.EqualTo(12)); + } + + [Test] + public void EveryOtherTuesdayUntilTheEndOfTheYear_Test() + { + // An event taking place between 07:00 and 08:00, beginning July 5 (a Tuesday) + var vEvent = new CalendarEvent { - // (The number of US thanksgivings between 2000 and 2016) - // An event taking place between 07:00 and 19:00, beginning July 5 (a Tuesday) - var vEvent = new CalendarEvent - { - DtStart = new CalDateTime(DateTime.Parse("2000-11-23T07:00")), - DtEnd = new CalDateTime(DateTime.Parse("2000-11-23T19:00")), - }; - - // Recurring every other Tuesday until Dec 31 - var rrule = new RecurrencePattern(FrequencyType.Yearly, 1) - { - Frequency = FrequencyType.Yearly, - Interval = 1, - ByMonth = new List { 11 }, - ByDay = new List { new WeekDay { DayOfWeek = DayOfWeek.Thursday, Offset = 4 } }, - Until = DateTime.MaxValue - }; - vEvent.RecurrenceRules = new List { rrule }; - - var searchStart = DateTime.Parse("2000-01-01"); - var searchEnd = DateTime.Parse("2017-01-01"); - var usThanksgivings = vEvent.GetOccurrences(searchStart, searchEnd); - - Assert.That(usThanksgivings, Has.Count.EqualTo(17)); - foreach (var thanksgiving in usThanksgivings) - { - Assert.That(thanksgiving.Period.StartTime.DayOfWeek == DayOfWeek.Thursday, Is.True); - } - } + DtStart = new CalDateTime(DateTime.Parse("2016-07-05T07:00")), + DtEnd = new CalDateTime(DateTime.Parse("2016-07-05T08:00")), + }; - [Test] - public void DailyExceptSunday_Test() + // Recurring every other Tuesday until Dec 31 + var rrule = new RecurrencePattern(FrequencyType.Weekly, 2) { - //An event that happens daily through 2016, except for Sundays - var vEvent = new CalendarEvent - { - DtStart = new CalDateTime(DateTime.Parse("2016-01-01T07:00")), - DtEnd = new CalDateTime(DateTime.Parse("2016-12-31T08:00")), - RecurrenceRules = new List { new RecurrencePattern(FrequencyType.Daily, 1)}, - }; - - //Define the exceptions: Sunday - var exceptionRule = new RecurrencePattern(FrequencyType.Weekly, 1) - { - ByDay = new List { new WeekDay(DayOfWeek.Sunday) } - }; - vEvent.ExceptionRules = new List {exceptionRule}; - - var calendar = new Calendar(); - calendar.Events.Add(vEvent); - - // We are essentially counting all the days that aren't Sunday in 2016, so there should be 314 - var searchStart = DateTime.Parse("2015-12-31"); - var searchEnd = DateTime.Parse("2017-01-01"); - var occurrences = calendar.GetOccurrences(searchStart, searchEnd); - Assert.That(occurrences, Has.Count.EqualTo(314)); + Until = DateTime.Parse("2016-12-31T11:59:59") + }; + vEvent.RecurrenceRules = new List { rrule }; + + // Count every other Tuesday between July 1 and Dec 31. + // The first Tuesday is July 5. There should be 13 in total + var searchStart = DateTime.Parse("2010-01-01"); + var searchEnd = DateTime.Parse("2016-12-31"); + var tuesdays = vEvent.GetOccurrences(searchStart, searchEnd); + + Assert.That(tuesdays, Has.Count.EqualTo(13)); + } + + [Test] + public void FourthThursdayOfNovember_Tests() + { + // (The number of US thanksgivings between 2000 and 2016) + // An event taking place between 07:00 and 19:00, beginning July 5 (a Tuesday) + var vEvent = new CalendarEvent + { + DtStart = new CalDateTime(DateTime.Parse("2000-11-23T07:00")), + DtEnd = new CalDateTime(DateTime.Parse("2000-11-23T19:00")), + }; + + // Recurring every other Tuesday until Dec 31 + var rrule = new RecurrencePattern(FrequencyType.Yearly, 1) + { + Frequency = FrequencyType.Yearly, + Interval = 1, + ByMonth = new List { 11 }, + ByDay = new List { new WeekDay { DayOfWeek = DayOfWeek.Thursday, Offset = 4 } }, + Until = DateTime.MaxValue + }; + vEvent.RecurrenceRules = new List { rrule }; + + var searchStart = DateTime.Parse("2000-01-01"); + var searchEnd = DateTime.Parse("2017-01-01"); + var usThanksgivings = vEvent.GetOccurrences(searchStart, searchEnd); + + Assert.That(usThanksgivings, Has.Count.EqualTo(17)); + foreach (var thanksgiving in usThanksgivings) + { + Assert.That(thanksgiving.Period.StartTime.DayOfWeek == DayOfWeek.Thursday, Is.True); } } -} + + [Test] + public void DailyExceptSunday_Test() + { + //An event that happens daily through 2016, except for Sundays + var vEvent = new CalendarEvent + { + DtStart = new CalDateTime(DateTime.Parse("2016-01-01T07:00")), + DtEnd = new CalDateTime(DateTime.Parse("2016-12-31T08:00")), + RecurrenceRules = new List { new RecurrencePattern(FrequencyType.Daily, 1) }, + }; + + //Define the exceptions: Sunday + var exceptionRule = new RecurrencePattern(FrequencyType.Weekly, 1) + { + ByDay = new List { new WeekDay(DayOfWeek.Sunday) } + }; + vEvent.ExceptionRules = new List { exceptionRule }; + + var calendar = new Calendar(); + calendar.Events.Add(vEvent); + + // We are essentially counting all the days that aren't Sunday in 2016, so there should be 314 + var searchStart = DateTime.Parse("2015-12-31"); + var searchEnd = DateTime.Parse("2017-01-01"); + var occurrences = calendar.GetOccurrences(searchStart, searchEnd); + Assert.That(occurrences, Has.Count.EqualTo(314)); + } +} \ No newline at end of file diff --git a/Ical.Net.Tests/EqualityAndHashingTests.cs b/Ical.Net.Tests/EqualityAndHashingTests.cs index 3c32eb683..a9c926527 100644 --- a/Ical.Net.Tests/EqualityAndHashingTests.cs +++ b/Ical.Net.Tests/EqualityAndHashingTests.cs @@ -1,346 +1,351 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Serialization; -using Ical.Net.Utility; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Serialization; +using Ical.Net.Utility; +using NUnit.Framework; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +public class EqualityAndHashingTests { - public class EqualityAndHashingTests - { - private const string _someTz = "America/Los_Angeles"; - private static readonly DateTime _nowTime = DateTime.Parse("2016-07-16T16:47:02.9310521-04:00"); - private static readonly DateTime _later = _nowTime.AddHours(1); + private const string _someTz = "America/Los_Angeles"; + private static readonly DateTime _nowTime = DateTime.Parse("2016-07-16T16:47:02.9310521-04:00"); + private static readonly DateTime _later = _nowTime.AddHours(1); - [Test, TestCaseSource(nameof(CalDateTime_TestCases))] - public void CalDateTime_Tests(CalDateTime incomingDt, CalDateTime expectedDt) + [Test, TestCaseSource(nameof(CalDateTime_TestCases))] + public void CalDateTime_Tests(CalDateTime incomingDt, CalDateTime expectedDt) + { + Assert.Multiple(() => { - Assert.Multiple(() => - { - Assert.That(expectedDt.Value, Is.EqualTo(incomingDt.Value)); - Assert.That(expectedDt.GetHashCode(), Is.EqualTo(incomingDt.GetHashCode())); - Assert.That(expectedDt.TzId, Is.EqualTo(incomingDt.TzId)); - Assert.That(incomingDt.Equals(expectedDt), Is.True); - }); - } + Assert.That(expectedDt.Value, Is.EqualTo(incomingDt.Value)); + Assert.That(expectedDt.GetHashCode(), Is.EqualTo(incomingDt.GetHashCode())); + Assert.That(expectedDt.TzId, Is.EqualTo(incomingDt.TzId)); + Assert.That(incomingDt.Equals(expectedDt), Is.True); + }); + } - public static IEnumerable CalDateTime_TestCases() - { - var nowCalDt = new CalDateTime(_nowTime); - yield return new TestCaseData(nowCalDt, new CalDateTime(_nowTime)).SetName("Now, no time zone"); + public static IEnumerable CalDateTime_TestCases() + { + var nowCalDt = new CalDateTime(_nowTime); + yield return new TestCaseData(nowCalDt, new CalDateTime(_nowTime)).SetName("Now, no time zone"); - var nowCalDtWithTz = new CalDateTime(_nowTime, _someTz); - yield return new TestCaseData(nowCalDtWithTz, new CalDateTime(_nowTime, _someTz)).SetName("Now, with time zone"); - } + var nowCalDtWithTz = new CalDateTime(_nowTime, _someTz); + yield return new TestCaseData(nowCalDtWithTz, new CalDateTime(_nowTime, _someTz)).SetName("Now, with time zone"); + } - [Test] - public void RecurrencePatternTests() - { - var patternA = GetSimpleRecurrencePattern(); - var patternB = GetSimpleRecurrencePattern(); + [Test] + public void RecurrencePatternTests() + { + var patternA = GetSimpleRecurrencePattern(); + var patternB = GetSimpleRecurrencePattern(); - Assert.That(patternB, Is.EqualTo(patternA)); - Assert.That(patternB.GetHashCode(), Is.EqualTo(patternA.GetHashCode())); - } + Assert.That(patternB, Is.EqualTo(patternA)); + Assert.That(patternB.GetHashCode(), Is.EqualTo(patternA.GetHashCode())); + } - [Test, TestCaseSource(nameof(Event_TestCases))] - public void Event_Tests(CalendarEvent incoming, CalendarEvent expected) + [Test, TestCaseSource(nameof(Event_TestCases))] + public void Event_Tests(CalendarEvent incoming, CalendarEvent expected) + { + Assert.Multiple(() => { - Assert.Multiple(() => - { - Assert.That(expected.DtStart, Is.EqualTo(incoming.DtStart)); - Assert.That(expected.DtEnd, Is.EqualTo(incoming.DtEnd)); - Assert.That(expected.Location, Is.EqualTo(incoming.Location)); - Assert.That(expected.Status, Is.EqualTo(incoming.Status)); - Assert.That(expected.IsActive, Is.EqualTo(incoming.IsActive)); - Assert.That(expected.Duration, Is.EqualTo(incoming.Duration)); - Assert.That(expected.Transparency, Is.EqualTo(incoming.Transparency)); - Assert.That(expected.GetHashCode(), Is.EqualTo(incoming.GetHashCode())); - Assert.That(incoming.Equals(expected), Is.True); - }); - } + Assert.That(expected.DtStart, Is.EqualTo(incoming.DtStart)); + Assert.That(expected.DtEnd, Is.EqualTo(incoming.DtEnd)); + Assert.That(expected.Location, Is.EqualTo(incoming.Location)); + Assert.That(expected.Status, Is.EqualTo(incoming.Status)); + Assert.That(expected.IsActive, Is.EqualTo(incoming.IsActive)); + Assert.That(expected.Duration, Is.EqualTo(incoming.Duration)); + Assert.That(expected.Transparency, Is.EqualTo(incoming.Transparency)); + Assert.That(expected.GetHashCode(), Is.EqualTo(incoming.GetHashCode())); + Assert.That(incoming.Equals(expected), Is.True); + }); + } + + private static RecurrencePattern GetSimpleRecurrencePattern() => new RecurrencePattern(FrequencyType.Daily, 1) + { + Count = 5 + }; + + private static CalendarEvent GetSimpleEvent() => new CalendarEvent + { + DtStart = new CalDateTime(_nowTime), + DtEnd = new CalDateTime(_later), + Duration = TimeSpan.FromHours(1), + }; + + private static string SerializeEvent(CalendarEvent e) => new CalendarSerializer().SerializeToString(new Calendar { Events = { e } }); + + + public static IEnumerable Event_TestCases() + { + var outgoing = GetSimpleEvent(); + var expected = GetSimpleEvent(); + yield return new TestCaseData(outgoing, expected).SetName("Events with start, end, and duration"); + + var fiveA = GetSimpleRecurrencePattern(); + var fiveB = GetSimpleRecurrencePattern(); + + outgoing = GetSimpleEvent(); + expected = GetSimpleEvent(); + outgoing.RecurrenceRules = new List { fiveA }; + expected.RecurrenceRules = new List { fiveB }; + yield return new TestCaseData(outgoing, expected).SetName("Events with start, end, duration, and one recurrence rule"); + } - private static RecurrencePattern GetSimpleRecurrencePattern() => new RecurrencePattern(FrequencyType.Daily, 1) + [Test] + public void Calendar_Tests() + { + var rruleA = new RecurrencePattern(FrequencyType.Daily, 1) { Count = 5 }; - private static CalendarEvent GetSimpleEvent() => new CalendarEvent + var e = new CalendarEvent { DtStart = new CalDateTime(_nowTime), DtEnd = new CalDateTime(_later), Duration = TimeSpan.FromHours(1), + RecurrenceRules = new List { rruleA }, }; - private static string SerializeEvent(CalendarEvent e) => new CalendarSerializer().SerializeToString(new Calendar { Events = { e } }); - + var actualCalendar = new Calendar(); + actualCalendar.Events.Add(e); - public static IEnumerable Event_TestCases() + //Work around referential equality... + var rruleB = new RecurrencePattern(FrequencyType.Daily, 1) { - var outgoing = GetSimpleEvent(); - var expected = GetSimpleEvent(); - yield return new TestCaseData(outgoing, expected).SetName("Events with start, end, and duration"); - - var fiveA = GetSimpleRecurrencePattern(); - var fiveB = GetSimpleRecurrencePattern(); - - outgoing = GetSimpleEvent(); - expected = GetSimpleEvent(); - outgoing.RecurrenceRules = new List { fiveA }; - expected.RecurrenceRules = new List { fiveB }; - yield return new TestCaseData(outgoing, expected).SetName("Events with start, end, duration, and one recurrence rule"); - } + Count = 5 + }; - [Test] - public void Calendar_Tests() + var expectedCalendar = new Calendar(); + expectedCalendar.Events.Add(new CalendarEvent { - var rruleA = new RecurrencePattern(FrequencyType.Daily, 1) - { - Count = 5 - }; - - var e = new CalendarEvent - { - DtStart = new CalDateTime(_nowTime), - DtEnd = new CalDateTime(_later), - Duration = TimeSpan.FromHours(1), - RecurrenceRules = new List { rruleA }, - }; + DtStart = new CalDateTime(_nowTime), + DtEnd = new CalDateTime(_later), + Duration = TimeSpan.FromHours(1), + RecurrenceRules = new List { rruleB }, + }); - var actualCalendar = new Calendar(); - actualCalendar.Events.Add(e); + Assert.Multiple(() => + { + Assert.That(expectedCalendar.GetHashCode(), Is.EqualTo(actualCalendar.GetHashCode())); + Assert.That(actualCalendar.Equals(expectedCalendar), Is.True); + }); + } - //Work around referential equality... - var rruleB = new RecurrencePattern(FrequencyType.Daily, 1) - { - Count = 5 - }; + [Test, TestCaseSource(nameof(VTimeZone_TestCases))] + public void VTimeZone_Tests(VTimeZone actual, VTimeZone expected) + { + Assert.Multiple(() => + { + Assert.That(expected.Url, Is.EqualTo(actual.Url)); + Assert.That(expected.TzId, Is.EqualTo(actual.TzId)); + Assert.That(expected, Is.EqualTo(actual)); + Assert.That(expected.GetHashCode(), Is.EqualTo(actual.GetHashCode())); + }); + } - var expectedCalendar = new Calendar(); - expectedCalendar.Events.Add(new CalendarEvent - { - DtStart = new CalDateTime(_nowTime), - DtEnd = new CalDateTime(_later), - Duration = TimeSpan.FromHours(1), - RecurrenceRules = new List { rruleB }, - }); + public static IEnumerable VTimeZone_TestCases() + { + const string nzSt = "New Zealand Standard Time"; + var first = new VTimeZone + { + TzId = nzSt, + }; + var second = new VTimeZone(nzSt); + yield return new TestCaseData(first, second); - Assert.Multiple(() => - { - Assert.That(expectedCalendar.GetHashCode(), Is.EqualTo(actualCalendar.GetHashCode())); - Assert.That(actualCalendar.Equals(expectedCalendar), Is.True); - }); - } + first.Url = new Uri("http://example.com/"); + second.Url = new Uri("http://example.com"); + yield return new TestCaseData(first, second); + } - [Test, TestCaseSource(nameof(VTimeZone_TestCases))] - public void VTimeZone_Tests(VTimeZone actual, VTimeZone expected) + [Test, TestCaseSource(nameof(Attendees_TestCases))] + public void Attendees_Tests(Attendee actual, Attendee expected) + { + Assert.Multiple(() => { - Assert.Multiple(() => - { - Assert.That(expected.Url, Is.EqualTo(actual.Url)); - Assert.That(expected.TzId, Is.EqualTo(actual.TzId)); - Assert.That(expected, Is.EqualTo(actual)); - Assert.That(expected.GetHashCode(), Is.EqualTo(actual.GetHashCode())); - }); - } + Assert.That(actual.GetHashCode(), Is.EqualTo(expected.GetHashCode())); + Assert.That(actual, Is.EqualTo(expected)); + }); + } - public static IEnumerable VTimeZone_TestCases() + public static IEnumerable Attendees_TestCases() + { + var tentative1 = new Attendee("MAILTO:james@example.com") { - const string nzSt = "New Zealand Standard Time"; - var first = new VTimeZone - { - TzId = nzSt, - }; - var second = new VTimeZone(nzSt); - yield return new TestCaseData(first, second); - - first.Url = new Uri("http://example.com/"); - second.Url = new Uri("http://example.com"); - yield return new TestCaseData(first, second); - } - - [Test, TestCaseSource(nameof(Attendees_TestCases))] - public void Attendees_Tests(Attendee actual, Attendee expected) + CommonName = "James Tentative", + Role = ParticipationRole.RequiredParticipant, + Rsvp = true, + ParticipationStatus = EventParticipationStatus.Tentative + }; + var tentative2 = new Attendee("MAILTO:james@example.com") { - Assert.Multiple(() => - { - Assert.That(actual.GetHashCode(), Is.EqualTo(expected.GetHashCode())); - Assert.That(actual, Is.EqualTo(expected)); - }); - } + CommonName = "James Tentative", + Role = ParticipationRole.RequiredParticipant, + Rsvp = true, + ParticipationStatus = EventParticipationStatus.Tentative + }; + yield return new TestCaseData(tentative1, tentative2).SetName("Simple attendee test case"); - public static IEnumerable Attendees_TestCases() + var complex1 = new Attendee("MAILTO:mary@example.com") { - var tentative1 = new Attendee("MAILTO:james@example.com") - { - CommonName = "James Tentative", - Role = ParticipationRole.RequiredParticipant, - Rsvp = true, - ParticipationStatus = EventParticipationStatus.Tentative - }; - var tentative2 = new Attendee("MAILTO:james@example.com") - { - CommonName = "James Tentative", - Role = ParticipationRole.RequiredParticipant, - Rsvp = true, - ParticipationStatus = EventParticipationStatus.Tentative - }; - yield return new TestCaseData(tentative1, tentative2).SetName("Simple attendee test case"); - - var complex1 = new Attendee("MAILTO:mary@example.com") - { - CommonName = "Mary Accepted", - Rsvp = true, - ParticipationStatus = EventParticipationStatus.Accepted, - SentBy = new Uri("mailto:someone@example.com"), - DirectoryEntry = new Uri("ldap://example.com:6666/o=eDABC Industries,c=3DUS??(cn=3DBMary Accepted)"), - Type = "CuType", - Members = new List { "Group A", "Group B" }, - Role = ParticipationRole.Chair, - DelegatedTo = new List { "Peon A", "Peon B" }, - DelegatedFrom = new List { "Bigwig A", "Bigwig B" } - }; - var complex2 = new Attendee("MAILTO:mary@example.com") - { - CommonName = "Mary Accepted", - Rsvp = true, - ParticipationStatus = EventParticipationStatus.Accepted, - SentBy = new Uri("mailto:someone@example.com"), - DirectoryEntry = new Uri("ldap://example.com:6666/o=eDABC Industries,c=3DUS??(cn=3DBMary Accepted)"), - Type = "CuType", - Members = new List { "Group A", "Group B" }, - Role = ParticipationRole.Chair, - DelegatedTo = new List { "Peon A", "Peon B" }, - DelegatedFrom = new List { "Bigwig A", "Bigwig B" } - }; - yield return new TestCaseData(complex1, complex2).SetName("Complex attendee test"); - } - - [Test, TestCaseSource(nameof(CalendarCollection_TestCases))] - public void CalendarCollection_Tests(string rawCalendar) + CommonName = "Mary Accepted", + Rsvp = true, + ParticipationStatus = EventParticipationStatus.Accepted, + SentBy = new Uri("mailto:someone@example.com"), + DirectoryEntry = new Uri("ldap://example.com:6666/o=eDABC Industries,c=3DUS??(cn=3DBMary Accepted)"), + Type = "CuType", + Members = new List { "Group A", "Group B" }, + Role = ParticipationRole.Chair, + DelegatedTo = new List { "Peon A", "Peon B" }, + DelegatedFrom = new List { "Bigwig A", "Bigwig B" } + }; + var complex2 = new Attendee("MAILTO:mary@example.com") { - var a = Calendar.Load(IcsFiles.UsHolidays); - var b = Calendar.Load(IcsFiles.UsHolidays); + CommonName = "Mary Accepted", + Rsvp = true, + ParticipationStatus = EventParticipationStatus.Accepted, + SentBy = new Uri("mailto:someone@example.com"), + DirectoryEntry = new Uri("ldap://example.com:6666/o=eDABC Industries,c=3DUS??(cn=3DBMary Accepted)"), + Type = "CuType", + Members = new List { "Group A", "Group B" }, + Role = ParticipationRole.Chair, + DelegatedTo = new List { "Peon A", "Peon B" }, + DelegatedFrom = new List { "Bigwig A", "Bigwig B" } + }; + yield return new TestCaseData(complex1, complex2).SetName("Complex attendee test"); + } - Assert.That(a, Is.Not.Null); - Assert.That(b, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(b.GetHashCode(), Is.EqualTo(a.GetHashCode())); - Assert.That(b, Is.EqualTo(a)); - }); - } + [Test, TestCaseSource(nameof(CalendarCollection_TestCases))] + public void CalendarCollection_Tests(string rawCalendar) + { + var a = Calendar.Load(IcsFiles.UsHolidays); + var b = Calendar.Load(IcsFiles.UsHolidays); - public static IEnumerable CalendarCollection_TestCases() + Assert.That(a, Is.Not.Null); + Assert.That(b, Is.Not.Null); + Assert.Multiple(() => { - yield return new TestCaseData(IcsFiles.Google1).SetName("Google calendar test case"); - yield return new TestCaseData(IcsFiles.Parse1).SetName("Weird file parse test case"); - yield return new TestCaseData(IcsFiles.UsHolidays).SetName("US Holidays (quite large)"); - } + Assert.That(b.GetHashCode(), Is.EqualTo(a.GetHashCode())); + Assert.That(b, Is.EqualTo(a)); + }); + } + + public static IEnumerable CalendarCollection_TestCases() + { + yield return new TestCaseData(IcsFiles.Google1).SetName("Google calendar test case"); + yield return new TestCaseData(IcsFiles.Parse1).SetName("Weird file parse test case"); + yield return new TestCaseData(IcsFiles.UsHolidays).SetName("US Holidays (quite large)"); + } - [Test] - public void Resources_Tests() + [Test] + public void Resources_Tests() + { + var origContents = new[] { "Foo", "Bar" }; + var e = GetSimpleEvent(); + e.Resources = new List(origContents); + Assert.That(e.Resources.Count == 2, Is.True); + + e.Resources.Add("Baz"); + Assert.That(e.Resources.Count == 3, Is.True); + var serialized = SerializeEvent(e); + Assert.That(serialized.Contains("Baz"), Is.True); + + e.Resources.Remove("Baz"); + Assert.That(e.Resources.Count == 2, Is.True); + serialized = SerializeEvent(e); + Assert.That(serialized.Contains("Baz"), Is.False); + + e.Resources.Add("Hello"); + Assert.That(e.Resources.Contains("Hello"), Is.True); + serialized = SerializeEvent(e); + Assert.That(serialized.Contains("Hello"), Is.True); + + e.Resources.Clear(); + e.Resources.AddRange(origContents); + Assert.That(origContents, Is.EquivalentTo(e.Resources)); + serialized = SerializeEvent(e); + Assert.Multiple(() => { - var origContents = new[] { "Foo", "Bar" }; - var e = GetSimpleEvent(); - e.Resources = new List(origContents); - Assert.That(e.Resources.Count == 2, Is.True); - - e.Resources.Add("Baz"); - Assert.That(e.Resources.Count == 3, Is.True); - var serialized = SerializeEvent(e); - Assert.That(serialized.Contains("Baz"), Is.True); - - e.Resources.Remove("Baz"); - Assert.That(e.Resources.Count == 2, Is.True); - serialized = SerializeEvent(e); + Assert.That(serialized.Contains("Foo"), Is.True); + Assert.That(serialized.Contains("Bar"), Is.True); Assert.That(serialized.Contains("Baz"), Is.False); + Assert.That(serialized.Contains("Hello"), Is.False); + }); + } - e.Resources.Add("Hello"); - Assert.That(e.Resources.Contains("Hello"), Is.True); - serialized = SerializeEvent(e); - Assert.That(serialized.Contains("Hello"), Is.True); - - e.Resources.Clear(); - e.Resources.AddRange(origContents); - Assert.That(origContents, Is.EquivalentTo(e.Resources)); - serialized = SerializeEvent(e); - Assert.Multiple(() => - { - Assert.That(serialized.Contains("Foo"), Is.True); - Assert.That(serialized.Contains("Bar"), Is.True); - Assert.That(serialized.Contains("Baz"), Is.False); - Assert.That(serialized.Contains("Hello"), Is.False); - }); - } - - internal static (byte[] original, byte[] copy) GetAttachments() - { - var payload = Encoding.UTF8.GetBytes("This is an attachment!"); - var payloadCopy = new byte[payload.Length]; - Array.Copy(payload, payloadCopy, payload.Length); - return (payload, payloadCopy); - } + internal static (byte[] original, byte[] copy) GetAttachments() + { + var payload = Encoding.UTF8.GetBytes("This is an attachment!"); + var payloadCopy = new byte[payload.Length]; + Array.Copy(payload, payloadCopy, payload.Length); + return (payload, payloadCopy); + } - [Test, TestCaseSource(nameof(RecurringComponentAttachment_TestCases))] - public void RecurringComponentAttachmentTests(RecurringComponent noAttachment, RecurringComponent withAttachment) - { - var attachments = GetAttachments(); + [Test, TestCaseSource(nameof(RecurringComponentAttachment_TestCases))] + public void RecurringComponentAttachmentTests(RecurringComponent noAttachment, RecurringComponent withAttachment) + { + var attachments = GetAttachments(); - Assert.That(withAttachment, Is.Not.EqualTo(noAttachment)); - Assert.That(withAttachment.GetHashCode(), Is.Not.EqualTo(noAttachment.GetHashCode())); + Assert.That(withAttachment, Is.Not.EqualTo(noAttachment)); + Assert.That(withAttachment.GetHashCode(), Is.Not.EqualTo(noAttachment.GetHashCode())); - noAttachment.Attachments.Add(new Attachment(attachments.copy)); + noAttachment.Attachments.Add(new Attachment(attachments.copy)); - Assert.That(withAttachment, Is.EqualTo(noAttachment)); - Assert.That(withAttachment.GetHashCode(), Is.EqualTo(noAttachment.GetHashCode())); - } + Assert.That(withAttachment, Is.EqualTo(noAttachment)); + Assert.That(withAttachment.GetHashCode(), Is.EqualTo(noAttachment.GetHashCode())); + } - public static IEnumerable RecurringComponentAttachment_TestCases() - { - var attachments = GetAttachments(); - - var journalNoAttach = new Journal { Start = new CalDateTime(_nowTime), Summary = "A summary!", Class = "Some class!" }; - var journalWithAttach = new Journal { Start = new CalDateTime(_nowTime), Summary = "A summary!", Class = "Some class!" }; - journalWithAttach.Attachments.Add(new Attachment(attachments.original)); - yield return new TestCaseData(journalNoAttach, journalWithAttach).SetName("Journal recurring component attachment"); - - var todoNoAttach = new Todo { Start = new CalDateTime(_nowTime), Summary = "A summary!", Class = "Some class!" }; - var todoWithAttach = new Todo { Start = new CalDateTime(_nowTime), Summary = "A summary!", Class = "Some class!" }; - todoWithAttach.Attachments.Add(new Attachment(attachments.original)); - yield return new TestCaseData(todoNoAttach, todoWithAttach).SetName("Todo recurring component attachment"); - - var eventNoAttach = GetSimpleEvent(); - var eventWithAttach = GetSimpleEvent(); - eventWithAttach.Attachments.Add(new Attachment(attachments.original)); - yield return new TestCaseData(eventNoAttach, eventWithAttach).SetName("Event recurring component attachment"); - } + public static IEnumerable RecurringComponentAttachment_TestCases() + { + var attachments = GetAttachments(); + + var journalNoAttach = new Journal { Start = new CalDateTime(_nowTime), Summary = "A summary!", Class = "Some class!" }; + var journalWithAttach = new Journal { Start = new CalDateTime(_nowTime), Summary = "A summary!", Class = "Some class!" }; + journalWithAttach.Attachments.Add(new Attachment(attachments.original)); + yield return new TestCaseData(journalNoAttach, journalWithAttach).SetName("Journal recurring component attachment"); + + var todoNoAttach = new Todo { Start = new CalDateTime(_nowTime), Summary = "A summary!", Class = "Some class!" }; + var todoWithAttach = new Todo { Start = new CalDateTime(_nowTime), Summary = "A summary!", Class = "Some class!" }; + todoWithAttach.Attachments.Add(new Attachment(attachments.original)); + yield return new TestCaseData(todoNoAttach, todoWithAttach).SetName("Todo recurring component attachment"); + + var eventNoAttach = GetSimpleEvent(); + var eventWithAttach = GetSimpleEvent(); + eventWithAttach.Attachments.Add(new Attachment(attachments.original)); + yield return new TestCaseData(eventNoAttach, eventWithAttach).SetName("Event recurring component attachment"); + } - [Test, TestCaseSource(nameof(PeriodTestCases))] - public void PeriodTests(Period a, Period b) + [Test, TestCaseSource(nameof(PeriodTestCases))] + public void PeriodTests(Period a, Period b) + { + Assert.Multiple(() => { - Assert.Multiple(() => - { - Assert.That(b.GetHashCode(), Is.EqualTo(a.GetHashCode())); - Assert.That(b, Is.EqualTo(a)); - }); - } + Assert.That(b.GetHashCode(), Is.EqualTo(a.GetHashCode())); + Assert.That(b, Is.EqualTo(a)); + }); + } - public static IEnumerable PeriodTestCases() - { - yield return new TestCaseData(new Period(new CalDateTime(_nowTime)), new Period(new CalDateTime(_nowTime))) - .SetName("Two identical CalDateTimes are equal"); - } + public static IEnumerable PeriodTestCases() + { + yield return new TestCaseData(new Period(new CalDateTime(_nowTime)), new Period(new CalDateTime(_nowTime))) + .SetName("Two identical CalDateTimes are equal"); + } - [Test] - public void PeriodListTests() - { - var startTimesA = new List + [Test] + public void PeriodListTests() + { + var startTimesA = new List { new DateTime(2017, 03, 02, 06, 00, 00), new DateTime(2017, 03, 03, 06, 00, 00), @@ -382,14 +387,14 @@ public void PeriodListTests() new DateTime(2017, 05, 01, 06, 00, 00), } .Select(dt => new Period(new CalDateTime(dt))).ToList(); - var a = new PeriodList(); - foreach (var period in startTimesA) - { - a.Add(period); - } + var a = new PeriodList(); + foreach (var period in startTimesA) + { + a.Add(period); + } - //Difference from A: first element became the second, and last element became the second-to-last element - var startTimesB = new List + //Difference from A: first element became the second, and last element became the second-to-last element + var startTimesB = new List { new DateTime(2017, 03, 03, 06, 00, 00), new DateTime(2017, 03, 02, 06, 00, 00), @@ -431,70 +436,69 @@ public void PeriodListTests() new DateTime(2017, 04, 28, 06, 00, 00), } .Select(dt => new Period(new CalDateTime(dt))).ToList(); - var b = new PeriodList(); - foreach (var period in startTimesB) - { - b.Add(period); - } - - var collectionEqual = CollectionHelpers.Equals(a, b); - Assert.Multiple(() => - { - Assert.That(collectionEqual, Is.EqualTo(true)); - Assert.That(b.GetHashCode(), Is.EqualTo(a.GetHashCode())); - }); - - var listOfListA = new List { a }; - var listOfListB = new List { b }; - Assert.That(CollectionHelpers.Equals(listOfListA, listOfListB), Is.True); - - var aThenB = new List { a, b }; - var bThenA = new List { b, a }; - Assert.That(CollectionHelpers.Equals(aThenB, bThenA), Is.True); + var b = new PeriodList(); + foreach (var period in startTimesB) + { + b.Add(period); } - [Test] - public void CalDateTimeTests() + var collectionEqual = CollectionHelpers.Equals(a, b); + Assert.Multiple(() => { - var nowLocal = DateTime.Now; - var nowUtc = nowLocal.ToUniversalTime(); + Assert.That(collectionEqual, Is.EqualTo(true)); + Assert.That(b.GetHashCode(), Is.EqualTo(a.GetHashCode())); + }); - var asLocal = new CalDateTime(nowLocal, "America/New_York"); - var asUtc = new CalDateTime(nowUtc, "UTC"); + var listOfListA = new List { a }; + var listOfListB = new List { b }; + Assert.That(CollectionHelpers.Equals(listOfListA, listOfListB), Is.True); - Assert.That(asUtc, Is.Not.EqualTo(asLocal)); - } + var aThenB = new List { a, b }; + var bThenA = new List { b, a }; + Assert.That(CollectionHelpers.Equals(aThenB, bThenA), Is.True); + } - private void TestComparison(Func calOp, Func intOp) - { - int? intSome = 1; - int? intGreater = 2; + [Test] + public void CalDateTimeTests() + { + var nowLocal = DateTime.Now; + var nowUtc = nowLocal.ToUniversalTime(); - var dtSome = new CalDateTime(2018, 1, 1); - var dtGreater = new CalDateTime(2019, 1, 1); + var asLocal = new CalDateTime(nowLocal, "America/New_York"); + var asUtc = new CalDateTime(nowUtc, "UTC"); - Assert.Multiple(() => - { - Assert.That(calOp(null, null), Is.EqualTo(intOp(null, null))); - Assert.That(calOp(null, dtSome), Is.EqualTo(intOp(null, intSome))); - Assert.That(calOp(dtSome, null), Is.EqualTo(intOp(intSome, null))); - Assert.That(calOp(dtSome, dtSome), Is.EqualTo(intOp(intSome, intSome))); - Assert.That(calOp(dtSome, dtGreater), Is.EqualTo(intOp(intSome, intGreater))); - Assert.That(calOp(dtGreater, dtSome), Is.EqualTo(intOp(intGreater, intSome))); - }); - } + Assert.That(asUtc, Is.Not.EqualTo(asLocal)); + } + + private void TestComparison(Func calOp, Func intOp) + { + int? intSome = 1; + int? intGreater = 2; + + var dtSome = new CalDateTime(2018, 1, 1); + var dtGreater = new CalDateTime(2019, 1, 1); - [Test] - public void CalDateTimeComparisonOperatorTests() + Assert.Multiple(() => { - // Assumption: comparison operators on CalDateTime are expected to - // work like operators on Nullable. - TestComparison((dt1, dt2) => dt1 == dt2, (i1, i2) => i1 == i2); - TestComparison((dt1, dt2) => dt1 != dt2, (i1, i2) => i1 != i2); - TestComparison((dt1, dt2) => dt1 > dt2, (i1, i2) => i1 > i2); - TestComparison((dt1, dt2) => dt1 >= dt2, (i1, i2) => i1 >= i2); - TestComparison((dt1, dt2) => dt1 < dt2, (i1, i2) => i1 < i2); - TestComparison((dt1, dt2) => dt1 <= dt2, (i1, i2) => i1 <= i2); - } + Assert.That(calOp(null, null), Is.EqualTo(intOp(null, null))); + Assert.That(calOp(null, dtSome), Is.EqualTo(intOp(null, intSome))); + Assert.That(calOp(dtSome, null), Is.EqualTo(intOp(intSome, null))); + Assert.That(calOp(dtSome, dtSome), Is.EqualTo(intOp(intSome, intSome))); + Assert.That(calOp(dtSome, dtGreater), Is.EqualTo(intOp(intSome, intGreater))); + Assert.That(calOp(dtGreater, dtSome), Is.EqualTo(intOp(intGreater, intSome))); + }); + } + + [Test] + public void CalDateTimeComparisonOperatorTests() + { + // Assumption: comparison operators on CalDateTime are expected to + // work like operators on Nullable. + TestComparison((dt1, dt2) => dt1 == dt2, (i1, i2) => i1 == i2); + TestComparison((dt1, dt2) => dt1 != dt2, (i1, i2) => i1 != i2); + TestComparison((dt1, dt2) => dt1 > dt2, (i1, i2) => i1 > i2); + TestComparison((dt1, dt2) => dt1 >= dt2, (i1, i2) => i1 >= i2); + TestComparison((dt1, dt2) => dt1 < dt2, (i1, i2) => i1 < i2); + TestComparison((dt1, dt2) => dt1 <= dt2, (i1, i2) => i1 <= i2); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/FreeBusyTest.cs b/Ical.Net.Tests/FreeBusyTest.cs index 16293c3ff..2371c8ab0 100644 --- a/Ical.Net.Tests/FreeBusyTest.cs +++ b/Ical.Net.Tests/FreeBusyTest.cs @@ -1,33 +1,37 @@ +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using Ical.Net.CalendarComponents; using Ical.Net.DataTypes; using NUnit.Framework; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +[TestFixture] +public class FreeBusyTest { - [TestFixture] - public class FreeBusyTest + /// + /// Ensures that GetFreeBusyStatus() return the correct status. + /// + [Test, Category("FreeBusy")] + public void GetFreeBusyStatus1() { - /// - /// Ensures that GetFreeBusyStatus() return the correct status. - /// - [Test, Category("FreeBusy")] - public void GetFreeBusyStatus1() - { - Calendar cal = new Calendar(); + Calendar cal = new Calendar(); - CalendarEvent evt = cal.Create(); - evt.Summary = "Test event"; - evt.Start = new CalDateTime(2010, 10, 1, 8, 0, 0); - evt.End = new CalDateTime(2010, 10, 1, 9, 0, 0); + CalendarEvent evt = cal.Create(); + evt.Summary = "Test event"; + evt.Start = new CalDateTime(2010, 10, 1, 8, 0, 0); + evt.End = new CalDateTime(2010, 10, 1, 9, 0, 0); - var freeBusy = cal.GetFreeBusy(new CalDateTime(2010, 10, 1, 0, 0, 0), new CalDateTime(2010, 10, 7, 11, 59, 59)); - Assert.Multiple(() => - { - Assert.That(freeBusy.GetFreeBusyStatus(new CalDateTime(2010, 10, 1, 7, 59, 59)), Is.EqualTo(FreeBusyStatus.Free)); - Assert.That(freeBusy.GetFreeBusyStatus(new CalDateTime(2010, 10, 1, 8, 0, 0)), Is.EqualTo(FreeBusyStatus.Busy)); - Assert.That(freeBusy.GetFreeBusyStatus(new CalDateTime(2010, 10, 1, 8, 59, 59)), Is.EqualTo(FreeBusyStatus.Busy)); - Assert.That(freeBusy.GetFreeBusyStatus(new CalDateTime(2010, 10, 1, 9, 0, 0)), Is.EqualTo(FreeBusyStatus.Free)); - }); - } + var freeBusy = cal.GetFreeBusy(new CalDateTime(2010, 10, 1, 0, 0, 0), new CalDateTime(2010, 10, 7, 11, 59, 59)); + Assert.Multiple(() => + { + Assert.That(freeBusy.GetFreeBusyStatus(new CalDateTime(2010, 10, 1, 7, 59, 59)), Is.EqualTo(FreeBusyStatus.Free)); + Assert.That(freeBusy.GetFreeBusyStatus(new CalDateTime(2010, 10, 1, 8, 0, 0)), Is.EqualTo(FreeBusyStatus.Busy)); + Assert.That(freeBusy.GetFreeBusyStatus(new CalDateTime(2010, 10, 1, 8, 59, 59)), Is.EqualTo(FreeBusyStatus.Busy)); + Assert.That(freeBusy.GetFreeBusyStatus(new CalDateTime(2010, 10, 1, 9, 0, 0)), Is.EqualTo(FreeBusyStatus.Free)); + }); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/GetOccurrenceTests.cs b/Ical.Net.Tests/GetOccurrenceTests.cs index 66a2e1bb2..fe3f91d3d 100644 --- a/Ical.Net.Tests/GetOccurrenceTests.cs +++ b/Ical.Net.Tests/GetOccurrenceTests.cs @@ -1,100 +1,105 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Evaluation; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Evaluation; +using NUnit.Framework; + +namespace Ical.Net.Tests; -namespace Ical.Net.Tests +internal class GetOccurrenceTests { - internal class GetOccurrenceTests + public static CalendarCollection GetCalendars(string incoming) => CalendarCollection.Load(incoming); + + [Test] + public void WrongDurationTest() { - public static CalendarCollection GetCalendars(string incoming) => CalendarCollection.Load(incoming); + var firstStart = new CalDateTime(DateTime.Parse("2016-01-01")); + var firstEnd = new CalDateTime(DateTime.Parse("2016-01-05")); + var vEvent = new CalendarEvent { DtStart = firstStart, DtEnd = firstEnd, }; + + var secondStart = new CalDateTime(DateTime.Parse("2016-03-01")); + var secondEnd = new CalDateTime(DateTime.Parse("2016-03-05")); + var vEvent2 = new CalendarEvent { DtStart = secondStart, DtEnd = secondEnd, }; + + var calendar = new Calendar(); + calendar.Events.Add(vEvent); + calendar.Events.Add(vEvent2); + + var searchStart = DateTime.Parse("2015-12-29"); + var searchEnd = DateTime.Parse("2017-02-10"); + var occurrences = calendar.GetOccurrences(searchStart, searchEnd).OrderBy(o => o.Period.StartTime).ToList(); + + var firstOccurrence = occurrences.First(); + var firstStartCopy = firstStart.Copy(); + var firstEndCopy = firstEnd.Copy(); + Assert.Multiple(() => + { + Assert.That(firstOccurrence.Period.StartTime, Is.EqualTo(firstStartCopy)); + Assert.That(firstOccurrence.Period.EndTime, Is.EqualTo(firstEndCopy)); + }); + + var secondOccurrence = occurrences.Last(); + var secondStartCopy = secondStart.Copy(); + var secondEndCopy = secondEnd.Copy(); + Assert.Multiple(() => + { + Assert.That(secondOccurrence.Period.StartTime, Is.EqualTo(secondStartCopy)); + Assert.That(secondOccurrence.Period.EndTime, Is.EqualTo(secondEndCopy)); + }); + } - [Test] - public void WrongDurationTest() + [Test] + public void SkippedOccurrenceOnWeeklyPattern() + { + const int evaluationsCount = 1000; + var eventStart = new CalDateTime(new DateTime(2016, 1, 1, 10, 0, 0, DateTimeKind.Utc)); + var eventEnd = new CalDateTime(new DateTime(2016, 1, 1, 11, 0, 0, DateTimeKind.Utc)); + var vEvent = new CalendarEvent { - var firstStart = new CalDateTime(DateTime.Parse("2016-01-01")); - var firstEnd = new CalDateTime(DateTime.Parse("2016-01-05")); - var vEvent = new CalendarEvent {DtStart = firstStart, DtEnd = firstEnd,}; - - var secondStart = new CalDateTime(DateTime.Parse("2016-03-01")); - var secondEnd = new CalDateTime(DateTime.Parse("2016-03-05")); - var vEvent2 = new CalendarEvent {DtStart = secondStart, DtEnd = secondEnd,}; - - var calendar = new Calendar(); - calendar.Events.Add(vEvent); - calendar.Events.Add(vEvent2); - - var searchStart = DateTime.Parse("2015-12-29"); - var searchEnd = DateTime.Parse("2017-02-10"); - var occurrences = calendar.GetOccurrences(searchStart, searchEnd).OrderBy(o => o.Period.StartTime).ToList(); - - var firstOccurrence = occurrences.First(); - var firstStartCopy = firstStart.Copy(); - var firstEndCopy = firstEnd.Copy(); - Assert.Multiple(() => - { - Assert.That(firstOccurrence.Period.StartTime, Is.EqualTo(firstStartCopy)); - Assert.That(firstOccurrence.Period.EndTime, Is.EqualTo(firstEndCopy)); - }); - - var secondOccurrence = occurrences.Last(); - var secondStartCopy = secondStart.Copy(); - var secondEndCopy = secondEnd.Copy(); - Assert.Multiple(() => - { - Assert.That(secondOccurrence.Period.StartTime, Is.EqualTo(secondStartCopy)); - Assert.That(secondOccurrence.Period.EndTime, Is.EqualTo(secondEndCopy)); - }); - } + DtStart = eventStart, + DtEnd = eventEnd, + }; - [Test] - public void SkippedOccurrenceOnWeeklyPattern() + var pattern = new RecurrencePattern + { + Frequency = FrequencyType.Weekly, + ByDay = new List { new WeekDay(DayOfWeek.Friday) } + }; + vEvent.RecurrenceRules.Add(pattern); + var calendar = new Calendar(); + calendar.Events.Add(vEvent); + + var intervalStart = eventStart; + var intervalEnd = intervalStart.AddDays(7 * evaluationsCount); + + var occurrences = RecurrenceUtil.GetOccurrences( + recurrable: vEvent, + periodStart: intervalStart, + periodEnd: intervalEnd, + includeReferenceDateInResults: false); + var occurrenceSet = new HashSet(occurrences.Select(o => o.Period.StartTime)); + + Assert.That(occurrenceSet, Has.Count.EqualTo(evaluationsCount)); + + for (var currentOccurrence = intervalStart; currentOccurrence.CompareTo(intervalEnd) < 0; currentOccurrence = (CalDateTime) currentOccurrence.AddDays(7)) { - const int evaluationsCount = 1000; - var eventStart = new CalDateTime(new DateTime(2016, 1, 1, 10, 0, 0, DateTimeKind.Utc)); - var eventEnd = new CalDateTime(new DateTime(2016, 1, 1, 11, 0, 0, DateTimeKind.Utc)); - var vEvent = new CalendarEvent - { - DtStart = eventStart, - DtEnd = eventEnd, - }; - - var pattern = new RecurrencePattern - { - Frequency = FrequencyType.Weekly, - ByDay = new List { new WeekDay(DayOfWeek.Friday) } - }; - vEvent.RecurrenceRules.Add(pattern); - var calendar = new Calendar(); - calendar.Events.Add(vEvent); - - var intervalStart = eventStart; - var intervalEnd = intervalStart.AddDays(7 * evaluationsCount); - - var occurrences = RecurrenceUtil.GetOccurrences( - recurrable: vEvent, - periodStart: intervalStart, - periodEnd: intervalEnd, - includeReferenceDateInResults: false); - var occurrenceSet = new HashSet(occurrences.Select(o => o.Period.StartTime)); - - Assert.That(occurrenceSet, Has.Count.EqualTo(evaluationsCount)); - - for (var currentOccurrence = intervalStart; currentOccurrence.CompareTo(intervalEnd) < 0; currentOccurrence = (CalDateTime)currentOccurrence.AddDays(7)) - { - var contains = occurrenceSet.Contains(currentOccurrence); - Assert.That(contains, Is.True, $"Collection does not contain {currentOccurrence}, but it is a {currentOccurrence.DayOfWeek}"); - } + var contains = occurrenceSet.Contains(currentOccurrence); + Assert.That(contains, Is.True, $"Collection does not contain {currentOccurrence}, but it is a {currentOccurrence.DayOfWeek}"); } + } - [Test] - public void EnumerationChangedException() - { - const string ical = @"BEGIN:VCALENDAR + [Test] + public void EnumerationChangedException() + { + const string ical = @"BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN @@ -138,19 +143,19 @@ public void EnumerationChangedException() END:VCALENDAR"; - var calendar = GetCalendars(ical); - var date = new DateTime(2016, 10, 11); - var occurrences = calendar.GetOccurrences(date); + var calendar = GetCalendars(ical); + var date = new DateTime(2016, 10, 11); + var occurrences = calendar.GetOccurrences(date); - //We really want to make sure this doesn't explode - Assert.That(occurrences, Has.Count.EqualTo(1)); - } + //We really want to make sure this doesn't explode + Assert.That(occurrences, Has.Count.EqualTo(1)); + } - [Test] - public void GetOccurrencesShouldEnumerate() - { - const string ical = - @"BEGIN:VCALENDAR + [Test] + public void GetOccurrencesShouldEnumerate() + { + const string ical = + @"BEGIN:VCALENDAR PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 2.2//EN VERSION:2.0 BEGIN:VTIMEZONE @@ -202,11 +207,10 @@ public void GetOccurrencesShouldEnumerate() END:VCALENDAR "; - var collection = Calendar.Load(ical); - var startCheck = new DateTime(2016, 11, 11); - var occurrences = collection.GetOccurrences(startCheck, startCheck.AddMonths(1)); + var collection = Calendar.Load(ical); + var startCheck = new DateTime(2016, 11, 11); + var occurrences = collection.GetOccurrences(startCheck, startCheck.AddMonths(1)); - Assert.That(occurrences.Count == 4, Is.True); - } + Assert.That(occurrences.Count == 4, Is.True); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/IcsFiles.cs b/Ical.Net.Tests/IcsFiles.cs index 64ddc45ce..135080b2f 100644 --- a/Ical.Net.Tests/IcsFiles.cs +++ b/Ical.Net.Tests/IcsFiles.cs @@ -1,167 +1,171 @@ -using System.IO; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System.IO; using System.Reflection; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +internal class IcsFiles { - internal class IcsFiles - { - private static readonly Assembly _assembly = typeof(IcsFiles).GetTypeInfo().Assembly; + private static readonly Assembly _assembly = typeof(IcsFiles).GetTypeInfo().Assembly; - internal static string ReadStream(string manifestResource) + internal static string ReadStream(string manifestResource) + { + using (var stream = _assembly.GetManifestResourceStream(manifestResource)) { - using (var stream = _assembly.GetManifestResourceStream(manifestResource)) - { - return new StreamReader(stream).ReadToEnd(); - } + return new StreamReader(stream).ReadToEnd(); } + } - internal static string Alarm1 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM1.ics"); - internal static string Alarm2 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM2.ics"); - internal static string Alarm3 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM3.ics"); - internal static string Alarm4 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM4.ics"); - internal static string Alarm5 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM5.ics"); - internal static string Alarm6 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM6.ics"); - internal static string Alarm7 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM7.ics"); - internal static string Attachment3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Attachment3.ics"); - internal static string Attachment4 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Attachment4.ics"); - internal static string Attendee1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Attendee1.ics"); - internal static string Attendee2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Attendee2.ics"); - internal static string Bug1741093 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug1741093.ics"); - internal static string Bug2033495 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Bug2033495.ics"); - internal static string Bug2148092 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Bug2148092.ics"); - internal static string Bug2912657 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug2912657.ics"); - internal static string Bug2916581 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug2916581.ics"); - internal static string Bug2938007 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Bug2938007.ics"); - internal static string Bug2959692 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug2959692.ics"); - internal static string Bug2966236 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug2966236.ics"); - internal static string Bug3007244 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug3007244.ics"); - internal static string ByMonth1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.ByMonth1.ics"); - internal static string ByMonth2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.ByMonth2.ics"); - internal static string ByMonthDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.ByMonthDay1.ics"); - internal static string Calendar1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Calendar1.ics"); - internal static string CalendarParameters2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.CalendarParameters2.ics"); - internal static string CaseInsensitive1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.CaseInsensitive1.ics"); - internal static string CaseInsensitive2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.CaseInsensitive2.ics"); - internal static string CaseInsensitive3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.CaseInsensitive3.ics"); - internal static string CaseInsensitive4 => ReadStream("Ical.Net.Tests.Calendars.Serialization.CaseInsensitive4.ics"); - internal static string Categories1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Categories1.ics"); - internal static string Daily1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Daily1.ics"); - internal static string DailyByDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyByDay1.ics"); - internal static string DailyByHourMinute1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyByHourMinute1.ics"); - internal static string DailyCount1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyCount1.ics"); - internal static string DailyCount2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyCount2.ics"); - internal static string DailyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyInterval1.ics"); - internal static string DailyInterval2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyInterval2.ics"); - internal static string DailyUntil1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyUntil1.ics"); - internal static string DateTime1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.DateTime1.ics"); - internal static string DateTime2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.DateTime2.ics"); - internal static string Duration1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Duration1.ics"); - internal static string Empty1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Empty1.ics"); - internal static string EmptyLines1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.EmptyLines1.ics"); - internal static string EmptyLines2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.EmptyLines2.ics"); - internal static string EmptyLines3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.EmptyLines3.ics"); - internal static string EmptyLines4 => ReadStream("Ical.Net.Tests.Calendars.Serialization.EmptyLines4.ics"); - internal static string Encoding1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Encoding1.ics"); - internal static string Encoding2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Encoding2.ics"); - internal static string Encoding3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Encoding3.ics"); - internal static string Event1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Event1.ics"); - internal static string Event2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Event2.ics"); - internal static string Event3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Event3.ics"); - internal static string Event4 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Event4.ics"); - internal static string EventStatus => ReadStream("Ical.Net.Tests.Calendars.Serialization.EventStatus.ics"); - internal static string GeographicLocation1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.GeographicLocation1.ics"); - internal static string Google1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Google1.ics"); - internal static string Hourly1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Hourly1.ics"); - internal static string HourlyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.HourlyInterval1.ics"); - internal static string HourlyInterval2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.HourlyInterval2.ics"); - internal static string HourlyUntil1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.HourlyUntil1.ics"); - internal static string Journal1 => ReadStream("Ical.Net.Tests.Calendars.Journal.JOURNAL1.ics"); - internal static string Journal2 => ReadStream("Ical.Net.Tests.Calendars.Journal.JOURNAL2.ics"); - internal static string Language1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Language1.ics"); - internal static string Language2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Language2.ics"); - internal static string Language3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Language3.ics"); - internal static string Language4 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Language4.ics"); - internal static string Minutely1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Minutely1.ics"); - internal static string MinutelyByHour1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyByHour1.ics"); - internal static string MinutelyCount1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyCount1.ics"); - internal static string MinutelyCount2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyCount2.ics"); - internal static string MinutelyCount3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyCount3.ics"); - internal static string MinutelyCount4 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyCount4.ics"); - internal static string MinutelyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyInterval1.ics"); - internal static string Monthly1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Monthly1.ics"); - internal static string MonthlyByDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyByDay1.ics"); - internal static string MonthlyByMonthDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyByMonthDay1.ics"); - internal static string MonthlyByMonthDay2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyByMonthDay2.ics"); - internal static string MonthlyBySetPos1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyBySetPos1.ics"); - internal static string MonthlyBySetPos2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyBySetPos2.ics"); - internal static string MonthlyCountByDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByDay1.ics"); - internal static string MonthlyCountByDay2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByDay2.ics"); - internal static string MonthlyCountByDay3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByDay3.ics"); - internal static string MonthlyCountByMonthDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByMonthDay1.ics"); - internal static string MonthlyCountByMonthDay2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByMonthDay2.ics"); - internal static string MonthlyCountByMonthDay3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByMonthDay3.ics"); - internal static string MonthlyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyInterval1.ics"); - internal static string MonthlyUntilByDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyUntilByDay1.ics"); - internal static string Outlook2007LineFolds => ReadStream("Ical.Net.Tests.Calendars.Serialization.Outlook2007LineFolds.ics"); - internal static string Parameter1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Parameter1.ics"); - internal static string Parameter2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Parameter2.ics"); - internal static string Parse1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Parse1.ics"); - internal static string Parse17 => ReadStream("Ical.Net.Tests.Calendars.Serialization.PARSE17.ics"); - internal static string ProdId1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.ProdID1.ics"); - internal static string ProdId2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.ProdID2.ics"); - internal static string Property1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Property1.ics"); - internal static string RecurrenceDates1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.RecurrenceDates1.ics"); - internal static string RequestStatus1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.RequestStatus1.ics"); - internal static string Secondly1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Secondly1.ics"); - internal static string TimeZone1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.TimeZone1.ics"); - internal static string TimeZone2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.TimeZone2.ics"); - internal static string TimeZone3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.TimeZone3.ics"); - internal static string Todo1 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo1.ics"); - internal static string Todo2 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo2.ics"); - internal static string Todo3 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo3.ics"); - internal static string Todo4 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo4.ics"); - internal static string Todo5 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo5.ics"); - internal static string Todo6 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo6.ics"); - internal static string Todo7 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo7.ics"); - internal static string Todo8 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo8.ics"); - internal static string Todo9 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo9.ics"); - internal static string Transparency1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Transparency1.ics"); - internal static string Transparency2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Transparency2.ics"); - internal static string Trigger1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Trigger1.ics"); - internal static string UsHolidays => ReadStream("Ical.Net.Tests.Calendars.Serialization.USHolidays.ics"); - internal static string WeeklyCount1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyCount1.ics"); - internal static string WeeklyCountWkst1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyCountWkst1.ics"); - internal static string WeeklyCountWkst2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyCountWkst2.ics"); - internal static string WeeklyCountWkst3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyCountWkst3.ics"); - internal static string WeeklyCountWkst4 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyCountWkst4.ics"); - internal static string WeeklyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyInterval1.ics"); - internal static string WeeklyUntil1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyUntil1.ics"); - internal static string WeeklyUntilWkst1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyUntilWkst1.ics"); - internal static string WeeklyUntilWkst2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyUntilWkst2.ics"); - internal static string WeeklyWeekStartsLastYear => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyWeekStartsLastYear.ics"); - internal static string WeeklyWkst1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyWkst1.ics"); - internal static string XProperty1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.XProperty1.ics"); - internal static string XProperty2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.XProperty2.ics"); - internal static string Yearly1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Yearly1.ics"); - internal static string YearlyByDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByDay1.ics"); - internal static string YearlyByMonth1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByMonth1.ics"); - internal static string YearlyByMonth2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByMonth2.ics"); - internal static string YearlyByMonth3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByMonth3.ics"); - internal static string YearlyByMonthDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByMonthDay1.ics"); - internal static string YearlyBySetPos1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyBySetPos1.ics"); - internal static string YearlyByWeekNo1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByWeekNo1.ics"); - internal static string YearlyByWeekNo2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByWeekNo2.ics"); - internal static string YearlyByWeekNo3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByWeekNo3.ics"); - internal static string YearlyByWeekNo4 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByWeekNo4.ics"); - internal static string YearlyByWeekNo5 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByWeekNo5.ics"); - internal static string YearlyComplex1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyComplex1.ics"); - internal static string YearlyCountByMonth1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyCountByMonth1.ics"); - internal static string YearlyCountByYearDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyCountByYearDay1.ics"); - internal static string YearlyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyInterval1.ics"); + internal static string Alarm1 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM1.ics"); + internal static string Alarm2 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM2.ics"); + internal static string Alarm3 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM3.ics"); + internal static string Alarm4 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM4.ics"); + internal static string Alarm5 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM5.ics"); + internal static string Alarm6 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM6.ics"); + internal static string Alarm7 => ReadStream("Ical.Net.Tests.Calendars.Alarm.ALARM7.ics"); + internal static string Attachment3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Attachment3.ics"); + internal static string Attachment4 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Attachment4.ics"); + internal static string Attendee1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Attendee1.ics"); + internal static string Attendee2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Attendee2.ics"); + internal static string Bug1741093 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug1741093.ics"); + internal static string Bug2033495 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Bug2033495.ics"); + internal static string Bug2148092 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Bug2148092.ics"); + internal static string Bug2912657 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug2912657.ics"); + internal static string Bug2916581 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug2916581.ics"); + internal static string Bug2938007 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Bug2938007.ics"); + internal static string Bug2959692 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug2959692.ics"); + internal static string Bug2966236 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug2966236.ics"); + internal static string Bug3007244 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Bug3007244.ics"); + internal static string ByMonth1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.ByMonth1.ics"); + internal static string ByMonth2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.ByMonth2.ics"); + internal static string ByMonthDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.ByMonthDay1.ics"); + internal static string Calendar1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Calendar1.ics"); + internal static string CalendarParameters2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.CalendarParameters2.ics"); + internal static string CaseInsensitive1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.CaseInsensitive1.ics"); + internal static string CaseInsensitive2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.CaseInsensitive2.ics"); + internal static string CaseInsensitive3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.CaseInsensitive3.ics"); + internal static string CaseInsensitive4 => ReadStream("Ical.Net.Tests.Calendars.Serialization.CaseInsensitive4.ics"); + internal static string Categories1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Categories1.ics"); + internal static string Daily1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Daily1.ics"); + internal static string DailyByDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyByDay1.ics"); + internal static string DailyByHourMinute1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyByHourMinute1.ics"); + internal static string DailyCount1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyCount1.ics"); + internal static string DailyCount2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyCount2.ics"); + internal static string DailyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyInterval1.ics"); + internal static string DailyInterval2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyInterval2.ics"); + internal static string DailyUntil1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.DailyUntil1.ics"); + internal static string DateTime1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.DateTime1.ics"); + internal static string DateTime2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.DateTime2.ics"); + internal static string Duration1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Duration1.ics"); + internal static string Empty1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Empty1.ics"); + internal static string EmptyLines1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.EmptyLines1.ics"); + internal static string EmptyLines2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.EmptyLines2.ics"); + internal static string EmptyLines3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.EmptyLines3.ics"); + internal static string EmptyLines4 => ReadStream("Ical.Net.Tests.Calendars.Serialization.EmptyLines4.ics"); + internal static string Encoding1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Encoding1.ics"); + internal static string Encoding2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Encoding2.ics"); + internal static string Encoding3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Encoding3.ics"); + internal static string Event1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Event1.ics"); + internal static string Event2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Event2.ics"); + internal static string Event3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Event3.ics"); + internal static string Event4 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Event4.ics"); + internal static string EventStatus => ReadStream("Ical.Net.Tests.Calendars.Serialization.EventStatus.ics"); + internal static string GeographicLocation1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.GeographicLocation1.ics"); + internal static string Google1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Google1.ics"); + internal static string Hourly1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Hourly1.ics"); + internal static string HourlyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.HourlyInterval1.ics"); + internal static string HourlyInterval2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.HourlyInterval2.ics"); + internal static string HourlyUntil1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.HourlyUntil1.ics"); + internal static string Journal1 => ReadStream("Ical.Net.Tests.Calendars.Journal.JOURNAL1.ics"); + internal static string Journal2 => ReadStream("Ical.Net.Tests.Calendars.Journal.JOURNAL2.ics"); + internal static string Language1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Language1.ics"); + internal static string Language2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Language2.ics"); + internal static string Language3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Language3.ics"); + internal static string Language4 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Language4.ics"); + internal static string Minutely1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Minutely1.ics"); + internal static string MinutelyByHour1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyByHour1.ics"); + internal static string MinutelyCount1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyCount1.ics"); + internal static string MinutelyCount2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyCount2.ics"); + internal static string MinutelyCount3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyCount3.ics"); + internal static string MinutelyCount4 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyCount4.ics"); + internal static string MinutelyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MinutelyInterval1.ics"); + internal static string Monthly1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Monthly1.ics"); + internal static string MonthlyByDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyByDay1.ics"); + internal static string MonthlyByMonthDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyByMonthDay1.ics"); + internal static string MonthlyByMonthDay2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyByMonthDay2.ics"); + internal static string MonthlyBySetPos1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyBySetPos1.ics"); + internal static string MonthlyBySetPos2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyBySetPos2.ics"); + internal static string MonthlyCountByDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByDay1.ics"); + internal static string MonthlyCountByDay2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByDay2.ics"); + internal static string MonthlyCountByDay3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByDay3.ics"); + internal static string MonthlyCountByMonthDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByMonthDay1.ics"); + internal static string MonthlyCountByMonthDay2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByMonthDay2.ics"); + internal static string MonthlyCountByMonthDay3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyCountByMonthDay3.ics"); + internal static string MonthlyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyInterval1.ics"); + internal static string MonthlyUntilByDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.MonthlyUntilByDay1.ics"); + internal static string Outlook2007LineFolds => ReadStream("Ical.Net.Tests.Calendars.Serialization.Outlook2007LineFolds.ics"); + internal static string Parameter1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Parameter1.ics"); + internal static string Parameter2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Parameter2.ics"); + internal static string Parse1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Parse1.ics"); + internal static string Parse17 => ReadStream("Ical.Net.Tests.Calendars.Serialization.PARSE17.ics"); + internal static string ProdId1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.ProdID1.ics"); + internal static string ProdId2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.ProdID2.ics"); + internal static string Property1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Property1.ics"); + internal static string RecurrenceDates1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.RecurrenceDates1.ics"); + internal static string RequestStatus1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.RequestStatus1.ics"); + internal static string Secondly1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Secondly1.ics"); + internal static string TimeZone1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.TimeZone1.ics"); + internal static string TimeZone2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.TimeZone2.ics"); + internal static string TimeZone3 => ReadStream("Ical.Net.Tests.Calendars.Serialization.TimeZone3.ics"); + internal static string Todo1 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo1.ics"); + internal static string Todo2 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo2.ics"); + internal static string Todo3 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo3.ics"); + internal static string Todo4 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo4.ics"); + internal static string Todo5 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo5.ics"); + internal static string Todo6 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo6.ics"); + internal static string Todo7 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo7.ics"); + internal static string Todo8 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo8.ics"); + internal static string Todo9 => ReadStream("Ical.Net.Tests.Calendars.Todo.Todo9.ics"); + internal static string Transparency1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Transparency1.ics"); + internal static string Transparency2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Transparency2.ics"); + internal static string Trigger1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.Trigger1.ics"); + internal static string UsHolidays => ReadStream("Ical.Net.Tests.Calendars.Serialization.USHolidays.ics"); + internal static string WeeklyCount1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyCount1.ics"); + internal static string WeeklyCountWkst1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyCountWkst1.ics"); + internal static string WeeklyCountWkst2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyCountWkst2.ics"); + internal static string WeeklyCountWkst3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyCountWkst3.ics"); + internal static string WeeklyCountWkst4 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyCountWkst4.ics"); + internal static string WeeklyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyInterval1.ics"); + internal static string WeeklyUntil1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyUntil1.ics"); + internal static string WeeklyUntilWkst1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyUntilWkst1.ics"); + internal static string WeeklyUntilWkst2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyUntilWkst2.ics"); + internal static string WeeklyWeekStartsLastYear => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyWeekStartsLastYear.ics"); + internal static string WeeklyWkst1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.WeeklyWkst1.ics"); + internal static string XProperty1 => ReadStream("Ical.Net.Tests.Calendars.Serialization.XProperty1.ics"); + internal static string XProperty2 => ReadStream("Ical.Net.Tests.Calendars.Serialization.XProperty2.ics"); + internal static string Yearly1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.Yearly1.ics"); + internal static string YearlyByDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByDay1.ics"); + internal static string YearlyByMonth1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByMonth1.ics"); + internal static string YearlyByMonth2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByMonth2.ics"); + internal static string YearlyByMonth3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByMonth3.ics"); + internal static string YearlyByMonthDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByMonthDay1.ics"); + internal static string YearlyBySetPos1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyBySetPos1.ics"); + internal static string YearlyByWeekNo1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByWeekNo1.ics"); + internal static string YearlyByWeekNo2 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByWeekNo2.ics"); + internal static string YearlyByWeekNo3 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByWeekNo3.ics"); + internal static string YearlyByWeekNo4 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByWeekNo4.ics"); + internal static string YearlyByWeekNo5 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyByWeekNo5.ics"); + internal static string YearlyComplex1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyComplex1.ics"); + internal static string YearlyCountByMonth1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyCountByMonth1.ics"); + internal static string YearlyCountByYearDay1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyCountByYearDay1.ics"); + internal static string YearlyInterval1 => ReadStream("Ical.Net.Tests.Calendars.Recurrence.YearlyInterval1.ics"); - internal static string RecurrrenceTestCases => ReadStream("Ical.Net.Tests.Calendars.Recurrence.RecurrenceTestCases.txt"); + internal static string RecurrrenceTestCases => ReadStream("Ical.Net.Tests.Calendars.Recurrence.RecurrenceTestCases.txt"); - internal static string LibicalIcalrecurTest => ReadStream("Ical.Net.Tests.contrib.libical.icalrecur_test.out"); + internal static string LibicalIcalrecurTest => ReadStream("Ical.Net.Tests.contrib.libical.icalrecur_test.out"); - } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/JournalTest.cs b/Ical.Net.Tests/JournalTest.cs index 14b2f3b29..7fa4fff86 100644 --- a/Ical.Net.Tests/JournalTest.cs +++ b/Ical.Net.Tests/JournalTest.cs @@ -1,68 +1,72 @@ -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Linq; +using NUnit.Framework; + +namespace Ical.Net.Tests; -namespace Ical.Net.Tests +[TestFixture] +public class JournalTest { - [TestFixture] - public class JournalTest + [Test, Category("Journal")] + public void Journal1() { - [Test, Category("Journal")] - public void Journal1() + var iCal = Calendar.Load(IcsFiles.Journal1); + ProgramTest.TestCal(iCal); + Assert.That(iCal.Journals, Has.Count.EqualTo(1)); + var j = iCal.Journals[0]; + + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.Journal1); - ProgramTest.TestCal(iCal); - Assert.That(iCal.Journals, Has.Count.EqualTo(1)); - var j = iCal.Journals[0]; + Assert.That(j, Is.Not.Null, "Journal entry was null"); + Assert.That(j.Status, Is.EqualTo(JournalStatus.Draft), "Journal entry should have been in DRAFT status, but it was in " + j.Status + " status."); + Assert.That(j.Class, Is.EqualTo("PUBLIC"), "Journal class should have been PUBLIC, but was " + j.Class + "."); + }); + Assert.That(j.Start, Is.Null); + } - Assert.Multiple(() => - { - Assert.That(j, Is.Not.Null, "Journal entry was null"); - Assert.That(j.Status, Is.EqualTo(JournalStatus.Draft), "Journal entry should have been in DRAFT status, but it was in " + j.Status + " status."); - Assert.That(j.Class, Is.EqualTo("PUBLIC"), "Journal class should have been PUBLIC, but was " + j.Class + "."); - }); - Assert.That(j.Start, Is.Null); - } + [Test, Category("Journal")] + public void Journal2() + { + var iCal = Calendar.Load(IcsFiles.Journal2); + ProgramTest.TestCal(iCal); + Assert.That(iCal.Journals, Has.Count.EqualTo(1)); + var j = iCal.Journals.First(); - [Test, Category("Journal")] - public void Journal2() + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.Journal2); - ProgramTest.TestCal(iCal); - Assert.That(iCal.Journals, Has.Count.EqualTo(1)); - var j = iCal.Journals.First(); - - Assert.Multiple(() => - { - Assert.That(j, Is.Not.Null, "Journal entry was null"); - Assert.That(j.Status, Is.EqualTo(JournalStatus.Final), "Journal entry should have been in FINAL status, but it was in " + j.Status + " status."); - Assert.That(j.Class, Is.EqualTo("PRIVATE"), "Journal class should have been PRIVATE, but was " + j.Class + "."); - Assert.That(j.Organizer.CommonName, Is.EqualTo("JohnSmith"), "Organizer common name should have been JohnSmith, but was " + j.Organizer.CommonName); - Assert.That( - string.Equals( - j.Organizer.SentBy.OriginalString, - "mailto:jane_doe@host.com", - StringComparison.OrdinalIgnoreCase), - Is.True, - "Organizer should have had been SENT-BY 'mailto:jane_doe@host.com'; it was sent by '" + j.Organizer.SentBy + "'"); - Assert.That( - string.Equals( - j.Organizer.DirectoryEntry.OriginalString, - "ldap://host.com:6666/o=3DDC%20Associates,c=3DUS??(cn=3DJohn%20Smith)", - StringComparison.OrdinalIgnoreCase), - Is.True, - "Organizer's directory entry should have been 'ldap://host.com:6666/o=3DDC%20Associates,c=3DUS??(cn=3DJohn%20Smith)', but it was '" + j.Organizer.DirectoryEntry + "'"); - Assert.That( - j.Organizer.Value.OriginalString, - Is.EqualTo("MAILTO:jsmith@host.com")); - Assert.That( - j.Organizer.Value.UserInfo, - Is.EqualTo("jsmith")); - Assert.That( - j.Organizer.Value.Host, - Is.EqualTo("host.com")); - Assert.That(j.Start, Is.Null); - }); - } + Assert.That(j, Is.Not.Null, "Journal entry was null"); + Assert.That(j.Status, Is.EqualTo(JournalStatus.Final), "Journal entry should have been in FINAL status, but it was in " + j.Status + " status."); + Assert.That(j.Class, Is.EqualTo("PRIVATE"), "Journal class should have been PRIVATE, but was " + j.Class + "."); + Assert.That(j.Organizer.CommonName, Is.EqualTo("JohnSmith"), "Organizer common name should have been JohnSmith, but was " + j.Organizer.CommonName); + Assert.That( + string.Equals( + j.Organizer.SentBy.OriginalString, + "mailto:jane_doe@host.com", + StringComparison.OrdinalIgnoreCase), + Is.True, + "Organizer should have had been SENT-BY 'mailto:jane_doe@host.com'; it was sent by '" + j.Organizer.SentBy + "'"); + Assert.That( + string.Equals( + j.Organizer.DirectoryEntry.OriginalString, + "ldap://host.com:6666/o=3DDC%20Associates,c=3DUS??(cn=3DJohn%20Smith)", + StringComparison.OrdinalIgnoreCase), + Is.True, + "Organizer's directory entry should have been 'ldap://host.com:6666/o=3DDC%20Associates,c=3DUS??(cn=3DJohn%20Smith)', but it was '" + j.Organizer.DirectoryEntry + "'"); + Assert.That( + j.Organizer.Value.OriginalString, + Is.EqualTo("MAILTO:jsmith@host.com")); + Assert.That( + j.Organizer.Value.UserInfo, + Is.EqualTo("jsmith")); + Assert.That( + j.Organizer.Value.Host, + Is.EqualTo("host.com")); + Assert.That(j.Start, Is.Null); + }); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/ProgramTest.cs b/Ical.Net.Tests/ProgramTest.cs index f35b5eeaf..f842f0c91 100644 --- a/Ical.Net.Tests/ProgramTest.cs +++ b/Ical.Net.Tests/ProgramTest.cs @@ -1,179 +1,183 @@ -using Ical.Net.DataTypes; -using Ical.Net.Utility; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Linq; +using Ical.Net.DataTypes; +using Ical.Net.Utility; +using NUnit.Framework; + +namespace Ical.Net.Tests; -namespace Ical.Net.Tests +[TestFixture] +public class ProgramTest { - [TestFixture] - public class ProgramTest + [Test] + public void LoadAndDisplayCalendar() { - [Test] - public void LoadAndDisplayCalendar() - { - // The following code loads and displays an iCalendar - // with US Holidays for 2006. - var iCal = Calendar.Load(IcsFiles.UsHolidays); - Assert.That(iCal, Is.Not.Null, "iCalendar did not load."); - } + // The following code loads and displays an iCalendar + // with US Holidays for 2006. + var iCal = Calendar.Load(IcsFiles.UsHolidays); + Assert.That(iCal, Is.Not.Null, "iCalendar did not load."); + } - private const string _tzid = "US-Eastern"; + private const string _tzid = "US-Eastern"; - public static void TestCal(Calendar cal) - { - Assert.That(cal, Is.Not.Null, "The iCalendar was not loaded"); - if (cal.Events.Count > 0) - Assert.That(cal.Events.Count == 1, Is.True, "Calendar should contain 1 event; however, the iCalendar loaded " + cal.Events.Count + " events"); - else if (cal.Todos.Count > 0) - Assert.That(cal.Todos.Count == 1, Is.True, "Calendar should contain 1 todo; however, the iCalendar loaded " + cal.Todos.Count + " todos"); - } + public static void TestCal(Calendar cal) + { + Assert.That(cal, Is.Not.Null, "The iCalendar was not loaded"); + if (cal.Events.Count > 0) + Assert.That(cal.Events.Count == 1, Is.True, "Calendar should contain 1 event; however, the iCalendar loaded " + cal.Events.Count + " events"); + else if (cal.Todos.Count > 0) + Assert.That(cal.Todos.Count == 1, Is.True, "Calendar should contain 1 todo; however, the iCalendar loaded " + cal.Todos.Count + " todos"); + } - /// - /// The following test is an aggregate of MonthlyCountByMonthDay3() and MonthlyByDay1() in the - /// - [Test] - public void Merge1() - { - var iCal1 = Calendar.Load(IcsFiles.MonthlyCountByMonthDay3); - var iCal2 = Calendar.Load(IcsFiles.MonthlyByDay1); + /// + /// The following test is an aggregate of MonthlyCountByMonthDay3() and MonthlyByDay1() in the + /// + [Test] + public void Merge1() + { + var iCal1 = Calendar.Load(IcsFiles.MonthlyCountByMonthDay3); + var iCal2 = Calendar.Load(IcsFiles.MonthlyByDay1); - // Change the UID of the 2nd event to make sure it's different - iCal2.Events[iCal1.Events[0].Uid].Uid = "1234567890"; - iCal1.MergeWith(iCal2); + // Change the UID of the 2nd event to make sure it's different + iCal2.Events[iCal1.Events[0].Uid].Uid = "1234567890"; + iCal1.MergeWith(iCal2); - var evt1 = iCal1.Events.First(); - var evt2 = iCal1.Events.Skip(1).First(); + var evt1 = iCal1.Events.First(); + var evt2 = iCal1.Events.Skip(1).First(); - // Get occurrences for the first event - var occurrences = evt1.GetOccurrences( - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(2000, 1, 1, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); + // Get occurrences for the first event + var occurrences = evt1.GetOccurrences( + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(2000, 1, 1, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); - var dateTimes = new[] - { - new CalDateTime(1997, 9, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 11, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 13, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 15, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 10, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 11, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 12, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 13, 9, 0, 0, _tzid) - }; - - var timeZones = new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - }; - - for (var i = 0; i < dateTimes.Length; i++) - { - IDateTime dt = dateTimes[i]; - var start = occurrences[i].Period.StartTime; - Assert.That(start, Is.EqualTo(dt)); + var dateTimes = new[] + { + new CalDateTime(1997, 9, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 11, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 13, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 15, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 10, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 11, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 12, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 13, 9, 0, 0, _tzid) + }; + + var timeZones = new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + }; + + for (var i = 0; i < dateTimes.Length; i++) + { + IDateTime dt = dateTimes[i]; + var start = occurrences[i].Period.StartTime; + Assert.That(start, Is.EqualTo(dt)); - var expectedZone = DateUtil.GetZone(dt.TimeZoneName); - var actualZone = DateUtil.GetZone(timeZones[i]); + var expectedZone = DateUtil.GetZone(dt.TimeZoneName); + var actualZone = DateUtil.GetZone(timeZones[i]); - //Assert.AreEqual(); + //Assert.AreEqual(); - //Normalize the time zones and then compare equality - Assert.That(actualZone, Is.EqualTo(expectedZone)); + //Normalize the time zones and then compare equality + Assert.That(actualZone, Is.EqualTo(expectedZone)); - //Assert.IsTrue(dt.TimeZoneName == TimeZones[i], "Event " + dt + " should occur in the " + TimeZones[i] + " timezone"); - } + //Assert.IsTrue(dt.TimeZoneName == TimeZones[i], "Event " + dt + " should occur in the " + TimeZones[i] + " timezone"); + } - Assert.That(occurrences.Count == dateTimes.Length, Is.True, "There should be exactly " + dateTimes.Length + " occurrences; there were " + occurrences.Count); + Assert.That(occurrences.Count == dateTimes.Length, Is.True, "There should be exactly " + dateTimes.Length + " occurrences; there were " + occurrences.Count); - // Get occurrences for the 2nd event - occurrences = evt2.GetOccurrences( - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1998, 4, 1, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); + // Get occurrences for the 2nd event + occurrences = evt2.GetOccurrences( + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1998, 4, 1, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); - var dateTimes1 = new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 9, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 23, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 4, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 11, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 18, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 25, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 6, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 13, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 20, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 27, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 3, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 10, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 17, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 24, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 31, 9, 0, 0, _tzid) - }; - - var timeZones1 = new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - }; - - for (var i = 0; i < dateTimes1.Length; i++) + var dateTimes1 = new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 9, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 23, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 4, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 11, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 18, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 25, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 6, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 13, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 20, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 27, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 3, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 10, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 17, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 24, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 31, 9, 0, 0, _tzid) + }; + + var timeZones1 = new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + }; + + for (var i = 0; i < dateTimes1.Length; i++) + { + IDateTime dt = dateTimes1[i]; + var start = occurrences[i].Period.StartTime; + Assert.Multiple(() => { - IDateTime dt = dateTimes1[i]; - var start = occurrences[i].Period.StartTime; - Assert.Multiple(() => - { - Assert.That(start, Is.EqualTo(dt)); - Assert.That(dt.TimeZoneName == timeZones1[i], Is.True, "Event " + dt + " should occur in the " + timeZones1[i] + " timezone"); - }); - } - - Assert.That(occurrences, Has.Count.EqualTo(dateTimes1.Length), "There should be exactly " + dateTimes1.Length + " occurrences; there were " + occurrences.Count); + Assert.That(start, Is.EqualTo(dt)); + Assert.That(dt.TimeZoneName == timeZones1[i], Is.True, "Event " + dt + " should occur in the " + timeZones1[i] + " timezone"); + }); } - [Test] - public void SystemTimeZone3() + Assert.That(occurrences, Has.Count.EqualTo(dateTimes1.Length), "There should be exactly " + dateTimes1.Length + " occurrences; there were " + occurrences.Count); + } + + [Test] + public void SystemTimeZone3() + { + // Per Jon Udell's test, we should be able to get all + // system time zones on the machine and ensure they + // are properly translated. + var zones = TimeZoneInfo.GetSystemTimeZones(); + foreach (var zone in zones) { - // Per Jon Udell's test, we should be able to get all - // system time zones on the machine and ensure they - // are properly translated. - var zones = TimeZoneInfo.GetSystemTimeZones(); - foreach (var zone in zones) + Assert.That(() => { - Assert.That(() => - { - TimeZoneInfo.FindSystemTimeZoneById(zone.Id); - }, Throws.Nothing, "Time zone should be found."); - } + TimeZoneInfo.FindSystemTimeZoneById(zone.Id); + }, Throws.Nothing, "Time zone should be found."); } } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/RecurrenceTests.cs b/Ical.Net.Tests/RecurrenceTests.cs index 862a5bec7..bac40fba3 100644 --- a/Ical.Net.Tests/RecurrenceTests.cs +++ b/Ical.Net.Tests/RecurrenceTests.cs @@ -1,10 +1,8 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Evaluation; -using Ical.Net.Serialization; -using Ical.Net.Serialization.DataTypes; -using Ical.Net.Utility; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections; using System.Collections.Generic; @@ -12,2979 +10,2987 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Evaluation; +using Ical.Net.Serialization; +using Ical.Net.Serialization.DataTypes; +using Ical.Net.Utility; +using NUnit.Framework; + +namespace Ical.Net.Tests; -namespace Ical.Net.Tests +[TestFixture] +public class RecurrenceTests { - [TestFixture] - public class RecurrenceTests - { - private const string _tzid = "US-Eastern"; - - private void EventOccurrenceTest( - Calendar cal, - IDateTime fromDate, - IDateTime toDate, - IDateTime[] dateTimes, - string[] timeZones, - int eventIndex - ) - { - var evt = cal.Events.Skip(eventIndex).First(); - var rule = evt.RecurrenceRules.FirstOrDefault(); + private const string _tzid = "US-Eastern"; + + private void EventOccurrenceTest( + Calendar cal, + IDateTime fromDate, + IDateTime toDate, + IDateTime[] dateTimes, + string[] timeZones, + int eventIndex + ) + { + var evt = cal.Events.Skip(eventIndex).First(); + var rule = evt.RecurrenceRules.FirstOrDefault(); #pragma warning disable 0618 - if (rule != null) rule.RestrictionType = RecurrenceRestrictionType.NoRestriction; + if (rule != null) rule.RestrictionType = RecurrenceRestrictionType.NoRestriction; #pragma warning restore 0618 - fromDate.AssociatedObject = cal; - toDate.AssociatedObject = cal; + fromDate.AssociatedObject = cal; + toDate.AssociatedObject = cal; - var occurrences = evt.GetOccurrences(fromDate, toDate) - .OrderBy(o => o.Period.StartTime) - .ToList(); + var occurrences = evt.GetOccurrences(fromDate, toDate) + .OrderBy(o => o.Period.StartTime) + .ToList(); - Assert.Multiple(() => - { - Assert.That( - occurrences, - Has.Count.EqualTo(dateTimes.Length), - "There should have been " + dateTimes.Length + " occurrences; there were " + occurrences.Count); - - if (evt.RecurrenceRules.Count > 0) - { - Assert.That(evt.RecurrenceRules, Has.Count.EqualTo(1)); - } - - for (var i = 0; i < dateTimes.Length; i++) - { - // Associate each incoming date/time with the calendar. - dateTimes[i].AssociatedObject = cal; - - var dt = dateTimes[i]; - Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur on " + dt); - if (timeZones != null) - Assert.That(dt.TimeZoneName, Is.EqualTo(timeZones[i]), - "Event " + dt + " should occur in the " + timeZones[i] + " timezone"); - } - }); - } - - private void EventOccurrenceTest( - Calendar cal, - IDateTime fromDate, - IDateTime toDate, - IDateTime[] dateTimes, - string[] timeZones - ) - { - EventOccurrenceTest(cal, fromDate, toDate, dateTimes, timeZones, 0); - } - - /// - /// See Page 45 of RFC 2445 - RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30 - /// - [Test, Category("Recurrence")] - public void YearlyComplex1() + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.YearlyComplex1); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); - var occurrences = evt.GetOccurrences( - new CalDateTime(2006, 1, 1, _tzid), - new CalDateTime(2011, 1, 1, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); - - IDateTime dt = new CalDateTime(2007, 1, 1, 8, 30, 0, _tzid); - var i = 0; - - while (dt.Year < 2011) - { - if (dt.GreaterThan(evt.Start) && - (dt.Year % 2 == 1) && // Every-other year from 2005 - (dt.Month == 1) && - (dt.DayOfWeek == DayOfWeek.Sunday)) - { - var dt1 = dt.AddHours(1); - Assert.Multiple(() => - { - Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur at " + dt); - Assert.That(occurrences[i + 1].Period.StartTime, Is.EqualTo(dt1), "Event should occur at " + dt); - }); - i += 2; - } + Assert.That( + occurrences, + Has.Count.EqualTo(dateTimes.Length), + "There should have been " + dateTimes.Length + " occurrences; there were " + occurrences.Count); - dt = dt.AddDays(1); + if (evt.RecurrenceRules.Count > 0) + { + Assert.That(evt.RecurrenceRules, Has.Count.EqualTo(1)); } - } - - /// - /// See Page 118 of RFC 2445 - RRULE:FREQ=DAILY;COUNT=10;INTERVAL=2 - /// - [Test, Category("Recurrence")] - public void DailyCount1() - { - var iCal = Calendar.Load(IcsFiles.DailyCount1); - EventOccurrenceTest( - iCal, - new CalDateTime(2006, 7, 1, _tzid), - new CalDateTime(2006, 9, 1, _tzid), - new[] - { - new CalDateTime(2006, 07, 18, 10, 00, 00, _tzid), - new CalDateTime(2006, 07, 20, 10, 00, 00, _tzid), - new CalDateTime(2006, 07, 22, 10, 00, 00, _tzid), - new CalDateTime(2006, 07, 24, 10, 00, 00, _tzid), - new CalDateTime(2006, 07, 26, 10, 00, 00, _tzid), - new CalDateTime(2006, 07, 28, 10, 00, 00, _tzid), - new CalDateTime(2006, 07, 30, 10, 00, 00, _tzid), - new CalDateTime(2006, 08, 01, 10, 00, 00, _tzid), - new CalDateTime(2006, 08, 03, 10, 00, 00, _tzid), - new CalDateTime(2006, 08, 05, 10, 00, 00, _tzid) - }, - null - ); - } - /// - /// See Page 118 of RFC 2445 - RRULE:FREQ=DAILY;UNTIL=19971224T000000Z - /// - [Test, Category("Recurrence")] - public void DailyUntil1() - { - var iCal = Calendar.Load(IcsFiles.DailyUntil1); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); + for (var i = 0; i < dateTimes.Length; i++) + { + // Associate each incoming date/time with the calendar. + dateTimes[i].AssociatedObject = cal; + + var dt = dateTimes[i]; + Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur on " + dt); + if (timeZones != null) + Assert.That(dt.TimeZoneName, Is.EqualTo(timeZones[i]), + "Event " + dt + " should occur in the " + timeZones[i] + " timezone"); + } + }); + } - var occurrences = evt.GetOccurrences( - new CalDateTime(1997, 9, 1, _tzid), - new CalDateTime(1998, 1, 1, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); + private void EventOccurrenceTest( + Calendar cal, + IDateTime fromDate, + IDateTime toDate, + IDateTime[] dateTimes, + string[] timeZones + ) + { + EventOccurrenceTest(cal, fromDate, toDate, dateTimes, timeZones, 0); + } - IDateTime dt = new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid); - var i = 0; - while (dt.Year < 1998) + /// + /// See Page 45 of RFC 2445 - RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9;BYMINUTE=30 + /// + [Test, Category("Recurrence")] + public void YearlyComplex1() + { + var iCal = Calendar.Load(IcsFiles.YearlyComplex1); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); + var occurrences = evt.GetOccurrences( + new CalDateTime(2006, 1, 1, _tzid), + new CalDateTime(2011, 1, 1, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); + + IDateTime dt = new CalDateTime(2007, 1, 1, 8, 30, 0, _tzid); + var i = 0; + + while (dt.Year < 2011) + { + if (dt.GreaterThan(evt.Start) && + (dt.Year % 2 == 1) && // Every-other year from 2005 + (dt.Month == 1) && + (dt.DayOfWeek == DayOfWeek.Sunday)) { - if (dt.GreaterThanOrEqual(evt.Start) && - dt.LessThan(new CalDateTime(1997, 12, 24, 0, 0, 0, _tzid))) + var dt1 = dt.AddHours(1); + Assert.Multiple(() => { - Assert.Multiple(() => - { - Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur at " + dt); - Assert.That( - (dt.LessThan(new CalDateTime(1997, 10, 26, _tzid)) && dt.TimeZoneName == "US-Eastern") || - (dt.GreaterThan(new CalDateTime(1997, 10, 26, _tzid)) && dt.TimeZoneName == "US-Eastern"), - Is.True, - "Event " + dt + " doesn't occur in the correct time zone (including Daylight & Standard time zones)"); - }); - i++; - } - - dt = dt.AddDays(1); + Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur at " + dt); + Assert.That(occurrences[i + 1].Period.StartTime, Is.EqualTo(dt1), "Event should occur at " + dt); + }); + i += 2; } - } - /// - /// See Page 118 of RFC 2445 - RRULE:FREQ=DAILY;INTERVAL=2 - /// - [Test, Category("Recurrence")] - public void Daily1() - { - var iCal = Calendar.Load(IcsFiles.Daily1); - EventOccurrenceTest( - iCal, - new CalDateTime(1997, 9, 1, _tzid), - new CalDateTime(1997, 12, 4, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 4, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 6, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 8, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 18, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 20, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 22, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 24, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 26, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 28, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 4, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 6, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 8, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 18, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 20, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 22, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 24, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 26, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 28, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 1, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 3, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 5, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 7, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 9, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 11, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 13, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 15, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 17, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 19, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 21, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 23, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 25, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 27, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 29, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 1, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 3, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); + dt = dt.AddDays(1); } + } - /// - /// See Page 119 of RFC 2445 - RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 - /// - [Test, Category("Recurrence")] - public void DailyCount2() - { - var iCal = Calendar.Load(IcsFiles.DailyCount2); - EventOccurrenceTest( - iCal, - new CalDateTime(1997, 9, 1, _tzid), - new CalDateTime(1998, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 22, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 12, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 118 of RFC 2445 - RRULE:FREQ=DAILY;COUNT=10;INTERVAL=2 + /// + [Test, Category("Recurrence")] + public void DailyCount1() + { + var iCal = Calendar.Load(IcsFiles.DailyCount1); + EventOccurrenceTest( + iCal, + new CalDateTime(2006, 7, 1, _tzid), + new CalDateTime(2006, 9, 1, _tzid), + new[] + { + new CalDateTime(2006, 07, 18, 10, 00, 00, _tzid), + new CalDateTime(2006, 07, 20, 10, 00, 00, _tzid), + new CalDateTime(2006, 07, 22, 10, 00, 00, _tzid), + new CalDateTime(2006, 07, 24, 10, 00, 00, _tzid), + new CalDateTime(2006, 07, 26, 10, 00, 00, _tzid), + new CalDateTime(2006, 07, 28, 10, 00, 00, _tzid), + new CalDateTime(2006, 07, 30, 10, 00, 00, _tzid), + new CalDateTime(2006, 08, 01, 10, 00, 00, _tzid), + new CalDateTime(2006, 08, 03, 10, 00, 00, _tzid), + new CalDateTime(2006, 08, 05, 10, 00, 00, _tzid) + }, + null + ); + } - /// - /// See Page 119 of RFC 2445 - RRULE:FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1 - /// - [Test, Category("Recurrence")] - public void ByMonth1() - { - var iCal = Calendar.Load(IcsFiles.ByMonth1); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); + /// + /// See Page 118 of RFC 2445 - RRULE:FREQ=DAILY;UNTIL=19971224T000000Z + /// + [Test, Category("Recurrence")] + public void DailyUntil1() + { + var iCal = Calendar.Load(IcsFiles.DailyUntil1); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); - var occurrences = evt.GetOccurrences( - new CalDateTime(1998, 1, 1, _tzid), - new CalDateTime(2000, 12, 31, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); + var occurrences = evt.GetOccurrences( + new CalDateTime(1997, 9, 1, _tzid), + new CalDateTime(1998, 1, 1, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); - IDateTime dt = new CalDateTime(1998, 1, 1, 9, 0, 0, _tzid); - var i = 0; - while (dt.Year < 2001) + IDateTime dt = new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid); + var i = 0; + while (dt.Year < 1998) + { + if (dt.GreaterThanOrEqual(evt.Start) && + dt.LessThan(new CalDateTime(1997, 12, 24, 0, 0, 0, _tzid))) { - if (dt.GreaterThanOrEqual(evt.Start) && - dt.Month == 1 && - dt.LessThanOrEqual(new CalDateTime(2000, 1, 31, 9, 0, 0, _tzid))) + Assert.Multiple(() => { Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur at " + dt); - i++; - } - - dt = dt.AddDays(1); + Assert.That( + (dt.LessThan(new CalDateTime(1997, 10, 26, _tzid)) && dt.TimeZoneName == "US-Eastern") || + (dt.GreaterThan(new CalDateTime(1997, 10, 26, _tzid)) && dt.TimeZoneName == "US-Eastern"), + Is.True, + "Event " + dt + " doesn't occur in the correct time zone (including Daylight & Standard time zones)"); + }); + i++; } - } - /// - /// See Page 119 of RFC 2445 - RRULE:FREQ=YEARLY;UNTIL=20000131T150000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA - /// - /// The example was slightly modified to fix a suspected flaw in the design of - /// the example RRULEs. UNTIL is always UTC time, but it expected the actual - /// time to correspond to other time zones. Odd. - /// - /// - [Test, Category("Recurrence")] - public void ByMonth2() - { - var iCal1 = Calendar.Load(IcsFiles.ByMonth1); - var iCal2 = Calendar.Load(IcsFiles.ByMonth2); - ProgramTest.TestCal(iCal1); - ProgramTest.TestCal(iCal2); - var evt1 = iCal1.Events.First(); - var evt2 = iCal2.Events.First(); - - var evt1Occurrences = evt1.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(2000, 12, 31)).OrderBy(o => o.Period.StartTime).ToList(); - var evt2Occurrences = evt2.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(2000, 12, 31)).OrderBy(o => o.Period.StartTime).ToList(); - Assert.That(evt1Occurrences.Count == evt2Occurrences.Count, Is.True, "ByMonth1 does not match ByMonth2 as it should"); - for (var i = 0; i < evt1Occurrences.Count; i++) - Assert.That(evt2Occurrences[i].Period, Is.EqualTo(evt1Occurrences[i].Period), "PERIOD " + i + " from ByMonth1 (" + evt1Occurrences[i] + ") does not match PERIOD " + i + " from ByMonth2 (" + evt2Occurrences[i] + ")"); + dt = dt.AddDays(1); } + } - /// - /// See Page 119 of RFC 2445 - RRULE:FREQ=WEEKLY;COUNT=10 - /// - [Test, Category("Recurrence")] - public void WeeklyCount1() - { - var iCal = Calendar.Load(IcsFiles.WeeklyCount1); - EventOccurrenceTest( - iCal, - new CalDateTime(1997, 9, 1, _tzid), - new CalDateTime(1998, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 9, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 23, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 7, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 21, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 28, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 4, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 118 of RFC 2445 - RRULE:FREQ=DAILY;INTERVAL=2 + /// + [Test, Category("Recurrence")] + public void Daily1() + { + var iCal = Calendar.Load(IcsFiles.Daily1); + EventOccurrenceTest( + iCal, + new CalDateTime(1997, 9, 1, _tzid), + new CalDateTime(1997, 12, 4, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 4, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 6, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 8, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 18, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 20, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 22, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 24, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 26, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 28, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 4, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 6, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 8, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 18, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 20, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 22, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 24, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 26, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 28, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 1, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 3, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 5, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 7, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 9, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 11, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 13, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 15, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 17, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 19, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 21, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 23, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 25, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 27, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 29, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 1, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 3, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 119 of RFC 2445 - RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z - /// - [Test, Category("Recurrence")] - public void WeeklyUntil1() - { - var iCal = Calendar.Load(IcsFiles.WeeklyUntil1); - EventOccurrenceTest( - iCal, - new CalDateTime(1997, 9, 1, _tzid), - new CalDateTime(1999, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 9, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 23, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 7, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 21, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 28, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 4, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 11, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 18, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 25, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 9, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 23, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 119 of RFC 2445 - RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 + /// + [Test, Category("Recurrence")] + public void DailyCount2() + { + var iCal = Calendar.Load(IcsFiles.DailyCount2); + EventOccurrenceTest( + iCal, + new CalDateTime(1997, 9, 1, _tzid), + new CalDateTime(1998, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 22, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 12, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 119 of RFC 2445 - RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU - /// - [Test, Category("Recurrence")] - public void WeeklyWkst1() - { - var iCal = Calendar.Load(IcsFiles.WeeklyWkst1); - EventOccurrenceTest( - iCal, - new CalDateTime(1997, 9, 1, _tzid), - new CalDateTime(1998, 1, 31, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 28, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 11, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 25, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 9, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 23, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 6, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 20, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 119 of RFC 2445 - RRULE:FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1 + /// + [Test, Category("Recurrence")] + public void ByMonth1() + { + var iCal = Calendar.Load(IcsFiles.ByMonth1); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); - /// - /// See Page 119 of RFC 2445 - RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH - /// - [Test, Category("Recurrence")] - public void WeeklyUntilWkst1() - { - var iCal = Calendar.Load(IcsFiles.WeeklyUntilWkst1); - EventOccurrenceTest( - iCal, - new CalDateTime(1997, 9, 1, _tzid), - new CalDateTime(1999, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 4, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 9, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 11, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 18, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 23, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 25, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 2, 9, 0, 0, _tzid) - }, - null - ); - } + var occurrences = evt.GetOccurrences( + new CalDateTime(1998, 1, 1, _tzid), + new CalDateTime(2000, 12, 31, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); - /// - /// See Page 120 of RFC 2445 - RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH - /// - [Test, Category("Recurrence")] - public void WeeklyCountWkst1() + IDateTime dt = new CalDateTime(1998, 1, 1, 9, 0, 0, _tzid); + var i = 0; + while (dt.Year < 2001) { - var iCal1 = Calendar.Load(IcsFiles.WeeklyUntilWkst1); - var iCal2 = Calendar.Load(IcsFiles.WeeklyCountWkst1); - ProgramTest.TestCal(iCal1); - ProgramTest.TestCal(iCal2); - var evt1 = iCal1.Events.First(); - var evt2 = iCal2.Events.First(); - - var evt1Occ = evt1.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(1999, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); - var evt2Occ = evt2.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(1999, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); - Assert.That(evt2Occ, Has.Count.EqualTo(evt1Occ.Count), "WeeklyCountWkst1() does not match WeeklyUntilWkst1() as it should"); - for (var i = 0; i < evt1Occ.Count; i++) - { - Assert.That(evt2Occ[i].Period, Is.EqualTo(evt1Occ[i].Period), "PERIOD " + i + " from WeeklyUntilWkst1 (" + evt1Occ[i].Period + ") does not match PERIOD " + i + " from WeeklyCountWkst1 (" + evt2Occ[i].Period + ")"); + if (dt.GreaterThanOrEqual(evt.Start) && + dt.Month == 1 && + dt.LessThanOrEqual(new CalDateTime(2000, 1, 31, 9, 0, 0, _tzid))) + { + Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dt), "Event should occur at " + dt); + i++; } - } - /// - /// See Page 120 of RFC 2445 - RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR - /// - [Test, Category("Recurrence")] - public void WeeklyUntilWkst2() - { - var iCal = Calendar.Load(IcsFiles.WeeklyUntilWkst2); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 3, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 5, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 15, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 17, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 19, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 29, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 1, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 3, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 13, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 15, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 17, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 27, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 29, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 31, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 24, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 26, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 28, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 8, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 22, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); + dt = dt.AddDays(1); } + } - /// - /// Tests to ensure FREQUENCY=WEEKLY with INTERVAL=2 works when starting evaluation from an "off" week - /// - [Test, Category("Recurrence")] - public void WeeklyUntilWkst2_1() - { - var iCal = Calendar.Load(IcsFiles.WeeklyUntilWkst2); - EventOccurrenceTest( - iCal, - new CalDateTime(1997, 9, 9, _tzid), - new CalDateTime(1999, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 15, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 17, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 19, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 29, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 1, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 3, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 13, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 15, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 17, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 27, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 29, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 31, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 24, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 26, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 28, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 8, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 22, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 119 of RFC 2445 - RRULE:FREQ=YEARLY;UNTIL=20000131T150000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA + /// + /// The example was slightly modified to fix a suspected flaw in the design of + /// the example RRULEs. UNTIL is always UTC time, but it expected the actual + /// time to correspond to other time zones. Odd. + /// + /// + [Test, Category("Recurrence")] + public void ByMonth2() + { + var iCal1 = Calendar.Load(IcsFiles.ByMonth1); + var iCal2 = Calendar.Load(IcsFiles.ByMonth2); + ProgramTest.TestCal(iCal1); + ProgramTest.TestCal(iCal2); + var evt1 = iCal1.Events.First(); + var evt2 = iCal2.Events.First(); + + var evt1Occurrences = evt1.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(2000, 12, 31)).OrderBy(o => o.Period.StartTime).ToList(); + var evt2Occurrences = evt2.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(2000, 12, 31)).OrderBy(o => o.Period.StartTime).ToList(); + Assert.That(evt1Occurrences.Count == evt2Occurrences.Count, Is.True, "ByMonth1 does not match ByMonth2 as it should"); + for (var i = 0; i < evt1Occurrences.Count; i++) + Assert.That(evt2Occurrences[i].Period, Is.EqualTo(evt1Occurrences[i].Period), "PERIOD " + i + " from ByMonth1 (" + evt1Occurrences[i] + ") does not match PERIOD " + i + " from ByMonth2 (" + evt2Occurrences[i] + ")"); + } - /// - /// See Page 120 of RFC 2445 - RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH - /// - [Test, Category("Recurrence")] - public void WeeklyCountWkst2() - { - var iCal = Calendar.Load(IcsFiles.WeeklyCountWkst2); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 4, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 18, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 16, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 119 of RFC 2445 - RRULE:FREQ=WEEKLY;COUNT=10 + /// + [Test, Category("Recurrence")] + public void WeeklyCount1() + { + var iCal = Calendar.Load(IcsFiles.WeeklyCount1); + EventOccurrenceTest( + iCal, + new CalDateTime(1997, 9, 1, _tzid), + new CalDateTime(1998, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 9, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 23, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 7, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 21, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 28, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 4, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 120 of RFC 2445 - RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR - /// - [Test, Category("Recurrence")] - public void MonthlyCountByDay1() - { - var iCal = Calendar.Load(IcsFiles.MonthlyCountByDay1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 5, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 3, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 7, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 5, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 2, 9, 0, 0, _tzid), - new CalDateTime(1998, 2, 6, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 6, 9, 0, 0, _tzid), - new CalDateTime(1998, 4, 3, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 1, 9, 0, 0, _tzid), - new CalDateTime(1998, 6, 5, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 119 of RFC 2445 - RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z + /// + [Test, Category("Recurrence")] + public void WeeklyUntil1() + { + var iCal = Calendar.Load(IcsFiles.WeeklyUntil1); + EventOccurrenceTest( + iCal, + new CalDateTime(1997, 9, 1, _tzid), + new CalDateTime(1999, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 9, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 23, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 7, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 21, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 28, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 4, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 11, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 18, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 25, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 9, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 23, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 120 of RFC 2445 - RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR - /// - [Test, Category("Recurrence")] - public void MonthlyUntilByDay1() - { - var iCal = Calendar.Load(IcsFiles.MonthlyUntilByDay1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 5, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 3, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 7, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 5, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 119 of RFC 2445 - RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU + /// + [Test, Category("Recurrence")] + public void WeeklyWkst1() + { + var iCal = Calendar.Load(IcsFiles.WeeklyWkst1); + EventOccurrenceTest( + iCal, + new CalDateTime(1997, 9, 1, _tzid), + new CalDateTime(1998, 1, 31, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 28, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 11, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 25, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 9, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 23, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 6, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 20, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 120 of RFC 2445 - RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU - /// - [Test, Category("Recurrence")] - public void MonthlyCountByDay2() - { - var iCal = Calendar.Load(IcsFiles.MonthlyCountByDay2); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 7, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 28, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 30, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 4, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 25, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 1, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 29, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 3, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 31, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 119 of RFC 2445 - RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH + /// + [Test, Category("Recurrence")] + public void WeeklyUntilWkst1() + { + var iCal = Calendar.Load(IcsFiles.WeeklyUntilWkst1); + EventOccurrenceTest( + iCal, + new CalDateTime(1997, 9, 1, _tzid), + new CalDateTime(1999, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 4, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 9, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 11, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 18, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 23, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 25, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 2, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 121 of RFC 2445 - RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO - /// - [Test, Category("Recurrence")] - public void MonthlyCountByDay3() - { - var iCal = Calendar.Load(IcsFiles.MonthlyCountByDay3); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 22, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 20, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 17, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 22, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 19, 9, 0, 0, _tzid), - new CalDateTime(1998, 2, 16, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 120 of RFC 2445 - RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH + /// + [Test, Category("Recurrence")] + public void WeeklyCountWkst1() + { + var iCal1 = Calendar.Load(IcsFiles.WeeklyUntilWkst1); + var iCal2 = Calendar.Load(IcsFiles.WeeklyCountWkst1); + ProgramTest.TestCal(iCal1); + ProgramTest.TestCal(iCal2); + var evt1 = iCal1.Events.First(); + var evt2 = iCal2.Events.First(); - /// - /// See Page 121 of RFC 2445 - RRULE:FREQ=MONTHLY;BYMONTHDAY=-3 - /// - [Test, Category("Recurrence")] - public void ByMonthDay1() + var evt1Occ = evt1.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(1999, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); + var evt2Occ = evt2.GetOccurrences(new CalDateTime(1997, 9, 1), new CalDateTime(1999, 1, 1)).OrderBy(o => o.Period.StartTime).ToList(); + Assert.That(evt2Occ, Has.Count.EqualTo(evt1Occ.Count), "WeeklyCountWkst1() does not match WeeklyUntilWkst1() as it should"); + for (var i = 0; i < evt1Occ.Count; i++) { - var iCal = Calendar.Load(IcsFiles.ByMonthDay1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1998, 3, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 28, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 29, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 28, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 29, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 29, 9, 0, 0, _tzid), - new CalDateTime(1998, 2, 26, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); + Assert.That(evt2Occ[i].Period, Is.EqualTo(evt1Occ[i].Period), "PERIOD " + i + " from WeeklyUntilWkst1 (" + evt1Occ[i].Period + ") does not match PERIOD " + i + " from WeeklyCountWkst1 (" + evt2Occ[i].Period + ")"); } + } - /// - /// See Page 121 of RFC 2445 - RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15 - /// - [Test, Category("Recurrence")] - public void MonthlyCountByMonthDay1() - { - var iCal = Calendar.Load(IcsFiles.MonthlyCountByMonthDay1); + /// + /// See Page 120 of RFC 2445 - RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU;BYDAY=MO,WE,FR + /// + [Test, Category("Recurrence")] + public void WeeklyUntilWkst2() + { + var iCal = Calendar.Load(IcsFiles.WeeklyUntilWkst2); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 3, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 5, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 15, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 17, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 19, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 29, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 1, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 3, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 13, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 15, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 17, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 27, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 29, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 31, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 24, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 26, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 28, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 8, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 22, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1998, 3, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 15, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 15, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 15, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 15, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 2, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 15, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// Tests to ensure FREQUENCY=WEEKLY with INTERVAL=2 works when starting evaluation from an "off" week + /// + [Test, Category("Recurrence")] + public void WeeklyUntilWkst2_1() + { + var iCal = Calendar.Load(IcsFiles.WeeklyUntilWkst2); + EventOccurrenceTest( + iCal, + new CalDateTime(1997, 9, 9, _tzid), + new CalDateTime(1999, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 15, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 17, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 19, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 29, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 1, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 3, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 13, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 15, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 17, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 27, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 29, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 31, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 24, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 26, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 28, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 8, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 22, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 121 of RFC 2445 - RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1 - /// - [Test, Category("Recurrence")] - public void MonthlyCountByMonthDay2() - { - var iCal = Calendar.Load(IcsFiles.MonthlyCountByMonthDay2); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1998, 3, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 1, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 31, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 1, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 1, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 31, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 1, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 31, 9, 0, 0, _tzid), - new CalDateTime(1998, 2, 1, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 120 of RFC 2445 - RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH + /// + [Test, Category("Recurrence")] + public void WeeklyCountWkst2() + { + var iCal = Calendar.Load(IcsFiles.WeeklyCountWkst2); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 4, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 18, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 16, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 121 of RFC 2445 - RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15 - /// - [Test, Category("Recurrence")] - public void MonthlyCountByMonthDay3() - { - var iCal = Calendar.Load(IcsFiles.MonthlyCountByMonthDay3); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(2000, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 11, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 13, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 15, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 10, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 11, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 12, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 13, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 120 of RFC 2445 - RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR + /// + [Test, Category("Recurrence")] + public void MonthlyCountByDay1() + { + var iCal = Calendar.Load(IcsFiles.MonthlyCountByDay1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 5, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 3, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 7, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 5, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 2, 9, 0, 0, _tzid), + new CalDateTime(1998, 2, 6, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 6, 9, 0, 0, _tzid), + new CalDateTime(1998, 4, 3, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 1, 9, 0, 0, _tzid), + new CalDateTime(1998, 6, 5, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 122 of RFC 2445 - RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU - /// - [Test, Category("Recurrence")] - public void MonthlyByDay1() - { - var iCal = Calendar.Load(IcsFiles.MonthlyByDay1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1998, 4, 1, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 9, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 23, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 4, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 11, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 18, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 25, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 6, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 13, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 20, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 27, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 3, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 10, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 17, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 24, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 31, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 120 of RFC 2445 - RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR + /// + [Test, Category("Recurrence")] + public void MonthlyUntilByDay1() + { + var iCal = Calendar.Load(IcsFiles.MonthlyUntilByDay1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 5, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 3, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 7, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 5, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 122 of RFC 2445 - RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7 - /// - [Test, Category("Recurrence")] - public void YearlyByMonth1() - { - var iCal = Calendar.Load(IcsFiles.YearlyByMonth1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(2002, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 6, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 7, 10, 9, 0, 0, _tzid), - new CalDateTime(1998, 6, 10, 9, 0, 0, _tzid), - new CalDateTime(1998, 7, 10, 9, 0, 0, _tzid), - new CalDateTime(1999, 6, 10, 9, 0, 0, _tzid), - new CalDateTime(1999, 7, 10, 9, 0, 0, _tzid), - new CalDateTime(2000, 6, 10, 9, 0, 0, _tzid), - new CalDateTime(2000, 7, 10, 9, 0, 0, _tzid), - new CalDateTime(2001, 6, 10, 9, 0, 0, _tzid), - new CalDateTime(2001, 7, 10, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 120 of RFC 2445 - RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU + /// + [Test, Category("Recurrence")] + public void MonthlyCountByDay2() + { + var iCal = Calendar.Load(IcsFiles.MonthlyCountByDay2); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 7, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 28, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 30, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 4, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 25, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 1, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 29, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 3, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 31, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 122 of RFC 2445 - RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3 - /// - [Test, Category("Recurrence")] - public void YearlyCountByMonth1() - { - var iCal = Calendar.Load(IcsFiles.YearlyCountByMonth1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(2003, 4, 1, _tzid), - new[] - { - new CalDateTime(1997, 3, 10, 9, 0, 0, _tzid), - new CalDateTime(1999, 1, 10, 9, 0, 0, _tzid), - new CalDateTime(1999, 2, 10, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 10, 9, 0, 0, _tzid), - new CalDateTime(2001, 1, 10, 9, 0, 0, _tzid), - new CalDateTime(2001, 2, 10, 9, 0, 0, _tzid), - new CalDateTime(2001, 3, 10, 9, 0, 0, _tzid), - new CalDateTime(2003, 1, 10, 9, 0, 0, _tzid), - new CalDateTime(2003, 2, 10, 9, 0, 0, _tzid), - new CalDateTime(2003, 3, 10, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 121 of RFC 2445 - RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO + /// + [Test, Category("Recurrence")] + public void MonthlyCountByDay3() + { + var iCal = Calendar.Load(IcsFiles.MonthlyCountByDay3); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 22, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 20, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 17, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 22, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 19, 9, 0, 0, _tzid), + new CalDateTime(1998, 2, 16, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 122 of RFC 2445 - RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200 - /// - [Test, Category("Recurrence")] - public void YearlyCountByYearDay1() - { - var iCal = Calendar.Load(IcsFiles.YearlyCountByYearDay1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(2007, 1, 1, _tzid), - new[] - { - new CalDateTime(1997, 1, 1, 9, 0, 0, _tzid), - new CalDateTime(1997, 4, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 7, 19, 9, 0, 0, _tzid), - new CalDateTime(2000, 1, 1, 9, 0, 0, _tzid), - new CalDateTime(2000, 4, 9, 9, 0, 0, _tzid), - new CalDateTime(2000, 7, 18, 9, 0, 0, _tzid), - new CalDateTime(2003, 1, 1, 9, 0, 0, _tzid), - new CalDateTime(2003, 4, 10, 9, 0, 0, _tzid), - new CalDateTime(2003, 7, 19, 9, 0, 0, _tzid), - new CalDateTime(2006, 1, 1, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 121 of RFC 2445 - RRULE:FREQ=MONTHLY;BYMONTHDAY=-3 + /// + [Test, Category("Recurrence")] + public void ByMonthDay1() + { + var iCal = Calendar.Load(IcsFiles.ByMonthDay1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1998, 3, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 28, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 29, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 28, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 29, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 29, 9, 0, 0, _tzid), + new CalDateTime(1998, 2, 26, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 123 of RFC 2445 - RRULE:FREQ=YEARLY;BYDAY=20MO - /// - [Test, Category("Recurrence")] - public void YearlyByDay1() - { - var iCal = Calendar.Load(IcsFiles.YearlyByDay1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 12, 31, _tzid), - new[] - { - new CalDateTime(1997, 5, 19, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 18, 9, 0, 0, _tzid), - new CalDateTime(1999, 5, 17, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 121 of RFC 2445 - RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15 + /// + [Test, Category("Recurrence")] + public void MonthlyCountByMonthDay1() + { + var iCal = Calendar.Load(IcsFiles.MonthlyCountByMonthDay1); - /// - /// Ordering of byweekno should not matter - /// - [Test, Category("Recurrence")] - public void WeekNoOrderingShouldNotMatter() - { - var start = new DateTime(2019, 1, 1); - var end = new DateTime(2019, 12, 31); - var rpe1 = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=YEARLY;WKST=MO;BYDAY=MO;BYWEEKNO=1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53")); - var rpe2 = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=YEARLY;WKST=MO;BYDAY=MO;BYWEEKNO=53,51,49,47,45,43,41,39,37,35,33,31,29,27,25,23,21,19,17,15,13,11,9,7,5,3,1")); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1998, 3, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 15, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 15, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 15, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 15, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 2, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 15, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - var recurringPeriods1 = rpe1.Evaluate(new CalDateTime(start), start, end, false); - var recurringPeriods2 = rpe2.Evaluate(new CalDateTime(start), start, end, false); + /// + /// See Page 121 of RFC 2445 - RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1 + /// + [Test, Category("Recurrence")] + public void MonthlyCountByMonthDay2() + { + var iCal = Calendar.Load(IcsFiles.MonthlyCountByMonthDay2); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1998, 3, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 1, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 31, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 1, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 1, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 31, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 1, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 31, 9, 0, 0, _tzid), + new CalDateTime(1998, 2, 1, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - Assert.That(recurringPeriods2, Has.Count.EqualTo(recurringPeriods1.Count)); - } + /// + /// See Page 121 of RFC 2445 - RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15 + /// + [Test, Category("Recurrence")] + public void MonthlyCountByMonthDay3() + { + var iCal = Calendar.Load(IcsFiles.MonthlyCountByMonthDay3); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(2000, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 11, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 13, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 15, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 10, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 11, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 12, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 13, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 123 of RFC 2445 - RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO - /// - [Test, Category("Recurrence")] - public void YearlyByWeekNo1() - { - var iCal = Calendar.Load(IcsFiles.YearlyByWeekNo1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 12, 31, _tzid), - new[] - { - new CalDateTime(1997, 5, 12, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 11, 9, 0, 0, _tzid), - new CalDateTime(1999, 5, 17, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 122 of RFC 2445 - RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU + /// + [Test, Category("Recurrence")] + public void MonthlyByDay1() + { + var iCal = Calendar.Load(IcsFiles.MonthlyByDay1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1998, 4, 1, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 9, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 23, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 4, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 11, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 18, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 25, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 6, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 13, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 20, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 27, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 3, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 10, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 17, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 24, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 31, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// DTSTART;TZID=US-Eastern:19970512T090000 - /// RRULE:FREQ=YEARLY;BYWEEKNO=20 - /// Includes Monday in week 20 (since 19970512 is a Monday) - /// of each year. - /// See http://lists.calconnect.org/pipermail/caldeveloper-l/2010-April/000042.html - /// and related threads for a fairly in-depth discussion about this topic. - /// - [Test, Category("Recurrence")] - public void YearlyByWeekNo2() - { - var iCal = Calendar.Load(IcsFiles.YearlyByWeekNo2); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 12, 31, _tzid), - new[] - { - new CalDateTime(1997, 5, 12, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 11, 9, 0, 0, _tzid), - new CalDateTime(1999, 5, 17, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 122 of RFC 2445 - RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7 + /// + [Test, Category("Recurrence")] + public void YearlyByMonth1() + { + var iCal = Calendar.Load(IcsFiles.YearlyByMonth1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(2002, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 6, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 7, 10, 9, 0, 0, _tzid), + new CalDateTime(1998, 6, 10, 9, 0, 0, _tzid), + new CalDateTime(1998, 7, 10, 9, 0, 0, _tzid), + new CalDateTime(1999, 6, 10, 9, 0, 0, _tzid), + new CalDateTime(1999, 7, 10, 9, 0, 0, _tzid), + new CalDateTime(2000, 6, 10, 9, 0, 0, _tzid), + new CalDateTime(2000, 7, 10, 9, 0, 0, _tzid), + new CalDateTime(2001, 6, 10, 9, 0, 0, _tzid), + new CalDateTime(2001, 7, 10, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// DTSTART;TZID=US-Eastern:20020101T100000 - /// RRULE:FREQ=YEARLY;BYWEEKNO=1 - /// Ensures that 20021230 part of week 1 in 2002. - /// See http://lists.calconnect.org/pipermail/caldeveloper-l/2010-April/000042.html - /// and related threads for a fairly in-depth discussion about this topic. - /// - [Test, Category("Recurrence")] - public void YearlyByWeekNo3() - { - var iCal = Calendar.Load(IcsFiles.YearlyByWeekNo3); - EventOccurrenceTest( - iCal, - new CalDateTime(2001, 1, 1, _tzid), - new CalDateTime(2003, 1, 31, _tzid), - new[] - { - new CalDateTime(2002, 1, 1, 10, 0, 0, _tzid), - new CalDateTime(2002, 12, 31, 10, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 122 of RFC 2445 - RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3 + /// + [Test, Category("Recurrence")] + public void YearlyCountByMonth1() + { + var iCal = Calendar.Load(IcsFiles.YearlyCountByMonth1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(2003, 4, 1, _tzid), + new[] + { + new CalDateTime(1997, 3, 10, 9, 0, 0, _tzid), + new CalDateTime(1999, 1, 10, 9, 0, 0, _tzid), + new CalDateTime(1999, 2, 10, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 10, 9, 0, 0, _tzid), + new CalDateTime(2001, 1, 10, 9, 0, 0, _tzid), + new CalDateTime(2001, 2, 10, 9, 0, 0, _tzid), + new CalDateTime(2001, 3, 10, 9, 0, 0, _tzid), + new CalDateTime(2003, 1, 10, 9, 0, 0, _tzid), + new CalDateTime(2003, 2, 10, 9, 0, 0, _tzid), + new CalDateTime(2003, 3, 10, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO,TU,WE,TH,FR,SA,SU - /// Includes every day in week 20. - /// See http://lists.calconnect.org/pipermail/caldeveloper-l/2010-April/000042.html - /// and related threads for a fairly in-depth discussion about this topic. - /// - [Test, Category("Recurrence")] - public void YearlyByWeekNo4() - { - var iCal = Calendar.Load(IcsFiles.YearlyByWeekNo4); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 12, 31, _tzid), - new[] - { - new CalDateTime(1997, 5, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 5, 13, 9, 0, 0, _tzid), - new CalDateTime(1997, 5, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 5, 15, 9, 0, 0, _tzid), - new CalDateTime(1997, 5, 16, 9, 0, 0, _tzid), - new CalDateTime(1997, 5, 17, 9, 0, 0, _tzid), - new CalDateTime(1997, 5, 18, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 11, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 12, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 13, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 14, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 15, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 16, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 17, 9, 0, 0, _tzid), - new CalDateTime(1999, 5, 17, 9, 0, 0, _tzid), - new CalDateTime(1999, 5, 18, 9, 0, 0, _tzid), - new CalDateTime(1999, 5, 19, 9, 0, 0, _tzid), - new CalDateTime(1999, 5, 20, 9, 0, 0, _tzid), - new CalDateTime(1999, 5, 21, 9, 0, 0, _tzid), - new CalDateTime(1999, 5, 22, 9, 0, 0, _tzid), - new CalDateTime(1999, 5, 23, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 122 of RFC 2445 - RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200 + /// + [Test, Category("Recurrence")] + public void YearlyCountByYearDay1() + { + var iCal = Calendar.Load(IcsFiles.YearlyCountByYearDay1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(2007, 1, 1, _tzid), + new[] + { + new CalDateTime(1997, 1, 1, 9, 0, 0, _tzid), + new CalDateTime(1997, 4, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 7, 19, 9, 0, 0, _tzid), + new CalDateTime(2000, 1, 1, 9, 0, 0, _tzid), + new CalDateTime(2000, 4, 9, 9, 0, 0, _tzid), + new CalDateTime(2000, 7, 18, 9, 0, 0, _tzid), + new CalDateTime(2003, 1, 1, 9, 0, 0, _tzid), + new CalDateTime(2003, 4, 10, 9, 0, 0, _tzid), + new CalDateTime(2003, 7, 19, 9, 0, 0, _tzid), + new CalDateTime(2006, 1, 1, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// DTSTART;TZID=US-Eastern:20020101T100000 - /// RRULE:FREQ=YEARLY;BYWEEKNO=1;BYDAY=MO,TU,WE,TH,FR,SA,SU - /// Ensures that 20021230 and 20021231 are in week 1. - /// Also ensures 20011231 is NOT in the result. - /// See http://lists.calconnect.org/pipermail/caldeveloper-l/2010-April/000042.html - /// and related threads for a fairly in-depth discussion about this topic. - /// - [Test, Category("Recurrence")] - public void YearlyByWeekNo5() - { - var iCal = Calendar.Load(IcsFiles.YearlyByWeekNo5); - EventOccurrenceTest( - iCal, - new CalDateTime(2001, 1, 1, _tzid), - new CalDateTime(2003, 1, 31, _tzid), - new[] - { - new CalDateTime(2002, 1, 1, 10, 0, 0, _tzid), - new CalDateTime(2002, 1, 2, 10, 0, 0, _tzid), - new CalDateTime(2002, 1, 3, 10, 0, 0, _tzid), - new CalDateTime(2002, 1, 4, 10, 0, 0, _tzid), - new CalDateTime(2002, 1, 5, 10, 0, 0, _tzid), - new CalDateTime(2002, 1, 6, 10, 0, 0, _tzid), - new CalDateTime(2002, 12, 30, 10, 0, 0, _tzid), - new CalDateTime(2002, 12, 31, 10, 0, 0, _tzid), - new CalDateTime(2003, 1, 1, 10, 0, 0, _tzid), - new CalDateTime(2003, 1, 2, 10, 0, 0, _tzid), - new CalDateTime(2003, 1, 3, 10, 0, 0, _tzid), - new CalDateTime(2003, 1, 4, 10, 0, 0, _tzid), - new CalDateTime(2003, 1, 5, 10, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 123 of RFC 2445 - RRULE:FREQ=YEARLY;BYDAY=20MO + /// + [Test, Category("Recurrence")] + public void YearlyByDay1() + { + var iCal = Calendar.Load(IcsFiles.YearlyByDay1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 12, 31, _tzid), + new[] + { + new CalDateTime(1997, 5, 19, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 18, 9, 0, 0, _tzid), + new CalDateTime(1999, 5, 17, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 123 of RFC 2445 - RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH - /// - [Test, Category("Recurrence")] - public void YearlyByMonth2() - { - var iCal = Calendar.Load(IcsFiles.YearlyByMonth2); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 12, 31, _tzid), - new[] - { - new CalDateTime(1997, 3, 13, 9, 0, 0, _tzid), - new CalDateTime(1997, 3, 20, 9, 0, 0, _tzid), - new CalDateTime(1997, 3, 27, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 5, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 12, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 19, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 26, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 4, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 11, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 18, 9, 0, 0, _tzid), - new CalDateTime(1999, 3, 25, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// Ordering of byweekno should not matter + /// + [Test, Category("Recurrence")] + public void WeekNoOrderingShouldNotMatter() + { + var start = new DateTime(2019, 1, 1); + var end = new DateTime(2019, 12, 31); + var rpe1 = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=YEARLY;WKST=MO;BYDAY=MO;BYWEEKNO=1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53")); + var rpe2 = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=YEARLY;WKST=MO;BYDAY=MO;BYWEEKNO=53,51,49,47,45,43,41,39,37,35,33,31,29,27,25,23,21,19,17,15,13,11,9,7,5,3,1")); - /// - /// See Page 123 of RFC 2445 - RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8 - /// - [Test, Category("Recurrence")] - public void YearlyByMonth3() - { - var iCal = Calendar.Load(IcsFiles.YearlyByMonth3); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1999, 12, 31, _tzid), - new[] - { - new CalDateTime(1997, 6, 5, 9, 0, 0, _tzid), - new CalDateTime(1997, 6, 12, 9, 0, 0, _tzid), - new CalDateTime(1997, 6, 19, 9, 0, 0, _tzid), - new CalDateTime(1997, 6, 26, 9, 0, 0, _tzid), - new CalDateTime(1997, 7, 3, 9, 0, 0, _tzid), - new CalDateTime(1997, 7, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 7, 17, 9, 0, 0, _tzid), - new CalDateTime(1997, 7, 24, 9, 0, 0, _tzid), - new CalDateTime(1997, 7, 31, 9, 0, 0, _tzid), - new CalDateTime(1997, 8, 7, 9, 0, 0, _tzid), - new CalDateTime(1997, 8, 14, 9, 0, 0, _tzid), - new CalDateTime(1997, 8, 21, 9, 0, 0, _tzid), - new CalDateTime(1997, 8, 28, 9, 0, 0, _tzid), - new CalDateTime(1998, 6, 4, 9, 0, 0, _tzid), - new CalDateTime(1998, 6, 11, 9, 0, 0, _tzid), - new CalDateTime(1998, 6, 18, 9, 0, 0, _tzid), - new CalDateTime(1998, 6, 25, 9, 0, 0, _tzid), - new CalDateTime(1998, 7, 2, 9, 0, 0, _tzid), - new CalDateTime(1998, 7, 9, 9, 0, 0, _tzid), - new CalDateTime(1998, 7, 16, 9, 0, 0, _tzid), - new CalDateTime(1998, 7, 23, 9, 0, 0, _tzid), - new CalDateTime(1998, 7, 30, 9, 0, 0, _tzid), - new CalDateTime(1998, 8, 6, 9, 0, 0, _tzid), - new CalDateTime(1998, 8, 13, 9, 0, 0, _tzid), - new CalDateTime(1998, 8, 20, 9, 0, 0, _tzid), - new CalDateTime(1998, 8, 27, 9, 0, 0, _tzid), - new CalDateTime(1999, 6, 3, 9, 0, 0, _tzid), - new CalDateTime(1999, 6, 10, 9, 0, 0, _tzid), - new CalDateTime(1999, 6, 17, 9, 0, 0, _tzid), - new CalDateTime(1999, 6, 24, 9, 0, 0, _tzid), - new CalDateTime(1999, 7, 1, 9, 0, 0, _tzid), - new CalDateTime(1999, 7, 8, 9, 0, 0, _tzid), - new CalDateTime(1999, 7, 15, 9, 0, 0, _tzid), - new CalDateTime(1999, 7, 22, 9, 0, 0, _tzid), - new CalDateTime(1999, 7, 29, 9, 0, 0, _tzid), - new CalDateTime(1999, 8, 5, 9, 0, 0, _tzid), - new CalDateTime(1999, 8, 12, 9, 0, 0, _tzid), - new CalDateTime(1999, 8, 19, 9, 0, 0, _tzid), - new CalDateTime(1999, 8, 26, 9, 0, 0, _tzid) - }, - null - ); - } + var recurringPeriods1 = rpe1.Evaluate(new CalDateTime(start), start, end, false); + var recurringPeriods2 = rpe2.Evaluate(new CalDateTime(start), start, end, false); - /// - /// See Page 123 of RFC 2445: - /// EXDATE;TZID=US-Eastern:19970902T090000 - /// RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13 - /// - [Test, Category("Recurrence")] - public void MonthlyByMonthDay1() - { - var iCal = Calendar.Load(IcsFiles.MonthlyByMonthDay1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(2000, 12, 31, _tzid), - new[] - { - new CalDateTime(1998, 2, 13, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 13, 9, 0, 0, _tzid), - new CalDateTime(1998, 11, 13, 9, 0, 0, _tzid), - new CalDateTime(1999, 8, 13, 9, 0, 0, _tzid), - new CalDateTime(2000, 10, 13, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + Assert.That(recurringPeriods2, Has.Count.EqualTo(recurringPeriods1.Count)); + } - /// - /// See Page 124 of RFC 2445 - RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13 - /// - [Test, Category("Recurrence")] - public void MonthlyByMonthDay2() - { - var iCal = Calendar.Load(IcsFiles.MonthlyByMonthDay2); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1998, 6, 30, _tzid), - new[] - { - new CalDateTime(1997, 9, 13, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 11, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 8, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 13, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 10, 9, 0, 0, _tzid), - new CalDateTime(1998, 2, 7, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 7, 9, 0, 0, _tzid), - new CalDateTime(1998, 4, 11, 9, 0, 0, _tzid), - new CalDateTime(1998, 5, 9, 9, 0, 0, _tzid), - new CalDateTime(1998, 6, 13, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// See Page 123 of RFC 2445 - RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO + /// + [Test, Category("Recurrence")] + public void YearlyByWeekNo1() + { + var iCal = Calendar.Load(IcsFiles.YearlyByWeekNo1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 12, 31, _tzid), + new[] + { + new CalDateTime(1997, 5, 12, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 11, 9, 0, 0, _tzid), + new CalDateTime(1999, 5, 17, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 124 of RFC 2445 - RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8 - /// - [Test, Category("Recurrence")] - public void YearlyByMonthDay1() - { - var iCal = Calendar.Load(IcsFiles.YearlyByMonthDay1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(2004, 12, 31, _tzid), - new[] - { - new CalDateTime(1996, 11, 5, 9, 0, 0, _tzid), - new CalDateTime(2000, 11, 7, 9, 0, 0, _tzid), - new CalDateTime(2004, 11, 2, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// DTSTART;TZID=US-Eastern:19970512T090000 + /// RRULE:FREQ=YEARLY;BYWEEKNO=20 + /// Includes Monday in week 20 (since 19970512 is a Monday) + /// of each year. + /// See http://lists.calconnect.org/pipermail/caldeveloper-l/2010-April/000042.html + /// and related threads for a fairly in-depth discussion about this topic. + /// + [Test, Category("Recurrence")] + public void YearlyByWeekNo2() + { + var iCal = Calendar.Load(IcsFiles.YearlyByWeekNo2); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 12, 31, _tzid), + new[] + { + new CalDateTime(1997, 5, 12, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 11, 9, 0, 0, _tzid), + new CalDateTime(1999, 5, 17, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 124 of RFC 2445 - RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3 - /// - [Test, Category("Recurrence")] - public void MonthlyBySetPos1() - { - var iCal = Calendar.Load(IcsFiles.MonthlyBySetPos1); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(2004, 12, 31, _tzid), - new[] - { - new CalDateTime(1997, 9, 4, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 7, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 6, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// DTSTART;TZID=US-Eastern:20020101T100000 + /// RRULE:FREQ=YEARLY;BYWEEKNO=1 + /// Ensures that 20021230 part of week 1 in 2002. + /// See http://lists.calconnect.org/pipermail/caldeveloper-l/2010-April/000042.html + /// and related threads for a fairly in-depth discussion about this topic. + /// + [Test, Category("Recurrence")] + public void YearlyByWeekNo3() + { + var iCal = Calendar.Load(IcsFiles.YearlyByWeekNo3); + EventOccurrenceTest( + iCal, + new CalDateTime(2001, 1, 1, _tzid), + new CalDateTime(2003, 1, 31, _tzid), + new[] + { + new CalDateTime(2002, 1, 1, 10, 0, 0, _tzid), + new CalDateTime(2002, 12, 31, 10, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 124 of RFC 2445 - RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2 - /// - [Test, Category("Recurrence")] - public void MonthlyBySetPos2() - { - var iCal = Calendar.Load(IcsFiles.MonthlyBySetPos2); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1998, 3, 31, _tzid), - new[] - { - new CalDateTime(1997, 9, 29, 9, 0, 0, _tzid), - new CalDateTime(1997, 10, 30, 9, 0, 0, _tzid), - new CalDateTime(1997, 11, 27, 9, 0, 0, _tzid), - new CalDateTime(1997, 12, 30, 9, 0, 0, _tzid), - new CalDateTime(1998, 1, 29, 9, 0, 0, _tzid), - new CalDateTime(1998, 2, 26, 9, 0, 0, _tzid), - new CalDateTime(1998, 3, 30, 9, 0, 0, _tzid) - }, - new[] - { - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern", - "US-Eastern" - } - ); - } + /// + /// RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO,TU,WE,TH,FR,SA,SU + /// Includes every day in week 20. + /// See http://lists.calconnect.org/pipermail/caldeveloper-l/2010-April/000042.html + /// and related threads for a fairly in-depth discussion about this topic. + /// + [Test, Category("Recurrence")] + public void YearlyByWeekNo4() + { + var iCal = Calendar.Load(IcsFiles.YearlyByWeekNo4); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 12, 31, _tzid), + new[] + { + new CalDateTime(1997, 5, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 5, 13, 9, 0, 0, _tzid), + new CalDateTime(1997, 5, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 5, 15, 9, 0, 0, _tzid), + new CalDateTime(1997, 5, 16, 9, 0, 0, _tzid), + new CalDateTime(1997, 5, 17, 9, 0, 0, _tzid), + new CalDateTime(1997, 5, 18, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 11, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 12, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 13, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 14, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 15, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 16, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 17, 9, 0, 0, _tzid), + new CalDateTime(1999, 5, 17, 9, 0, 0, _tzid), + new CalDateTime(1999, 5, 18, 9, 0, 0, _tzid), + new CalDateTime(1999, 5, 19, 9, 0, 0, _tzid), + new CalDateTime(1999, 5, 20, 9, 0, 0, _tzid), + new CalDateTime(1999, 5, 21, 9, 0, 0, _tzid), + new CalDateTime(1999, 5, 22, 9, 0, 0, _tzid), + new CalDateTime(1999, 5, 23, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 125 of RFC 2445 - RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z - /// FIXME: The UNTIL time on this item has been altered to 19970902T190000Z to - /// match the local EDT time occurrence of 3:00pm. Is the RFC example incorrect? - /// - [Test, Category("Recurrence")] - public void HourlyUntil1() - { - var iCal = Calendar.Load(IcsFiles.HourlyUntil1); - EventOccurrenceTest( - iCal, - fromDate: new CalDateTime(1996, 1, 1, _tzid), - toDate: new CalDateTime(1998, 3, 31, _tzid), - dateTimes: new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 12, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 15, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 18, 0, 0, _tzid), - }, - timeZones: null - ); - } + /// + /// DTSTART;TZID=US-Eastern:20020101T100000 + /// RRULE:FREQ=YEARLY;BYWEEKNO=1;BYDAY=MO,TU,WE,TH,FR,SA,SU + /// Ensures that 20021230 and 20021231 are in week 1. + /// Also ensures 20011231 is NOT in the result. + /// See http://lists.calconnect.org/pipermail/caldeveloper-l/2010-April/000042.html + /// and related threads for a fairly in-depth discussion about this topic. + /// + [Test, Category("Recurrence")] + public void YearlyByWeekNo5() + { + var iCal = Calendar.Load(IcsFiles.YearlyByWeekNo5); + EventOccurrenceTest( + iCal, + new CalDateTime(2001, 1, 1, _tzid), + new CalDateTime(2003, 1, 31, _tzid), + new[] + { + new CalDateTime(2002, 1, 1, 10, 0, 0, _tzid), + new CalDateTime(2002, 1, 2, 10, 0, 0, _tzid), + new CalDateTime(2002, 1, 3, 10, 0, 0, _tzid), + new CalDateTime(2002, 1, 4, 10, 0, 0, _tzid), + new CalDateTime(2002, 1, 5, 10, 0, 0, _tzid), + new CalDateTime(2002, 1, 6, 10, 0, 0, _tzid), + new CalDateTime(2002, 12, 30, 10, 0, 0, _tzid), + new CalDateTime(2002, 12, 31, 10, 0, 0, _tzid), + new CalDateTime(2003, 1, 1, 10, 0, 0, _tzid), + new CalDateTime(2003, 1, 2, 10, 0, 0, _tzid), + new CalDateTime(2003, 1, 3, 10, 0, 0, _tzid), + new CalDateTime(2003, 1, 4, 10, 0, 0, _tzid), + new CalDateTime(2003, 1, 5, 10, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 125 of RFC 2445 - RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6 - /// - [Test, Category("Recurrence")] - public void MinutelyCount1() - { - var iCal = Calendar.Load(IcsFiles.MinutelyCount1); - EventOccurrenceTest( - iCal, - new CalDateTime(1997, 9, 2, _tzid), - new CalDateTime(1997, 9, 3, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 9, 15, 0, _tzid), - new CalDateTime(1997, 9, 2, 9, 30, 0, _tzid), - new CalDateTime(1997, 9, 2, 9, 45, 0, _tzid), - new CalDateTime(1997, 9, 2, 10, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 10, 15, 0, _tzid) - }, - null - ); - } + /// + /// See Page 123 of RFC 2445 - RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH + /// + [Test, Category("Recurrence")] + public void YearlyByMonth2() + { + var iCal = Calendar.Load(IcsFiles.YearlyByMonth2); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 12, 31, _tzid), + new[] + { + new CalDateTime(1997, 3, 13, 9, 0, 0, _tzid), + new CalDateTime(1997, 3, 20, 9, 0, 0, _tzid), + new CalDateTime(1997, 3, 27, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 5, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 12, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 19, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 26, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 4, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 11, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 18, 9, 0, 0, _tzid), + new CalDateTime(1999, 3, 25, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 125 of RFC 2445 - RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4 - /// - [Test, Category("Recurrence")] - public void MinutelyCount2() - { - var iCal = Calendar.Load(IcsFiles.MinutelyCount2); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1998, 12, 31, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 10, 30, 0, _tzid), - new CalDateTime(1997, 9, 2, 12, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 13, 30, 0, _tzid) - }, - null - ); - } + /// + /// See Page 123 of RFC 2445 - RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8 + /// + [Test, Category("Recurrence")] + public void YearlyByMonth3() + { + var iCal = Calendar.Load(IcsFiles.YearlyByMonth3); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1999, 12, 31, _tzid), + new[] + { + new CalDateTime(1997, 6, 5, 9, 0, 0, _tzid), + new CalDateTime(1997, 6, 12, 9, 0, 0, _tzid), + new CalDateTime(1997, 6, 19, 9, 0, 0, _tzid), + new CalDateTime(1997, 6, 26, 9, 0, 0, _tzid), + new CalDateTime(1997, 7, 3, 9, 0, 0, _tzid), + new CalDateTime(1997, 7, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 7, 17, 9, 0, 0, _tzid), + new CalDateTime(1997, 7, 24, 9, 0, 0, _tzid), + new CalDateTime(1997, 7, 31, 9, 0, 0, _tzid), + new CalDateTime(1997, 8, 7, 9, 0, 0, _tzid), + new CalDateTime(1997, 8, 14, 9, 0, 0, _tzid), + new CalDateTime(1997, 8, 21, 9, 0, 0, _tzid), + new CalDateTime(1997, 8, 28, 9, 0, 0, _tzid), + new CalDateTime(1998, 6, 4, 9, 0, 0, _tzid), + new CalDateTime(1998, 6, 11, 9, 0, 0, _tzid), + new CalDateTime(1998, 6, 18, 9, 0, 0, _tzid), + new CalDateTime(1998, 6, 25, 9, 0, 0, _tzid), + new CalDateTime(1998, 7, 2, 9, 0, 0, _tzid), + new CalDateTime(1998, 7, 9, 9, 0, 0, _tzid), + new CalDateTime(1998, 7, 16, 9, 0, 0, _tzid), + new CalDateTime(1998, 7, 23, 9, 0, 0, _tzid), + new CalDateTime(1998, 7, 30, 9, 0, 0, _tzid), + new CalDateTime(1998, 8, 6, 9, 0, 0, _tzid), + new CalDateTime(1998, 8, 13, 9, 0, 0, _tzid), + new CalDateTime(1998, 8, 20, 9, 0, 0, _tzid), + new CalDateTime(1998, 8, 27, 9, 0, 0, _tzid), + new CalDateTime(1999, 6, 3, 9, 0, 0, _tzid), + new CalDateTime(1999, 6, 10, 9, 0, 0, _tzid), + new CalDateTime(1999, 6, 17, 9, 0, 0, _tzid), + new CalDateTime(1999, 6, 24, 9, 0, 0, _tzid), + new CalDateTime(1999, 7, 1, 9, 0, 0, _tzid), + new CalDateTime(1999, 7, 8, 9, 0, 0, _tzid), + new CalDateTime(1999, 7, 15, 9, 0, 0, _tzid), + new CalDateTime(1999, 7, 22, 9, 0, 0, _tzid), + new CalDateTime(1999, 7, 29, 9, 0, 0, _tzid), + new CalDateTime(1999, 8, 5, 9, 0, 0, _tzid), + new CalDateTime(1999, 8, 12, 9, 0, 0, _tzid), + new CalDateTime(1999, 8, 19, 9, 0, 0, _tzid), + new CalDateTime(1999, 8, 26, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// See https://sourceforge.net/projects/dday-ical/forums/forum/656447/topic/3827441 - /// - [Test, Category("Recurrence")] - public void MinutelyCount3() - { - var iCal = Calendar.Load(IcsFiles.MinutelyCount3); - EventOccurrenceTest( - iCal, - new CalDateTime(2010, 8, 27, _tzid), - new CalDateTime(2010, 8, 28, _tzid), - new[] - { - new CalDateTime(2010, 8, 27, 11, 0, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 1, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 2, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 3, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 4, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 5, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 6, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 7, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 8, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 9, 0, _tzid) - }, - null - ); - } + /// + /// See Page 123 of RFC 2445: + /// EXDATE;TZID=US-Eastern:19970902T090000 + /// RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13 + /// + [Test, Category("Recurrence")] + public void MonthlyByMonthDay1() + { + var iCal = Calendar.Load(IcsFiles.MonthlyByMonthDay1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(2000, 12, 31, _tzid), + new[] + { + new CalDateTime(1998, 2, 13, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 13, 9, 0, 0, _tzid), + new CalDateTime(1998, 11, 13, 9, 0, 0, _tzid), + new CalDateTime(1999, 8, 13, 9, 0, 0, _tzid), + new CalDateTime(2000, 10, 13, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See https://sourceforge.net/projects/dday-ical/forums/forum/656447/topic/3827441 - /// - [Test, Category("Recurrence")] - public void MinutelyCount4() - { - var iCal = Calendar.Load(IcsFiles.MinutelyCount4); - EventOccurrenceTest( - iCal, - new CalDateTime(2010, 8, 27, _tzid), - new CalDateTime(2010, 8, 28, _tzid), - new[] - { - new CalDateTime(2010, 8, 27, 11, 0, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 7, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 14, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 21, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 28, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 35, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 42, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 49, 0, _tzid), - new CalDateTime(2010, 8, 27, 11, 56, 0, _tzid), - new CalDateTime(2010, 8, 27, 12, 3, 0, _tzid) - }, - null - ); - } + /// + /// See Page 124 of RFC 2445 - RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13 + /// + [Test, Category("Recurrence")] + public void MonthlyByMonthDay2() + { + var iCal = Calendar.Load(IcsFiles.MonthlyByMonthDay2); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1998, 6, 30, _tzid), + new[] + { + new CalDateTime(1997, 9, 13, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 11, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 8, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 13, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 10, 9, 0, 0, _tzid), + new CalDateTime(1998, 2, 7, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 7, 9, 0, 0, _tzid), + new CalDateTime(1998, 4, 11, 9, 0, 0, _tzid), + new CalDateTime(1998, 5, 9, 9, 0, 0, _tzid), + new CalDateTime(1998, 6, 13, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 125 of RFC 2445 - RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40 - /// - [Test, Category("Recurrence")] - public void DailyByHourMinute1() - { - var iCal = Calendar.Load(IcsFiles.DailyByHourMinute1); - EventOccurrenceTest( - iCal, - new CalDateTime(1997, 9, 2, _tzid), - new CalDateTime(1997, 9, 4, _tzid), - new[] - { - new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 9, 20, 0, _tzid), - new CalDateTime(1997, 9, 2, 9, 40, 0, _tzid), - new CalDateTime(1997, 9, 2, 10, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 10, 20, 0, _tzid), - new CalDateTime(1997, 9, 2, 10, 40, 0, _tzid), - new CalDateTime(1997, 9, 2, 11, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 11, 20, 0, _tzid), - new CalDateTime(1997, 9, 2, 11, 40, 0, _tzid), - new CalDateTime(1997, 9, 2, 12, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 12, 20, 0, _tzid), - new CalDateTime(1997, 9, 2, 12, 40, 0, _tzid), - new CalDateTime(1997, 9, 2, 13, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 13, 20, 0, _tzid), - new CalDateTime(1997, 9, 2, 13, 40, 0, _tzid), - new CalDateTime(1997, 9, 2, 14, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 14, 20, 0, _tzid), - new CalDateTime(1997, 9, 2, 14, 40, 0, _tzid), - new CalDateTime(1997, 9, 2, 15, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 15, 20, 0, _tzid), - new CalDateTime(1997, 9, 2, 15, 40, 0, _tzid), - new CalDateTime(1997, 9, 2, 16, 0, 0, _tzid), - new CalDateTime(1997, 9, 2, 16, 20, 0, _tzid), - new CalDateTime(1997, 9, 2, 16, 40, 0, _tzid), - new CalDateTime(1997, 9, 3, 9, 0, 0, _tzid), - new CalDateTime(1997, 9, 3, 9, 20, 0, _tzid), - new CalDateTime(1997, 9, 3, 9, 40, 0, _tzid), - new CalDateTime(1997, 9, 3, 10, 0, 0, _tzid), - new CalDateTime(1997, 9, 3, 10, 20, 0, _tzid), - new CalDateTime(1997, 9, 3, 10, 40, 0, _tzid), - new CalDateTime(1997, 9, 3, 11, 0, 0, _tzid), - new CalDateTime(1997, 9, 3, 11, 20, 0, _tzid), - new CalDateTime(1997, 9, 3, 11, 40, 0, _tzid), - new CalDateTime(1997, 9, 3, 12, 0, 0, _tzid), - new CalDateTime(1997, 9, 3, 12, 20, 0, _tzid), - new CalDateTime(1997, 9, 3, 12, 40, 0, _tzid), - new CalDateTime(1997, 9, 3, 13, 0, 0, _tzid), - new CalDateTime(1997, 9, 3, 13, 20, 0, _tzid), - new CalDateTime(1997, 9, 3, 13, 40, 0, _tzid), - new CalDateTime(1997, 9, 3, 14, 0, 0, _tzid), - new CalDateTime(1997, 9, 3, 14, 20, 0, _tzid), - new CalDateTime(1997, 9, 3, 14, 40, 0, _tzid), - new CalDateTime(1997, 9, 3, 15, 0, 0, _tzid), - new CalDateTime(1997, 9, 3, 15, 20, 0, _tzid), - new CalDateTime(1997, 9, 3, 15, 40, 0, _tzid), - new CalDateTime(1997, 9, 3, 16, 0, 0, _tzid), - new CalDateTime(1997, 9, 3, 16, 20, 0, _tzid), - new CalDateTime(1997, 9, 3, 16, 40, 0, _tzid) - }, - null - ); - } + /// + /// See Page 124 of RFC 2445 - RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8 + /// + [Test, Category("Recurrence")] + public void YearlyByMonthDay1() + { + var iCal = Calendar.Load(IcsFiles.YearlyByMonthDay1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(2004, 12, 31, _tzid), + new[] + { + new CalDateTime(1996, 11, 5, 9, 0, 0, _tzid), + new CalDateTime(2000, 11, 7, 9, 0, 0, _tzid), + new CalDateTime(2004, 11, 2, 9, 0, 0, _tzid) + }, + null + ); + } - /// - /// See Page 125 of RFC 2445 - RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16 - /// - [Test, Category("Recurrence")] - public void MinutelyByHour1() - { - var iCal1 = Calendar.Load(IcsFiles.DailyByHourMinute1); - var iCal2 = Calendar.Load(IcsFiles.MinutelyByHour1); - ProgramTest.TestCal(iCal1); - ProgramTest.TestCal(iCal2); - var evt1 = iCal1.Events.First(); - var evt2 = iCal2.Events.First(); - - var evt1Occ = evt1.GetOccurrences(new CalDateTime(1997, 9, 1, _tzid), new CalDateTime(1997, 9, 3, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); - var evt2Occ = evt2.GetOccurrences(new CalDateTime(1997, 9, 1, _tzid), new CalDateTime(1997, 9, 3, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); - Assert.That(evt1Occ.Count == evt2Occ.Count, Is.True, "MinutelyByHour1() does not match DailyByHourMinute1() as it should"); - for (var i = 0; i < evt1Occ.Count; i++) - Assert.That(evt2Occ[i].Period, Is.EqualTo(evt1Occ[i].Period), "PERIOD " + i + " from DailyByHourMinute1 (" + evt1Occ[i].Period + ") does not match PERIOD " + i + " from MinutelyByHour1 (" + evt2Occ[i].Period + ")"); - } + /// + /// See Page 124 of RFC 2445 - RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3 + /// + [Test, Category("Recurrence")] + public void MonthlyBySetPos1() + { + var iCal = Calendar.Load(IcsFiles.MonthlyBySetPos1); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(2004, 12, 31, _tzid), + new[] + { + new CalDateTime(1997, 9, 4, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 7, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 6, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 125 of RFC 2445 - RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO - /// - [Test, Category("Recurrence")] - public void WeeklyCountWkst3() - { - var iCal = Calendar.Load(IcsFiles.WeeklyCountWkst3); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1998, 12, 31, _tzid), - new[] - { - new CalDateTime(1997, 8, 5, 9, 0, 0, _tzid), - new CalDateTime(1997, 8, 10, 9, 0, 0, _tzid), - new CalDateTime(1997, 8, 19, 9, 0, 0, _tzid), - new CalDateTime(1997, 8, 24, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 124 of RFC 2445 - RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2 + /// + [Test, Category("Recurrence")] + public void MonthlyBySetPos2() + { + var iCal = Calendar.Load(IcsFiles.MonthlyBySetPos2); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1998, 3, 31, _tzid), + new[] + { + new CalDateTime(1997, 9, 29, 9, 0, 0, _tzid), + new CalDateTime(1997, 10, 30, 9, 0, 0, _tzid), + new CalDateTime(1997, 11, 27, 9, 0, 0, _tzid), + new CalDateTime(1997, 12, 30, 9, 0, 0, _tzid), + new CalDateTime(1998, 1, 29, 9, 0, 0, _tzid), + new CalDateTime(1998, 2, 26, 9, 0, 0, _tzid), + new CalDateTime(1998, 3, 30, 9, 0, 0, _tzid) + }, + new[] + { + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern", + "US-Eastern" + } + ); + } - /// - /// See Page 125 of RFC 2445 - RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU - /// This is the same as WeeklyCountWkst3, except WKST is SU, which changes the results. - /// - [Test, Category("Recurrence")] - public void WeeklyCountWkst4() - { - var iCal = Calendar.Load(IcsFiles.WeeklyCountWkst4); - EventOccurrenceTest( - iCal, - new CalDateTime(1996, 1, 1, _tzid), - new CalDateTime(1998, 12, 31, _tzid), - new[] - { - new CalDateTime(1997, 8, 5, 9, 0, 0, _tzid), - new CalDateTime(1997, 8, 17, 9, 0, 0, _tzid), - new CalDateTime(1997, 8, 19, 9, 0, 0, _tzid), - new CalDateTime(1997, 8, 31, 9, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 125 of RFC 2445 - RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z + /// FIXME: The UNTIL time on this item has been altered to 19970902T190000Z to + /// match the local EDT time occurrence of 3:00pm. Is the RFC example incorrect? + /// + [Test, Category("Recurrence")] + public void HourlyUntil1() + { + var iCal = Calendar.Load(IcsFiles.HourlyUntil1); + EventOccurrenceTest( + iCal, + fromDate: new CalDateTime(1996, 1, 1, _tzid), + toDate: new CalDateTime(1998, 3, 31, _tzid), + dateTimes: new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 12, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 15, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 18, 0, 0, _tzid), + }, + timeZones: null + ); + } - /// - /// Tests WEEKLY Frequencies to ensure that those with an INTERVAL > 1 - /// are correctly handled. See Bug #1741093 - WEEKLY frequency eval behaves strangely. - /// - [Test, Category("Recurrence")] - public void Bug1741093() - { - var iCal = Calendar.Load(IcsFiles.Bug1741093); - EventOccurrenceTest( - iCal, - new CalDateTime(2007, 7, 1, _tzid), - new CalDateTime(2007, 8, 1, _tzid), - new[] - { - new CalDateTime(2007, 7, 2, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 3, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 4, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 5, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 6, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 16, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 17, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 18, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 19, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 20, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 30, 8, 0, 0, _tzid), - new CalDateTime(2007, 7, 31, 8, 0, 0, _tzid) - }, - null - ); - } + /// + /// See Page 125 of RFC 2445 - RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6 + /// + [Test, Category("Recurrence")] + public void MinutelyCount1() + { + var iCal = Calendar.Load(IcsFiles.MinutelyCount1); + EventOccurrenceTest( + iCal, + new CalDateTime(1997, 9, 2, _tzid), + new CalDateTime(1997, 9, 3, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 9, 15, 0, _tzid), + new CalDateTime(1997, 9, 2, 9, 30, 0, _tzid), + new CalDateTime(1997, 9, 2, 9, 45, 0, _tzid), + new CalDateTime(1997, 9, 2, 10, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 10, 15, 0, _tzid) + }, + null + ); + } - [Test, Category("Recurrence")] - public void Secondly_DefinedNumberOfOccurrences_ShouldSucceed() - { - var iCal = Calendar.Load(IcsFiles.Secondly1); + /// + /// See Page 125 of RFC 2445 - RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4 + /// + [Test, Category("Recurrence")] + public void MinutelyCount2() + { + var iCal = Calendar.Load(IcsFiles.MinutelyCount2); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1998, 12, 31, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 10, 30, 0, _tzid), + new CalDateTime(1997, 9, 2, 12, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 13, 30, 0, _tzid) + }, + null + ); + } - var start = new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid); - var end = new CalDateTime(2007, 6, 21, 8, 1, 0, _tzid); // End period is exclusive, not inclusive. + /// + /// See https://sourceforge.net/projects/dday-ical/forums/forum/656447/topic/3827441 + /// + [Test, Category("Recurrence")] + public void MinutelyCount3() + { + var iCal = Calendar.Load(IcsFiles.MinutelyCount3); + EventOccurrenceTest( + iCal, + new CalDateTime(2010, 8, 27, _tzid), + new CalDateTime(2010, 8, 28, _tzid), + new[] + { + new CalDateTime(2010, 8, 27, 11, 0, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 1, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 2, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 3, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 4, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 5, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 6, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 7, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 8, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 9, 0, _tzid) + }, + null + ); + } - var dateTimes = new List(); - for (var dt = start; dt.LessThan(end); dt = (CalDateTime) dt.AddSeconds(1)) + /// + /// See https://sourceforge.net/projects/dday-ical/forums/forum/656447/topic/3827441 + /// + [Test, Category("Recurrence")] + public void MinutelyCount4() + { + var iCal = Calendar.Load(IcsFiles.MinutelyCount4); + EventOccurrenceTest( + iCal, + new CalDateTime(2010, 8, 27, _tzid), + new CalDateTime(2010, 8, 28, _tzid), + new[] { - dateTimes.Add(new CalDateTime(dt)); - } + new CalDateTime(2010, 8, 27, 11, 0, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 7, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 14, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 21, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 28, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 35, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 42, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 49, 0, _tzid), + new CalDateTime(2010, 8, 27, 11, 56, 0, _tzid), + new CalDateTime(2010, 8, 27, 12, 3, 0, _tzid) + }, + null + ); + } - EventOccurrenceTest(iCal, start, end, dateTimes.ToArray(), null); - } + /// + /// See Page 125 of RFC 2445 - RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40 + /// + [Test, Category("Recurrence")] + public void DailyByHourMinute1() + { + var iCal = Calendar.Load(IcsFiles.DailyByHourMinute1); + EventOccurrenceTest( + iCal, + new CalDateTime(1997, 9, 2, _tzid), + new CalDateTime(1997, 9, 4, _tzid), + new[] + { + new CalDateTime(1997, 9, 2, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 9, 20, 0, _tzid), + new CalDateTime(1997, 9, 2, 9, 40, 0, _tzid), + new CalDateTime(1997, 9, 2, 10, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 10, 20, 0, _tzid), + new CalDateTime(1997, 9, 2, 10, 40, 0, _tzid), + new CalDateTime(1997, 9, 2, 11, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 11, 20, 0, _tzid), + new CalDateTime(1997, 9, 2, 11, 40, 0, _tzid), + new CalDateTime(1997, 9, 2, 12, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 12, 20, 0, _tzid), + new CalDateTime(1997, 9, 2, 12, 40, 0, _tzid), + new CalDateTime(1997, 9, 2, 13, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 13, 20, 0, _tzid), + new CalDateTime(1997, 9, 2, 13, 40, 0, _tzid), + new CalDateTime(1997, 9, 2, 14, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 14, 20, 0, _tzid), + new CalDateTime(1997, 9, 2, 14, 40, 0, _tzid), + new CalDateTime(1997, 9, 2, 15, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 15, 20, 0, _tzid), + new CalDateTime(1997, 9, 2, 15, 40, 0, _tzid), + new CalDateTime(1997, 9, 2, 16, 0, 0, _tzid), + new CalDateTime(1997, 9, 2, 16, 20, 0, _tzid), + new CalDateTime(1997, 9, 2, 16, 40, 0, _tzid), + new CalDateTime(1997, 9, 3, 9, 0, 0, _tzid), + new CalDateTime(1997, 9, 3, 9, 20, 0, _tzid), + new CalDateTime(1997, 9, 3, 9, 40, 0, _tzid), + new CalDateTime(1997, 9, 3, 10, 0, 0, _tzid), + new CalDateTime(1997, 9, 3, 10, 20, 0, _tzid), + new CalDateTime(1997, 9, 3, 10, 40, 0, _tzid), + new CalDateTime(1997, 9, 3, 11, 0, 0, _tzid), + new CalDateTime(1997, 9, 3, 11, 20, 0, _tzid), + new CalDateTime(1997, 9, 3, 11, 40, 0, _tzid), + new CalDateTime(1997, 9, 3, 12, 0, 0, _tzid), + new CalDateTime(1997, 9, 3, 12, 20, 0, _tzid), + new CalDateTime(1997, 9, 3, 12, 40, 0, _tzid), + new CalDateTime(1997, 9, 3, 13, 0, 0, _tzid), + new CalDateTime(1997, 9, 3, 13, 20, 0, _tzid), + new CalDateTime(1997, 9, 3, 13, 40, 0, _tzid), + new CalDateTime(1997, 9, 3, 14, 0, 0, _tzid), + new CalDateTime(1997, 9, 3, 14, 20, 0, _tzid), + new CalDateTime(1997, 9, 3, 14, 40, 0, _tzid), + new CalDateTime(1997, 9, 3, 15, 0, 0, _tzid), + new CalDateTime(1997, 9, 3, 15, 20, 0, _tzid), + new CalDateTime(1997, 9, 3, 15, 40, 0, _tzid), + new CalDateTime(1997, 9, 3, 16, 0, 0, _tzid), + new CalDateTime(1997, 9, 3, 16, 20, 0, _tzid), + new CalDateTime(1997, 9, 3, 16, 40, 0, _tzid) + }, + null + ); + } - [Test, Category("Recurrence")] - public void Minutely_DefinedNumberOfOccurrences_ShouldSucceed() - { - var iCal = Calendar.Load(IcsFiles.Minutely1); + /// + /// See Page 125 of RFC 2445 - RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16 + /// + [Test, Category("Recurrence")] + public void MinutelyByHour1() + { + var iCal1 = Calendar.Load(IcsFiles.DailyByHourMinute1); + var iCal2 = Calendar.Load(IcsFiles.MinutelyByHour1); + ProgramTest.TestCal(iCal1); + ProgramTest.TestCal(iCal2); + var evt1 = iCal1.Events.First(); + var evt2 = iCal2.Events.First(); + + var evt1Occ = evt1.GetOccurrences(new CalDateTime(1997, 9, 1, _tzid), new CalDateTime(1997, 9, 3, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); + var evt2Occ = evt2.GetOccurrences(new CalDateTime(1997, 9, 1, _tzid), new CalDateTime(1997, 9, 3, _tzid)).OrderBy(o => o.Period.StartTime).ToList(); + Assert.That(evt1Occ.Count == evt2Occ.Count, Is.True, "MinutelyByHour1() does not match DailyByHourMinute1() as it should"); + for (var i = 0; i < evt1Occ.Count; i++) + Assert.That(evt2Occ[i].Period, Is.EqualTo(evt1Occ[i].Period), "PERIOD " + i + " from DailyByHourMinute1 (" + evt1Occ[i].Period + ") does not match PERIOD " + i + " from MinutelyByHour1 (" + evt2Occ[i].Period + ")"); + } - var start = new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid); - var end = new CalDateTime(2007, 6, 21, 12, 0, 1, _tzid); // End period is exclusive, not inclusive. + /// + /// See Page 125 of RFC 2445 - RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO + /// + [Test, Category("Recurrence")] + public void WeeklyCountWkst3() + { + var iCal = Calendar.Load(IcsFiles.WeeklyCountWkst3); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1998, 12, 31, _tzid), + new[] + { + new CalDateTime(1997, 8, 5, 9, 0, 0, _tzid), + new CalDateTime(1997, 8, 10, 9, 0, 0, _tzid), + new CalDateTime(1997, 8, 19, 9, 0, 0, _tzid), + new CalDateTime(1997, 8, 24, 9, 0, 0, _tzid) + }, + null + ); + } - var dateTimes = new List(); - for (var dt = start; dt.LessThan(end); dt = (CalDateTime)dt.AddMinutes(1)) + /// + /// See Page 125 of RFC 2445 - RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU + /// This is the same as WeeklyCountWkst3, except WKST is SU, which changes the results. + /// + [Test, Category("Recurrence")] + public void WeeklyCountWkst4() + { + var iCal = Calendar.Load(IcsFiles.WeeklyCountWkst4); + EventOccurrenceTest( + iCal, + new CalDateTime(1996, 1, 1, _tzid), + new CalDateTime(1998, 12, 31, _tzid), + new[] { - dateTimes.Add(new CalDateTime(dt)); - } + new CalDateTime(1997, 8, 5, 9, 0, 0, _tzid), + new CalDateTime(1997, 8, 17, 9, 0, 0, _tzid), + new CalDateTime(1997, 8, 19, 9, 0, 0, _tzid), + new CalDateTime(1997, 8, 31, 9, 0, 0, _tzid) + }, + null + ); + } - EventOccurrenceTest(iCal, start, end, dateTimes.ToArray(), null); - } + /// + /// Tests WEEKLY Frequencies to ensure that those with an INTERVAL > 1 + /// are correctly handled. See Bug #1741093 - WEEKLY frequency eval behaves strangely. + /// + [Test, Category("Recurrence")] + public void Bug1741093() + { + var iCal = Calendar.Load(IcsFiles.Bug1741093); + EventOccurrenceTest( + iCal, + new CalDateTime(2007, 7, 1, _tzid), + new CalDateTime(2007, 8, 1, _tzid), + new[] + { + new CalDateTime(2007, 7, 2, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 3, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 4, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 5, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 6, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 16, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 17, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 18, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 19, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 20, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 30, 8, 0, 0, _tzid), + new CalDateTime(2007, 7, 31, 8, 0, 0, _tzid) + }, + null + ); + } - [Test, Category("Recurrence")] - public void Hourly_DefinedNumberOfOccurrences_ShouldSucceed() + [Test, Category("Recurrence")] + public void Secondly_DefinedNumberOfOccurrences_ShouldSucceed() + { + var iCal = Calendar.Load(IcsFiles.Secondly1); + + var start = new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid); + var end = new CalDateTime(2007, 6, 21, 8, 1, 0, _tzid); // End period is exclusive, not inclusive. + + var dateTimes = new List(); + for (var dt = start; dt.LessThan(end); dt = (CalDateTime) dt.AddSeconds(1)) { - var iCal = Calendar.Load(IcsFiles.Hourly1); + dateTimes.Add(new CalDateTime(dt)); + } - var start = new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid); - var end = new CalDateTime(2007, 6, 25, 8, 0, 1, _tzid); // End period is exclusive, not inclusive. + EventOccurrenceTest(iCal, start, end, dateTimes.ToArray(), null); + } - var dateTimes = new List(); - for (var dt = start; dt.LessThan(end); dt = (CalDateTime)dt.AddHours(1)) - { - dateTimes.Add(new CalDateTime(dt)); - } + [Test, Category("Recurrence")] + public void Minutely_DefinedNumberOfOccurrences_ShouldSucceed() + { + var iCal = Calendar.Load(IcsFiles.Minutely1); - EventOccurrenceTest(iCal, start, end, dateTimes.ToArray(), null); - } + var start = new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid); + var end = new CalDateTime(2007, 6, 21, 12, 0, 1, _tzid); // End period is exclusive, not inclusive. - /// - /// Ensures that "off-month" calculation works correctly - /// - [Test, Category("Recurrence")] - public void MonthlyInterval1() + var dateTimes = new List(); + for (var dt = start; dt.LessThan(end); dt = (CalDateTime) dt.AddMinutes(1)) { - var iCal = Calendar.Load(IcsFiles.MonthlyInterval1); - EventOccurrenceTest( - iCal, - new CalDateTime(2008, 1, 1, 7, 0, 0, _tzid), - new CalDateTime(2008, 2, 29, 7, 0, 0, _tzid), - new[] - { - new CalDateTime(2008, 2, 11, 7, 0, 0, _tzid), - new CalDateTime(2008, 2, 12, 7, 0, 0, _tzid) - }, - null - ); + dateTimes.Add(new CalDateTime(dt)); } - /// - /// Ensures that "off-year" calculation works correctly - /// - [Test, Category("Recurrence")] - public void YearlyInterval1() - { - var iCal = Calendar.Load(IcsFiles.YearlyInterval1); - EventOccurrenceTest( - iCal, - new CalDateTime(2006, 1, 1, 7, 0, 0, _tzid), - new CalDateTime(2007, 1, 31, 7, 0, 0, _tzid), - new[] - { - new CalDateTime(2007, 1, 8, 7, 0, 0, _tzid), - new CalDateTime(2007, 1, 9, 7, 0, 0, _tzid) - }, - null - ); - } + EventOccurrenceTest(iCal, start, end, dateTimes.ToArray(), null); + } - /// - /// Ensures that "off-day" calculation works correctly - /// - [Test, Category("Recurrence")] - public void DailyInterval1() - { - var iCal = Calendar.Load(IcsFiles.DailyInterval1); - EventOccurrenceTest( - iCal, - new CalDateTime(2007, 4, 11, 7, 0, 0, _tzid), - new CalDateTime(2007, 4, 16, 7, 0, 0, _tzid), - new[] - { - new CalDateTime(2007, 4, 12, 7, 0, 0, _tzid), - new CalDateTime(2007, 4, 15, 7, 0, 0, _tzid) - }, - null - ); - } + [Test, Category("Recurrence")] + public void Hourly_DefinedNumberOfOccurrences_ShouldSucceed() + { + var iCal = Calendar.Load(IcsFiles.Hourly1); - /// - /// Ensures that "off-hour" calculation works correctly - /// - [Test, Category("Recurrence")] - public void HourlyInterval1() - { - var iCal = Calendar.Load(IcsFiles.HourlyInterval1); - EventOccurrenceTest( - iCal, - new CalDateTime(2007, 4, 9, 10, 0, 0, _tzid), - new CalDateTime(2007, 4, 10, 20, 0, 0, _tzid), - new[] - { - // NOTE: this instance is included in the result set because it ends - // after the start of the evaluation period. - // See bug #3007244. - // https://sourceforge.net/tracker/?func=detail&aid=3007244&group_id=187422&atid=921236 - new CalDateTime(2007, 4, 9, 7, 0, 0, _tzid), - new CalDateTime(2007, 4, 10, 1, 0, 0, _tzid), - new CalDateTime(2007, 4, 10, 19, 0, 0, _tzid) - }, - null - ); - } + var start = new CalDateTime(2007, 6, 21, 8, 0, 0, _tzid); + var end = new CalDateTime(2007, 6, 25, 8, 0, 1, _tzid); // End period is exclusive, not inclusive. - /// - /// Ensures that the following recurrence functions properly. - /// The desired result is "The last Weekend-day of September for the next 10 years." - /// This specifically tests the BYSETPOS=-1 to accomplish this. - /// - [Test, Category("Recurrence")] - public void YearlyBySetPos1() + var dateTimes = new List(); + for (var dt = start; dt.LessThan(end); dt = (CalDateTime) dt.AddHours(1)) { - var iCal = Calendar.Load(IcsFiles.YearlyBySetPos1); - EventOccurrenceTest( - iCal, - new CalDateTime(2009, 1, 1, 0, 0, 0, _tzid), - new CalDateTime(2020, 1, 1, 0, 0, 0, _tzid), - new[] - { - new CalDateTime(2009, 9, 27, 5, 30, 0), - new CalDateTime(2010, 9, 26, 5, 30, 0), - new CalDateTime(2011, 9, 25, 5, 30, 0), - new CalDateTime(2012, 9, 30, 5, 30, 0), - new CalDateTime(2013, 9, 29, 5, 30, 0), - new CalDateTime(2014, 9, 28, 5, 30, 0), - new CalDateTime(2015, 9, 27, 5, 30, 0), - new CalDateTime(2016, 9, 25, 5, 30, 0), - new CalDateTime(2017, 9, 30, 5, 30, 0), - new CalDateTime(2018, 9, 30, 5, 30, 0) - }, - null - ); + dateTimes.Add(new CalDateTime(dt)); } - /// - /// Ensures that GetOccurrences() always returns a single occurrence - /// for a non-recurring event. - /// - [Test, Category("Recurrence")] - public void Empty1() - { - var iCal = Calendar.Load(IcsFiles.Empty1); - EventOccurrenceTest( - iCal, - new CalDateTime(2009, 1, 1, 0, 0, 0, _tzid), - new CalDateTime(2010, 1, 1, 0, 0, 0, _tzid), - new[] - { - new CalDateTime(2009, 9, 27, 5, 30, 0) - }, - null - ); - } + EventOccurrenceTest(iCal, start, end, dateTimes.ToArray(), null); + } - /// - /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for an HOURLY frequency. - /// - [Test, Category("Recurrence")] - public void HourlyInterval2() - { - var iCal = Calendar.Load(IcsFiles.HourlyInterval2); - EventOccurrenceTest( - iCal, + /// + /// Ensures that "off-month" calculation works correctly + /// + [Test, Category("Recurrence")] + public void MonthlyInterval1() + { + var iCal = Calendar.Load(IcsFiles.MonthlyInterval1); + EventOccurrenceTest( + iCal, + new CalDateTime(2008, 1, 1, 7, 0, 0, _tzid), + new CalDateTime(2008, 2, 29, 7, 0, 0, _tzid), + new[] + { + new CalDateTime(2008, 2, 11, 7, 0, 0, _tzid), + new CalDateTime(2008, 2, 12, 7, 0, 0, _tzid) + }, + null + ); + } + + /// + /// Ensures that "off-year" calculation works correctly + /// + [Test, Category("Recurrence")] + public void YearlyInterval1() + { + var iCal = Calendar.Load(IcsFiles.YearlyInterval1); + EventOccurrenceTest( + iCal, + new CalDateTime(2006, 1, 1, 7, 0, 0, _tzid), + new CalDateTime(2007, 1, 31, 7, 0, 0, _tzid), + new[] + { + new CalDateTime(2007, 1, 8, 7, 0, 0, _tzid), + new CalDateTime(2007, 1, 9, 7, 0, 0, _tzid) + }, + null + ); + } + + /// + /// Ensures that "off-day" calculation works correctly + /// + [Test, Category("Recurrence")] + public void DailyInterval1() + { + var iCal = Calendar.Load(IcsFiles.DailyInterval1); + EventOccurrenceTest( + iCal, + new CalDateTime(2007, 4, 11, 7, 0, 0, _tzid), + new CalDateTime(2007, 4, 16, 7, 0, 0, _tzid), + new[] + { + new CalDateTime(2007, 4, 12, 7, 0, 0, _tzid), + new CalDateTime(2007, 4, 15, 7, 0, 0, _tzid) + }, + null + ); + } + + /// + /// Ensures that "off-hour" calculation works correctly + /// + [Test, Category("Recurrence")] + public void HourlyInterval1() + { + var iCal = Calendar.Load(IcsFiles.HourlyInterval1); + EventOccurrenceTest( + iCal, + new CalDateTime(2007, 4, 9, 10, 0, 0, _tzid), + new CalDateTime(2007, 4, 10, 20, 0, 0, _tzid), + new[] + { + // NOTE: this instance is included in the result set because it ends + // after the start of the evaluation period. + // See bug #3007244. + // https://sourceforge.net/tracker/?func=detail&aid=3007244&group_id=187422&atid=921236 + new CalDateTime(2007, 4, 9, 7, 0, 0, _tzid), + new CalDateTime(2007, 4, 10, 1, 0, 0, _tzid), + new CalDateTime(2007, 4, 10, 19, 0, 0, _tzid) + }, + null + ); + } + + /// + /// Ensures that the following recurrence functions properly. + /// The desired result is "The last Weekend-day of September for the next 10 years." + /// This specifically tests the BYSETPOS=-1 to accomplish this. + /// + [Test, Category("Recurrence")] + public void YearlyBySetPos1() + { + var iCal = Calendar.Load(IcsFiles.YearlyBySetPos1); + EventOccurrenceTest( + iCal, + new CalDateTime(2009, 1, 1, 0, 0, 0, _tzid), + new CalDateTime(2020, 1, 1, 0, 0, 0, _tzid), + new[] + { + new CalDateTime(2009, 9, 27, 5, 30, 0), + new CalDateTime(2010, 9, 26, 5, 30, 0), + new CalDateTime(2011, 9, 25, 5, 30, 0), + new CalDateTime(2012, 9, 30, 5, 30, 0), + new CalDateTime(2013, 9, 29, 5, 30, 0), + new CalDateTime(2014, 9, 28, 5, 30, 0), + new CalDateTime(2015, 9, 27, 5, 30, 0), + new CalDateTime(2016, 9, 25, 5, 30, 0), + new CalDateTime(2017, 9, 30, 5, 30, 0), + new CalDateTime(2018, 9, 30, 5, 30, 0) + }, + null + ); + } + + /// + /// Ensures that GetOccurrences() always returns a single occurrence + /// for a non-recurring event. + /// + [Test, Category("Recurrence")] + public void Empty1() + { + var iCal = Calendar.Load(IcsFiles.Empty1); + EventOccurrenceTest( + iCal, + new CalDateTime(2009, 1, 1, 0, 0, 0, _tzid), + new CalDateTime(2010, 1, 1, 0, 0, 0, _tzid), + new[] + { + new CalDateTime(2009, 9, 27, 5, 30, 0) + }, + null + ); + } + + /// + /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for an HOURLY frequency. + /// + [Test, Category("Recurrence")] + public void HourlyInterval2() + { + var iCal = Calendar.Load(IcsFiles.HourlyInterval2); + EventOccurrenceTest( + iCal, + new CalDateTime(2007, 4, 9, 7, 0, 0), + new CalDateTime(2007, 4, 10, 23, 0, 1), // End time is exclusive, not inclusive + new[] + { new CalDateTime(2007, 4, 9, 7, 0, 0), - new CalDateTime(2007, 4, 10, 23, 0, 1), // End time is exclusive, not inclusive - new[] - { - new CalDateTime(2007, 4, 9, 7, 0, 0), - new CalDateTime(2007, 4, 9, 11, 0, 0), - new CalDateTime(2007, 4, 9, 15, 0, 0), - new CalDateTime(2007, 4, 9, 19, 0, 0), - new CalDateTime(2007, 4, 9, 23, 0, 0), - new CalDateTime(2007, 4, 10, 3, 0, 0), - new CalDateTime(2007, 4, 10, 7, 0, 0), - new CalDateTime(2007, 4, 10, 11, 0, 0), - new CalDateTime(2007, 4, 10, 15, 0, 0), - new CalDateTime(2007, 4, 10, 19, 0, 0), - new CalDateTime(2007, 4, 10, 23, 0, 0) - }, - null - ); - } + new CalDateTime(2007, 4, 9, 11, 0, 0), + new CalDateTime(2007, 4, 9, 15, 0, 0), + new CalDateTime(2007, 4, 9, 19, 0, 0), + new CalDateTime(2007, 4, 9, 23, 0, 0), + new CalDateTime(2007, 4, 10, 3, 0, 0), + new CalDateTime(2007, 4, 10, 7, 0, 0), + new CalDateTime(2007, 4, 10, 11, 0, 0), + new CalDateTime(2007, 4, 10, 15, 0, 0), + new CalDateTime(2007, 4, 10, 19, 0, 0), + new CalDateTime(2007, 4, 10, 23, 0, 0) + }, + null + ); + } - /// - /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for an MINUTELY frequency. - /// - [Test, Category("Recurrence")] - public void MinutelyInterval1() - { - var iCal = Calendar.Load(IcsFiles.MinutelyInterval1); - EventOccurrenceTest( - iCal, + /// + /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for an MINUTELY frequency. + /// + [Test, Category("Recurrence")] + public void MinutelyInterval1() + { + var iCal = Calendar.Load(IcsFiles.MinutelyInterval1); + EventOccurrenceTest( + iCal, + new CalDateTime(2007, 4, 9, 7, 0, 0), + new CalDateTime(2007, 4, 9, 12, 0, 1), // End time is exclusive, not inclusive + new[] + { new CalDateTime(2007, 4, 9, 7, 0, 0), - new CalDateTime(2007, 4, 9, 12, 0, 1), // End time is exclusive, not inclusive - new[] - { - new CalDateTime(2007, 4, 9, 7, 0, 0), - new CalDateTime(2007, 4, 9, 7, 30, 0), - new CalDateTime(2007, 4, 9, 8, 0, 0), - new CalDateTime(2007, 4, 9, 8, 30, 0), - new CalDateTime(2007, 4, 9, 9, 0, 0), - new CalDateTime(2007, 4, 9, 9, 30, 0), - new CalDateTime(2007, 4, 9, 10, 0, 0), - new CalDateTime(2007, 4, 9, 10, 30, 0), - new CalDateTime(2007, 4, 9, 11, 0, 0), - new CalDateTime(2007, 4, 9, 11, 30, 0), - new CalDateTime(2007, 4, 9, 12, 0, 0) - }, - null - ); - } + new CalDateTime(2007, 4, 9, 7, 30, 0), + new CalDateTime(2007, 4, 9, 8, 0, 0), + new CalDateTime(2007, 4, 9, 8, 30, 0), + new CalDateTime(2007, 4, 9, 9, 0, 0), + new CalDateTime(2007, 4, 9, 9, 30, 0), + new CalDateTime(2007, 4, 9, 10, 0, 0), + new CalDateTime(2007, 4, 9, 10, 30, 0), + new CalDateTime(2007, 4, 9, 11, 0, 0), + new CalDateTime(2007, 4, 9, 11, 30, 0), + new CalDateTime(2007, 4, 9, 12, 0, 0) + }, + null + ); + } - /// - /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for an DAILY frequency with an INTERVAL. - /// - [Test, Category("Recurrence")] - public void DailyInterval2() - { - var iCal = Calendar.Load(IcsFiles.DailyInterval2); - EventOccurrenceTest( - iCal, + /// + /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for an DAILY frequency with an INTERVAL. + /// + [Test, Category("Recurrence")] + public void DailyInterval2() + { + var iCal = Calendar.Load(IcsFiles.DailyInterval2); + EventOccurrenceTest( + iCal, + new CalDateTime(2007, 4, 9, 7, 0, 0), + new CalDateTime(2007, 4, 27, 7, 0, 1), // End time is exclusive, not inclusive + new[] + { new CalDateTime(2007, 4, 9, 7, 0, 0), - new CalDateTime(2007, 4, 27, 7, 0, 1), // End time is exclusive, not inclusive - new[] - { - new CalDateTime(2007, 4, 9, 7, 0, 0), - new CalDateTime(2007, 4, 11, 7, 0, 0), - new CalDateTime(2007, 4, 13, 7, 0, 0), - new CalDateTime(2007, 4, 15, 7, 0, 0), - new CalDateTime(2007, 4, 17, 7, 0, 0), - new CalDateTime(2007, 4, 19, 7, 0, 0), - new CalDateTime(2007, 4, 21, 7, 0, 0), - new CalDateTime(2007, 4, 23, 7, 0, 0), - new CalDateTime(2007, 4, 25, 7, 0, 0), - new CalDateTime(2007, 4, 27, 7, 0, 0) - }, - null - ); - } + new CalDateTime(2007, 4, 11, 7, 0, 0), + new CalDateTime(2007, 4, 13, 7, 0, 0), + new CalDateTime(2007, 4, 15, 7, 0, 0), + new CalDateTime(2007, 4, 17, 7, 0, 0), + new CalDateTime(2007, 4, 19, 7, 0, 0), + new CalDateTime(2007, 4, 21, 7, 0, 0), + new CalDateTime(2007, 4, 23, 7, 0, 0), + new CalDateTime(2007, 4, 25, 7, 0, 0), + new CalDateTime(2007, 4, 27, 7, 0, 0) + }, + null + ); + } - /// - /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for an DAILY frequency with a BYDAY value. - /// - [Test, Category("Recurrence")] - public void DailyByDay1() - { - var iCal = Calendar.Load(IcsFiles.DailyByDay1); - EventOccurrenceTest( - iCal, + /// + /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for an DAILY frequency with a BYDAY value. + /// + [Test, Category("Recurrence")] + public void DailyByDay1() + { + var iCal = Calendar.Load(IcsFiles.DailyByDay1); + EventOccurrenceTest( + iCal, + new CalDateTime(2007, 9, 10, 7, 0, 0), + new CalDateTime(2007, 9, 27, 7, 0, 1), // End time is exclusive, not inclusive + new[] + { new CalDateTime(2007, 9, 10, 7, 0, 0), - new CalDateTime(2007, 9, 27, 7, 0, 1), // End time is exclusive, not inclusive - new[] - { - new CalDateTime(2007, 9, 10, 7, 0, 0), - new CalDateTime(2007, 9, 13, 7, 0, 0), - new CalDateTime(2007, 9, 17, 7, 0, 0), - new CalDateTime(2007, 9, 20, 7, 0, 0), - new CalDateTime(2007, 9, 24, 7, 0, 0), - new CalDateTime(2007, 9, 27, 7, 0, 0) - }, - null - ); - } + new CalDateTime(2007, 9, 13, 7, 0, 0), + new CalDateTime(2007, 9, 17, 7, 0, 0), + new CalDateTime(2007, 9, 20, 7, 0, 0), + new CalDateTime(2007, 9, 24, 7, 0, 0), + new CalDateTime(2007, 9, 27, 7, 0, 0) + }, + null + ); + } - /// - /// Ensures that DateUtil.AddWeeks works properly when week number is for previous year for selected date. - /// - [Test, Category("Recurrence")] - public void WeeklyWeekStartsLastYear() - { - var iCal = Calendar.Load(IcsFiles.WeeklyWeekStartsLastYear); - EventOccurrenceTest( - iCal, - new CalDateTime(2012, 1, 1, 7, 0, 0), - new CalDateTime(2012, 1, 15, 11, 59, 59), - new[] - { - new CalDateTime(2012, 1, 2, 7, 0, 0), - new CalDateTime(2012, 1, 3, 7, 0, 0), - new CalDateTime(2012, 1, 4, 7, 0, 0), - new CalDateTime(2012, 1, 5, 7, 0, 0), - new CalDateTime(2012, 1, 6, 7, 0, 0), - new CalDateTime(2012, 1, 9, 7, 0, 0), - new CalDateTime(2012, 1, 10, 7, 0, 0), - new CalDateTime(2012, 1, 11, 7, 0, 0), - new CalDateTime(2012, 1, 12, 7, 0, 0), - new CalDateTime(2012, 1, 13, 7, 0, 0) - }, - null - ); - } + /// + /// Ensures that DateUtil.AddWeeks works properly when week number is for previous year for selected date. + /// + [Test, Category("Recurrence")] + public void WeeklyWeekStartsLastYear() + { + var iCal = Calendar.Load(IcsFiles.WeeklyWeekStartsLastYear); + EventOccurrenceTest( + iCal, + new CalDateTime(2012, 1, 1, 7, 0, 0), + new CalDateTime(2012, 1, 15, 11, 59, 59), + new[] + { + new CalDateTime(2012, 1, 2, 7, 0, 0), + new CalDateTime(2012, 1, 3, 7, 0, 0), + new CalDateTime(2012, 1, 4, 7, 0, 0), + new CalDateTime(2012, 1, 5, 7, 0, 0), + new CalDateTime(2012, 1, 6, 7, 0, 0), + new CalDateTime(2012, 1, 9, 7, 0, 0), + new CalDateTime(2012, 1, 10, 7, 0, 0), + new CalDateTime(2012, 1, 11, 7, 0, 0), + new CalDateTime(2012, 1, 12, 7, 0, 0), + new CalDateTime(2012, 1, 13, 7, 0, 0) + }, + null + ); + } + + /// + /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for a WEEKLY frequency with an INTERVAL. + /// + [Test, Category("Recurrence")] + public void WeeklyInterval1() + { + var iCal = Calendar.Load(IcsFiles.WeeklyInterval1); + EventOccurrenceTest( + iCal, + new CalDateTime(2007, 9, 10, 7, 0, 0), + new CalDateTime(2007, 12, 31, 11, 59, 59), + new[] + { + new CalDateTime(2007, 9, 10, 7, 0, 0), + new CalDateTime(2007, 9, 24, 7, 0, 0), + new CalDateTime(2007, 10, 8, 7, 0, 0), + new CalDateTime(2007, 10, 22, 7, 0, 0), + new CalDateTime(2007, 11, 5, 7, 0, 0), + new CalDateTime(2007, 11, 19, 7, 0, 0), + new CalDateTime(2007, 12, 3, 7, 0, 0), + new CalDateTime(2007, 12, 17, 7, 0, 0), + new CalDateTime(2007, 12, 31, 7, 0, 0) + }, + null + ); + } + + /// + /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for a MONTHLY frequency. + /// + [Test, Category("Recurrence")] + public void Monthly1() + { + var iCal = Calendar.Load(IcsFiles.Monthly1); + EventOccurrenceTest( + iCal, + new CalDateTime(2007, 9, 10, 7, 0, 0), + new CalDateTime(2008, 9, 10, 7, 0, 1), // Period end is exclusive, not inclusive + new[] + { + new CalDateTime(2007, 9, 10, 7, 0, 0), + new CalDateTime(2007, 10, 10, 7, 0, 0), + new CalDateTime(2007, 11, 10, 7, 0, 0), + new CalDateTime(2007, 12, 10, 7, 0, 0), + new CalDateTime(2008, 1, 10, 7, 0, 0), + new CalDateTime(2008, 2, 10, 7, 0, 0), + new CalDateTime(2008, 3, 10, 7, 0, 0), + new CalDateTime(2008, 4, 10, 7, 0, 0), + new CalDateTime(2008, 5, 10, 7, 0, 0), + new CalDateTime(2008, 6, 10, 7, 0, 0), + new CalDateTime(2008, 7, 10, 7, 0, 0), + new CalDateTime(2008, 8, 10, 7, 0, 0), + new CalDateTime(2008, 9, 10, 7, 0, 0) + }, + null + ); + } + + /// + /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for a YEARLY frequency. + /// + [Test, Category("Recurrence")] + public void Yearly1() + { + var iCal = Calendar.Load(IcsFiles.Yearly1); + EventOccurrenceTest( + iCal, + new CalDateTime(2007, 9, 10, 7, 0, 0), + new CalDateTime(2020, 9, 10, 7, 0, 1), // Period end is exclusive, not inclusive + new[] + { + new CalDateTime(2007, 9, 10, 7, 0, 0), + new CalDateTime(2008, 9, 10, 7, 0, 0), + new CalDateTime(2009, 9, 10, 7, 0, 0), + new CalDateTime(2010, 9, 10, 7, 0, 0), + new CalDateTime(2011, 9, 10, 7, 0, 0), + new CalDateTime(2012, 9, 10, 7, 0, 0), + new CalDateTime(2013, 9, 10, 7, 0, 0), + new CalDateTime(2014, 9, 10, 7, 0, 0), + new CalDateTime(2015, 9, 10, 7, 0, 0), + new CalDateTime(2016, 9, 10, 7, 0, 0), + new CalDateTime(2017, 9, 10, 7, 0, 0), + new CalDateTime(2018, 9, 10, 7, 0, 0), + new CalDateTime(2019, 9, 10, 7, 0, 0), + new CalDateTime(2020, 9, 10, 7, 0, 0) + }, + null + ); + } + + /// + /// Tests a bug with WEEKLY recurrence values used with UNTIL. + /// https://sourceforge.net/tracker/index.php?func=detail&aid=2912657&group_id=187422&atid=921236 + /// Sourceforge.net bug #2912657 + /// + [Test, Category("Recurrence")] + public void Bug2912657() + { + var iCal = Calendar.Load(IcsFiles.Bug2912657); + var localTzid = iCal.TimeZones[0].TzId; + + // Daily recurrence + EventOccurrenceTest( + iCal, + new CalDateTime(2009, 12, 4, 0, 0, 0, localTzid), + new CalDateTime(2009, 12, 12, 0, 0, 0, localTzid), + new[] + { + new CalDateTime(2009, 12, 4, 2, 00, 00, localTzid), + new CalDateTime(2009, 12, 5, 2, 00, 00, localTzid), + new CalDateTime(2009, 12, 6, 2, 00, 00, localTzid), + new CalDateTime(2009, 12, 7, 2, 00, 00, localTzid), + new CalDateTime(2009, 12, 8, 2, 00, 00, localTzid), + new CalDateTime(2009, 12, 9, 2, 00, 00, localTzid), + new CalDateTime(2009, 12, 10, 2, 00, 00, localTzid) + }, + null, + 0 + ); + + // Weekly with UNTIL value + EventOccurrenceTest( + iCal, + new CalDateTime(2009, 12, 4, localTzid), + new CalDateTime(2009, 12, 10, localTzid), + new[] + { + new CalDateTime(2009, 12, 4, 2, 00, 00, localTzid) + }, + null, + 1 + ); + + // Weekly with COUNT=2 + EventOccurrenceTest( + iCal, + new CalDateTime(2009, 12, 4, localTzid), + new CalDateTime(2009, 12, 12, localTzid), + new[] + { + new CalDateTime(2009, 12, 4, 2, 00, 00, localTzid), + new CalDateTime(2009, 12, 11, 2, 00, 00, localTzid) + }, + null, + 2 + ); + } + + /// + /// Tests a bug with WEEKLY recurrence values that cross year boundaries. + /// https://sourceforge.net/tracker/?func=detail&aid=2916581&group_id=187422&atid=921236 + /// Sourceforge.net bug #2916581 + /// + [Test, Category("Recurrence")] + public void Bug2916581() + { + var iCal = Calendar.Load(IcsFiles.Bug2916581); + var localTzid = iCal.TimeZones[0].TzId; + + // Weekly across year boundary + EventOccurrenceTest( + iCal, + new CalDateTime(2009, 12, 25, 0, 0, 0, localTzid), + new CalDateTime(2010, 1, 3, 0, 0, 0, localTzid), + new[] + { + new CalDateTime(2009, 12, 25, 11, 00, 00, localTzid), + new CalDateTime(2010, 1, 1, 11, 00, 00, localTzid) + }, + null, + 0 + ); + + // Weekly across year boundary + EventOccurrenceTest( + iCal, + new CalDateTime(2009, 12, 25, 0, 0, 0, localTzid), + new CalDateTime(2010, 1, 3, 0, 0, 0, localTzid), + new[] + { + new CalDateTime(2009, 12, 26, 11, 00, 00, localTzid), + new CalDateTime(2010, 1, 2, 11, 00, 00, localTzid) + }, + null, + 1 + ); + } + + /// + /// Tests a bug with WEEKLY recurrence values + /// https://sourceforge.net/tracker/?func=detail&aid=2959692&group_id=187422&atid=921236 + /// Sourceforge.net bug #2959692 + /// + [Test, Category("Recurrence")] + public void Bug2959692() + { + var iCal = Calendar.Load(IcsFiles.Bug2959692); + var localTzid = iCal.TimeZones[0].TzId; + + EventOccurrenceTest( + iCal, + new CalDateTime(2008, 1, 1, 0, 0, 0, localTzid), + new CalDateTime(2008, 4, 1, 0, 0, 0, localTzid), + new[] + { + new CalDateTime(2008, 1, 3, 17, 00, 00, localTzid), + new CalDateTime(2008, 1, 17, 17, 00, 00, localTzid), + new CalDateTime(2008, 1, 31, 17, 00, 00, localTzid), + new CalDateTime(2008, 2, 14, 17, 00, 00, localTzid), + new CalDateTime(2008, 2, 28, 17, 00, 00, localTzid), + new CalDateTime(2008, 3, 13, 17, 00, 00, localTzid), + new CalDateTime(2008, 3, 27, 17, 00, 00, localTzid) + }, + null, + 0 + ); + } + + /// + /// Tests a bug with DAILY recurrence values + /// https://sourceforge.net/tracker/?func=detail&aid=2966236&group_id=187422&atid=921236 + /// Sourceforge.net bug #2966236 + /// + [Test, Category("Recurrence")] + public void Bug2966236() + { + var iCal = Calendar.Load(IcsFiles.Bug2966236); + var localTzid = iCal.TimeZones[0].TzId; + + EventOccurrenceTest( + iCal, + new CalDateTime(2010, 1, 1, 0, 0, 0, localTzid), + new CalDateTime(2010, 3, 1, 0, 0, 0, localTzid), + new[] + { + new CalDateTime(2010, 1, 19, 8, 00, 00, localTzid), + new CalDateTime(2010, 1, 26, 8, 00, 00, localTzid), + new CalDateTime(2010, 2, 2, 8, 00, 00, localTzid), + new CalDateTime(2010, 2, 9, 8, 00, 00, localTzid), + new CalDateTime(2010, 2, 16, 8, 00, 00, localTzid), + new CalDateTime(2010, 2, 23, 8, 00, 00, localTzid) + }, + null, + 0 + ); + + EventOccurrenceTest( + iCal, + new CalDateTime(2010, 2, 1, 0, 0, 0, localTzid), + new CalDateTime(2010, 3, 1, 0, 0, 0, localTzid), + new[] + { + new CalDateTime(2010, 2, 2, 8, 00, 00, localTzid), + new CalDateTime(2010, 2, 9, 8, 00, 00, localTzid), + new CalDateTime(2010, 2, 16, 8, 00, 00, localTzid), + new CalDateTime(2010, 2, 23, 8, 00, 00, localTzid) + }, + null, + 0 + ); + } + + /// + /// Tests a bug with events that span a very long period of time. (i.e. weeks, months, etc.) + /// https://sourceforge.net/tracker/?func=detail&aid=3007244&group_id=187422&atid=921236 + /// Sourceforge.net bug #3007244 + /// + [Test, Category("Recurrence")] + public void Bug3007244() + { + var iCal = Calendar.Load(IcsFiles.Bug3007244); + + EventOccurrenceTest( + cal: iCal, + fromDate: new CalDateTime(2010, 7, 18, 0, 0, 0), + toDate: new CalDateTime(2010, 7, 26, 0, 0, 0), + dateTimes: new[] { new CalDateTime(2010, 05, 23, 0, 0, 0), }, + timeZones: null, + eventIndex: 0 + ); + + EventOccurrenceTest( + cal: iCal, + fromDate: new CalDateTime(2011, 7, 18, 0, 0, 0), + toDate: new CalDateTime(2011, 7, 26, 0, 0, 0), + dateTimes: new[] { new CalDateTime(2011, 05, 23, 0, 0, 0), }, + timeZones: null, + eventIndex: 0 + ); + } - /// - /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for a WEEKLY frequency with an INTERVAL. - /// - [Test, Category("Recurrence")] - public void WeeklyInterval1() - { - var iCal = Calendar.Load(IcsFiles.WeeklyInterval1); - EventOccurrenceTest( - iCal, - new CalDateTime(2007, 9, 10, 7, 0, 0), - new CalDateTime(2007, 12, 31, 11, 59, 59), - new[] - { - new CalDateTime(2007, 9, 10, 7, 0, 0), - new CalDateTime(2007, 9, 24, 7, 0, 0), - new CalDateTime(2007, 10, 8, 7, 0, 0), - new CalDateTime(2007, 10, 22, 7, 0, 0), - new CalDateTime(2007, 11, 5, 7, 0, 0), - new CalDateTime(2007, 11, 19, 7, 0, 0), - new CalDateTime(2007, 12, 3, 7, 0, 0), - new CalDateTime(2007, 12, 17, 7, 0, 0), - new CalDateTime(2007, 12, 31, 7, 0, 0) - }, - null - ); - } + /// + /// Tests bug BYWEEKNO not working + /// + [Test, Category("Recurrence")] + public void BugByWeekNoNotWorking() + { + var start = new DateTime(2019, 1, 1); + var end = new DateTime(2019, 12, 31); + var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=WEEKLY;BYDAY=MO;BYWEEKNO=2")); - /// - /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for a MONTHLY frequency. - /// - [Test, Category("Recurrence")] - public void Monthly1() - { - var iCal = Calendar.Load(IcsFiles.Monthly1); - EventOccurrenceTest( - iCal, - new CalDateTime(2007, 9, 10, 7, 0, 0), - new CalDateTime(2008, 9, 10, 7, 0, 1), // Period end is exclusive, not inclusive - new[] - { - new CalDateTime(2007, 9, 10, 7, 0, 0), - new CalDateTime(2007, 10, 10, 7, 0, 0), - new CalDateTime(2007, 11, 10, 7, 0, 0), - new CalDateTime(2007, 12, 10, 7, 0, 0), - new CalDateTime(2008, 1, 10, 7, 0, 0), - new CalDateTime(2008, 2, 10, 7, 0, 0), - new CalDateTime(2008, 3, 10, 7, 0, 0), - new CalDateTime(2008, 4, 10, 7, 0, 0), - new CalDateTime(2008, 5, 10, 7, 0, 0), - new CalDateTime(2008, 6, 10, 7, 0, 0), - new CalDateTime(2008, 7, 10, 7, 0, 0), - new CalDateTime(2008, 8, 10, 7, 0, 0), - new CalDateTime(2008, 9, 10, 7, 0, 0) - }, - null - ); - } + var recurringPeriods = rpe.Evaluate(new CalDateTime(start), start, end, false); - /// - /// Ensures that RecurrencePattern.GetNextOccurrence() functions properly for a YEARLY frequency. - /// - [Test, Category("Recurrence")] - public void Yearly1() - { - var iCal = Calendar.Load(IcsFiles.Yearly1); - EventOccurrenceTest( - iCal, - new CalDateTime(2007, 9, 10, 7, 0, 0), - new CalDateTime(2020, 9, 10, 7, 0, 1), // Period end is exclusive, not inclusive - new[] - { - new CalDateTime(2007, 9, 10, 7, 0, 0), - new CalDateTime(2008, 9, 10, 7, 0, 0), - new CalDateTime(2009, 9, 10, 7, 0, 0), - new CalDateTime(2010, 9, 10, 7, 0, 0), - new CalDateTime(2011, 9, 10, 7, 0, 0), - new CalDateTime(2012, 9, 10, 7, 0, 0), - new CalDateTime(2013, 9, 10, 7, 0, 0), - new CalDateTime(2014, 9, 10, 7, 0, 0), - new CalDateTime(2015, 9, 10, 7, 0, 0), - new CalDateTime(2016, 9, 10, 7, 0, 0), - new CalDateTime(2017, 9, 10, 7, 0, 0), - new CalDateTime(2018, 9, 10, 7, 0, 0), - new CalDateTime(2019, 9, 10, 7, 0, 0), - new CalDateTime(2020, 9, 10, 7, 0, 0) - }, - null - ); - } + Assert.That(recurringPeriods, Has.Count.EqualTo(1)); + Assert.That(recurringPeriods.First().StartTime, Is.EqualTo(new CalDateTime(2019, 1, 7))); + } - /// - /// Tests a bug with WEEKLY recurrence values used with UNTIL. - /// https://sourceforge.net/tracker/index.php?func=detail&aid=2912657&group_id=187422&atid=921236 - /// Sourceforge.net bug #2912657 - /// - [Test, Category("Recurrence")] - public void Bug2912657() - { - var iCal = Calendar.Load(IcsFiles.Bug2912657); - var localTzid = iCal.TimeZones[0].TzId; - - // Daily recurrence - EventOccurrenceTest( - iCal, - new CalDateTime(2009, 12, 4, 0, 0, 0, localTzid), - new CalDateTime(2009, 12, 12, 0, 0, 0, localTzid), - new[] - { - new CalDateTime(2009, 12, 4, 2, 00, 00, localTzid), - new CalDateTime(2009, 12, 5, 2, 00, 00, localTzid), - new CalDateTime(2009, 12, 6, 2, 00, 00, localTzid), - new CalDateTime(2009, 12, 7, 2, 00, 00, localTzid), - new CalDateTime(2009, 12, 8, 2, 00, 00, localTzid), - new CalDateTime(2009, 12, 9, 2, 00, 00, localTzid), - new CalDateTime(2009, 12, 10, 2, 00, 00, localTzid) - }, - null, - 0 - ); - - // Weekly with UNTIL value - EventOccurrenceTest( - iCal, - new CalDateTime(2009, 12, 4, localTzid), - new CalDateTime(2009, 12, 10, localTzid), - new[] - { - new CalDateTime(2009, 12, 4, 2, 00, 00, localTzid) - }, - null, - 1 - ); - - // Weekly with COUNT=2 - EventOccurrenceTest( - iCal, - new CalDateTime(2009, 12, 4, localTzid), - new CalDateTime(2009, 12, 12, localTzid), - new[] - { - new CalDateTime(2009, 12, 4, 2, 00, 00, localTzid), - new CalDateTime(2009, 12, 11, 2, 00, 00, localTzid) - }, - null, - 2 - ); - } + /// + /// Tests bug BYMONTH while FREQ=WEEKLY not working + /// + [Test, Category("Recurrence")] + public void BugByMonthWhileFreqIsWeekly() + { + var start = new DateTime(2020, 1, 1); + var end = new DateTime(2020, 12, 31); + var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=WEEKLY;BYDAY=MO;BYMONTH=1")); - /// - /// Tests a bug with WEEKLY recurrence values that cross year boundaries. - /// https://sourceforge.net/tracker/?func=detail&aid=2916581&group_id=187422&atid=921236 - /// Sourceforge.net bug #2916581 - /// - [Test, Category("Recurrence")] - public void Bug2916581() - { - var iCal = Calendar.Load(IcsFiles.Bug2916581); - var localTzid = iCal.TimeZones[0].TzId; - - // Weekly across year boundary - EventOccurrenceTest( - iCal, - new CalDateTime(2009, 12, 25, 0, 0, 0, localTzid), - new CalDateTime(2010, 1, 3, 0, 0, 0, localTzid), - new[] - { - new CalDateTime(2009, 12, 25, 11, 00, 00, localTzid), - new CalDateTime(2010, 1, 1, 11, 00, 00, localTzid) - }, - null, - 0 - ); - - // Weekly across year boundary - EventOccurrenceTest( - iCal, - new CalDateTime(2009, 12, 25, 0, 0, 0, localTzid), - new CalDateTime(2010, 1, 3, 0, 0, 0, localTzid), - new[] - { - new CalDateTime(2009, 12, 26, 11, 00, 00, localTzid), - new CalDateTime(2010, 1, 2, 11, 00, 00, localTzid) - }, - null, - 1 - ); - } + var recurringPeriods = rpe.Evaluate(new CalDateTime(start), start, end, false).OrderBy(x => x).ToList(); - /// - /// Tests a bug with WEEKLY recurrence values - /// https://sourceforge.net/tracker/?func=detail&aid=2959692&group_id=187422&atid=921236 - /// Sourceforge.net bug #2959692 - /// - [Test, Category("Recurrence")] - public void Bug2959692() + Assert.That(recurringPeriods, Has.Count.EqualTo(4)); + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.Bug2959692); - var localTzid = iCal.TimeZones[0].TzId; - - EventOccurrenceTest( - iCal, - new CalDateTime(2008, 1, 1, 0, 0, 0, localTzid), - new CalDateTime(2008, 4, 1, 0, 0, 0, localTzid), - new[] - { - new CalDateTime(2008, 1, 3, 17, 00, 00, localTzid), - new CalDateTime(2008, 1, 17, 17, 00, 00, localTzid), - new CalDateTime(2008, 1, 31, 17, 00, 00, localTzid), - new CalDateTime(2008, 2, 14, 17, 00, 00, localTzid), - new CalDateTime(2008, 2, 28, 17, 00, 00, localTzid), - new CalDateTime(2008, 3, 13, 17, 00, 00, localTzid), - new CalDateTime(2008, 3, 27, 17, 00, 00, localTzid) - }, - null, - 0 - ); - } + Assert.That(recurringPeriods[0].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 6))); + Assert.That(recurringPeriods[1].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 13))); + Assert.That(recurringPeriods[2].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 20))); + Assert.That(recurringPeriods[3].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 27))); + }); + } - /// - /// Tests a bug with DAILY recurrence values - /// https://sourceforge.net/tracker/?func=detail&aid=2966236&group_id=187422&atid=921236 - /// Sourceforge.net bug #2966236 - /// - [Test, Category("Recurrence")] - public void Bug2966236() + [Test, Category("Recurrence")] + public void ReccurencePattern_MaxDate_StopsOnCount() + { + var evt = new CalendarEvent { - var iCal = Calendar.Load(IcsFiles.Bug2966236); - var localTzid = iCal.TimeZones[0].TzId; - - EventOccurrenceTest( - iCal, - new CalDateTime(2010, 1, 1, 0, 0, 0, localTzid), - new CalDateTime(2010, 3, 1, 0, 0, 0, localTzid), - new[] - { - new CalDateTime(2010, 1, 19, 8, 00, 00, localTzid), - new CalDateTime(2010, 1, 26, 8, 00, 00, localTzid), - new CalDateTime(2010, 2, 2, 8, 00, 00, localTzid), - new CalDateTime(2010, 2, 9, 8, 00, 00, localTzid), - new CalDateTime(2010, 2, 16, 8, 00, 00, localTzid), - new CalDateTime(2010, 2, 23, 8, 00, 00, localTzid) - }, - null, - 0 - ); - - EventOccurrenceTest( - iCal, - new CalDateTime(2010, 2, 1, 0, 0, 0, localTzid), - new CalDateTime(2010, 3, 1, 0, 0, 0, localTzid), - new[] - { - new CalDateTime(2010, 2, 2, 8, 00, 00, localTzid), - new CalDateTime(2010, 2, 9, 8, 00, 00, localTzid), - new CalDateTime(2010, 2, 16, 8, 00, 00, localTzid), - new CalDateTime(2010, 2, 23, 8, 00, 00, localTzid) - }, - null, - 0 - ); - } + Start = new CalDateTime(2018, 1, 1, 12, 0, 0), + Duration = TimeSpan.FromHours(1) + }; - /// - /// Tests a bug with events that span a very long period of time. (i.e. weeks, months, etc.) - /// https://sourceforge.net/tracker/?func=detail&aid=3007244&group_id=187422&atid=921236 - /// Sourceforge.net bug #3007244 - /// - [Test, Category("Recurrence")] - public void Bug3007244() - { - var iCal = Calendar.Load(IcsFiles.Bug3007244); - - EventOccurrenceTest( - cal: iCal, - fromDate: new CalDateTime(2010, 7, 18, 0, 0, 0), - toDate: new CalDateTime(2010, 7, 26, 0, 0, 0), - dateTimes: new[] { new CalDateTime(2010, 05, 23, 0, 0, 0), }, - timeZones: null, - eventIndex: 0 - ); - - EventOccurrenceTest( - cal: iCal, - fromDate: new CalDateTime(2011, 7, 18, 0, 0, 0), - toDate: new CalDateTime(2011, 7, 26, 0, 0, 0), - dateTimes: new[] { new CalDateTime(2011, 05, 23, 0, 0, 0), }, - timeZones: null, - eventIndex: 0 - ); - } - - /// - /// Tests bug BYWEEKNO not working - /// - [Test, Category("Recurrence")] - public void BugByWeekNoNotWorking() + var pattern = new RecurrencePattern { - var start = new DateTime(2019, 1, 1); - var end = new DateTime(2019, 12, 31); - var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=WEEKLY;BYDAY=MO;BYWEEKNO=2")); + Frequency = FrequencyType.Daily, + Count = 10 + }; - var recurringPeriods = rpe.Evaluate(new CalDateTime(start), start, end, false); + evt.RecurrenceRules.Add(pattern); - Assert.That(recurringPeriods, Has.Count.EqualTo(1)); - Assert.That(recurringPeriods.First().StartTime, Is.EqualTo(new CalDateTime(2019, 1, 7))); - } + var occurrences = evt.GetOccurrences(new DateTime(2018, 1, 1), DateTime.MaxValue); + Assert.That(occurrences, Has.Count.EqualTo(10), "There should be 10 occurrences of this event."); + } - /// - /// Tests bug BYMONTH while FREQ=WEEKLY not working - /// - [Test, Category("Recurrence")] - public void BugByMonthWhileFreqIsWeekly() - { - var start = new DateTime(2020, 1, 1); - var end = new DateTime(2020, 12, 31); - var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=WEEKLY;BYDAY=MO;BYMONTH=1")); + /// + /// Tests bug BYMONTH while FREQ=MONTHLY not working + /// + [Test, Category("Recurrence")] + public void BugByMonthWhileFreqIsMonthly() + { + var start = new DateTime(2020, 1, 1); + var end = new DateTime(2020, 12, 31); + var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=MONTHLY;BYDAY=MO;BYMONTH=1")); - var recurringPeriods = rpe.Evaluate(new CalDateTime(start), start, end, false).OrderBy(x => x).ToList(); + var recurringPeriods = rpe.Evaluate(new CalDateTime(start), start, end, false).OrderBy(x => x).ToList(); - Assert.That(recurringPeriods, Has.Count.EqualTo(4)); - Assert.Multiple(() => - { - Assert.That(recurringPeriods[0].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 6))); - Assert.That(recurringPeriods[1].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 13))); - Assert.That(recurringPeriods[2].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 20))); - Assert.That(recurringPeriods[3].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 27))); - }); - } - - [Test, Category("Recurrence")] - public void ReccurencePattern_MaxDate_StopsOnCount() + Assert.That(recurringPeriods, Has.Count.EqualTo(4)); + Assert.Multiple(() => { - var evt = new CalendarEvent - { - Start = new CalDateTime(2018, 1, 1, 12, 0, 0), - Duration = TimeSpan.FromHours(1) - }; - - var pattern = new RecurrencePattern - { - Frequency = FrequencyType.Daily, - Count = 10 - }; - - evt.RecurrenceRules.Add(pattern); - - var occurrences = evt.GetOccurrences(new DateTime(2018, 1, 1), DateTime.MaxValue); - Assert.That(occurrences, Has.Count.EqualTo(10), "There should be 10 occurrences of this event."); - } + Assert.That(recurringPeriods[0].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 6))); + Assert.That(recurringPeriods[1].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 13))); + Assert.That(recurringPeriods[2].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 20))); + Assert.That(recurringPeriods[3].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 27))); + }); + } - /// - /// Tests bug BYMONTH while FREQ=MONTHLY not working - /// - [Test, Category("Recurrence")] - public void BugByMonthWhileFreqIsMonthly() + /// + /// Tests bug #3119920 - missing weekly occurences + /// See https://sourceforge.net/tracker/?func=detail&aid=3119920&group_id=187422&atid=921236 + /// + [Test, Category("Recurrence")] + public void Bug3119920() + { + using (var sr = new StringReader("FREQ=WEEKLY;UNTIL=20251126T120000;INTERVAL=1;BYDAY=MO")) { - var start = new DateTime(2020, 1, 1); - var end = new DateTime(2020, 12, 31); - var rpe = new RecurrencePatternEvaluator(new RecurrencePattern("FREQ=MONTHLY;BYDAY=MO;BYMONTH=1")); + var start = DateTime.Parse("2010-11-27 9:00:00"); + var serializer = new RecurrencePatternSerializer(); + var rp = (RecurrencePattern) serializer.Deserialize(sr); + var rpe = new RecurrencePatternEvaluator(rp); + var recurringPeriods = rpe.Evaluate(new CalDateTime(start), start, rp.Until, false); - var recurringPeriods = rpe.Evaluate(new CalDateTime(start), start, end, false).OrderBy(x => x).ToList(); + var period = recurringPeriods.ElementAt(recurringPeriods.Count - 1); - Assert.That(recurringPeriods, Has.Count.EqualTo(4)); - Assert.Multiple(() => - { - Assert.That(recurringPeriods[0].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 6))); - Assert.That(recurringPeriods[1].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 13))); - Assert.That(recurringPeriods[2].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 20))); - Assert.That(recurringPeriods[3].StartTime, Is.EqualTo(new CalDateTime(2020, 1, 27))); - }); + Assert.That(period.StartTime, Is.EqualTo(new CalDateTime(2025, 11, 24, 9, 0, 0))); } + } - /// - /// Tests bug #3119920 - missing weekly occurences - /// See https://sourceforge.net/tracker/?func=detail&aid=3119920&group_id=187422&atid=921236 - /// - [Test, Category("Recurrence")] - public void Bug3119920() + /// + /// Tests bug #3178652 - 29th day of February in recurrence problems + /// See https://sourceforge.net/tracker/?func=detail&aid=3178652&group_id=187422&atid=921236 + /// + [Test, Category("Recurrence")] + public void Bug3178652() + { + var evt = new CalendarEvent { - using (var sr = new StringReader("FREQ=WEEKLY;UNTIL=20251126T120000;INTERVAL=1;BYDAY=MO")) - { - var start = DateTime.Parse("2010-11-27 9:00:00"); - var serializer = new RecurrencePatternSerializer(); - var rp = (RecurrencePattern)serializer.Deserialize(sr); - var rpe = new RecurrencePatternEvaluator(rp); - var recurringPeriods = rpe.Evaluate(new CalDateTime(start), start, rp.Until, false); - - var period = recurringPeriods.ElementAt(recurringPeriods.Count - 1); + Start = new CalDateTime(2011, 1, 29, 11, 0, 0), + Duration = TimeSpan.FromHours(1.5), + Summary = "29th February Test" + }; - Assert.That(period.StartTime, Is.EqualTo(new CalDateTime(2025, 11, 24, 9, 0, 0))); - } - } - - /// - /// Tests bug #3178652 - 29th day of February in recurrence problems - /// See https://sourceforge.net/tracker/?func=detail&aid=3178652&group_id=187422&atid=921236 - /// - [Test, Category("Recurrence")] - public void Bug3178652() + var pattern = new RecurrencePattern { - var evt = new CalendarEvent - { - Start = new CalDateTime(2011, 1, 29, 11, 0, 0), - Duration = TimeSpan.FromHours(1.5), - Summary = "29th February Test" - }; + Frequency = FrequencyType.Monthly, + Until = new DateTime(2011, 12, 25, 0, 0, 0, DateTimeKind.Utc), + FirstDayOfWeek = DayOfWeek.Sunday, + ByMonthDay = new List(new[] { 29 }) + }; - var pattern = new RecurrencePattern { - Frequency = FrequencyType.Monthly, - Until = new DateTime(2011, 12, 25, 0, 0, 0, DateTimeKind.Utc), - FirstDayOfWeek = DayOfWeek.Sunday, - ByMonthDay = new List(new[] { 29 }) - }; + evt.RecurrenceRules.Add(pattern); - evt.RecurrenceRules.Add(pattern); - - var occurrences = evt.GetOccurrences(new DateTime(2011, 1, 1), new DateTime(2012, 1, 1)); - Assert.That(occurrences, Has.Count.EqualTo(10), "There should be 10 occurrences of this event, one for each month except February and December."); - } + var occurrences = evt.GetOccurrences(new DateTime(2011, 1, 1), new DateTime(2012, 1, 1)); + Assert.That(occurrences, Has.Count.EqualTo(10), "There should be 10 occurrences of this event, one for each month except February and December."); + } - /// - /// Tests bug #3292737 - Google Repeating Task Until Time Bug - /// See https://sourceforge.net/tracker/?func=detail&aid=3292737&group_id=187422&atid=921236 - /// - [Test, Category("Recurrence")] - public void Bug3292737() + /// + /// Tests bug #3292737 - Google Repeating Task Until Time Bug + /// See https://sourceforge.net/tracker/?func=detail&aid=3292737&group_id=187422&atid=921236 + /// + [Test, Category("Recurrence")] + public void Bug3292737() + { + using (var sr = new StringReader("FREQ=WEEKLY;UNTIL=20251126")) { - using (var sr = new StringReader("FREQ=WEEKLY;UNTIL=20251126")) - { - var serializer = new RecurrencePatternSerializer(); - var rp = (RecurrencePattern)serializer.Deserialize(sr); + var serializer = new RecurrencePatternSerializer(); + var rp = (RecurrencePattern) serializer.Deserialize(sr); - Assert.That(rp, Is.Not.Null); - Assert.That(rp.Until, Is.EqualTo(new DateTime(2025, 11, 26))); - } + Assert.That(rp, Is.Not.Null); + Assert.That(rp.Until, Is.EqualTo(new DateTime(2025, 11, 26))); } + } - /// - /// Tests Issue #432 - /// See https://github.com/rianjs/ical.net/issues/432 - /// - [Test, Category("Recurrence")] - public void Issue432() + /// + /// Tests Issue #432 + /// See https://github.com/rianjs/ical.net/issues/432 + /// + [Test, Category("Recurrence")] + public void Issue432() + { + var rrule = new RecurrencePattern { - var rrule = new RecurrencePattern - { - Frequency = FrequencyType.Daily, - Until = DateTime.Today.AddMonths(4), - }; - var vEvent = new CalendarEvent - { - Start = new CalDateTime(DateTime.Parse("2019-01-04T08:00Z")), - }; + Frequency = FrequencyType.Daily, + Until = DateTime.Today.AddMonths(4), + }; + var vEvent = new CalendarEvent + { + Start = new CalDateTime(DateTime.Parse("2019-01-04T08:00Z")), + }; - vEvent.RecurrenceRules.Add(rrule); + vEvent.RecurrenceRules.Add(rrule); - //Testing on both the first day and the next, results used to be different - for (var i = 0; i <= 1; i++) - { - var checkTime = DateTime.Parse("2019-01-04T08:00Z"); - checkTime = checkTime.AddDays(i); - //Valid asking for the exact moment - var occurrences = vEvent.GetOccurrences(checkTime, checkTime); - Assert.That(occurrences, Has.Count.EqualTo(1)); + //Testing on both the first day and the next, results used to be different + for (var i = 0; i <= 1; i++) + { + var checkTime = DateTime.Parse("2019-01-04T08:00Z"); + checkTime = checkTime.AddDays(i); + //Valid asking for the exact moment + var occurrences = vEvent.GetOccurrences(checkTime, checkTime); + Assert.That(occurrences, Has.Count.EqualTo(1)); - //Valid if asking for a range starting at the same moment - occurrences = vEvent.GetOccurrences(checkTime, checkTime.AddSeconds(1)); - Assert.That(occurrences, Has.Count.EqualTo(1)); + //Valid if asking for a range starting at the same moment + occurrences = vEvent.GetOccurrences(checkTime, checkTime.AddSeconds(1)); + Assert.That(occurrences, Has.Count.EqualTo(1)); - //Valid if asking for a range starting before and ending after - occurrences = vEvent.GetOccurrences(checkTime.AddSeconds(-1), checkTime.AddSeconds(1)); - Assert.That(occurrences, Has.Count.EqualTo(1)); + //Valid if asking for a range starting before and ending after + occurrences = vEvent.GetOccurrences(checkTime.AddSeconds(-1), checkTime.AddSeconds(1)); + Assert.That(occurrences, Has.Count.EqualTo(1)); - //Not valid if asking for a range starting before but ending at the same moment - occurrences = vEvent.GetOccurrences(checkTime.AddSeconds(-1), checkTime); - Assert.That(occurrences.Count, Is.EqualTo(0)); - } + //Not valid if asking for a range starting before but ending at the same moment + occurrences = vEvent.GetOccurrences(checkTime.AddSeconds(-1), checkTime); + Assert.That(occurrences.Count, Is.EqualTo(0)); } + } - [Test, Category("Recurrence")] - public void Issue432_AllDay() + [Test, Category("Recurrence")] + public void Issue432_AllDay() + { + var vEvent = new CalendarEvent { - var vEvent = new CalendarEvent - { - Start = new CalDateTime(DateTime.Parse("2020-01-11T00:00")), - End = new CalDateTime(DateTime.Parse("2020-01-11T00:00")), - IsAllDay = true, - }; + Start = new CalDateTime(DateTime.Parse("2020-01-11T00:00")), + End = new CalDateTime(DateTime.Parse("2020-01-11T00:00")), + IsAllDay = true, + }; - var occurrences = vEvent.GetOccurrences(DateTime.Parse("2020-01-10T00:00"), DateTime.Parse("2020-01-11T00:00")); - Assert.That(occurrences.Count, Is.EqualTo(0)); - } + var occurrences = vEvent.GetOccurrences(DateTime.Parse("2020-01-10T00:00"), DateTime.Parse("2020-01-11T00:00")); + Assert.That(occurrences.Count, Is.EqualTo(0)); + } - /// - /// Tests the iCal holidays downloaded from apple.com - /// - [Test, Category("Recurrence")] - public void UsHolidays() - { - var iCal = Calendar.Load(IcsFiles.UsHolidays); - Assert.That(iCal, Is.Not.Null, "iCalendar was not loaded."); - var items = new Dictionary - { - { "Christmas", new CalDateTime(2006, 12, 25)}, - {"Thanksgiving", new CalDateTime(2006, 11, 23)}, - {"Veteran's Day", new CalDateTime(2006, 11, 11)}, - {"Halloween", new CalDateTime(2006, 10, 31)}, - {"Daylight Saving Time Ends", new CalDateTime(2006, 10, 29)}, - {"Columbus Day", new CalDateTime(2006, 10, 9)}, - {"Labor Day", new CalDateTime(2006, 9, 4)}, - {"Independence Day", new CalDateTime(2006, 7, 4)}, - {"Father's Day", new CalDateTime(2006, 6, 18)}, - {"Flag Day", new CalDateTime(2006, 6, 14)}, - {"John F. Kennedy's Birthday", new CalDateTime(2006, 5, 29)}, - {"Memorial Day", new CalDateTime(2006, 5, 29)}, - {"Mother's Day", new CalDateTime(2006, 5, 14)}, - {"Cinco de Mayo", new CalDateTime(2006, 5, 5)}, - {"Earth Day", new CalDateTime(2006, 4, 22)}, - {"Easter", new CalDateTime(2006, 4, 16)}, - {"Tax Day", new CalDateTime(2006, 4, 15)}, - {"Daylight Saving Time Begins", new CalDateTime(2006, 4, 2)}, - {"April Fool's Day", new CalDateTime(2006, 4, 1)}, - {"St. Patrick's Day", new CalDateTime(2006, 3, 17)}, - {"Washington's Birthday", new CalDateTime(2006, 2, 22)}, - {"President's Day", new CalDateTime(2006, 2, 20)}, - {"Valentine's Day", new CalDateTime(2006, 2, 14)}, - {"Lincoln's Birthday", new CalDateTime(2006, 2, 12)}, - {"Groundhog Day", new CalDateTime(2006, 2, 2)}, - {"Martin Luther King, Jr. Day", new CalDateTime(2006, 1, 16)}, - { "New Year's Day", new CalDateTime(2006, 1, 1)}, - }; - - var occurrences = iCal.GetOccurrences( - new CalDateTime(2006, 1, 1), - new CalDateTime(2006, 12, 31)); - - Assert.That(occurrences, Has.Count.EqualTo(items.Count), "The number of holidays did not evaluate correctly."); - foreach (var o in occurrences) - { - var evt = o.Source as CalendarEvent; - Assert.That(evt, Is.Not.Null); - Assert.Multiple(() => - { - Assert.That(items.ContainsKey(evt.Summary), Is.True, "Holiday text '" + evt.Summary + "' did not match known holidays."); - Assert.That(o.Period.StartTime, Is.EqualTo(items[evt.Summary]), "Date/time of holiday '" + evt.Summary + "' did not match."); - }); - } + /// + /// Tests the iCal holidays downloaded from apple.com + /// + [Test, Category("Recurrence")] + public void UsHolidays() + { + var iCal = Calendar.Load(IcsFiles.UsHolidays); + Assert.That(iCal, Is.Not.Null, "iCalendar was not loaded."); + var items = new Dictionary + { + { "Christmas", new CalDateTime(2006, 12, 25)}, + {"Thanksgiving", new CalDateTime(2006, 11, 23)}, + {"Veteran's Day", new CalDateTime(2006, 11, 11)}, + {"Halloween", new CalDateTime(2006, 10, 31)}, + {"Daylight Saving Time Ends", new CalDateTime(2006, 10, 29)}, + {"Columbus Day", new CalDateTime(2006, 10, 9)}, + {"Labor Day", new CalDateTime(2006, 9, 4)}, + {"Independence Day", new CalDateTime(2006, 7, 4)}, + {"Father's Day", new CalDateTime(2006, 6, 18)}, + {"Flag Day", new CalDateTime(2006, 6, 14)}, + {"John F. Kennedy's Birthday", new CalDateTime(2006, 5, 29)}, + {"Memorial Day", new CalDateTime(2006, 5, 29)}, + {"Mother's Day", new CalDateTime(2006, 5, 14)}, + {"Cinco de Mayo", new CalDateTime(2006, 5, 5)}, + {"Earth Day", new CalDateTime(2006, 4, 22)}, + {"Easter", new CalDateTime(2006, 4, 16)}, + {"Tax Day", new CalDateTime(2006, 4, 15)}, + {"Daylight Saving Time Begins", new CalDateTime(2006, 4, 2)}, + {"April Fool's Day", new CalDateTime(2006, 4, 1)}, + {"St. Patrick's Day", new CalDateTime(2006, 3, 17)}, + {"Washington's Birthday", new CalDateTime(2006, 2, 22)}, + {"President's Day", new CalDateTime(2006, 2, 20)}, + {"Valentine's Day", new CalDateTime(2006, 2, 14)}, + {"Lincoln's Birthday", new CalDateTime(2006, 2, 12)}, + {"Groundhog Day", new CalDateTime(2006, 2, 2)}, + {"Martin Luther King, Jr. Day", new CalDateTime(2006, 1, 16)}, + { "New Year's Day", new CalDateTime(2006, 1, 1)}, + }; + + var occurrences = iCal.GetOccurrences( + new CalDateTime(2006, 1, 1), + new CalDateTime(2006, 12, 31)); + + Assert.That(occurrences, Has.Count.EqualTo(items.Count), "The number of holidays did not evaluate correctly."); + foreach (var o in occurrences) + { + var evt = o.Source as CalendarEvent; + Assert.That(evt, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(items.ContainsKey(evt.Summary), Is.True, "Holiday text '" + evt.Summary + "' did not match known holidays."); + Assert.That(o.Period.StartTime, Is.EqualTo(items[evt.Summary]), "Date/time of holiday '" + evt.Summary + "' did not match."); + }); } + } - /// - /// Ensures that the StartTime and EndTime of periods have - /// HasTime set to true if the beginning time had HasTime set - /// to false. - /// - [Category("Recurrence")] - [TestCase("SECONDLY", 1, true)] - [TestCase("MINUTELY", 60, true)] - [TestCase("HOURLY", 3600, true)] - [TestCase("DAILY", 24*3600, false)] - public void Evaluate1(string freq, int secsPerInterval, bool hasTime) - { - Calendar cal = new Calendar(); + /// + /// Ensures that the StartTime and EndTime of periods have + /// HasTime set to true if the beginning time had HasTime set + /// to false. + /// + [Category("Recurrence")] + [TestCase("SECONDLY", 1, true)] + [TestCase("MINUTELY", 60, true)] + [TestCase("HOURLY", 3600, true)] + [TestCase("DAILY", 24 * 3600, false)] + public void Evaluate1(string freq, int secsPerInterval, bool hasTime) + { + Calendar cal = new Calendar(); - CalendarEvent evt = cal.Create(); - evt.Summary = "Event summary"; + CalendarEvent evt = cal.Create(); + evt.Summary = "Event summary"; - // Start at midnight, UTC time - evt.Start = new CalDateTime(DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc)) { HasTime = false }; + // Start at midnight, UTC time + evt.Start = new CalDateTime(DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc)) { HasTime = false }; - // This case (DTSTART of type DATE and FREQ=MINUTELY) is undefined in RFC 5545. - // ical.net handles the case by pretending DTSTART has the time set to midnight. - evt.RecurrenceRules.Add(new RecurrencePattern($"FREQ={freq};INTERVAL=10;COUNT=5")); + // This case (DTSTART of type DATE and FREQ=MINUTELY) is undefined in RFC 5545. + // ical.net handles the case by pretending DTSTART has the time set to midnight. + evt.RecurrenceRules.Add(new RecurrencePattern($"FREQ={freq};INTERVAL=10;COUNT=5")); #pragma warning disable 0618 - evt.RecurrenceRules[0].RestrictionType = RecurrenceRestrictionType.NoRestriction; + evt.RecurrenceRules[0].RestrictionType = RecurrenceRestrictionType.NoRestriction; #pragma warning restore 0618 - var occurrences = evt.GetOccurrences(CalDateTime.Today.AddDays(-1), CalDateTime.Today.AddDays(100)) - .OrderBy(x => x) - .ToList(); - - var startDates = occurrences.Select(x => x.Period.StartTime.Value).ToList(); + var occurrences = evt.GetOccurrences(CalDateTime.Today.AddDays(-1), CalDateTime.Today.AddDays(100)) + .OrderBy(x => x) + .ToList(); - var expectedStartDates = Enumerable.Range(0, 5) - .Select(i => DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc).AddSeconds(i * secsPerInterval * 10)) - .ToList(); + var startDates = occurrences.Select(x => x.Period.StartTime.Value).ToList(); - Assert.Multiple(() => - { - Assert.That(occurrences.Select(x => x.Period.StartTime.HasTime == hasTime), Is.All.True); - Assert.That(startDates, Is.EqualTo(expectedStartDates)); - }); - } + var expectedStartDates = Enumerable.Range(0, 5) + .Select(i => DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc).AddSeconds(i * secsPerInterval * 10)) + .ToList(); - [Test, Category("Recurrence")] - public void RecurrencePattern1() + Assert.Multiple(() => { - // NOTE: evaluators are not generally meant to be used directly like this. - // However, this does make a good test to ensure they behave as they should. - var pattern = new RecurrencePattern("FREQ=SECONDLY;INTERVAL=10"); + Assert.That(occurrences.Select(x => x.Period.StartTime.HasTime == hasTime), Is.All.True); + Assert.That(startDates, Is.EqualTo(expectedStartDates)); + }); + } + + [Test, Category("Recurrence")] + public void RecurrencePattern1() + { + // NOTE: evaluators are not generally meant to be used directly like this. + // However, this does make a good test to ensure they behave as they should. + var pattern = new RecurrencePattern("FREQ=SECONDLY;INTERVAL=10"); #pragma warning disable 0618 - pattern.RestrictionType = RecurrenceRestrictionType.NoRestriction; + pattern.RestrictionType = RecurrenceRestrictionType.NoRestriction; #pragma warning restore 0618 - var us = new CultureInfo("en-US"); + var us = new CultureInfo("en-US"); - var startDate = new CalDateTime(DateTime.Parse("3/30/08 11:59:40 PM", us)); - var fromDate = new CalDateTime(DateTime.Parse("3/30/08 11:59:40 PM", us)); - var toDate = new CalDateTime(DateTime.Parse("3/31/08 12:00:11 AM", us)); + var startDate = new CalDateTime(DateTime.Parse("3/30/08 11:59:40 PM", us)); + var fromDate = new CalDateTime(DateTime.Parse("3/30/08 11:59:40 PM", us)); + var toDate = new CalDateTime(DateTime.Parse("3/31/08 12:00:11 AM", us)); - var evaluator = pattern.GetService(typeof(IEvaluator)) as IEvaluator; - Assert.That(evaluator, Is.Not.Null); + var evaluator = pattern.GetService(typeof(IEvaluator)) as IEvaluator; + Assert.That(evaluator, Is.Not.Null); - var occurrences = evaluator.Evaluate( - startDate, - DateUtil.SimpleDateTimeToMatch(fromDate, startDate), + var occurrences = evaluator.Evaluate( + startDate, + DateUtil.SimpleDateTimeToMatch(fromDate, startDate), DateUtil.SimpleDateTimeToMatch(toDate, startDate), false) - .OrderBy(o => o.StartTime) - .ToList(); - Assert.That(occurrences, Has.Count.EqualTo(4)); - Assert.Multiple(() => - { - Assert.That(occurrences[0].StartTime, Is.EqualTo(new CalDateTime(DateTime.Parse("03/30/08 11:59:40 PM", us)))); - Assert.That(occurrences[1].StartTime, Is.EqualTo(new CalDateTime(DateTime.Parse("03/30/08 11:59:50 PM", us)))); - Assert.That(occurrences[2].StartTime, Is.EqualTo(new CalDateTime(DateTime.Parse("03/31/08 12:00:00 AM", us)))); - Assert.That(occurrences[3].StartTime, Is.EqualTo(new CalDateTime(DateTime.Parse("03/31/08 12:00:10 AM", us)))); - }); - } + .OrderBy(o => o.StartTime) + .ToList(); + Assert.That(occurrences, Has.Count.EqualTo(4)); + Assert.Multiple(() => + { + Assert.That(occurrences[0].StartTime, Is.EqualTo(new CalDateTime(DateTime.Parse("03/30/08 11:59:40 PM", us)))); + Assert.That(occurrences[1].StartTime, Is.EqualTo(new CalDateTime(DateTime.Parse("03/30/08 11:59:50 PM", us)))); + Assert.That(occurrences[2].StartTime, Is.EqualTo(new CalDateTime(DateTime.Parse("03/31/08 12:00:00 AM", us)))); + Assert.That(occurrences[3].StartTime, Is.EqualTo(new CalDateTime(DateTime.Parse("03/31/08 12:00:10 AM", us)))); + }); + } - [Test, Category("Recurrence")] - public void RecurrencePattern2() - { - // NOTE: evaluators are generally not meant to be used directly like this. - // However, this does make a good test to ensure they behave as they should. - var pattern = new RecurrencePattern("FREQ=MINUTELY;INTERVAL=1"); + [Test, Category("Recurrence")] + public void RecurrencePattern2() + { + // NOTE: evaluators are generally not meant to be used directly like this. + // However, this does make a good test to ensure they behave as they should. + var pattern = new RecurrencePattern("FREQ=MINUTELY;INTERVAL=1"); - var us = new CultureInfo("en-US"); + var us = new CultureInfo("en-US"); - var startDate = new CalDateTime(DateTime.Parse("3/31/2008 12:00:10 AM", us)); - var fromDate = new CalDateTime(DateTime.Parse("4/1/2008 10:08:10 AM", us)); - var toDate = new CalDateTime(DateTime.Parse("4/1/2008 10:43:23 AM", us)); + var startDate = new CalDateTime(DateTime.Parse("3/31/2008 12:00:10 AM", us)); + var fromDate = new CalDateTime(DateTime.Parse("4/1/2008 10:08:10 AM", us)); + var toDate = new CalDateTime(DateTime.Parse("4/1/2008 10:43:23 AM", us)); - var evaluator = pattern.GetService(typeof(IEvaluator)) as IEvaluator; - Assert.That(evaluator, Is.Not.Null); + var evaluator = pattern.GetService(typeof(IEvaluator)) as IEvaluator; + Assert.That(evaluator, Is.Not.Null); - var occurrences = evaluator.Evaluate( - startDate, - DateUtil.SimpleDateTimeToMatch(fromDate, startDate), - DateUtil.SimpleDateTimeToMatch(toDate, startDate), - false); - Assert.That(occurrences.Count, Is.Not.EqualTo(0)); - } + var occurrences = evaluator.Evaluate( + startDate, + DateUtil.SimpleDateTimeToMatch(fromDate, startDate), + DateUtil.SimpleDateTimeToMatch(toDate, startDate), + false); + Assert.That(occurrences.Count, Is.Not.EqualTo(0)); + } - [Test, Category("Recurrence")] - public void GetOccurrences1() - { - Calendar cal = new Calendar(); - CalendarEvent evt = cal.Create(); - evt.Start = new CalDateTime(2009, 11, 18, 5, 0, 0); - evt.End = new CalDateTime(2009, 11, 18, 5, 10, 0); - evt.RecurrenceRules.Add(new RecurrencePattern(FrequencyType.Daily)); - evt.Summary = "xxxxxxxxxxxxx"; - - var previousDateAndTime = new CalDateTime(2009, 11, 17, 0, 15, 0); - var previousDateOnly = new CalDateTime(2009, 11, 17, 23, 15, 0); - var laterDateOnly = new CalDateTime(2009, 11, 19, 3, 15, 0); - var laterDateAndTime = new CalDateTime(2009, 11, 19, 11, 0, 0); - var end = new CalDateTime(2009, 11, 23, 0, 0, 0); + [Test, Category("Recurrence")] + public void GetOccurrences1() + { + Calendar cal = new Calendar(); + CalendarEvent evt = cal.Create(); + evt.Start = new CalDateTime(2009, 11, 18, 5, 0, 0); + evt.End = new CalDateTime(2009, 11, 18, 5, 10, 0); + evt.RecurrenceRules.Add(new RecurrencePattern(FrequencyType.Daily)); + evt.Summary = "xxxxxxxxxxxxx"; - var occurrences = evt.GetOccurrences(previousDateAndTime, end); - Assert.That(occurrences, Has.Count.EqualTo(5)); + var previousDateAndTime = new CalDateTime(2009, 11, 17, 0, 15, 0); + var previousDateOnly = new CalDateTime(2009, 11, 17, 23, 15, 0); + var laterDateOnly = new CalDateTime(2009, 11, 19, 3, 15, 0); + var laterDateAndTime = new CalDateTime(2009, 11, 19, 11, 0, 0); + var end = new CalDateTime(2009, 11, 23, 0, 0, 0); - occurrences = evt.GetOccurrences(previousDateOnly, end); - Assert.That(occurrences, Has.Count.EqualTo(5)); + var occurrences = evt.GetOccurrences(previousDateAndTime, end); + Assert.That(occurrences, Has.Count.EqualTo(5)); - occurrences = evt.GetOccurrences(laterDateOnly, end); - Assert.That(occurrences, Has.Count.EqualTo(4)); + occurrences = evt.GetOccurrences(previousDateOnly, end); + Assert.That(occurrences, Has.Count.EqualTo(5)); - occurrences = evt.GetOccurrences(laterDateAndTime, end); - Assert.That(occurrences, Has.Count.EqualTo(3)); + occurrences = evt.GetOccurrences(laterDateOnly, end); + Assert.That(occurrences, Has.Count.EqualTo(4)); - // Add ByHour "9" and "12" - evt.RecurrenceRules[0].ByHour.Add(9); - evt.RecurrenceRules[0].ByHour.Add(12); + occurrences = evt.GetOccurrences(laterDateAndTime, end); + Assert.That(occurrences, Has.Count.EqualTo(3)); - // Clear the evaluation so we can calculate recurrences again. - evt.ClearEvaluation(); + // Add ByHour "9" and "12" + evt.RecurrenceRules[0].ByHour.Add(9); + evt.RecurrenceRules[0].ByHour.Add(12); - occurrences = evt.GetOccurrences(previousDateAndTime, end); - Assert.That(occurrences, Has.Count.EqualTo(10)); + // Clear the evaluation so we can calculate recurrences again. + evt.ClearEvaluation(); - occurrences = evt.GetOccurrences(previousDateOnly, end); - Assert.That(occurrences, Has.Count.EqualTo(10)); + occurrences = evt.GetOccurrences(previousDateAndTime, end); + Assert.That(occurrences, Has.Count.EqualTo(10)); - occurrences = evt.GetOccurrences(laterDateOnly, end); - Assert.That(occurrences, Has.Count.EqualTo(8)); + occurrences = evt.GetOccurrences(previousDateOnly, end); + Assert.That(occurrences, Has.Count.EqualTo(10)); - occurrences = evt.GetOccurrences(laterDateAndTime, end); - Assert.That(occurrences, Has.Count.EqualTo(7)); - } + occurrences = evt.GetOccurrences(laterDateOnly, end); + Assert.That(occurrences, Has.Count.EqualTo(8)); - [Test, Category("Recurrence")] - public void Test1() - { - Calendar cal = new Calendar(); - CalendarEvent evt = cal.Create(); - evt.Summary = "Event summary"; - evt.Start = new CalDateTime(DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc)); - - RecurrencePattern recur = new RecurrencePattern(); - evt.RecurrenceRules.Add(recur); - - Assert.That(() => - { - _ = evt.GetOccurrences(DateTime.Today.AddDays(1), DateTime.Today.AddDays(2)); - }, Throws.Exception, "An exception should be thrown when evaluating a recurrence with no specified FREQUENCY"); - } + occurrences = evt.GetOccurrences(laterDateAndTime, end); + Assert.That(occurrences, Has.Count.EqualTo(7)); + } - [Test, Category("Recurrence")] - public void Test2() - { - Calendar cal = new Calendar(); - CalendarEvent evt = cal.Create(); - evt.Summary = "Event summary"; - evt.Start = new CalDateTime(DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc)); - - RecurrencePattern recur = new RecurrencePattern(); - recur.Frequency = FrequencyType.Daily; - recur.Count = 3; - recur.ByDay.Add(new WeekDay(DayOfWeek.Monday)); - recur.ByDay.Add(new WeekDay(DayOfWeek.Wednesday)); - recur.ByDay.Add(new WeekDay(DayOfWeek.Friday)); - evt.RecurrenceRules.Add(recur); + [Test, Category("Recurrence")] + public void Test1() + { + Calendar cal = new Calendar(); + CalendarEvent evt = cal.Create(); + evt.Summary = "Event summary"; + evt.Start = new CalDateTime(DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc)); - var serializer = new RecurrencePatternSerializer(); - Assert.That(string.Compare(serializer.SerializeToString(recur), "FREQ=DAILY;COUNT=3;BYDAY=MO,WE,FR", StringComparison.Ordinal) == 0, - Is.True, - "Serialized recurrence string is incorrect"); - } + RecurrencePattern recur = new RecurrencePattern(); + evt.RecurrenceRules.Add(recur); - [Test, Category("Recurrence")] - public void Test4() + Assert.That(() => { - RecurrencePattern rpattern = new RecurrencePattern(); - rpattern.ByDay.Add(new WeekDay(DayOfWeek.Saturday)); - rpattern.ByDay.Add(new WeekDay(DayOfWeek.Sunday)); + _ = evt.GetOccurrences(DateTime.Today.AddDays(1), DateTime.Today.AddDays(2)); + }, Throws.Exception, "An exception should be thrown when evaluating a recurrence with no specified FREQUENCY"); + } + + [Test, Category("Recurrence")] + public void Test2() + { + Calendar cal = new Calendar(); + CalendarEvent evt = cal.Create(); + evt.Summary = "Event summary"; + evt.Start = new CalDateTime(DateTime.SpecifyKind(DateTime.Today, DateTimeKind.Utc)); + + RecurrencePattern recur = new RecurrencePattern(); + recur.Frequency = FrequencyType.Daily; + recur.Count = 3; + recur.ByDay.Add(new WeekDay(DayOfWeek.Monday)); + recur.ByDay.Add(new WeekDay(DayOfWeek.Wednesday)); + recur.ByDay.Add(new WeekDay(DayOfWeek.Friday)); + evt.RecurrenceRules.Add(recur); + + var serializer = new RecurrencePatternSerializer(); + Assert.That(string.Compare(serializer.SerializeToString(recur), "FREQ=DAILY;COUNT=3;BYDAY=MO,WE,FR", StringComparison.Ordinal) == 0, + Is.True, + "Serialized recurrence string is incorrect"); + } + + [Test, Category("Recurrence")] + public void Test4() + { + RecurrencePattern rpattern = new RecurrencePattern(); + rpattern.ByDay.Add(new WeekDay(DayOfWeek.Saturday)); + rpattern.ByDay.Add(new WeekDay(DayOfWeek.Sunday)); - rpattern.Frequency = FrequencyType.Weekly; + rpattern.Frequency = FrequencyType.Weekly; - IDateTime evtStart = new CalDateTime(2006, 12, 1); - IDateTime evtEnd = new CalDateTime(2007, 1, 1); + IDateTime evtStart = new CalDateTime(2006, 12, 1); + IDateTime evtEnd = new CalDateTime(2007, 1, 1); - var evaluator = rpattern.GetService(typeof(IEvaluator)) as IEvaluator; - Assert.That(evaluator, Is.Not.Null); + var evaluator = rpattern.GetService(typeof(IEvaluator)) as IEvaluator; + Assert.That(evaluator, Is.Not.Null); - // Add the exception dates - var periods = evaluator.Evaluate( + // Add the exception dates + var periods = evaluator.Evaluate( evtStart, - DateUtil.GetSimpleDateTimeData(evtStart), + DateUtil.GetSimpleDateTimeData(evtStart), DateUtil.SimpleDateTimeToMatch(evtEnd, evtStart), false) - .OrderBy(p => p.StartTime) - .ToList(); - Assert.That(periods, Has.Count.EqualTo(10)); - Assert.Multiple(() => - { - Assert.That(periods[0].StartTime.Day, Is.EqualTo(2)); - Assert.That(periods[1].StartTime.Day, Is.EqualTo(3)); - Assert.That(periods[2].StartTime.Day, Is.EqualTo(9)); - Assert.That(periods[3].StartTime.Day, Is.EqualTo(10)); - Assert.That(periods[4].StartTime.Day, Is.EqualTo(16)); - Assert.That(periods[5].StartTime.Day, Is.EqualTo(17)); - Assert.That(periods[6].StartTime.Day, Is.EqualTo(23)); - Assert.That(periods[7].StartTime.Day, Is.EqualTo(24)); - Assert.That(periods[8].StartTime.Day, Is.EqualTo(30)); - Assert.That(periods[9].StartTime.Day, Is.EqualTo(31)); - }); - } + .OrderBy(p => p.StartTime) + .ToList(); + Assert.That(periods, Has.Count.EqualTo(10)); + Assert.Multiple(() => + { + Assert.That(periods[0].StartTime.Day, Is.EqualTo(2)); + Assert.That(periods[1].StartTime.Day, Is.EqualTo(3)); + Assert.That(periods[2].StartTime.Day, Is.EqualTo(9)); + Assert.That(periods[3].StartTime.Day, Is.EqualTo(10)); + Assert.That(periods[4].StartTime.Day, Is.EqualTo(16)); + Assert.That(periods[5].StartTime.Day, Is.EqualTo(17)); + Assert.That(periods[6].StartTime.Day, Is.EqualTo(23)); + Assert.That(periods[7].StartTime.Day, Is.EqualTo(24)); + Assert.That(periods[8].StartTime.Day, Is.EqualTo(30)); + Assert.That(periods[9].StartTime.Day, Is.EqualTo(31)); + }); + } - [Test, Category("Recurrence")] - public void ExDateShouldFilterOutAllPeriods() - { - //One-day event starting Aug 23 (inclusive), ending Aug 24 (exclusive), repeating daily until Aug 24 (exclusive). - //I.e. an event that occupies all of Aug 23, and no more, with zero recurrences. - //Then exclude Aug 23 and Aug 24 from the set of recurrences. - const string ical = @"BEGIN:VCALENDAR + [Test, Category("Recurrence")] + public void ExDateShouldFilterOutAllPeriods() + { + //One-day event starting Aug 23 (inclusive), ending Aug 24 (exclusive), repeating daily until Aug 24 (exclusive). + //I.e. an event that occupies all of Aug 23, and no more, with zero recurrences. + //Then exclude Aug 23 and Aug 24 from the set of recurrences. + const string ical = @"BEGIN:VCALENDAR BEGIN:VEVENT DTSTART;VALUE=DATE:20120823 DTEND;VALUE=DATE:20120824 @@ -2996,21 +3002,21 @@ public void ExDateShouldFilterOutAllPeriods() TRANSP:TRANSPARENT END:VEVENT END:VCALENDAR"; - var calendar = Calendar.Load(ical); - var firstEvent = calendar.Events.First(); - var startSearch = new CalDateTime(2010, 1, 1, _tzid); - var endSearch = new CalDateTime(2016, 12, 31, _tzid); + var calendar = Calendar.Load(ical); + var firstEvent = calendar.Events.First(); + var startSearch = new CalDateTime(2010, 1, 1, _tzid); + var endSearch = new CalDateTime(2016, 12, 31, _tzid); - var occurrences = firstEvent.GetOccurrences(startSearch, endSearch).Select(o => o.Period).ToList(); - Assert.That(occurrences.Count == 0, Is.True); - } + var occurrences = firstEvent.GetOccurrences(startSearch, endSearch).Select(o => o.Period).ToList(); + Assert.That(occurrences.Count == 0, Is.True); + } - [Test, Category("Recurrence")] - public void RDateShouldBeUnionedWithRecurrenceSet() - { - //Issues #118 and #107 on Github - const string ical = -@"BEGIN:VCALENDAR + [Test, Category("Recurrence")] + public void RDateShouldBeUnionedWithRecurrenceSet() + { + //Issues #118 and #107 on Github + const string ical = + @"BEGIN:VCALENDAR PRODID:-//ddaysoftware.com//NONSGML DDay.iCal 1.0//EN VERSION:2.0 BEGIN:VEVENT @@ -3025,32 +3031,32 @@ public void RDateShouldBeUnionedWithRecurrenceSet() END:VEVENT END:VCALENDAR"; - var calendar = Calendar.Load(ical); - var firstEvent = calendar.Events.First(); - var startSearch = new CalDateTime(DateTime.Parse("2015-08-28T07:00:00"), _tzid); - var endSearch = new CalDateTime(DateTime.Parse("2016-08-28T07:00:00").AddDays(7), _tzid); + var calendar = Calendar.Load(ical); + var firstEvent = calendar.Events.First(); + var startSearch = new CalDateTime(DateTime.Parse("2015-08-28T07:00:00"), _tzid); + var endSearch = new CalDateTime(DateTime.Parse("2016-08-28T07:00:00").AddDays(7), _tzid); - var occurrences = firstEvent.GetOccurrences(startSearch, endSearch) - .Select(o => o.Period) - .OrderBy(p => p.StartTime) - .ToList(); + var occurrences = firstEvent.GetOccurrences(startSearch, endSearch) + .Select(o => o.Period) + .OrderBy(p => p.StartTime) + .ToList(); - var firstExpectedOccurrence = new CalDateTime(DateTime.Parse("2016-08-29T08:00:00"), _tzid); - Assert.That(occurrences.First().StartTime, Is.EqualTo(firstExpectedOccurrence)); + var firstExpectedOccurrence = new CalDateTime(DateTime.Parse("2016-08-29T08:00:00"), _tzid); + Assert.That(occurrences.First().StartTime, Is.EqualTo(firstExpectedOccurrence)); - var firstExpectedRDate = new CalDateTime(DateTime.Parse("2016-08-30T10:00:00"), _tzid); - Assert.That(occurrences[1].StartTime.Equals(firstExpectedRDate), Is.True); + var firstExpectedRDate = new CalDateTime(DateTime.Parse("2016-08-30T10:00:00"), _tzid); + Assert.That(occurrences[1].StartTime.Equals(firstExpectedRDate), Is.True); - var secondExpectedRDate = new CalDateTime(DateTime.Parse("2016-08-31T10:00:00"), _tzid); - Assert.That(occurrences[2].StartTime.Equals(secondExpectedRDate), Is.True); - } + var secondExpectedRDate = new CalDateTime(DateTime.Parse("2016-08-31T10:00:00"), _tzid); + Assert.That(occurrences[2].StartTime.Equals(secondExpectedRDate), Is.True); + } - [Test] - public void OccurrenceMustBeCompletelyContainedWithinSearchRange() - { - //https://github.com/rianjs/ical.net/issues/121 + [Test] + public void OccurrenceMustBeCompletelyContainedWithinSearchRange() + { + //https://github.com/rianjs/ical.net/issues/121 - const string ical = @"BEGIN:VCALENDAR + const string ical = @"BEGIN:VCALENDAR PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 2.2//EN VERSION:2.0 BEGIN:VEVENT @@ -3063,61 +3069,61 @@ public void OccurrenceMustBeCompletelyContainedWithinSearchRange() END:VEVENT END:VCALENDAR"; - var rrule = new RecurrencePattern(FrequencyType.Weekly, interval: 1) - { - Until = DateTime.Parse("2016-08-31T07:00:00"), - ByDay = new List { new WeekDay(DayOfWeek.Wednesday)}, - }; + var rrule = new RecurrencePattern(FrequencyType.Weekly, interval: 1) + { + Until = DateTime.Parse("2016-08-31T07:00:00"), + ByDay = new List { new WeekDay(DayOfWeek.Wednesday) }, + }; - var start = DateTime.Parse("2016-08-01T07:00:00"); - var end = start.AddHours(1); - var e = new CalendarEvent - { - DtStart = new CalDateTime(start, "UTC"), - DtEnd = new CalDateTime(end, "UTC"), - RecurrenceRules = new List { rrule }, - Summary = "This is an event", - Uid = "abab717c-1786-4efc-87dd-6859c2b48eb6", - }; + var start = DateTime.Parse("2016-08-01T07:00:00"); + var end = start.AddHours(1); + var e = new CalendarEvent + { + DtStart = new CalDateTime(start, "UTC"), + DtEnd = new CalDateTime(end, "UTC"), + RecurrenceRules = new List { rrule }, + Summary = "This is an event", + Uid = "abab717c-1786-4efc-87dd-6859c2b48eb6", + }; - var deserializedCalendar = Calendar.Load(ical); - var firstEvent = deserializedCalendar.Events.First(); - var calendar = new Calendar(); - calendar.Events.Add(e); + var deserializedCalendar = Calendar.Load(ical); + var firstEvent = deserializedCalendar.Events.First(); + var calendar = new Calendar(); + calendar.Events.Add(e); - Assert.That(firstEvent, Is.EqualTo(e)); + Assert.That(firstEvent, Is.EqualTo(e)); - var startSearch = new CalDateTime(DateTime.Parse("2016-07-01T00:00:00"), "UTC"); - var endSearch = new CalDateTime(DateTime.Parse("2016-08-31T07:00:00"), "UTC"); + var startSearch = new CalDateTime(DateTime.Parse("2016-07-01T00:00:00"), "UTC"); + var endSearch = new CalDateTime(DateTime.Parse("2016-08-31T07:00:00"), "UTC"); - var lastExpected = new CalDateTime(DateTime.Parse("2016-08-31T07:00:00"), "UTC"); - var occurrences = firstEvent.GetOccurrences(startSearch, endSearch) - .Select(o => o.Period) - .OrderBy(p => p.StartTime) - .ToList(); + var lastExpected = new CalDateTime(DateTime.Parse("2016-08-31T07:00:00"), "UTC"); + var occurrences = firstEvent.GetOccurrences(startSearch, endSearch) + .Select(o => o.Period) + .OrderBy(p => p.StartTime) + .ToList(); - Assert.That(occurrences.Last().StartTime.Equals(lastExpected), Is.False); + Assert.That(occurrences.Last().StartTime.Equals(lastExpected), Is.False); - //Create 1 second of overlap - endSearch = new CalDateTime(endSearch.Value.AddSeconds(1), "UTC"); - occurrences = firstEvent.GetOccurrences(startSearch, endSearch) - .Select(o => o.Period) - .OrderBy(p => p.StartTime) - .ToList(); + //Create 1 second of overlap + endSearch = new CalDateTime(endSearch.Value.AddSeconds(1), "UTC"); + occurrences = firstEvent.GetOccurrences(startSearch, endSearch) + .Select(o => o.Period) + .OrderBy(p => p.StartTime) + .ToList(); - Assert.That(occurrences.Last().StartTime.Equals(lastExpected), Is.True); - } + Assert.That(occurrences.Last().StartTime.Equals(lastExpected), Is.True); + } - /// - /// Evaluate relevancy and validity of the request. - /// Find a solution for issue #120 or close forever - /// - [Test, Ignore("Turn on in v3", Until = "2024-12-31")] - public void EventsWithShareUidsShouldGenerateASingleRecurrenceSet() - { - //https://github.com/rianjs/ical.net/issues/120 dated Sep 5, 2016 - const string ical = -@"BEGIN:VCALENDAR + /// + /// Evaluate relevancy and validity of the request. + /// Find a solution for issue #120 or close forever + /// + [Test, Ignore("Turn on in v3", Until = "2024-12-31")] + public void EventsWithShareUidsShouldGenerateASingleRecurrenceSet() + { + //https://github.com/rianjs/ical.net/issues/120 dated Sep 5, 2016 + const string ical = + @"BEGIN:VCALENDAR PRODID:-//Google Inc//Google Calendar 70.9054//EN VERSION:2.0 CALSCALE:GREGORIAN @@ -3171,218 +3177,218 @@ public void EventsWithShareUidsShouldGenerateASingleRecurrenceSet() END:VEVENT END:VCALENDAR"; - var calendars = CalendarCollection.Load(ical); - var events = calendars.SelectMany(c => c.Events).ToList(); - - var startSearch = DateTime.Parse("2016-08-01T00:00:00"); - var endSearch = startSearch.AddDays(45); - - //The API should be something like: - //var occurrences = calendar.GetOccurrences(string eventUid, DateTime startSearch, DateTime endSearch); + var calendars = CalendarCollection.Load(ical); + var events = calendars.SelectMany(c => c.Events).ToList(); - var occurrences = new HashSet(); + var startSearch = DateTime.Parse("2016-08-01T00:00:00"); + var endSearch = startSearch.AddDays(45); - var orderedOccurrences = occurrences - .Select(o => o.Period) - .OrderBy(p => p.StartTime) - .ToList(); + //The API should be something like: + //var occurrences = calendar.GetOccurrences(string eventUid, DateTime startSearch, DateTime endSearch); - var expectedSept1Start = new CalDateTime(DateTime.Parse("2016-09-01T16:30:00"), "Europe/Bucharest"); - var expectedSept1End = new CalDateTime(DateTime.Parse("2016-09-01T22:00:00"), "Europe/Bucharest"); - Assert.Multiple(() => - { - Assert.That(orderedOccurrences[3].StartTime, Is.EqualTo(expectedSept1Start)); - Assert.That(orderedOccurrences[3].EndTime, Is.EqualTo(expectedSept1End)); - }); + var occurrences = new HashSet(); - var expectedSept3Start = new CalDateTime(DateTime.Parse("2016-09-03T07:00:00"), "Europe/Bucharest"); - var expectedSept3End = new CalDateTime(DateTime.Parse("2016-09-01T12:30:00"), "Europe/Bucharest"); - Assert.Multiple(() => - { - Assert.That(orderedOccurrences[5].StartTime, Is.EqualTo(expectedSept3Start)); - Assert.That(orderedOccurrences[5].EndTime, Is.EqualTo(expectedSept3End)); - }); - } + var orderedOccurrences = occurrences + .Select(o => o.Period) + .OrderBy(p => p.StartTime) + .ToList(); - [Test] - public void AddExDateToEventAfterGetOccurrencesShouldRecomputeResult() + var expectedSept1Start = new CalDateTime(DateTime.Parse("2016-09-01T16:30:00"), "Europe/Bucharest"); + var expectedSept1End = new CalDateTime(DateTime.Parse("2016-09-01T22:00:00"), "Europe/Bucharest"); + Assert.Multiple(() => { - var searchStart = _now.AddDays(-1); - var searchEnd = _now.AddDays(7); - var e = GetEventWithRecurrenceRules(); - var occurrences = e.GetOccurrences(searchStart, searchEnd); - Assert.That(occurrences.Count == 5, Is.True); - - var exDate = _now.AddDays(1); - var period = new Period(new CalDateTime(exDate)); - var periodList = new PeriodList { period }; - e.ExceptionDates.Add(periodList); - occurrences = e.GetOccurrences(searchStart, searchEnd); - Assert.That(occurrences.Count == 4, Is.True); - - //Specifying just a date should "black out" that date - var excludeTwoDaysFromNow = _now.AddDays(2).Date; - period = new Period(new CalDateTime(excludeTwoDaysFromNow)); - periodList.Add(period); - occurrences = e.GetOccurrences(searchStart, searchEnd); - Assert.That(occurrences.Count == 3, Is.True); - } + Assert.That(orderedOccurrences[3].StartTime, Is.EqualTo(expectedSept1Start)); + Assert.That(orderedOccurrences[3].EndTime, Is.EqualTo(expectedSept1End)); + }); - private static readonly DateTime _now = DateTime.Now; - private static readonly DateTime _later = _now.AddHours(1); - private static CalendarEvent GetEventWithRecurrenceRules() + var expectedSept3Start = new CalDateTime(DateTime.Parse("2016-09-03T07:00:00"), "Europe/Bucharest"); + var expectedSept3End = new CalDateTime(DateTime.Parse("2016-09-01T12:30:00"), "Europe/Bucharest"); + Assert.Multiple(() => { - var dailyForFiveDays = new RecurrencePattern(FrequencyType.Daily, 1) - { - Count = 5, - }; + Assert.That(orderedOccurrences[5].StartTime, Is.EqualTo(expectedSept3Start)); + Assert.That(orderedOccurrences[5].EndTime, Is.EqualTo(expectedSept3End)); + }); + } - var calendarEvent = new CalendarEvent - { - Start = new CalDateTime(_now), - End = new CalDateTime(_later), - RecurrenceRules = new List { dailyForFiveDays }, - Resources = new List(new[] { "Foo", "Bar", "Baz" }), - }; - return calendarEvent; - } + [Test] + public void AddExDateToEventAfterGetOccurrencesShouldRecomputeResult() + { + var searchStart = _now.AddDays(-1); + var searchEnd = _now.AddDays(7); + var e = GetEventWithRecurrenceRules(); + var occurrences = e.GetOccurrences(searchStart, searchEnd); + Assert.That(occurrences.Count == 5, Is.True); + + var exDate = _now.AddDays(1); + var period = new Period(new CalDateTime(exDate)); + var periodList = new PeriodList { period }; + e.ExceptionDates.Add(periodList); + occurrences = e.GetOccurrences(searchStart, searchEnd); + Assert.That(occurrences.Count == 4, Is.True); + + //Specifying just a date should "black out" that date + var excludeTwoDaysFromNow = _now.AddDays(2).Date; + period = new Period(new CalDateTime(excludeTwoDaysFromNow)); + periodList.Add(period); + occurrences = e.GetOccurrences(searchStart, searchEnd); + Assert.That(occurrences.Count == 3, Is.True); + } - [Test] - public void ExDateFold_Tests() + private static readonly DateTime _now = DateTime.Now; + private static readonly DateTime _later = _now.AddHours(1); + private static CalendarEvent GetEventWithRecurrenceRules() + { + var dailyForFiveDays = new RecurrencePattern(FrequencyType.Daily, 1) { - var start = _now.AddYears(-1); - var end = start.AddHours(1); - var rrule = new RecurrencePattern(FrequencyType.Daily) { Until = start.AddYears(2) }; - var e = new CalendarEvent - { - DtStart = new CalDateTime(start), - DtEnd = new CalDateTime(end), - RecurrenceRules = new List { rrule } - }; - - var firstExclusion = new CalDateTime(start.AddDays(4)); - e.ExceptionDates = new List { new PeriodList { new Period(firstExclusion) } }; - var serialized = SerializationHelpers.SerializeToString(e); - Assert.That(Regex.Matches(serialized, "EXDATE:"), Has.Count.EqualTo(1)); - - var secondExclusion = new CalDateTime(start.AddDays(5)); - e.ExceptionDates.First().Add(new Period(secondExclusion)); - serialized = SerializationHelpers.SerializeToString(e); - Assert.That(Regex.Matches(serialized, "EXDATE:"), Has.Count.EqualTo(1)); - } + Count = 5, + }; - [Test] - public void ExDateTimeZone_Tests() + var calendarEvent = new CalendarEvent { - const string tzid = "Europe/Stockholm"; + Start = new CalDateTime(_now), + End = new CalDateTime(_later), + RecurrenceRules = new List { dailyForFiveDays }, + Resources = new List(new[] { "Foo", "Bar", "Baz" }), + }; + return calendarEvent; + } - //Repeat daily for 10 days - var rrule = GetSimpleRecurrencePattern(10); + [Test] + public void ExDateFold_Tests() + { + var start = _now.AddYears(-1); + var end = start.AddHours(1); + var rrule = new RecurrencePattern(FrequencyType.Daily) { Until = start.AddYears(2) }; + var e = new CalendarEvent + { + DtStart = new CalDateTime(start), + DtEnd = new CalDateTime(end), + RecurrenceRules = new List { rrule } + }; + + var firstExclusion = new CalDateTime(start.AddDays(4)); + e.ExceptionDates = new List { new PeriodList { new Period(firstExclusion) } }; + var serialized = SerializationHelpers.SerializeToString(e); + Assert.That(Regex.Matches(serialized, "EXDATE:"), Has.Count.EqualTo(1)); + + var secondExclusion = new CalDateTime(start.AddDays(5)); + e.ExceptionDates.First().Add(new Period(secondExclusion)); + serialized = SerializationHelpers.SerializeToString(e); + Assert.That(Regex.Matches(serialized, "EXDATE:"), Has.Count.EqualTo(1)); + } - var e = new CalendarEvent - { - DtStart = new CalDateTime(_now, tzid), - DtEnd = new CalDateTime(_later, tzid), - RecurrenceRules = new List { rrule }, - }; + [Test] + public void ExDateTimeZone_Tests() + { + const string tzid = "Europe/Stockholm"; + + //Repeat daily for 10 days + var rrule = GetSimpleRecurrencePattern(10); - var exceptionDateList = new PeriodList { TzId = tzid }; - exceptionDateList.Add(new Period(new CalDateTime(_now.AddDays(1)))); - e.ExceptionDates.Add(exceptionDateList); + var e = new CalendarEvent + { + DtStart = new CalDateTime(_now, tzid), + DtEnd = new CalDateTime(_later, tzid), + RecurrenceRules = new List { rrule }, + }; - var serialized = SerializationHelpers.SerializeToString(e); - const string expected = "TZID=Europe/Stockholm"; - Assert.That(Regex.Matches(serialized, expected), Has.Count.EqualTo(3)); + var exceptionDateList = new PeriodList { TzId = tzid }; + exceptionDateList.Add(new Period(new CalDateTime(_now.AddDays(1)))); + e.ExceptionDates.Add(exceptionDateList); - e.ExceptionDates.First().Add(new Period(new CalDateTime(_now.AddDays(2)))); - serialized = SerializationHelpers.SerializeToString(e); - Assert.That(Regex.Matches(serialized, expected), Has.Count.EqualTo(3)); - } + var serialized = SerializationHelpers.SerializeToString(e); + const string expected = "TZID=Europe/Stockholm"; + Assert.That(Regex.Matches(serialized, expected), Has.Count.EqualTo(3)); - [Test, Category("Recurrence")] - public void OneDayRange() + e.ExceptionDates.First().Add(new Period(new CalDateTime(_now.AddDays(2)))); + serialized = SerializationHelpers.SerializeToString(e); + Assert.That(Regex.Matches(serialized, expected), Has.Count.EqualTo(3)); + } + + [Test, Category("Recurrence")] + public void OneDayRange() + { + var vEvent = new CalendarEvent { - var vEvent = new CalendarEvent - { - Start = new CalDateTime(DateTime.Parse("2019-06-07 0:00:00")), - End = new CalDateTime(DateTime.Parse("2019-06-08 00:00:00")) - }; + Start = new CalDateTime(DateTime.Parse("2019-06-07 0:00:00")), + End = new CalDateTime(DateTime.Parse("2019-06-08 00:00:00")) + }; - //Testing on both the first day and the next, results used to be different - for (var i = 0; i <= 1; i++) - { - var checkTime = DateTime.Parse("2019-06-07 00:00:00"); - checkTime = checkTime.AddDays(i); + //Testing on both the first day and the next, results used to be different + for (var i = 0; i <= 1; i++) + { + var checkTime = DateTime.Parse("2019-06-07 00:00:00"); + checkTime = checkTime.AddDays(i); - //Valid if asking for a range starting at the same moment - var occurrences = vEvent.GetOccurrences(checkTime, checkTime.AddDays(1)); - Assert.That(occurrences, Has.Count.EqualTo(i == 0 ? 1 : 0)); - } + //Valid if asking for a range starting at the same moment + var occurrences = vEvent.GetOccurrences(checkTime, checkTime.AddDays(1)); + Assert.That(occurrences, Has.Count.EqualTo(i == 0 ? 1 : 0)); } + } - [Test, Category("Recurrence")] - public void SpecificMinute() + [Test, Category("Recurrence")] + public void SpecificMinute() + { + var rrule = new RecurrencePattern { - var rrule = new RecurrencePattern - { - Frequency = FrequencyType.Daily - }; - var vEvent = new CalendarEvent - { - Start = new CalDateTime(DateTime.Parse("2009-01-01 09:00:00")), - End = new CalDateTime(DateTime.Parse("2009-01-01 17:00:00")) - }; + Frequency = FrequencyType.Daily + }; + var vEvent = new CalendarEvent + { + Start = new CalDateTime(DateTime.Parse("2009-01-01 09:00:00")), + End = new CalDateTime(DateTime.Parse("2009-01-01 17:00:00")) + }; - vEvent.RecurrenceRules.Add(rrule); + vEvent.RecurrenceRules.Add(rrule); - // Exactly on start time - var testingTime = new DateTime(2019, 6, 7, 9, 0, 0); + // Exactly on start time + var testingTime = new DateTime(2019, 6, 7, 9, 0, 0); - var occurrences = vEvent.GetOccurrences(testingTime, testingTime); - Assert.That(occurrences, Has.Count.EqualTo(1)); + var occurrences = vEvent.GetOccurrences(testingTime, testingTime); + Assert.That(occurrences, Has.Count.EqualTo(1)); - // One second before end time - testingTime = new DateTime(2019, 6, 7, 16, 59, 59); + // One second before end time + testingTime = new DateTime(2019, 6, 7, 16, 59, 59); - occurrences = vEvent.GetOccurrences(testingTime, testingTime); - Assert.That(occurrences, Has.Count.EqualTo(1)); + occurrences = vEvent.GetOccurrences(testingTime, testingTime); + Assert.That(occurrences, Has.Count.EqualTo(1)); - // Exactly on end time - testingTime = new DateTime(2019, 6, 7, 17, 0, 0); + // Exactly on end time + testingTime = new DateTime(2019, 6, 7, 17, 0, 0); - occurrences = vEvent.GetOccurrences(testingTime, testingTime); - Assert.That(occurrences.Count, Is.EqualTo(0)); - } + occurrences = vEvent.GetOccurrences(testingTime, testingTime); + Assert.That(occurrences.Count, Is.EqualTo(0)); + } - private static RecurrencePattern GetSimpleRecurrencePattern(int count) => new RecurrencePattern(FrequencyType.Daily, 1) { Count = count, }; + private static RecurrencePattern GetSimpleRecurrencePattern(int count) => new RecurrencePattern(FrequencyType.Daily, 1) { Count = count, }; - private static CalendarEvent GetSimpleEvent() + private static CalendarEvent GetSimpleEvent() + { + var e = new CalendarEvent { - var e = new CalendarEvent - { - DtStart = new CalDateTime(_now, _tzid), - DtEnd = new CalDateTime(_later, _tzid), - }; - return e; - } + DtStart = new CalDateTime(_now, _tzid), + DtEnd = new CalDateTime(_later, _tzid), + }; + return e; + } - [Test] - public void RecurrenceRuleTests() - { - var five = GetSimpleRecurrencePattern(5); - var ten = GetSimpleRecurrencePattern(10); - Assert.That(ten, Is.Not.EqualTo(five)); - var eventA = GetSimpleEvent(); - eventA.RecurrenceRules.Add(five); - eventA.RecurrenceRules.Add(ten); + [Test] + public void RecurrenceRuleTests() + { + var five = GetSimpleRecurrencePattern(5); + var ten = GetSimpleRecurrencePattern(10); + Assert.That(ten, Is.Not.EqualTo(five)); + var eventA = GetSimpleEvent(); + eventA.RecurrenceRules.Add(five); + eventA.RecurrenceRules.Add(ten); - var eventB = GetSimpleEvent(); - eventB.RecurrenceRules.Add(ten); - eventB.RecurrenceRules.Add(five); + var eventB = GetSimpleEvent(); + eventB.RecurrenceRules.Add(ten); + eventB.RecurrenceRules.Add(five); - Assert.That(eventB, Is.EqualTo(eventA)); + Assert.That(eventB, Is.EqualTo(eventA)); - const string aString = @"BEGIN:VCALENDAR + const string aString = @"BEGIN:VCALENDAR PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 2.2//EN VERSION:2.0 BEGIN:VEVENT @@ -3402,7 +3408,7 @@ public void RecurrenceRuleTests() END:VEVENT END:VCALENDAR"; - const string bString = @"BEGIN:VCALENDAR + const string bString = @"BEGIN:VCALENDAR PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 2.2//EN VERSION:2.0 BEGIN:VEVENT @@ -3423,35 +3429,35 @@ public void RecurrenceRuleTests() END:VEVENT END:VCALENDAR"; - var simpleA = Calendar.Load(aString); - var normalA = Calendar.Load(aString); - var simpleB = Calendar.Load(bString); - var normalB = Calendar.Load(bString); - - var calendarList = new List { simpleA, normalA, simpleB, normalB }; - var eventList = new List - { - simpleA.Events.Single(), - normalA.Events.Single(), - simpleB.Events.Single(), - normalB.Events.Single(), - }; - - //GetHashCode tests also tests Equals() - var calendarSet = new HashSet(calendarList); - Assert.That(calendarSet, Has.Count.EqualTo(1)); - var eventSet = new HashSet(eventList); - Assert.That(eventSet, Has.Count.EqualTo(1)); - - var newEventList = new HashSet(); - newEventList.UnionWith(eventList); - Assert.That(newEventList, Has.Count.EqualTo(1)); - } + var simpleA = Calendar.Load(aString); + var normalA = Calendar.Load(aString); + var simpleB = Calendar.Load(bString); + var normalB = Calendar.Load(bString); + + var calendarList = new List { simpleA, normalA, simpleB, normalB }; + var eventList = new List + { + simpleA.Events.Single(), + normalA.Events.Single(), + simpleB.Events.Single(), + normalB.Events.Single(), + }; + + //GetHashCode tests also tests Equals() + var calendarSet = new HashSet(calendarList); + Assert.That(calendarSet, Has.Count.EqualTo(1)); + var eventSet = new HashSet(eventList); + Assert.That(eventSet, Has.Count.EqualTo(1)); + + var newEventList = new HashSet(); + newEventList.UnionWith(eventList); + Assert.That(newEventList, Has.Count.EqualTo(1)); + } - [Test] - public void ManyExclusionDatesEqualityTesting() - { - const string icalA = @"BEGIN:VCALENDAR + [Test] + public void ManyExclusionDatesEqualityTesting() + { + const string icalA = @"BEGIN:VCALENDAR PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 2.2//EN VERSION:2.0 BEGIN:VEVENT @@ -3473,7 +3479,7 @@ public void ManyExclusionDatesEqualityTesting() END:VEVENT END:VCALENDAR"; - const string icalB = @"BEGIN:VCALENDAR + const string icalB = @"BEGIN:VCALENDAR PRODID:-//github.com/rianjs/ical.net//NONSGML ical.net 2.2//EN VERSION:2.0 BEGIN:VEVENT @@ -3495,97 +3501,97 @@ public void ManyExclusionDatesEqualityTesting() END:VEVENT END:VCALENDAR"; - //The only textual difference between A and B is a different DTSTAMP, which is not considered significant for equality or hashing - - //Tautologies... - var collectionA = CalendarCollection.Load(icalA); - Assert.That(collectionA, Is.EqualTo(collectionA)); - Assert.That(collectionA.GetHashCode(), Is.EqualTo(collectionA.GetHashCode())); - var calendarA = collectionA.First(); - Assert.That(calendarA, Is.EqualTo(calendarA)); - Assert.That(calendarA.GetHashCode(), Is.EqualTo(calendarA.GetHashCode())); - var eventA = calendarA.Events.First(); - Assert.That(eventA, Is.EqualTo(eventA)); - Assert.That(eventA.GetHashCode(), Is.EqualTo(eventA.GetHashCode())); - - var collectionB = CalendarCollection.Load(icalB); - Assert.That(collectionB, Is.EqualTo(collectionB)); - Assert.That(collectionB.GetHashCode(), Is.EqualTo(collectionB.GetHashCode())); - var calendarB = collectionB.First(); - Assert.That(calendarB, Is.EqualTo(calendarB)); - Assert.That(calendarB.GetHashCode(), Is.EqualTo(calendarB.GetHashCode())); - var eventB = calendarB.Events.First(); - - Assert.Multiple(() => - { - //Comparing the two... - Assert.That(collectionB, Is.EqualTo(collectionA)); - Assert.That(collectionB.GetHashCode(), Is.EqualTo(collectionA.GetHashCode())); - Assert.That(calendarB, Is.EqualTo(calendarA)); - Assert.That(calendarB.GetHashCode(), Is.EqualTo(calendarA.GetHashCode())); - Assert.That(eventB, Is.EqualTo(eventA)); - Assert.That(eventB.GetHashCode(), Is.EqualTo(eventA.GetHashCode())); - }); - + //The only textual difference between A and B is a different DTSTAMP, which is not considered significant for equality or hashing + + //Tautologies... + var collectionA = CalendarCollection.Load(icalA); + Assert.That(collectionA, Is.EqualTo(collectionA)); + Assert.That(collectionA.GetHashCode(), Is.EqualTo(collectionA.GetHashCode())); + var calendarA = collectionA.First(); + Assert.That(calendarA, Is.EqualTo(calendarA)); + Assert.That(calendarA.GetHashCode(), Is.EqualTo(calendarA.GetHashCode())); + var eventA = calendarA.Events.First(); + Assert.That(eventA, Is.EqualTo(eventA)); + Assert.That(eventA.GetHashCode(), Is.EqualTo(eventA.GetHashCode())); + + var collectionB = CalendarCollection.Load(icalB); + Assert.That(collectionB, Is.EqualTo(collectionB)); + Assert.That(collectionB.GetHashCode(), Is.EqualTo(collectionB.GetHashCode())); + var calendarB = collectionB.First(); + Assert.That(calendarB, Is.EqualTo(calendarB)); + Assert.That(calendarB.GetHashCode(), Is.EqualTo(calendarB.GetHashCode())); + var eventB = calendarB.Events.First(); + + Assert.Multiple(() => + { + //Comparing the two... + Assert.That(collectionB, Is.EqualTo(collectionA)); + Assert.That(collectionB.GetHashCode(), Is.EqualTo(collectionA.GetHashCode())); + Assert.That(calendarB, Is.EqualTo(calendarA)); + Assert.That(calendarB.GetHashCode(), Is.EqualTo(calendarA.GetHashCode())); + Assert.That(eventB, Is.EqualTo(eventA)); + Assert.That(eventB.GetHashCode(), Is.EqualTo(eventA.GetHashCode())); + }); - var exDatesA = eventA.ExceptionDates; - var exDatesB = eventB.ExceptionDates; - Assert.That(exDatesB, Is.EqualTo(exDatesA)); - } + var exDatesA = eventA.ExceptionDates; + var exDatesB = eventB.ExceptionDates; + Assert.That(exDatesB, Is.EqualTo(exDatesA)); - [Test, TestCaseSource(nameof(UntilTimeZoneSerializationTestCases))] - public void UntilTimeZoneSerializationTests(string tzid, DateTimeKind expectedKind) - { - var now = DateTime.SpecifyKind(DateTime.Parse("2017-11-08 10:30:00"), expectedKind); - var later = now.AddHours(1); + } - var until = DateTime.SpecifyKind(now.AddDays(7), expectedKind); + [Test, TestCaseSource(nameof(UntilTimeZoneSerializationTestCases))] + public void UntilTimeZoneSerializationTests(string tzid, DateTimeKind expectedKind) + { + var now = DateTime.SpecifyKind(DateTime.Parse("2017-11-08 10:30:00"), expectedKind); + var later = now.AddHours(1); - var rrule = new RecurrencePattern(FrequencyType.Daily) - { - Until = until, - }; - var e = new CalendarEvent - { - Start = new CalDateTime(now, tzid), - End = new CalDateTime(later, tzid) - }; - e.RecurrenceRules.Add(rrule); - var calendar = new Calendar - { - Events = { e }, - }; + var until = DateTime.SpecifyKind(now.AddDays(7), expectedKind); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(calendar); + var rrule = new RecurrencePattern(FrequencyType.Daily) + { + Until = until, + }; + var e = new CalendarEvent + { + Start = new CalDateTime(now, tzid), + End = new CalDateTime(later, tzid) + }; + e.RecurrenceRules.Add(rrule); + var calendar = new Calendar + { + Events = { e }, + }; - const string contains = "20171108T103000"; - var expectedContains = expectedKind == DateTimeKind.Local - ? $"{contains}{SerializationConstants.LineBreak}" - : $"{contains}Z{SerializationConstants.LineBreak}"; + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(calendar); - Assert.That(serialized.Contains(expectedContains), Is.True); + const string contains = "20171108T103000"; + var expectedContains = expectedKind == DateTimeKind.Local + ? $"{contains}{SerializationConstants.LineBreak}" + : $"{contains}Z{SerializationConstants.LineBreak}"; - var deserializedKind = Calendar.Load(serialized).Events.First().RecurrenceRules.First().Until.Kind; + Assert.That(serialized.Contains(expectedContains), Is.True); - Assert.That(deserializedKind, Is.EqualTo(expectedKind)); - } + var deserializedKind = Calendar.Load(serialized).Events.First().RecurrenceRules.First().Until.Kind; - public static IEnumerable UntilTimeZoneSerializationTestCases() - { - yield return new TestCaseData("America/New_York", DateTimeKind.Local) - .SetName("IANA time time zone results in a local DateTimeKind"); - yield return new TestCaseData("Eastern Standard Time", DateTimeKind.Local) - .SetName("BCL time zone results in a Local DateTimeKind"); - yield return new TestCaseData("UTC", DateTimeKind.Utc) - .SetName("UTC results in DateTimeKind.Utc"); - } + Assert.That(deserializedKind, Is.EqualTo(expectedKind)); + } - [Test] - public void InclusiveRruleUntil() - { - const string icalText = @"BEGIN:VCALENDAR + public static IEnumerable UntilTimeZoneSerializationTestCases() + { + yield return new TestCaseData("America/New_York", DateTimeKind.Local) + .SetName("IANA time time zone results in a local DateTimeKind"); + yield return new TestCaseData("Eastern Standard Time", DateTimeKind.Local) + .SetName("BCL time zone results in a Local DateTimeKind"); + yield return new TestCaseData("UTC", DateTimeKind.Utc) + .SetName("UTC results in DateTimeKind.Utc"); + } + + [Test] + public void InclusiveRruleUntil() + { + const string icalText = @"BEGIN:VCALENDAR BEGIN:VEVENT DTSTART;VALUE=DATE:20180101 DTEND;VALUE=DATE:20180102 @@ -3603,124 +3609,123 @@ public void InclusiveRruleUntil() END:VEVENT END:VCALENDAR "; - const string timeZoneId = @"Eastern Standard Time"; - var calendar = Calendar.Load(icalText); - var firstEvent = calendar.Events.First(); - var startSearch = new CalDateTime(DateTime.Parse("2017-07-01T00:00:00"), timeZoneId); - var endSearch = new CalDateTime(DateTime.Parse("2018-07-01T00:00:00"), timeZoneId); - - var occurrences = firstEvent.GetOccurrences(startSearch, endSearch); - Assert.That(occurrences, Has.Count.EqualTo(5)); - } + const string timeZoneId = @"Eastern Standard Time"; + var calendar = Calendar.Load(icalText); + var firstEvent = calendar.Events.First(); + var startSearch = new CalDateTime(DateTime.Parse("2017-07-01T00:00:00"), timeZoneId); + var endSearch = new CalDateTime(DateTime.Parse("2018-07-01T00:00:00"), timeZoneId); + + var occurrences = firstEvent.GetOccurrences(startSearch, endSearch); + Assert.That(occurrences, Has.Count.EqualTo(5)); + } - public class RecurrenceTestCase - { - public int LineNumber { get; set; } + public class RecurrenceTestCase + { + public int LineNumber { get; set; } - public string RRule { get; set; } + public string RRule { get; set; } - public CalDateTime DtStart { get; set; } + public CalDateTime DtStart { get; set; } - public CalDateTime StartAt { get; set; } + public CalDateTime StartAt { get; set; } - public IReadOnlyList Instances { get; set; } + public IReadOnlyList Instances { get; set; } - public override string ToString() - => $"Line {LineNumber}: {DtStart}, {RRule}"; - } + public override string ToString() + => $"Line {LineNumber}: {DtStart}, {RRule}"; + } - private static IEnumerable ParseTestCaseFile(string fileContent) - { - RecurrenceTestCase current = null; + private static IEnumerable ParseTestCaseFile(string fileContent) + { + RecurrenceTestCase current = null; - var rd = new StringReader(fileContent); - var lineNo = 0; + var rd = new StringReader(fileContent); + var lineNo = 0; - for (string line = rd.ReadLine(); line != null; line = rd.ReadLine()) - { - lineNo++; + for (string line = rd.ReadLine(); line != null; line = rd.ReadLine()) + { + lineNo++; - if (string.IsNullOrEmpty(line)) + if (string.IsNullOrEmpty(line)) + { + if (current != null) { - if (current != null) - { - yield return current; - current = null; - } - continue; + yield return current; + current = null; } + continue; + } - if (line.StartsWith("#")) - continue; + if (line.StartsWith("#")) + continue; - current = current ?? new RecurrenceTestCase(); + current = current ?? new RecurrenceTestCase(); - var m = Regex.Match(line, @"^(?[A-Z-]+):(?.*)$"); - if (!m.Success) - continue; + var m = Regex.Match(line, @"^(?[A-Z-]+):(?.*)$"); + if (!m.Success) + continue; - var hdr = m.Groups["h"].Value; - var val = m.Groups["v"].Value; + var hdr = m.Groups["h"].Value; + var val = m.Groups["v"].Value; - switch (hdr) - { - case "RRULE": - current.RRule = val; - current.LineNumber = lineNo; - break; - - case "DTSTART": - current.DtStart = new CalDateTime(val) { TzId = "UTC" }; - break; - - case "START-AT": - current.StartAt = new CalDateTime(val) { TzId = "UTC" }; - break; - - case "INSTANCES": - current.Instances = val.Split(',').Select(dt => new CalDateTime(dt) { TzId = "UTC" }).ToList(); - break; - } + switch (hdr) + { + case "RRULE": + current.RRule = val; + current.LineNumber = lineNo; + break; + + case "DTSTART": + current.DtStart = new CalDateTime(val) { TzId = "UTC" }; + break; + + case "START-AT": + current.StartAt = new CalDateTime(val) { TzId = "UTC" }; + break; + + case "INSTANCES": + current.Instances = val.Split(',').Select(dt => new CalDateTime(dt) { TzId = "UTC" }).ToList(); + break; } - - if (current != null) - yield return current; } - private static IEnumerable TestLibicalTestCasesSource - => ParseTestCaseFile(IcsFiles.LibicalIcalrecurTest); + if (current != null) + yield return current; + } + + private static IEnumerable TestLibicalTestCasesSource + => ParseTestCaseFile(IcsFiles.LibicalIcalrecurTest); - [TestCaseSource(nameof(TestLibicalTestCasesSource))] - public void TestLibicalTestCases(RecurrenceTestCase testCase) - { - ExecuteRecurrenceTestCase(testCase); - } + [TestCaseSource(nameof(TestLibicalTestCasesSource))] + public void TestLibicalTestCases(RecurrenceTestCase testCase) + { + ExecuteRecurrenceTestCase(testCase); + } - private static IEnumerable TestFileBasedRecurrenceTestCaseSource - => ParseTestCaseFile(IcsFiles.RecurrrenceTestCases); + private static IEnumerable TestFileBasedRecurrenceTestCaseSource + => ParseTestCaseFile(IcsFiles.RecurrrenceTestCases); - [TestCaseSource(nameof(TestFileBasedRecurrenceTestCaseSource))] - public void TestFileBasedRecurrenceTestCase(RecurrenceTestCase testCase) - => ExecuteRecurrenceTestCase(testCase); + [TestCaseSource(nameof(TestFileBasedRecurrenceTestCaseSource))] + public void TestFileBasedRecurrenceTestCase(RecurrenceTestCase testCase) + => ExecuteRecurrenceTestCase(testCase); - public void ExecuteRecurrenceTestCase(RecurrenceTestCase testCase) - { - Calendar cal = new Calendar(); + public void ExecuteRecurrenceTestCase(RecurrenceTestCase testCase) + { + Calendar cal = new Calendar(); - CalendarEvent evt = cal.Create(); - evt.Summary = "Event summary"; + CalendarEvent evt = cal.Create(); + evt.Summary = "Event summary"; - // Start at midnight, UTC time - evt.Start = testCase.DtStart; - evt.RecurrenceRules.Add(new RecurrencePattern(testCase.RRule)); + // Start at midnight, UTC time + evt.Start = testCase.DtStart; + evt.RecurrenceRules.Add(new RecurrencePattern(testCase.RRule)); - var occurrences = evt.GetOccurrences(testCase.StartAt?.Value ?? DateTime.MinValue, DateTime.MaxValue) - .OrderBy(x => x) - .ToList(); + var occurrences = evt.GetOccurrences(testCase.StartAt?.Value ?? DateTime.MinValue, DateTime.MaxValue) + .OrderBy(x => x) + .ToList(); - var startDates = occurrences.Select(x => x.Period.StartTime).ToList(); + var startDates = occurrences.Select(x => x.Period.StartTime).ToList(); - Assert.That(startDates, Is.EqualTo(testCase.Instances)); - } + Assert.That(startDates, Is.EqualTo(testCase.Instances)); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/SerializationHelpers.cs b/Ical.Net.Tests/SerializationHelpers.cs index 15a5fa811..6900b1442 100644 --- a/Ical.Net.Tests/SerializationHelpers.cs +++ b/Ical.Net.Tests/SerializationHelpers.cs @@ -1,14 +1,18 @@ -using Ical.Net.CalendarComponents; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using Ical.Net.CalendarComponents; using Ical.Net.Serialization; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +internal class SerializationHelpers { - internal class SerializationHelpers - { - public static string SerializeToString(CalendarEvent calendarEvent) - => SerializeToString(new Calendar { Events = { calendarEvent } }); - - public static string SerializeToString(Calendar iCalendar) - => new CalendarSerializer().SerializeToString(iCalendar); - } -} + public static string SerializeToString(CalendarEvent calendarEvent) + => SerializeToString(new Calendar { Events = { calendarEvent } }); + + public static string SerializeToString(Calendar iCalendar) + => new CalendarSerializer().SerializeToString(iCalendar); +} \ No newline at end of file diff --git a/Ical.Net.Tests/SerializationTests.cs b/Ical.Net.Tests/SerializationTests.cs index a633c642f..fa6f74f4c 100644 --- a/Ical.Net.Tests/SerializationTests.cs +++ b/Ical.Net.Tests/SerializationTests.cs @@ -1,9 +1,8 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Serialization; -using Ical.Net.Serialization.DataTypes; -using Ical.Net.Utility; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections; using System.Collections.Generic; @@ -11,417 +10,423 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Serialization; +using Ical.Net.Serialization.DataTypes; +using Ical.Net.Utility; +using NUnit.Framework; + +namespace Ical.Net.Tests; -namespace Ical.Net.Tests +[TestFixture] +public class SerializationTests { - [TestFixture] - public class SerializationTests + private static readonly DateTime _nowTime = DateTime.Now; + private static readonly DateTime _later = _nowTime.AddHours(1); + private static CalendarSerializer GetNewSerializer() => new CalendarSerializer(); + private static string SerializeToString(Calendar c) => GetNewSerializer().SerializeToString(c); + private static string SerializeToString(CalendarEvent e) => SerializeToString(new Calendar { Events = { e } }); + private static CalendarEvent GetSimpleEvent() => new CalendarEvent { DtStart = new CalDateTime(_nowTime), DtEnd = new CalDateTime(_later), Duration = _later - _nowTime }; + private static Calendar UnserializeCalendar(string s) => Calendar.Load(s); + + internal static void CompareCalendars(Calendar cal1, Calendar cal2) { - private static readonly DateTime _nowTime = DateTime.Now; - private static readonly DateTime _later = _nowTime.AddHours(1); - private static CalendarSerializer GetNewSerializer() => new CalendarSerializer(); - private static string SerializeToString(Calendar c) => GetNewSerializer().SerializeToString(c); - private static string SerializeToString(CalendarEvent e) => SerializeToString(new Calendar { Events = { e } }); - private static CalendarEvent GetSimpleEvent() => new CalendarEvent { DtStart = new CalDateTime(_nowTime), DtEnd = new CalDateTime(_later), Duration = _later - _nowTime }; - private static Calendar UnserializeCalendar(string s) => Calendar.Load(s); - - internal static void CompareCalendars(Calendar cal1, Calendar cal2) - { - CompareComponents(cal1, cal2); + CompareComponents(cal1, cal2); - Assert.That(cal2.Children, Has.Count.EqualTo(cal1.Children.Count), "Children count is different between calendars."); + Assert.That(cal2.Children, Has.Count.EqualTo(cal1.Children.Count), "Children count is different between calendars."); - for (var i = 0; i < cal1.Children.Count; i++) + for (var i = 0; i < cal1.Children.Count; i++) + { + var component1 = cal1.Children[i] as ICalendarComponent; + var component2 = cal2.Children[i] as ICalendarComponent; + if (component1 != null && component2 != null) { - var component1 = cal1.Children[i] as ICalendarComponent; - var component2 = cal2.Children[i] as ICalendarComponent; - if (component1 != null && component2 != null) - { - CompareComponents(component1, component2); - } + CompareComponents(component1, component2); } } + } - internal static void CompareComponents(ICalendarComponent cb1, ICalendarComponent cb2) + internal static void CompareComponents(ICalendarComponent cb1, ICalendarComponent cb2) + { + foreach (var p1 in cb1.Properties) { - foreach (var p1 in cb1.Properties) + var isMatch = false; + foreach (var p2 in cb2.Properties.AllOf(p1.Name)) { - var isMatch = false; - foreach (var p2 in cb2.Properties.AllOf(p1.Name)) + Assert.That(p2, Is.EqualTo(p1), "The properties '" + p1.Name + "' are not equal."); + if (p1.Value is IComparable) { - Assert.That(p2, Is.EqualTo(p1), "The properties '" + p1.Name + "' are not equal."); - if (p1.Value is IComparable) - { - if (((IComparable)p1.Value).CompareTo(p2.Value) != 0) - continue; - } - else if (p1.Value is IEnumerable) - { - CompareEnumerables((IEnumerable)p1.Value, (IEnumerable)p2.Value, p1.Name); - } - else - { - Assert.That(p2.Value, Is.EqualTo(p1.Value), - "The '" + p1.Name + "' property values are not equal."); - } - - isMatch = true; - break; + if (((IComparable) p1.Value).CompareTo(p2.Value) != 0) + continue; } - - Assert.That(isMatch, Is.True, "Could not find a matching property - " + p1.Name + ":" + (p1.Value?.ToString() ?? string.Empty)); - } - - Assert.That(cb2.Children, Has.Count.EqualTo(cb1.Children.Count), "The number of children are not equal."); - for (var i = 0; i < cb1.Children.Count; i++) - { - var child1 = cb1.Children[i] as ICalendarComponent; - var child2 = cb2.Children[i] as ICalendarComponent; - if (child1 != null && child2 != null) + else if (p1.Value is IEnumerable) { - CompareComponents(child1, child2); + CompareEnumerables((IEnumerable) p1.Value, (IEnumerable) p2.Value, p1.Name); } else { - Assert.That(child2, Is.EqualTo(child1), "The child objects are not equal."); + Assert.That(p2.Value, Is.EqualTo(p1.Value), + "The '" + p1.Name + "' property values are not equal."); } + + isMatch = true; + break; } + + Assert.That(isMatch, Is.True, "Could not find a matching property - " + p1.Name + ":" + (p1.Value?.ToString() ?? string.Empty)); } - public static void CompareEnumerables(IEnumerable a1, IEnumerable a2, string value) + Assert.That(cb2.Children, Has.Count.EqualTo(cb1.Children.Count), "The number of children are not equal."); + for (var i = 0; i < cb1.Children.Count; i++) { - if (a1 == null && a2 == null) + var child1 = cb1.Children[i] as ICalendarComponent; + var child2 = cb2.Children[i] as ICalendarComponent; + if (child1 != null && child2 != null) { - return; + CompareComponents(child1, child2); } - - Assert.That((a1 == null && a2 != null) || (a1 != null && a2 == null), Is.False, value + " do not match - one item is null"); - - var enum1 = a1.GetEnumerator(); - var enum2 = a2.GetEnumerator(); - - while (enum1.MoveNext() && enum2.MoveNext()) + else { - Assert.That(enum2.Current, Is.EqualTo(enum1.Current), value + " do not match"); + Assert.That(child2, Is.EqualTo(child1), "The child objects are not equal."); } } + } - public static string InspectSerializedSection(string serialized, string sectionName, IEnumerable elements) + public static void CompareEnumerables(IEnumerable a1, IEnumerable a2, string value) + { + if (a1 == null && a2 == null) { - const string notFound = "expected '{0}' not found"; - var searchFor = "BEGIN:" + sectionName; - var begin = serialized.IndexOf(searchFor); - Assert.That(begin, Is.Not.EqualTo(-1), () => string.Format(notFound, searchFor)); - - searchFor = "END:" + sectionName; - var end = serialized.IndexOf(searchFor, begin); - Assert.That(end, Is.Not.EqualTo(-1), () => string.Format(notFound, searchFor)); + return; + } - var searchRegion = serialized.Substring(begin, end - begin + searchFor.Length); + Assert.That((a1 == null && a2 != null) || (a1 != null && a2 == null), Is.False, value + " do not match - one item is null"); - foreach (var e in elements) - { - Assert.That(searchRegion.Contains(SerializationConstants.LineBreak + e + SerializationConstants.LineBreak), Is.True, () => string.Format(notFound, e)); - } + var enum1 = a1.GetEnumerator(); + var enum2 = a2.GetEnumerator(); - return searchRegion; + while (enum1.MoveNext() && enum2.MoveNext()) + { + Assert.That(enum2.Current, Is.EqualTo(enum1.Current), value + " do not match"); } + } - //3 formats - UTC, local time as defined in vTimeZone, and floating, - //at some point it would be great to independently unit test string serialization of an IDateTime object, into its 3 forms - //http://www.kanzaki.com/docs/ical/dateTime.html - private static string CalDateString(IDateTime cdt) - { - var returnVar = $"{cdt.Year}{cdt.Month:D2}{cdt.Day:D2}T{cdt.Hour:D2}{cdt.Minute:D2}{cdt.Second:D2}"; - if (cdt.IsUtc) - { - return returnVar + 'Z'; - } + public static string InspectSerializedSection(string serialized, string sectionName, IEnumerable elements) + { + const string notFound = "expected '{0}' not found"; + var searchFor = "BEGIN:" + sectionName; + var begin = serialized.IndexOf(searchFor); + Assert.That(begin, Is.Not.EqualTo(-1), () => string.Format(notFound, searchFor)); - return string.IsNullOrEmpty(cdt.TzId) - ? returnVar - : $"TZID={cdt.TzId}:{returnVar}"; - } + searchFor = "END:" + sectionName; + var end = serialized.IndexOf(searchFor, begin); + Assert.That(end, Is.Not.EqualTo(-1), () => string.Format(notFound, searchFor)); + + var searchRegion = serialized.Substring(begin, end - begin + searchFor.Length); - //This method needs renaming - private static Dictionary GetValues(string serialized, string name, string value) + foreach (var e in elements) { - var lengthened = serialized.Replace(SerializationConstants.LineBreak + ' ', string.Empty); - //using a regex for now - for the sake of speed, it may be worth creating a C# text search later - var match = Regex.Match(lengthened, '^' + Regex.Escape(name) + "(;.+)?:" + Regex.Escape(value) + SerializationConstants.LineBreak, RegexOptions.Multiline); - Assert.That(match.Success, Is.True, $"could not find a(n) '{name}' with value '{value}'"); - return match.Groups[1].Value.Length == 0 - ? new Dictionary() - : match.Groups[1].Value.Substring(1).Split(';').Select(v => v.Split('=')).ToDictionary(v => v[0], v => v.Length > 1 ? v[1] : null); + Assert.That(searchRegion.Contains(SerializationConstants.LineBreak + e + SerializationConstants.LineBreak), Is.True, () => string.Format(notFound, e)); } - [Test, Category("Serialization"), Ignore("TODO: standard time, for NZ standard time (current example)")] - public void TimeZoneSerialize() + return searchRegion; + } + + //3 formats - UTC, local time as defined in vTimeZone, and floating, + //at some point it would be great to independently unit test string serialization of an IDateTime object, into its 3 forms + //http://www.kanzaki.com/docs/ical/dateTime.html + private static string CalDateString(IDateTime cdt) + { + var returnVar = $"{cdt.Year}{cdt.Month:D2}{cdt.Day:D2}T{cdt.Hour:D2}{cdt.Minute:D2}{cdt.Second:D2}"; + if (cdt.IsUtc) { - //ToDo: This test is broken as of 2016-07-13 - var cal = new Calendar - { - Method = "PUBLISH", - Version = "2.0" - }; - - const string exampleTz = "New Zealand Standard Time"; - var tzi = TimeZoneInfo.FindSystemTimeZoneById(exampleTz); - var tz = new VTimeZone(exampleTz); - cal.AddTimeZone(tz); - var evt = new CalendarEvent - { - Summary = "Testing", - Start = new CalDateTime(2016, 7, 14, tz.TzId), - End = new CalDateTime(2016, 7, 15, tz.TzId) - }; - cal.Events.Add(evt); - - var serializer = new CalendarSerializer(); - var serializedCalendar = serializer.SerializeToString(cal); - - var vTimezone = InspectSerializedSection(serializedCalendar, "VTIMEZONE", new[] { "TZID:" + tz.TzId }); - var o = tzi.BaseUtcOffset.ToString("hhmm", CultureInfo.InvariantCulture); - - InspectSerializedSection(vTimezone, "STANDARD", new[] {"TZNAME:" + tzi.StandardName, "TZOFFSETTO:" + o - //"DTSTART:20150402T030000", - //"RRULE:FREQ=YEARLY;BYDAY=1SU;BYHOUR=3;BYMINUTE=0;BYMONTH=4", - //"TZOFFSETFROM:+1300" - }); + return returnVar + 'Z'; + } + return string.IsNullOrEmpty(cdt.TzId) + ? returnVar + : $"TZID={cdt.TzId}:{returnVar}"; + } - InspectSerializedSection(vTimezone, "DAYLIGHT", new[] { "TZNAME:" + tzi.DaylightName, "TZOFFSETFROM:" + o }); - } - [Test, Category("Serialization")] - public void SerializeDeserialize() + //This method needs renaming + private static Dictionary GetValues(string serialized, string name, string value) + { + var lengthened = serialized.Replace(SerializationConstants.LineBreak + ' ', string.Empty); + //using a regex for now - for the sake of speed, it may be worth creating a C# text search later + var match = Regex.Match(lengthened, '^' + Regex.Escape(name) + "(;.+)?:" + Regex.Escape(value) + SerializationConstants.LineBreak, RegexOptions.Multiline); + Assert.That(match.Success, Is.True, $"could not find a(n) '{name}' with value '{value}'"); + return match.Groups[1].Value.Length == 0 + ? new Dictionary() + : match.Groups[1].Value.Substring(1).Split(';').Select(v => v.Split('=')).ToDictionary(v => v[0], v => v.Length > 1 ? v[1] : null); + } + + [Test, Category("Serialization"), Ignore("TODO: standard time, for NZ standard time (current example)")] + public void TimeZoneSerialize() + { + //ToDo: This test is broken as of 2016-07-13 + var cal = new Calendar { - //ToDo: This test is broken as of 2016-07-13 - var cal1 = new Calendar - { - Method = "PUBLISH", - Version = "2.0" - }; + Method = "PUBLISH", + Version = "2.0" + }; + + const string exampleTz = "New Zealand Standard Time"; + var tzi = TimeZoneInfo.FindSystemTimeZoneById(exampleTz); + var tz = new VTimeZone(exampleTz); + cal.AddTimeZone(tz); + var evt = new CalendarEvent + { + Summary = "Testing", + Start = new CalDateTime(2016, 7, 14, tz.TzId), + End = new CalDateTime(2016, 7, 15, tz.TzId) + }; + cal.Events.Add(evt); + + var serializer = new CalendarSerializer(); + var serializedCalendar = serializer.SerializeToString(cal); + + var vTimezone = InspectSerializedSection(serializedCalendar, "VTIMEZONE", new[] { "TZID:" + tz.TzId }); + var o = tzi.BaseUtcOffset.ToString("hhmm", CultureInfo.InvariantCulture); + + InspectSerializedSection(vTimezone, "STANDARD", new[] {"TZNAME:" + tzi.StandardName, "TZOFFSETTO:" + o + //"DTSTART:20150402T030000", + //"RRULE:FREQ=YEARLY;BYDAY=1SU;BYHOUR=3;BYMINUTE=0;BYMONTH=4", + //"TZOFFSETFROM:+1300" + }); - var evt = new CalendarEvent - { - Class = "PRIVATE", - Created = new CalDateTime(2010, 3, 25, 12, 53, 35), - DtStamp = new CalDateTime(2010, 3, 25, 12, 53, 35), - LastModified = new CalDateTime(2010, 3, 27, 13, 53, 35), - Sequence = 0, - Uid = "42f58d4f-847e-46f8-9f4a-ce52697682cf", - Priority = 5, - Location = "here", - Summary = "test", - DtStart = new CalDateTime(2012, 3, 25, 12, 50, 00), - DtEnd = new CalDateTime(2012, 3, 25, 13, 10, 00) - }; - cal1.Events.Add(evt); - - var serializer = new CalendarSerializer(); - var serializedCalendar = serializer.SerializeToString(cal1); - var cal2 = Calendar.Load(serializedCalendar); - CompareCalendars(cal1, cal2); - } - [Test, Category("Serialization")] - public void EventPropertiesSerialized() + InspectSerializedSection(vTimezone, "DAYLIGHT", new[] { "TZNAME:" + tzi.DaylightName, "TZOFFSETFROM:" + o }); + } + [Test, Category("Serialization")] + public void SerializeDeserialize() + { + //ToDo: This test is broken as of 2016-07-13 + var cal1 = new Calendar { - //ToDo: This test is broken as of 2016-07-13 - var cal = new Calendar - { - Method = "PUBLISH", - Version = "2.0" - }; + Method = "PUBLISH", + Version = "2.0" + }; - var evt = new CalendarEvent - { - Class = "PRIVATE", - Created = new CalDateTime(2010, 3, 25, 12, 53, 35), - DtStamp = new CalDateTime(2010, 3, 25, 12, 53, 35), - LastModified = new CalDateTime(2010, 3, 27, 13, 53, 35), - Sequence = 0, - Uid = "42f58d4f-847e-46f8-9f4a-ce52697682cf", - Priority = 5, - Location = "here", - Summary = "test", - DtStart = new CalDateTime(2012, 3, 25, 12, 50, 00), - DtEnd = new CalDateTime(2012, 3, 25, 13, 10, 00) - //not yet testing property below as serialized output currently does not comply with RTFC 2445 - //Transparency = TransparencyType.Opaque, - //Status = EventStatus.Confirmed - }; - cal.Events.Add(evt); - - var serializer = new CalendarSerializer(); - var serializedCalendar = serializer.SerializeToString(cal); - - Assert.Multiple(() => - { - Assert.That(serializedCalendar.StartsWith("BEGIN:VCALENDAR"), Is.True); - Assert.That(serializedCalendar.EndsWith("END:VCALENDAR" + SerializationConstants.LineBreak), Is.True); - }); + var evt = new CalendarEvent + { + Class = "PRIVATE", + Created = new CalDateTime(2010, 3, 25, 12, 53, 35), + DtStamp = new CalDateTime(2010, 3, 25, 12, 53, 35), + LastModified = new CalDateTime(2010, 3, 27, 13, 53, 35), + Sequence = 0, + Uid = "42f58d4f-847e-46f8-9f4a-ce52697682cf", + Priority = 5, + Location = "here", + Summary = "test", + DtStart = new CalDateTime(2012, 3, 25, 12, 50, 00), + DtEnd = new CalDateTime(2012, 3, 25, 13, 10, 00) + }; + cal1.Events.Add(evt); + + var serializer = new CalendarSerializer(); + var serializedCalendar = serializer.SerializeToString(cal1); + var cal2 = Calendar.Load(serializedCalendar); + CompareCalendars(cal1, cal2); + } - var expectProperties = new[] { "METHOD:PUBLISH", "VERSION:2.0" }; + [Test, Category("Serialization")] + public void EventPropertiesSerialized() + { + //ToDo: This test is broken as of 2016-07-13 + var cal = new Calendar + { + Method = "PUBLISH", + Version = "2.0" + }; - foreach (var p in expectProperties) - { - Assert.That(serializedCalendar.Contains(SerializationConstants.LineBreak + p + SerializationConstants.LineBreak), Is.True, "expected '" + p + "' not found"); - } + var evt = new CalendarEvent + { + Class = "PRIVATE", + Created = new CalDateTime(2010, 3, 25, 12, 53, 35), + DtStamp = new CalDateTime(2010, 3, 25, 12, 53, 35), + LastModified = new CalDateTime(2010, 3, 27, 13, 53, 35), + Sequence = 0, + Uid = "42f58d4f-847e-46f8-9f4a-ce52697682cf", + Priority = 5, + Location = "here", + Summary = "test", + DtStart = new CalDateTime(2012, 3, 25, 12, 50, 00), + DtEnd = new CalDateTime(2012, 3, 25, 13, 10, 00) + //not yet testing property below as serialized output currently does not comply with RTFC 2445 + //Transparency = TransparencyType.Opaque, + //Status = EventStatus.Confirmed + }; + cal.Events.Add(evt); + + var serializer = new CalendarSerializer(); + var serializedCalendar = serializer.SerializeToString(cal); + + Assert.Multiple(() => + { + Assert.That(serializedCalendar.StartsWith("BEGIN:VCALENDAR"), Is.True); + Assert.That(serializedCalendar.EndsWith("END:VCALENDAR" + SerializationConstants.LineBreak), Is.True); + }); - InspectSerializedSection(serializedCalendar, "VEVENT", - new[] - { - "CLASS:" + evt.Class, "CREATED:" + CalDateString(evt.Created), "DTSTAMP:" + CalDateString(evt.DtStamp), - "LAST-MODIFIED:" + CalDateString(evt.LastModified), "SEQUENCE:" + evt.Sequence, "UID:" + evt.Uid, "PRIORITY:" + evt.Priority, - "LOCATION:" + evt.Location, "SUMMARY:" + evt.Summary, "DTSTART:" + CalDateString(evt.DtStart), "DTEND:" + CalDateString(evt.DtEnd) - //"TRANSPARENCY:" + TransparencyType.Opaque.ToString().ToUpperInvariant(), - //"STATUS:" + EventStatus.Confirmed.ToString().ToUpperInvariant() - }); - } + var expectProperties = new[] { "METHOD:PUBLISH", "VERSION:2.0" }; - private static readonly IList _attendees = new List + foreach (var p in expectProperties) { - new Attendee("MAILTO:james@example.com") - { - CommonName = "James", - Role = ParticipationRole.RequiredParticipant, - Rsvp = true, - ParticipationStatus = EventParticipationStatus.Tentative - }, - new Attendee("MAILTO:mary@example.com") + Assert.That(serializedCalendar.Contains(SerializationConstants.LineBreak + p + SerializationConstants.LineBreak), Is.True, "expected '" + p + "' not found"); + } + + InspectSerializedSection(serializedCalendar, "VEVENT", + new[] { - CommonName = "Mary", - Role = ParticipationRole.RequiredParticipant, - Rsvp = true, - ParticipationStatus = EventParticipationStatus.Accepted - } - }.AsReadOnly(); + "CLASS:" + evt.Class, "CREATED:" + CalDateString(evt.Created), "DTSTAMP:" + CalDateString(evt.DtStamp), + "LAST-MODIFIED:" + CalDateString(evt.LastModified), "SEQUENCE:" + evt.Sequence, "UID:" + evt.Uid, "PRIORITY:" + evt.Priority, + "LOCATION:" + evt.Location, "SUMMARY:" + evt.Summary, "DTSTART:" + CalDateString(evt.DtStart), "DTEND:" + CalDateString(evt.DtEnd) + //"TRANSPARENCY:" + TransparencyType.Opaque.ToString().ToUpperInvariant(), + //"STATUS:" + EventStatus.Confirmed.ToString().ToUpperInvariant() + }); + } + + private static readonly IList _attendees = new List + { + new Attendee("MAILTO:james@example.com") + { + CommonName = "James", + Role = ParticipationRole.RequiredParticipant, + Rsvp = true, + ParticipationStatus = EventParticipationStatus.Tentative + }, + new Attendee("MAILTO:mary@example.com") + { + CommonName = "Mary", + Role = ParticipationRole.RequiredParticipant, + Rsvp = true, + ParticipationStatus = EventParticipationStatus.Accepted + } + }.AsReadOnly(); - [Test, Category("Serialization")] - public void AttendeesSerialized() + [Test, Category("Serialization")] + public void AttendeesSerialized() + { + var cal = new Calendar { - var cal = new Calendar - { - Method = "REQUEST", - Version = "2.0" - }; + Method = "REQUEST", + Version = "2.0" + }; - var evt = AttendeeTest.VEventFactory(); - cal.Events.Add(evt); - // new Uri() creates lowercase for the "MAILTO:" part - // according to the RFC 2368 specification - const string org = "MAILTO:james@example.com"; - evt.Organizer = new Organizer(org); + var evt = AttendeeTest.VEventFactory(); + cal.Events.Add(evt); + // new Uri() creates lowercase for the "MAILTO:" part + // according to the RFC 2368 specification + const string org = "MAILTO:james@example.com"; + evt.Organizer = new Organizer(org); - evt.Attendees.AddRange(_attendees); + evt.Attendees.AddRange(_attendees); - // Changing the ParticipationStatus just keeps the last status - evt.Attendees[0].ParticipationStatus = EventParticipationStatus.Declined; + // Changing the ParticipationStatus just keeps the last status + evt.Attendees[0].ParticipationStatus = EventParticipationStatus.Declined; - var serializer = new CalendarSerializer(); - var serializedCalendar = serializer.SerializeToString(cal); + var serializer = new CalendarSerializer(); + var serializedCalendar = serializer.SerializeToString(cal); - var vEvt = InspectSerializedSection(serializedCalendar, "VEVENT", new[] { "ORGANIZER:" + org }); + var vEvt = InspectSerializedSection(serializedCalendar, "VEVENT", new[] { "ORGANIZER:" + org }); - foreach (var a in evt.Attendees) + foreach (var a in evt.Attendees) + { + var vals = GetValues(vEvt, "ATTENDEE", a.Value.ToString()); + foreach (var v in new Dictionary + { + ["CN"] = a.CommonName, + ["ROLE"] = a.Role, + ["RSVP"] = a.Rsvp.ToString() + .ToUpperInvariant(), + ["PARTSTAT"] = a.ParticipationStatus + }) { - var vals = GetValues(vEvt, "ATTENDEE", a.Value.ToString()); - foreach (var v in new Dictionary - { - ["CN"] = a.CommonName, - ["ROLE"] = a.Role, - ["RSVP"] = a.Rsvp.ToString() - .ToUpperInvariant(), - ["PARTSTAT"] = a.ParticipationStatus - }) + Assert.Multiple(() => { - Assert.Multiple(() => - { - Assert.That(vals.ContainsKey(v.Key), Is.True, $"could not find key '{v.Key}'"); - Assert.That(vals[v.Key], Is.EqualTo(v.Value), $"ATTENDEE prop '{v.Key}' differ"); - }); - } + Assert.That(vals.ContainsKey(v.Key), Is.True, $"could not find key '{v.Key}'"); + Assert.That(vals[v.Key], Is.EqualTo(v.Value), $"ATTENDEE prop '{v.Key}' differ"); + }); } } + } - //todo test event: - //-GeographicLocation - //-Alarm + //todo test event: + //-GeographicLocation + //-Alarm - [Test] - public void ZeroTimeSpan_Test() - { - var result = new TimeSpanSerializer().SerializeToString(TimeSpan.Zero); - Assert.That("P0D".Equals(result, StringComparison.Ordinal), Is.True); - } + [Test] + public void ZeroTimeSpan_Test() + { + var result = new TimeSpanSerializer().SerializeToString(TimeSpan.Zero); + Assert.That("P0D".Equals(result, StringComparison.Ordinal), Is.True); + } - [Test] - public void DurationIsStable_Tests() + [Test] + public void DurationIsStable_Tests() + { + var e = GetSimpleEvent(); + var originalDuration = e.Duration; + var c = new Calendar(); + c.Events.Add(e); + var serialized = SerializeToString(c); + Assert.Multiple(() => { - var e = GetSimpleEvent(); - var originalDuration = e.Duration; - var c = new Calendar(); - c.Events.Add(e); - var serialized = SerializeToString(c); - Assert.Multiple(() => - { - Assert.That(e.Duration, Is.EqualTo(originalDuration)); - Assert.That(!serialized.Contains("DURATION"), Is.True); - }); - } + Assert.That(e.Duration, Is.EqualTo(originalDuration)); + Assert.That(!serialized.Contains("DURATION"), Is.True); + }); + } - [Test] - public void EventStatusAllCaps() - { - var e = GetSimpleEvent(); - e.Status = EventStatus.Confirmed; - var serialized = SerializeToString(e); - Assert.That(serialized.Contains(EventStatus.Confirmed, EventStatus.Comparison), Is.True); - - var calendar = UnserializeCalendar(serialized); - var eventStatus = calendar.Events.First().Status; - Assert.That(string.Equals(EventStatus.Confirmed, eventStatus, EventStatus.Comparison), Is.True); - } + [Test] + public void EventStatusAllCaps() + { + var e = GetSimpleEvent(); + e.Status = EventStatus.Confirmed; + var serialized = SerializeToString(e); + Assert.That(serialized.Contains(EventStatus.Confirmed, EventStatus.Comparison), Is.True); + + var calendar = UnserializeCalendar(serialized); + var eventStatus = calendar.Events.First().Status; + Assert.That(string.Equals(EventStatus.Confirmed, eventStatus, EventStatus.Comparison), Is.True); + } - [Test] - public void ToDoStatusAllCaps() + [Test] + public void ToDoStatusAllCaps() + { + var component = new Todo { - var component = new Todo - { - Status = TodoStatus.NeedsAction - }; + Status = TodoStatus.NeedsAction + }; - var c = new Calendar { Todos = { component } }; - var serialized = SerializeToString(c); - Assert.That(serialized.Contains(TodoStatus.NeedsAction, TodoStatus.Comparison), Is.True); + var c = new Calendar { Todos = { component } }; + var serialized = SerializeToString(c); + Assert.That(serialized.Contains(TodoStatus.NeedsAction, TodoStatus.Comparison), Is.True); - var calendar = UnserializeCalendar(serialized); - var status = calendar.Todos.First().Status; - Assert.That(string.Equals(TodoStatus.NeedsAction, status, TodoStatus.Comparison), Is.True); - } + var calendar = UnserializeCalendar(serialized); + var status = calendar.Todos.First().Status; + Assert.That(string.Equals(TodoStatus.NeedsAction, status, TodoStatus.Comparison), Is.True); + } - [Test] - public void JournalStatusAllCaps() + [Test] + public void JournalStatusAllCaps() + { + var component = new Journal { - var component = new Journal - { - Status = JournalStatus.Final, - }; + Status = JournalStatus.Final, + }; - var c = new Calendar { Journals = { component } }; - var serialized = SerializeToString(c); - Assert.That(serialized.Contains(JournalStatus.Final, JournalStatus.Comparison), Is.True); + var c = new Calendar { Journals = { component } }; + var serialized = SerializeToString(c); + Assert.That(serialized.Contains(JournalStatus.Final, JournalStatus.Comparison), Is.True); - var calendar = UnserializeCalendar(serialized); - var status = calendar.Journals.First().Status; - Assert.That(string.Equals(JournalStatus.Final, status, JournalStatus.Comparison), Is.True); - } + var calendar = UnserializeCalendar(serialized); + var status = calendar.Journals.First().Status; + Assert.That(string.Equals(JournalStatus.Final, status, JournalStatus.Comparison), Is.True); + } - [Test] - public void UnicodeDescription() - { - const string ics = @"BEGIN:VEVENT + [Test] + public void UnicodeDescription() + { + const string ics = @"BEGIN:VEVENT DTSTAMP:20171120T124856Z DTSTART;TZID=Europe/Helsinki:20160707T110000 DTEND;TZID=Europe/Helsinki:20160707T140000 @@ -443,21 +448,21 @@ xt some tex�t some text. TRANSP:TRANSPARENT STATUS:CONFIRMED END:VEVENT"; - var deserializedEvent = Calendar.Load(ics).Single(); + var deserializedEvent = Calendar.Load(ics).Single(); - Assert.Multiple(() => - { - Assert.That(deserializedEvent.Description.Contains("\t"), Is.True); - Assert.That(deserializedEvent.Description.Contains("�"), Is.True); - Assert.That(deserializedEvent.Description.Contains("�"), Is.True); - }); - } - - [Test] - public void TestStandardDaylightTimeZoneInfoDeserialization() + Assert.Multiple(() => { + Assert.That(deserializedEvent.Description.Contains("\t"), Is.True); + Assert.That(deserializedEvent.Description.Contains("�"), Is.True); + Assert.That(deserializedEvent.Description.Contains("�"), Is.True); + }); + } - const string ics = @"BEGIN:VTIMEZONE + [Test] + public void TestStandardDaylightTimeZoneInfoDeserialization() + { + + const string ics = @"BEGIN:VTIMEZONE TZID: BEGIN:STANDARD DTSTART:16010101T030000 @@ -472,108 +477,107 @@ public void TestStandardDaylightTimeZoneInfoDeserialization() RRULE:FREQ=YEARLY;INTERVAL=1;BYDAY=-1SU;BYMONTH=3 END:DAYLIGHT END:VTIMEZONE"; - var timeZone = Calendar.Load(ics).Single(); - Assert.That(timeZone, Is.Not.Null, "Expected the TimeZone to be successfully deserialized"); - var timeZoneInfos = timeZone.TimeZoneInfos; - Assert.Multiple(() => - { - Assert.That(timeZoneInfos, Is.Not.Null, "Expected TimeZoneInfos to be deserialized"); - Assert.That(timeZoneInfos, Has.Count.EqualTo(2), "Expected 2 TimeZoneInfos"); - Assert.That(timeZoneInfos[0].Name, Is.EqualTo("STANDARD")); - Assert.That(timeZoneInfos[0].OffsetFrom, Is.EqualTo(new UtcOffset("+0200"))); - Assert.That(timeZoneInfos[0].OffsetTo, Is.EqualTo(new UtcOffset("+0100"))); - Assert.That(timeZoneInfos[1].Name, Is.EqualTo("DAYLIGHT")); - Assert.That(timeZoneInfos[1].OffsetFrom, Is.EqualTo(new UtcOffset("+0100"))); - Assert.That(timeZoneInfos[1].OffsetTo, Is.EqualTo(new UtcOffset("+0200"))); - }); - } + var timeZone = Calendar.Load(ics).Single(); + Assert.That(timeZone, Is.Not.Null, "Expected the TimeZone to be successfully deserialized"); + var timeZoneInfos = timeZone.TimeZoneInfos; + Assert.Multiple(() => + { + Assert.That(timeZoneInfos, Is.Not.Null, "Expected TimeZoneInfos to be deserialized"); + Assert.That(timeZoneInfos, Has.Count.EqualTo(2), "Expected 2 TimeZoneInfos"); + Assert.That(timeZoneInfos[0].Name, Is.EqualTo("STANDARD")); + Assert.That(timeZoneInfos[0].OffsetFrom, Is.EqualTo(new UtcOffset("+0200"))); + Assert.That(timeZoneInfos[0].OffsetTo, Is.EqualTo(new UtcOffset("+0100"))); + Assert.That(timeZoneInfos[1].Name, Is.EqualTo("DAYLIGHT")); + Assert.That(timeZoneInfos[1].OffsetFrom, Is.EqualTo(new UtcOffset("+0100"))); + Assert.That(timeZoneInfos[1].OffsetTo, Is.EqualTo(new UtcOffset("+0200"))); + }); + } - [Test] - public void TestRRuleUntilSerialization() + [Test] + public void TestRRuleUntilSerialization() + { + var rrule = new RecurrencePattern(FrequencyType.Daily) { - var rrule = new RecurrencePattern(FrequencyType.Daily) - { - Until = _nowTime.AddDays(7), - }; - const string someTz = "Europe/Volgograd"; - var e = new CalendarEvent - { - Start = new CalDateTime(_nowTime, someTz), - End = new CalDateTime(_nowTime.AddHours(1), someTz), - RecurrenceRules = new List { rrule }, - }; - var c = new Calendar - { - Events = { e }, - }; - var serialized = new CalendarSerializer().SerializeToString(c); - var serializedUntilNotContainsZSuffix = serialized - .Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries) - .Single(line => line.StartsWith("RRULE:", StringComparison.Ordinal)); - var untilIndex = serializedUntilNotContainsZSuffix.IndexOf("UNTIL", StringComparison.Ordinal); - var until = serializedUntilNotContainsZSuffix.Substring(untilIndex); - - Assert.That(!until.EndsWith("Z"), Is.True); - } + Until = _nowTime.AddDays(7), + }; + const string someTz = "Europe/Volgograd"; + var e = new CalendarEvent + { + Start = new CalDateTime(_nowTime, someTz), + End = new CalDateTime(_nowTime.AddHours(1), someTz), + RecurrenceRules = new List { rrule }, + }; + var c = new Calendar + { + Events = { e }, + }; + var serialized = new CalendarSerializer().SerializeToString(c); + var serializedUntilNotContainsZSuffix = serialized + .Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries) + .Single(line => line.StartsWith("RRULE:", StringComparison.Ordinal)); + var untilIndex = serializedUntilNotContainsZSuffix.IndexOf("UNTIL", StringComparison.Ordinal); + var until = serializedUntilNotContainsZSuffix.Substring(untilIndex); + + Assert.That(!until.EndsWith("Z"), Is.True); + } - [Test(Description = "PRODID and VERSION should use ical.net values instead of preserving deserialized values")] - public void LibraryMetadataTests() + [Test(Description = "PRODID and VERSION should use ical.net values instead of preserving deserialized values")] + public void LibraryMetadataTests() + { + var c = new Calendar { - var c = new Calendar - { - ProductId = "FOO", - Version = "BAR" - }; - var serialized = new CalendarSerializer().SerializeToString(c); - var expectedProdid = $"PRODID:{LibraryMetadata.ProdId}"; - Assert.That(serialized.Contains(expectedProdid, StringComparison.Ordinal), Is.True); - - var expectedVersion = $"VERSION:{LibraryMetadata.Version}"; - Assert.That(serialized.Contains(expectedVersion, StringComparison.Ordinal), Is.True); - } + ProductId = "FOO", + Version = "BAR" + }; + var serialized = new CalendarSerializer().SerializeToString(c); + var expectedProdid = $"PRODID:{LibraryMetadata.ProdId}"; + Assert.That(serialized.Contains(expectedProdid, StringComparison.Ordinal), Is.True); + + var expectedVersion = $"VERSION:{LibraryMetadata.Version}"; + Assert.That(serialized.Contains(expectedVersion, StringComparison.Ordinal), Is.True); + } - [Test] - public void AttachmentFormatType() + [Test] + public void AttachmentFormatType() + { + var cal1 = new Calendar { - var cal1 = new Calendar + Events = { - Events = + new CalendarEvent { - new CalendarEvent + Attachments = { - Attachments = + new Attachment(Encoding.UTF8.GetBytes("{}")) { - new Attachment(Encoding.UTF8.GetBytes("{}")) - { - FormatType = "application/json", - }, + FormatType = "application/json", }, }, }, - }; - var serializer = new CalendarSerializer(); - var serializedCalendar = serializer.SerializeToString(cal1); - var cal2 = Calendar.Load(serializedCalendar); - Assert.That(cal2.Events.Single().Attachments.Single().FormatType, Is.EqualTo("application/json")); - } + }, + }; + var serializer = new CalendarSerializer(); + var serializedCalendar = serializer.SerializeToString(cal1); + var cal2 = Calendar.Load(serializedCalendar); + Assert.That(cal2.Events.Single().Attachments.Single().FormatType, Is.EqualTo("application/json")); + } - [Test(Description = "It should be possible to serialize a calendar component instead of a whole calendar")] - public void SerializeSubcomponent() + [Test(Description = "It should be possible to serialize a calendar component instead of a whole calendar")] + public void SerializeSubcomponent() + { + const string expectedString = "This is an expected string"; + var e = new CalendarEvent { - const string expectedString = "This is an expected string"; - var e = new CalendarEvent - { - Start = new CalDateTime(_nowTime), - End = new CalDateTime(_later), - Summary = expectedString, - }; + Start = new CalDateTime(_nowTime), + End = new CalDateTime(_later), + Summary = expectedString, + }; - var serialized = new CalendarSerializer().SerializeToString(e); - Assert.Multiple(() => - { - Assert.That(serialized.Contains(expectedString, StringComparison.Ordinal), Is.True); - Assert.That(!serialized.Contains("VCALENDAR", StringComparison.Ordinal), Is.True); - }); - } + var serialized = new CalendarSerializer().SerializeToString(e); + Assert.Multiple(() => + { + Assert.That(serialized.Contains(expectedString, StringComparison.Ordinal), Is.True); + Assert.That(!serialized.Contains("VCALENDAR", StringComparison.Ordinal), Is.True); + }); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/SimpleDeserializationTests.cs b/Ical.Net.Tests/SimpleDeserializationTests.cs index ee72332ae..f0c73aaf1 100644 --- a/Ical.Net.Tests/SimpleDeserializationTests.cs +++ b/Ical.Net.Tests/SimpleDeserializationTests.cs @@ -1,271 +1,276 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Serialization; -using Ical.Net.Serialization.DataTypes; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Text; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Serialization; +using Ical.Net.Serialization.DataTypes; +using NUnit.Framework; + +namespace Ical.Net.Tests; -namespace Ical.Net.Tests +[TestFixture] +public class SimpleDeserializationTests { - [TestFixture] - public class SimpleDeserializationTests + + [Test, Category("Deserialization")] + public void Attendee1() { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Attendee1)).Cast().Single(); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); + + var evt = iCal.Events.First(); + // Ensure there are 2 attendees + Assert.That(evt.Attendees, Has.Count.EqualTo(2)); + + var attendee1 = evt.Attendees; + var attendee2 = evt.Attendees[1]; - [Test, Category("Deserialization")] - public void Attendee1() + Assert.Multiple(() => { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Attendee1)).Cast().Single(); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); + // Values + Assert.That(attendee1[0].Value, Is.EqualTo(new Uri("mailto:joecool@example.com"))); + Assert.That(attendee2.Value, Is.EqualTo(new Uri("mailto:ildoit@example.com"))); + }); + Assert.Multiple(() => + { + // MEMBERS + Assert.That(attendee1[0].Members, Has.Count.EqualTo(1)); + Assert.That(attendee2.Members.Count, Is.EqualTo(0)); + Assert.That(attendee1[0].Members[0], Is.EqualTo(new Uri("mailto:DEV-GROUP@example.com").ToString())); + }); + Assert.Multiple(() => + { + // DELEGATED-FROM + Assert.That(attendee1[0].DelegatedFrom.Count, Is.EqualTo(0)); + Assert.That(attendee2.DelegatedFrom, Has.Count.EqualTo(1)); + Assert.That(attendee2.DelegatedFrom[0], Is.EqualTo(new Uri("mailto:immud@example.com").ToString())); + }); + Assert.Multiple(() => + { + // DELEGATED-TO + Assert.That(attendee1[0].DelegatedTo.Count, Is.EqualTo(0)); + Assert.That(attendee2.DelegatedTo.Count, Is.EqualTo(0)); + }); + } - var evt = iCal.Events.First(); - // Ensure there are 2 attendees - Assert.That(evt.Attendees, Has.Count.EqualTo(2)); + /// + /// Tests that multiple parameters of the + /// same name are correctly aggregated into + /// a single list. + /// + [Test, Category("Deserialization")] + public void Attendee2() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Attendee2)).Cast().Single(); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); - var attendee1 = evt.Attendees; - var attendee2 = evt.Attendees[1]; + var evt = iCal.Events.First(); + // Ensure there is 1 attendee + Assert.That(evt.Attendees, Has.Count.EqualTo(1)); - Assert.Multiple(() => - { - // Values - Assert.That(attendee1[0].Value, Is.EqualTo(new Uri("mailto:joecool@example.com"))); - Assert.That(attendee2.Value, Is.EqualTo(new Uri("mailto:ildoit@example.com"))); - }); - Assert.Multiple(() => - { - // MEMBERS - Assert.That(attendee1[0].Members, Has.Count.EqualTo(1)); - Assert.That(attendee2.Members.Count, Is.EqualTo(0)); - Assert.That(attendee1[0].Members[0], Is.EqualTo(new Uri("mailto:DEV-GROUP@example.com").ToString())); - }); - Assert.Multiple(() => - { - // DELEGATED-FROM - Assert.That(attendee1[0].DelegatedFrom.Count, Is.EqualTo(0)); - Assert.That(attendee2.DelegatedFrom, Has.Count.EqualTo(1)); - Assert.That(attendee2.DelegatedFrom[0], Is.EqualTo(new Uri("mailto:immud@example.com").ToString())); - }); - Assert.Multiple(() => - { - // DELEGATED-TO - Assert.That(attendee1[0].DelegatedTo.Count, Is.EqualTo(0)); - Assert.That(attendee2.DelegatedTo.Count, Is.EqualTo(0)); - }); - } + var attendee1 = evt.Attendees; - /// - /// Tests that multiple parameters of the - /// same name are correctly aggregated into - /// a single list. - /// - [Test, Category("Deserialization")] - public void Attendee2() + Assert.Multiple(() => { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Attendee2)).Cast().Single(); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); + // Values + Assert.That(attendee1[0].Value, Is.EqualTo(new Uri("mailto:joecool@example.com"))); - var evt = iCal.Events.First(); - // Ensure there is 1 attendee - Assert.That(evt.Attendees, Has.Count.EqualTo(1)); + // MEMBERS + Assert.That(attendee1[0].Members, Has.Count.EqualTo(3)); + }); + Assert.Multiple(() => + { + Assert.That(attendee1[0].Members[0], Is.EqualTo(new Uri("mailto:DEV-GROUP@example.com").ToString())); + Assert.That(attendee1[0].Members[1], Is.EqualTo(new Uri("mailto:ANOTHER-GROUP@example.com").ToString())); + Assert.That(attendee1[0].Members[2], Is.EqualTo(new Uri("mailto:THIRD-GROUP@example.com").ToString())); + }); + } - var attendee1 = evt.Attendees; + /// + /// Tests that Lotus Notes-style properties are properly handled. + /// https://sourceforge.net/tracker/?func=detail&aid=2033495&group_id=187422&atid=921236 + /// Sourceforge bug #2033495 + /// + [Test, Category("Deserialization")] + public void Bug2033495() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Bug2033495)).Cast().Single(); + Assert.Multiple(() => + { + Assert.That(iCal.Events, Has.Count.EqualTo(1)); + Assert.That(iCal.Properties["X-LOTUS-CHILD_UID"].Value, Is.EqualTo("XXX")); + }); + } - Assert.Multiple(() => - { - // Values - Assert.That(attendee1[0].Value, Is.EqualTo(new Uri("mailto:joecool@example.com"))); + /// + /// Tests bug #2938007 - involving the HasTime property in IDateTime values. + /// See https://sourceforge.net/tracker/?func=detail&aid=2938007&group_id=187422&atid=921236 + /// + [Test, Category("Deserialization")] + public void Bug2938007() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Bug2938007)).Cast().Single(); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); - // MEMBERS - Assert.That(attendee1[0].Members, Has.Count.EqualTo(3)); - }); - Assert.Multiple(() => - { - Assert.That(attendee1[0].Members[0], Is.EqualTo(new Uri("mailto:DEV-GROUP@example.com").ToString())); - Assert.That(attendee1[0].Members[1], Is.EqualTo(new Uri("mailto:ANOTHER-GROUP@example.com").ToString())); - Assert.That(attendee1[0].Members[2], Is.EqualTo(new Uri("mailto:THIRD-GROUP@example.com").ToString())); - }); - } + var evt = iCal.Events.First(); + Assert.Multiple(() => + { + Assert.That(evt.Start.HasTime, Is.EqualTo(true)); + Assert.That(evt.End.HasTime, Is.EqualTo(true)); + }); - /// - /// Tests that Lotus Notes-style properties are properly handled. - /// https://sourceforge.net/tracker/?func=detail&aid=2033495&group_id=187422&atid=921236 - /// Sourceforge bug #2033495 - /// - [Test, Category("Deserialization")] - public void Bug2033495() + foreach (var o in evt.GetOccurrences(new CalDateTime(2010, 1, 17, 0, 0, 0), new CalDateTime(2010, 2, 1, 0, 0, 0))) { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Bug2033495)).Cast().Single(); Assert.Multiple(() => { - Assert.That(iCal.Events, Has.Count.EqualTo(1)); - Assert.That(iCal.Properties["X-LOTUS-CHILD_UID"].Value, Is.EqualTo("XXX")); + Assert.That(o.Period.StartTime.HasTime, Is.EqualTo(true)); + Assert.That(o.Period.EndTime.HasTime, Is.EqualTo(true)); }); } + } - /// - /// Tests bug #2938007 - involving the HasTime property in IDateTime values. - /// See https://sourceforge.net/tracker/?func=detail&aid=2938007&group_id=187422&atid=921236 - /// - [Test, Category("Deserialization")] - public void Bug2938007() - { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Bug2938007)).Cast().Single(); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); - - var evt = iCal.Events.First(); - Assert.Multiple(() => - { - Assert.That(evt.Start.HasTime, Is.EqualTo(true)); - Assert.That(evt.End.HasTime, Is.EqualTo(true)); - }); + /// + /// Tests bug #3177278 - Serialize closes stream + /// See https://sourceforge.net/tracker/?func=detail&aid=3177278&group_id=187422&atid=921236 + /// + [Test, Category("Deserialization")] + public void Bug3177278() + { + var calendar = new Calendar(); + var serializer = new CalendarSerializer(); - foreach (var o in evt.GetOccurrences(new CalDateTime(2010, 1, 17, 0, 0, 0), new CalDateTime(2010, 2, 1, 0, 0, 0))) - { - Assert.Multiple(() => - { - Assert.That(o.Period.StartTime.HasTime, Is.EqualTo(true)); - Assert.That(o.Period.EndTime.HasTime, Is.EqualTo(true)); - }); - } - } + var ms = new MemoryStream(); + serializer.Serialize(calendar, ms, Encoding.UTF8); - /// - /// Tests bug #3177278 - Serialize closes stream - /// See https://sourceforge.net/tracker/?func=detail&aid=3177278&group_id=187422&atid=921236 - /// - [Test, Category("Deserialization")] - public void Bug3177278() - { - var calendar = new Calendar(); - var serializer = new CalendarSerializer(); + Assert.That(ms.CanWrite, Is.True); + } - var ms = new MemoryStream(); - serializer.Serialize(calendar, ms, Encoding.UTF8); + /// + /// Tests that a mixed-case VERSION property is loaded properly + /// + [Test, Category("Deserialization")] + public void CaseInsensitive4() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.CaseInsensitive4)).Cast().Single(); + Assert.That(iCal.Version, Is.EqualTo("2.5")); + } - Assert.That(ms.CanWrite, Is.True); - } + [Test, Category("Deserialization")] + public void Categories1_2() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Categories1)).Cast().Single(); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); - /// - /// Tests that a mixed-case VERSION property is loaded properly - /// - [Test, Category("Deserialization")] - public void CaseInsensitive4() + var items = new List(); + items.AddRange(new[] { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.CaseInsensitive4)).Cast().Single(); - Assert.That(iCal.Version, Is.EqualTo("2.5")); - } + "One", "Two", "Three", + "Four", "Five", "Six", + "Seven", "A string of text with nothing less than a comma, semicolon; and a newline\n." + }); - [Test, Category("Deserialization")] - public void Categories1_2() + var found = new Dictionary(); + foreach (var s in evt.Categories.Where(s => items.Contains(s))) { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Categories1)).Cast().Single(); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); - - var items = new List(); - items.AddRange(new[] - { - "One", "Two", "Three", - "Four", "Five", "Six", - "Seven", "A string of text with nothing less than a comma, semicolon; and a newline\n." - }); - - var found = new Dictionary(); - foreach (var s in evt.Categories.Where(s => items.Contains(s))) - { - found[s] = true; - } - - foreach (string item in items) - Assert.That(found.ContainsKey(item), Is.True, "Event should contain CATEGORY '" + item + "', but it was not found."); + found[s] = true; } - [Test, Category("Deserialization")] - public void EmptyLines1() - { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.EmptyLines1)).Cast().Single(); - Assert.That(iCal.Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); - } + foreach (string item in items) + Assert.That(found.ContainsKey(item), Is.True, "Event should contain CATEGORY '" + item + "', but it was not found."); + } - [Test, Category("Deserialization")] - public void EmptyLines2() - { - var calendars = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.EmptyLines2)).Cast().ToList(); - Assert.That(calendars, Has.Count.EqualTo(2)); - Assert.Multiple(() => - { - Assert.That(calendars[0].Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); - Assert.That(calendars[1].Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); - }); - } + [Test, Category("Deserialization")] + public void EmptyLines1() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.EmptyLines1)).Cast().Single(); + Assert.That(iCal.Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); + } - /// - /// Verifies that blank lines between components are allowed - /// (as occurs with some applications/parsers - i.e. KOrganizer) - /// - [Test, Category("Deserialization")] - public void EmptyLines3() + [Test, Category("Deserialization")] + public void EmptyLines2() + { + var calendars = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.EmptyLines2)).Cast().ToList(); + Assert.That(calendars, Has.Count.EqualTo(2)); + Assert.Multiple(() => { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.EmptyLines3)).Cast().Single(); - Assert.That(iCal.Todos, Has.Count.EqualTo(1), "iCalendar should have 1 todo"); - } + Assert.That(calendars[0].Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); + Assert.That(calendars[1].Events, Has.Count.EqualTo(2), "iCalendar should have 2 events"); + }); + } - /// - /// Similar to PARSE4 and PARSE5 tests. - /// - [Test, Category("Deserialization")] - public void EmptyLines4() - { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.EmptyLines4)).Cast().Single(); - Assert.That(iCal.Events, Has.Count.EqualTo(28)); - } + /// + /// Verifies that blank lines between components are allowed + /// (as occurs with some applications/parsers - i.e. KOrganizer) + /// + [Test, Category("Deserialization")] + public void EmptyLines3() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.EmptyLines3)).Cast().Single(); + Assert.That(iCal.Todos, Has.Count.EqualTo(1), "iCalendar should have 1 todo"); + } - [Test] - public void Encoding2() - { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Encoding2)).Cast().Single(); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); - - Assert.That( -evt.Attachments[0].ToString(), - Is.EqualTo("This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large.\r\n" + -"This is a test to try out base64 encoding without being too large."), - "Attached value does not match."); - } + /// + /// Similar to PARSE4 and PARSE5 tests. + /// + [Test, Category("Deserialization")] + public void EmptyLines4() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.EmptyLines4)).Cast().Single(); + Assert.That(iCal.Events, Has.Count.EqualTo(28)); + } - [Test] - public void Encoding3() - { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Encoding3)).Cast().Single(); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); + [Test] + public void Encoding2() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Encoding2)).Cast().Single(); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); + + Assert.That( + evt.Attachments[0].ToString(), + Is.EqualTo("This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large.\r\n" + + "This is a test to try out base64 encoding without being too large."), + "Attached value does not match."); + } - Assert.Multiple(() => - { - Assert.That(evt.Uid, Is.EqualTo("uuid1153170430406"), "UID should be 'uuid1153170430406'; it is " + evt.Uid); - Assert.That(evt.Sequence, Is.EqualTo(1), "SEQUENCE should be 1; it is " + evt.Sequence); - }); - } + [Test] + public void Encoding3() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Encoding3)).Cast().Single(); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); - [Test, Category("Deserialization")] - public void Event8() + Assert.Multiple(() => { - var sr = new StringReader(@"BEGIN:VCALENDAR + Assert.That(evt.Uid, Is.EqualTo("uuid1153170430406"), "UID should be 'uuid1153170430406'; it is " + evt.Uid); + Assert.That(evt.Sequence, Is.EqualTo(1), "SEQUENCE should be 1; it is " + evt.Sequence); + }); + } + + [Test, Category("Deserialization")] + public void Event8() + { + var sr = new StringReader(@"BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Computer\, Inc//iCal 1.0//EN CALSCALE:GREGORIAN @@ -295,287 +300,286 @@ public void Event8() END:VEVENT END:VCALENDAR "); - var iCal = SimpleDeserializer.Default.Deserialize(sr).Cast().Single(); - Assert.That(iCal.Events.Count == 2, Is.True, "There should be 2 events in the parsed calendar"); - Assert.That(iCal.Events["fd940618-45e2-4d19-b118-37fd7a8e3906"], Is.Not.Null, "Event fd940618-45e2-4d19-b118-37fd7a8e3906 should exist in the calendar"); - Assert.That(iCal.Events["ebfbd3e3-cc1e-4a64-98eb-ced2598b3908"], Is.Not.Null, "Event ebfbd3e3-cc1e-4a64-98eb-ced2598b3908 should exist in the calendar"); - } + var iCal = SimpleDeserializer.Default.Deserialize(sr).Cast().Single(); + Assert.That(iCal.Events.Count == 2, Is.True, "There should be 2 events in the parsed calendar"); + Assert.That(iCal.Events["fd940618-45e2-4d19-b118-37fd7a8e3906"], Is.Not.Null, "Event fd940618-45e2-4d19-b118-37fd7a8e3906 should exist in the calendar"); + Assert.That(iCal.Events["ebfbd3e3-cc1e-4a64-98eb-ced2598b3908"], Is.Not.Null, "Event ebfbd3e3-cc1e-4a64-98eb-ced2598b3908 should exist in the calendar"); + } - [Test] - public void GeographicLocation1_2() + [Test] + public void GeographicLocation1_2() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.GeographicLocation1)).Cast().Single(); + ProgramTest.TestCal(iCal); + var evt = iCal.Events.First(); + + Assert.Multiple(() => { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.GeographicLocation1)).Cast().Single(); - ProgramTest.TestCal(iCal); - var evt = iCal.Events.First(); + Assert.That(evt.GeographicLocation.Latitude, Is.EqualTo(37.386013), "Latitude should be 37.386013; it is not."); + Assert.That(evt.GeographicLocation.Longitude, Is.EqualTo(-122.082932), "Longitude should be -122.082932; it is not."); + }); + } - Assert.Multiple(() => - { - Assert.That(evt.GeographicLocation.Latitude, Is.EqualTo(37.386013), "Latitude should be 37.386013; it is not."); - Assert.That(evt.GeographicLocation.Longitude, Is.EqualTo(-122.082932), "Longitude should be -122.082932; it is not."); - }); - } + [Test, Category("Deserialization")] + public void Google1() + { + var tzId = "Europe/Berlin"; + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Google1)).Cast().Single(); + var evt = iCal.Events["594oeajmftl3r9qlkb476rpr3c@google.com"]; + Assert.That(evt, Is.Not.Null); - [Test, Category("Deserialization")] - public void Google1() - { - var tzId = "Europe/Berlin"; - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Google1)).Cast().Single(); - var evt = iCal.Events["594oeajmftl3r9qlkb476rpr3c@google.com"]; - Assert.That(evt, Is.Not.Null); + IDateTime dtStart = new CalDateTime(2006, 12, 18, tzId); + IDateTime dtEnd = new CalDateTime(2006, 12, 23, tzId); + var occurrences = iCal.GetOccurrences(dtStart, dtEnd).OrderBy(o => o.Period.StartTime).ToList(); - IDateTime dtStart = new CalDateTime(2006, 12, 18, tzId); - IDateTime dtEnd = new CalDateTime(2006, 12, 23, tzId); - var occurrences = iCal.GetOccurrences(dtStart, dtEnd).OrderBy(o => o.Period.StartTime).ToList(); + var dateTimes = new[] + { + new CalDateTime(2006, 12, 18, 7, 0, 0, tzId), + new CalDateTime(2006, 12, 19, 7, 0, 0, tzId), + new CalDateTime(2006, 12, 20, 7, 0, 0, tzId), + new CalDateTime(2006, 12, 21, 7, 0, 0, tzId), + new CalDateTime(2006, 12, 22, 7, 0, 0, tzId) + }; - var dateTimes = new[] - { - new CalDateTime(2006, 12, 18, 7, 0, 0, tzId), - new CalDateTime(2006, 12, 19, 7, 0, 0, tzId), - new CalDateTime(2006, 12, 20, 7, 0, 0, tzId), - new CalDateTime(2006, 12, 21, 7, 0, 0, tzId), - new CalDateTime(2006, 12, 22, 7, 0, 0, tzId) - }; + for (var i = 0; i < dateTimes.Length; i++) + Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dateTimes[i]), "Event should occur at " + dateTimes[i]); - for (var i = 0; i < dateTimes.Length; i++) - Assert.That(occurrences[i].Period.StartTime, Is.EqualTo(dateTimes[i]), "Event should occur at " + dateTimes[i]); + Assert.That(occurrences, Has.Count.EqualTo(dateTimes.Length), "There should be exactly " + dateTimes.Length + " occurrences; there were " + occurrences.Count); + } - Assert.That(occurrences, Has.Count.EqualTo(dateTimes.Length), "There should be exactly " + dateTimes.Length + " occurrences; there were " + occurrences.Count); - } + /// + /// Tests that valid RDATE properties are parsed correctly. + /// + [Test, Category("Deserialization")] + public void RecurrenceDates1() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.RecurrenceDates1)).Cast().Single(); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); + Assert.That(iCal.Events.First().RecurrenceDates, Has.Count.EqualTo(3)); - /// - /// Tests that valid RDATE properties are parsed correctly. - /// - [Test, Category("Deserialization")] - public void RecurrenceDates1() + Assert.Multiple(() => { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.RecurrenceDates1)).Cast().Single(); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); - Assert.That(iCal.Events.First().RecurrenceDates, Has.Count.EqualTo(3)); + Assert.That(iCal.Events.First().RecurrenceDates[0][0].StartTime, Is.EqualTo((CalDateTime) new DateTime(1997, 7, 14, 12, 30, 0, DateTimeKind.Utc))); + Assert.That(iCal.Events.First().RecurrenceDates[1][0].StartTime, Is.EqualTo((CalDateTime) new DateTime(1996, 4, 3, 2, 0, 0, DateTimeKind.Utc))); + Assert.That(iCal.Events.First().RecurrenceDates[1][0].EndTime, Is.EqualTo((CalDateTime) new DateTime(1996, 4, 3, 4, 0, 0, DateTimeKind.Utc))); + Assert.That(iCal.Events.First().RecurrenceDates[2][0].StartTime, Is.EqualTo(new CalDateTime(1997, 1, 1))); + Assert.That(iCal.Events.First().RecurrenceDates[2][1].StartTime, Is.EqualTo(new CalDateTime(1997, 1, 20))); + Assert.That(iCal.Events.First().RecurrenceDates[2][2].StartTime, Is.EqualTo(new CalDateTime(1997, 2, 17))); + Assert.That(iCal.Events.First().RecurrenceDates[2][3].StartTime, Is.EqualTo(new CalDateTime(1997, 4, 21))); + Assert.That(iCal.Events.First().RecurrenceDates[2][4].StartTime, Is.EqualTo(new CalDateTime(1997, 5, 26))); + Assert.That(iCal.Events.First().RecurrenceDates[2][5].StartTime, Is.EqualTo(new CalDateTime(1997, 7, 4))); + Assert.That(iCal.Events.First().RecurrenceDates[2][6].StartTime, Is.EqualTo(new CalDateTime(1997, 9, 1))); + Assert.That(iCal.Events.First().RecurrenceDates[2][7].StartTime, Is.EqualTo(new CalDateTime(1997, 10, 14))); + Assert.That(iCal.Events.First().RecurrenceDates[2][8].StartTime, Is.EqualTo(new CalDateTime(1997, 11, 28))); + Assert.That(iCal.Events.First().RecurrenceDates[2][9].StartTime, Is.EqualTo(new CalDateTime(1997, 11, 29))); + Assert.That(iCal.Events.First().RecurrenceDates[2][10].StartTime, Is.EqualTo(new CalDateTime(1997, 12, 25))); + }); + } - Assert.Multiple(() => - { - Assert.That(iCal.Events.First().RecurrenceDates[0][0].StartTime, Is.EqualTo((CalDateTime)new DateTime(1997, 7, 14, 12, 30, 0, DateTimeKind.Utc))); - Assert.That(iCal.Events.First().RecurrenceDates[1][0].StartTime, Is.EqualTo((CalDateTime)new DateTime(1996, 4, 3, 2, 0, 0, DateTimeKind.Utc))); - Assert.That(iCal.Events.First().RecurrenceDates[1][0].EndTime, Is.EqualTo((CalDateTime)new DateTime(1996, 4, 3, 4, 0, 0, DateTimeKind.Utc))); - Assert.That(iCal.Events.First().RecurrenceDates[2][0].StartTime, Is.EqualTo(new CalDateTime(1997, 1, 1))); - Assert.That(iCal.Events.First().RecurrenceDates[2][1].StartTime, Is.EqualTo(new CalDateTime(1997, 1, 20))); - Assert.That(iCal.Events.First().RecurrenceDates[2][2].StartTime, Is.EqualTo(new CalDateTime(1997, 2, 17))); - Assert.That(iCal.Events.First().RecurrenceDates[2][3].StartTime, Is.EqualTo(new CalDateTime(1997, 4, 21))); - Assert.That(iCal.Events.First().RecurrenceDates[2][4].StartTime, Is.EqualTo(new CalDateTime(1997, 5, 26))); - Assert.That(iCal.Events.First().RecurrenceDates[2][5].StartTime, Is.EqualTo(new CalDateTime(1997, 7, 4))); - Assert.That(iCal.Events.First().RecurrenceDates[2][6].StartTime, Is.EqualTo(new CalDateTime(1997, 9, 1))); - Assert.That(iCal.Events.First().RecurrenceDates[2][7].StartTime, Is.EqualTo(new CalDateTime(1997, 10, 14))); - Assert.That(iCal.Events.First().RecurrenceDates[2][8].StartTime, Is.EqualTo(new CalDateTime(1997, 11, 28))); - Assert.That(iCal.Events.First().RecurrenceDates[2][9].StartTime, Is.EqualTo(new CalDateTime(1997, 11, 29))); - Assert.That(iCal.Events.First().RecurrenceDates[2][10].StartTime, Is.EqualTo(new CalDateTime(1997, 12, 25))); - }); - } + /// + /// Tests that valid REQUEST-STATUS properties are parsed correctly. + /// + [Test, Category("Deserialization")] + public void RequestStatus1() + { + var iCal = Calendar.Load(IcsFiles.RequestStatus1); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); + Assert.That(iCal.Events.First().RequestStatuses, Has.Count.EqualTo(4)); - /// - /// Tests that valid REQUEST-STATUS properties are parsed correctly. - /// - [Test, Category("Deserialization")] - public void RequestStatus1() + var rs = iCal.Events.First().RequestStatuses[0]; + Assert.Multiple(() => { - var iCal = Calendar.Load(IcsFiles.RequestStatus1); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); - Assert.That(iCal.Events.First().RequestStatuses, Has.Count.EqualTo(4)); + Assert.That(rs.StatusCode.Primary, Is.EqualTo(2)); + Assert.That(rs.StatusCode.Secondary, Is.EqualTo(0)); + Assert.That(rs.Description, Is.EqualTo("Success")); + }); + Assert.That(rs.ExtraData, Is.Null); + + rs = iCal.Events.First().RequestStatuses[1]; + Assert.Multiple(() => + { + Assert.That(rs.StatusCode.Primary, Is.EqualTo(3)); + Assert.That(rs.StatusCode.Secondary, Is.EqualTo(1)); + Assert.That(rs.Description, Is.EqualTo("Invalid property value")); + Assert.That(rs.ExtraData, Is.EqualTo("DTSTART:96-Apr-01")); + }); + + rs = iCal.Events.First().RequestStatuses[2]; + Assert.Multiple(() => + { + Assert.That(rs.StatusCode.Primary, Is.EqualTo(2)); + Assert.That(rs.StatusCode.Secondary, Is.EqualTo(8)); + Assert.That(rs.Description, Is.EqualTo(" Success, repeating event ignored. Scheduled as a single event.")); + Assert.That(rs.ExtraData, Is.EqualTo("RRULE:FREQ=WEEKLY;INTERVAL=2")); + }); + + rs = iCal.Events.First().RequestStatuses[3]; + Assert.Multiple(() => + { + Assert.That(rs.StatusCode.Primary, Is.EqualTo(4)); + Assert.That(rs.StatusCode.Secondary, Is.EqualTo(1)); + Assert.That(rs.Description, Is.EqualTo("Event conflict. Date/time is busy.")); + }); + Assert.That(rs.ExtraData, Is.Null); + } - var rs = iCal.Events.First().RequestStatuses[0]; - Assert.Multiple(() => - { - Assert.That(rs.StatusCode.Primary, Is.EqualTo(2)); - Assert.That(rs.StatusCode.Secondary, Is.EqualTo(0)); - Assert.That(rs.Description, Is.EqualTo("Success")); - }); - Assert.That(rs.ExtraData, Is.Null); + /// + /// Tests that string escaping works with Text elements. + /// + [Test, Category("Deserialization")] + public void String2() + { + var serializer = new StringSerializer(); + var value = @"test\with\;characters"; + var unescaped = (string) serializer.Deserialize(new StringReader(value)); - rs = iCal.Events.First().RequestStatuses[1]; - Assert.Multiple(() => - { - Assert.That(rs.StatusCode.Primary, Is.EqualTo(3)); - Assert.That(rs.StatusCode.Secondary, Is.EqualTo(1)); - Assert.That(rs.Description, Is.EqualTo("Invalid property value")); - Assert.That(rs.ExtraData, Is.EqualTo("DTSTART:96-Apr-01")); - }); + Assert.That(unescaped, Is.EqualTo(@"test\with;characters"), "String unescaping was incorrect."); - rs = iCal.Events.First().RequestStatuses[2]; - Assert.Multiple(() => - { - Assert.That(rs.StatusCode.Primary, Is.EqualTo(2)); - Assert.That(rs.StatusCode.Secondary, Is.EqualTo(8)); - Assert.That(rs.Description, Is.EqualTo(" Success, repeating event ignored. Scheduled as a single event.")); - Assert.That(rs.ExtraData, Is.EqualTo("RRULE:FREQ=WEEKLY;INTERVAL=2")); - }); + value = @"C:\Path\To\My\New\Information"; + unescaped = (string) serializer.Deserialize(new StringReader(value)); + Assert.That(unescaped, Is.EqualTo("C:\\Path\\To\\My\new\\Information"), "String unescaping was incorrect."); - rs = iCal.Events.First().RequestStatuses[3]; - Assert.Multiple(() => - { - Assert.That(rs.StatusCode.Primary, Is.EqualTo(4)); - Assert.That(rs.StatusCode.Secondary, Is.EqualTo(1)); - Assert.That(rs.Description, Is.EqualTo("Event conflict. Date/time is busy.")); - }); - Assert.That(rs.ExtraData, Is.Null); - } + value = @"\""This\r\nis\Na\, test\""\;\\;,"; + unescaped = (string) serializer.Deserialize(new StringReader(value)); - /// - /// Tests that string escaping works with Text elements. - /// - [Test, Category("Deserialization")] - public void String2() - { - var serializer = new StringSerializer(); - var value = @"test\with\;characters"; - var unescaped = (string)serializer.Deserialize(new StringReader(value)); + Assert.That(unescaped, Is.EqualTo("\"This\\r\nis\na, test\";\\;,"), "String unescaping was incorrect."); + } - Assert.That(unescaped, Is.EqualTo(@"test\with;characters"), "String unescaping was incorrect."); + [Test, Category("Deserialization")] + public void Transparency2() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Transparency2)).Cast().Single(); - value = @"C:\Path\To\My\New\Information"; - unescaped = (string)serializer.Deserialize(new StringReader(value)); - Assert.That(unescaped, Is.EqualTo("C:\\Path\\To\\My\new\\Information"), "String unescaping was incorrect."); + Assert.That(iCal.Events, Has.Count.EqualTo(1)); + var evt = iCal.Events.First(); - value = @"\""This\r\nis\Na\, test\""\;\\;,"; - unescaped = (string)serializer.Deserialize(new StringReader(value)); + Assert.That(evt.Transparency, Is.EqualTo(TransparencyType.Transparent)); + } - Assert.That(unescaped, Is.EqualTo("\"This\\r\nis\na, test\";\\;,"), "String unescaping was incorrect."); - } + /// + /// Tests that DateTime values that are out-of-range are still parsed correctly + /// and set to the closest representable date/time in .NET. + /// + [Test, Category("Deserialization")] + public void DateTime1() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.DateTime1)).Cast().Single(); + Assert.That(iCal.Events, Has.Count.EqualTo(6)); - [Test, Category("Deserialization")] - public void Transparency2() - { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Transparency2)).Cast().Single(); + var evt = iCal.Events["nc2o66s0u36iesitl2l0b8inn8@google.com"]; + Assert.That(evt, Is.Not.Null); - Assert.That(iCal.Events, Has.Count.EqualTo(1)); - var evt = iCal.Events.First(); + // The "Created" date is out-of-bounds. It should be coerced to the + // closest representable date/time. + Assert.That(evt.Created.Value, Is.EqualTo(DateTime.MinValue)); + } - Assert.That(evt.Transparency, Is.EqualTo(TransparencyType.Transparent)); - } + [Test, Category("Deserialization"), Ignore("Ignore until @thoemy commits the EventStatus.ics file")] + public void EventStatus() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.EventStatus)).Cast().Single(); + Assert.That(iCal.Events, Has.Count.EqualTo(4)); - /// - /// Tests that DateTime values that are out-of-range are still parsed correctly - /// and set to the closest representable date/time in .NET. - /// - [Test, Category("Deserialization")] - public void DateTime1() + Assert.That(iCal.Events[0].Summary, Is.EqualTo("No status")); + Assert.That(iCal.Events[0].Status, Is.Null); + Assert.Multiple(() => { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.DateTime1)).Cast().Single(); - Assert.That(iCal.Events, Has.Count.EqualTo(6)); + Assert.That(iCal.Events[0].IsActive, Is.True); - var evt = iCal.Events["nc2o66s0u36iesitl2l0b8inn8@google.com"]; - Assert.That(evt, Is.Not.Null); + Assert.That(iCal.Events[1].Summary, Is.EqualTo("Confirmed")); + Assert.That(iCal.Events[1].Status, Is.EqualTo("CONFIRMED")); + Assert.That(iCal.Events[1].IsActive, Is.True); - // The "Created" date is out-of-bounds. It should be coerced to the - // closest representable date/time. - Assert.That(evt.Created.Value, Is.EqualTo(DateTime.MinValue)); - } + Assert.That(iCal.Events[2].Summary, Is.EqualTo("Cancelled")); + Assert.That(iCal.Events[2].Status, Is.EqualTo("CANCELLED")); + }); + Assert.That(iCal.Events[2].IsActive, Is.False); - [Test, Category("Deserialization"), Ignore("Ignore until @thoemy commits the EventStatus.ics file")] - public void EventStatus() + Assert.Multiple(() => { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.EventStatus)).Cast().Single(); - Assert.That(iCal.Events, Has.Count.EqualTo(4)); - - Assert.That(iCal.Events[0].Summary, Is.EqualTo("No status")); - Assert.That(iCal.Events[0].Status, Is.Null); - Assert.Multiple(() => - { - Assert.That(iCal.Events[0].IsActive, Is.True); - - Assert.That(iCal.Events[1].Summary, Is.EqualTo("Confirmed")); - Assert.That(iCal.Events[1].Status, Is.EqualTo("CONFIRMED")); - Assert.That(iCal.Events[1].IsActive, Is.True); + Assert.That(iCal.Events[3].Summary, Is.EqualTo("Tentative")); + Assert.That(iCal.Events[3].Status, Is.EqualTo("TENTATIVE")); + Assert.That(iCal.Events[3].IsActive, Is.True); + }); + } - Assert.That(iCal.Events[2].Summary, Is.EqualTo("Cancelled")); - Assert.That(iCal.Events[2].Status, Is.EqualTo("CANCELLED")); - }); - Assert.That(iCal.Events[2].IsActive, Is.False); + [Test, Category("Deserialization")] + public void Language4() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Language4)).Cast().Single(); + Assert.That(iCal, Is.Not.Null); + } - Assert.Multiple(() => - { - Assert.That(iCal.Events[3].Summary, Is.EqualTo("Tentative")); - Assert.That(iCal.Events[3].Status, Is.EqualTo("TENTATIVE")); - Assert.That(iCal.Events[3].IsActive, Is.True); - }); - } - - [Test, Category("Deserialization")] - public void Language4() - { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Language4)).Cast().Single(); - Assert.That(iCal, Is.Not.Null); - } + [Test, Category("Deserialization")] + public void Outlook2007_LineFolds1() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Outlook2007LineFolds)).Cast().Single(); + var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)); + Assert.That(events, Has.Count.EqualTo(1)); + } - [Test, Category("Deserialization")] - public void Outlook2007_LineFolds1() - { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Outlook2007LineFolds)).Cast().Single(); - var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)); - Assert.That(events, Has.Count.EqualTo(1)); - } + [Test, Category("Deserialization")] + public void Outlook2007_LineFolds2() + { + var longName = "The Exceptionally Long Named Meeting Room Whose Name Wraps Over Several Lines When Exported From Leading Calendar and Office Software Application Microsoft Office 2007"; + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Outlook2007LineFolds)).Cast().Single(); + var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)).OrderBy(o => o.Period.StartTime).ToList(); + Assert.That(((CalendarEvent) events[0].Source).Location, Is.EqualTo(longName)); + } - [Test, Category("Deserialization")] - public void Outlook2007_LineFolds2() - { - var longName = "The Exceptionally Long Named Meeting Room Whose Name Wraps Over Several Lines When Exported From Leading Calendar and Office Software Application Microsoft Office 2007"; - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Outlook2007LineFolds)).Cast().Single(); - var events = iCal.GetOccurrences(new CalDateTime(2009, 06, 20), new CalDateTime(2009, 06, 22)).OrderBy(o => o.Period.StartTime).ToList(); - Assert.That(((CalendarEvent)events[0].Source).Location, Is.EqualTo(longName)); - } + /// + /// Tests that multiple parameters are allowed in iCalObjects + /// + [Test, Category("Deserialization")] + public void Parameter1() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Parameter1)).Cast().Single(); - /// - /// Tests that multiple parameters are allowed in iCalObjects - /// - [Test, Category("Deserialization")] - public void Parameter1() + var evt = iCal.Events.First(); + IList parms = evt.Properties["DTSTART"].Parameters.AllOf("VALUE").ToList(); + Assert.That(parms, Has.Count.EqualTo(2)); + Assert.Multiple(() => { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Parameter1)).Cast().Single(); - - var evt = iCal.Events.First(); - IList parms = evt.Properties["DTSTART"].Parameters.AllOf("VALUE").ToList(); - Assert.That(parms, Has.Count.EqualTo(2)); - Assert.Multiple(() => - { - Assert.That(parms[0].Values.First(), Is.EqualTo("DATE")); - Assert.That(parms[1].Values.First(), Is.EqualTo("OTHER")); - }); - } + Assert.That(parms[0].Values.First(), Is.EqualTo("DATE")); + Assert.That(parms[1].Values.First(), Is.EqualTo("OTHER")); + }); + } - /// - /// Tests that empty parameters are allowed in iCalObjects - /// - [Test, Category("Deserialization")] - public void Parameter2() - { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Parameter2)).Cast().Single(); - Assert.That(iCal.Events, Has.Count.EqualTo(2)); - } + /// + /// Tests that empty parameters are allowed in iCalObjects + /// + [Test, Category("Deserialization")] + public void Parameter2() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Parameter2)).Cast().Single(); + Assert.That(iCal.Events, Has.Count.EqualTo(2)); + } - /// - /// Tests a calendar that should fail to properly parse. - /// - [Test, Category("Deserialization")] - public void Parse1() + /// + /// Tests a calendar that should fail to properly parse. + /// + [Test, Category("Deserialization")] + public void Parse1() + { + Assert.That(() => { - Assert.That(() => - { - var content = IcsFiles.Parse1; - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(content)).Cast().Single(); - }, Throws.Exception.TypeOf()); - } + var content = IcsFiles.Parse1; + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(content)).Cast().Single(); + }, Throws.Exception.TypeOf()); + } - /// - /// Tests that multiple properties are allowed in iCalObjects - /// - [Test, Category("Deserialization")] - public void Property1() - { - var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Property1)).Cast().Single(); + /// + /// Tests that multiple properties are allowed in iCalObjects + /// + [Test, Category("Deserialization")] + public void Property1() + { + var iCal = SimpleDeserializer.Default.Deserialize(new StringReader(IcsFiles.Property1)).Cast().Single(); - IList props = iCal.Properties.AllOf("VERSION").ToList(); - Assert.That(props, Has.Count.EqualTo(2)); + IList props = iCal.Properties.AllOf("VERSION").ToList(); + Assert.That(props, Has.Count.EqualTo(2)); - for (var i = 0; i < props.Count; i++) - Assert.That(props[i].Value, Is.EqualTo("2." + i)); - } + for (var i = 0; i < props.Count; i++) + Assert.That(props[i].Value, Is.EqualTo("2." + i)); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/SymmetricSerializationTests.cs b/Ical.Net.Tests/SymmetricSerializationTests.cs index 90e2c7f39..3ce04099e 100644 --- a/Ical.Net.Tests/SymmetricSerializationTests.cs +++ b/Ical.Net.Tests/SymmetricSerializationTests.cs @@ -1,233 +1,237 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Serialization; -using NUnit.Framework; -using NUnit.Framework.Interfaces; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Serialization; +using NUnit.Framework; +using NUnit.Framework.Interfaces; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +public class SymmetricSerializationTests { - public class SymmetricSerializationTests - { - private const string _ldapUri = "ldap://example.com:6666/o=eDABC Industries,c=3DUS??(cn=3DBMary Accepted)"; + private const string _ldapUri = "ldap://example.com:6666/o=eDABC Industries,c=3DUS??(cn=3DBMary Accepted)"; - private static readonly DateTime _nowTime = DateTime.Now; - private static readonly DateTime _later = _nowTime.AddHours(1); - private static CalendarSerializer GetNewSerializer() => new CalendarSerializer(); - private static string SerializeToString(Calendar c) => GetNewSerializer().SerializeToString(c); - private static CalendarEvent GetSimpleEvent() => new CalendarEvent {DtStart = new CalDateTime(_nowTime), DtEnd = new CalDateTime(_later), Duration = _later - _nowTime}; - private static Calendar UnserializeCalendar(string s) => Calendar.Load(s); + private static readonly DateTime _nowTime = DateTime.Now; + private static readonly DateTime _later = _nowTime.AddHours(1); + private static CalendarSerializer GetNewSerializer() => new CalendarSerializer(); + private static string SerializeToString(Calendar c) => GetNewSerializer().SerializeToString(c); + private static CalendarEvent GetSimpleEvent() => new CalendarEvent { DtStart = new CalDateTime(_nowTime), DtEnd = new CalDateTime(_later), Duration = _later - _nowTime }; + private static Calendar UnserializeCalendar(string s) => Calendar.Load(s); - [Test, TestCaseSource(nameof(Event_TestCases))] - public void Event_Tests(Calendar iCalendar) - { - var originalEvent = iCalendar.Events.Single(); + [Test, TestCaseSource(nameof(Event_TestCases))] + public void Event_Tests(Calendar iCalendar) + { + var originalEvent = iCalendar.Events.Single(); - var serializedCalendar = SerializeToString(iCalendar); - var unserializedCalendar = UnserializeCalendar(serializedCalendar); + var serializedCalendar = SerializeToString(iCalendar); + var unserializedCalendar = UnserializeCalendar(serializedCalendar); - var onlyEvent = unserializedCalendar.Events.Single(); + var onlyEvent = unserializedCalendar.Events.Single(); - Assert.Multiple(() => - { - Assert.That(onlyEvent.GetHashCode(), Is.EqualTo(originalEvent.GetHashCode())); - Assert.That(onlyEvent, Is.EqualTo(originalEvent)); - Assert.That(unserializedCalendar, Is.EqualTo(iCalendar)); - }); - } + Assert.Multiple(() => + { + Assert.That(onlyEvent.GetHashCode(), Is.EqualTo(originalEvent.GetHashCode())); + Assert.That(onlyEvent, Is.EqualTo(originalEvent)); + Assert.That(unserializedCalendar, Is.EqualTo(iCalendar)); + }); + } - public static IEnumerable Event_TestCases() + public static IEnumerable Event_TestCases() + { + var rrule = new RecurrencePattern(FrequencyType.Daily, 1) { Count = 5 }; + var e = new CalendarEvent { - var rrule = new RecurrencePattern(FrequencyType.Daily, 1) { Count = 5 }; - var e = new CalendarEvent - { - DtStart = new CalDateTime(_nowTime), - DtEnd = new CalDateTime(_later), - Duration = TimeSpan.FromHours(1), - RecurrenceRules = new List { rrule }, - }; - - var calendar = new Calendar(); - calendar.Events.Add(e); - yield return new TestCaseData(calendar).SetName("readme.md example"); - - e = GetSimpleEvent(); - e.Description = "This is an event description that is really rather long. Hopefully the line breaks work now, and it's serialized properly."; - calendar = new Calendar(); - calendar.Events.Add(e); - yield return new TestCaseData(calendar).SetName("Description serialization isn't working properly. Issue #60"); - } - - [Test] - public void VTimeZoneSerialization_Test() + DtStart = new CalDateTime(_nowTime), + DtEnd = new CalDateTime(_later), + Duration = TimeSpan.FromHours(1), + RecurrenceRules = new List { rrule }, + }; + + var calendar = new Calendar(); + calendar.Events.Add(e); + yield return new TestCaseData(calendar).SetName("readme.md example"); + + e = GetSimpleEvent(); + e.Description = "This is an event description that is really rather long. Hopefully the line breaks work now, and it's serialized properly."; + calendar = new Calendar(); + calendar.Events.Add(e); + yield return new TestCaseData(calendar).SetName("Description serialization isn't working properly. Issue #60"); + } + + [Test] + public void VTimeZoneSerialization_Test() + { + var originalCalendar = new Calendar(); + var tz = new VTimeZone + { + TzId = "New Zealand Standard Time" + }; + originalCalendar.AddTimeZone(tz); + var serializer = new CalendarSerializer(); + var serializedCalendar = serializer.SerializeToString(originalCalendar); + var unserializedCalendar = Calendar.Load(serializedCalendar); + + Assert.Multiple(() => { - var originalCalendar = new Calendar(); - var tz = new VTimeZone - { - TzId = "New Zealand Standard Time" - }; - originalCalendar.AddTimeZone(tz); - var serializer = new CalendarSerializer(); - var serializedCalendar = serializer.SerializeToString(originalCalendar); - var unserializedCalendar = Calendar.Load(serializedCalendar); - - Assert.Multiple(() => - { - Assert.That(unserializedCalendar.TimeZones, Is.EqualTo(originalCalendar.TimeZones).AsCollection); - Assert.That(unserializedCalendar, Is.EqualTo(originalCalendar)); - }); - Assert.That(unserializedCalendar.GetHashCode(), Is.EqualTo(originalCalendar.GetHashCode())); - } - - [Test, TestCaseSource(nameof(AttendeeSerialization_TestCases))] - public void AttendeeSerialization_Test(Attendee attendee) + Assert.That(unserializedCalendar.TimeZones, Is.EqualTo(originalCalendar.TimeZones).AsCollection); + Assert.That(unserializedCalendar, Is.EqualTo(originalCalendar)); + }); + Assert.That(unserializedCalendar.GetHashCode(), Is.EqualTo(originalCalendar.GetHashCode())); + } + + [Test, TestCaseSource(nameof(AttendeeSerialization_TestCases))] + public void AttendeeSerialization_Test(Attendee attendee) + { + var calendar = new Calendar(); + calendar.AddTimeZone(new VTimeZone("America/Los_Angeles")); + var someEvent = GetSimpleEvent(); + someEvent.Attendees = new List { attendee }; + calendar.Events.Add(someEvent); + + var serialized = SerializeToString(calendar); + var unserialized = UnserializeCalendar(serialized); + + Assert.Multiple(() => { - var calendar = new Calendar(); - calendar.AddTimeZone(new VTimeZone("America/Los_Angeles")); - var someEvent = GetSimpleEvent(); - someEvent.Attendees = new List {attendee}; - calendar.Events.Add(someEvent); - - var serialized = SerializeToString(calendar); - var unserialized = UnserializeCalendar(serialized); - - Assert.Multiple(() => - { - Assert.That(unserialized.GetHashCode(), Is.EqualTo(calendar.GetHashCode())); - Assert.That(calendar.Events.SequenceEqual(unserialized.Events), Is.True); - Assert.That(unserialized, Is.EqualTo(calendar)); - }); - } - - public static IEnumerable AttendeeSerialization_TestCases() + Assert.That(unserialized.GetHashCode(), Is.EqualTo(calendar.GetHashCode())); + Assert.That(calendar.Events.SequenceEqual(unserialized.Events), Is.True); + Assert.That(unserialized, Is.EqualTo(calendar)); + }); + } + + public static IEnumerable AttendeeSerialization_TestCases() + { + var complex1 = new Attendee("MAILTO:mary@example.com") { - var complex1 = new Attendee("MAILTO:mary@example.com") - { - CommonName = "Mary Accepted", - Rsvp = true, - ParticipationStatus = EventParticipationStatus.Accepted, - SentBy = new Uri("mailto:someone@example.com"), - DirectoryEntry = new Uri(_ldapUri), - Type = "CuType", - Members = new List { "Group A", "Group B" }, - Role = ParticipationRole.Chair, - DelegatedTo = new List { "Peon A", "Peon B" }, - DelegatedFrom = new List { "Bigwig A", "Bigwig B" } - }; - yield return new TestCaseData(complex1).SetName("Complex attendee"); - - var simple = new Attendee("MAILTO:james@example.com") - { - CommonName = "James James", - Role = ParticipationRole.RequiredParticipant, - Rsvp = true, - ParticipationStatus = EventParticipationStatus.Tentative - }; - yield return new TestCaseData(simple).SetName("Simple attendee"); - } - - [Test, TestCaseSource(nameof(BinaryAttachment_TestCases))] - public void BinaryAttachment_Tests(string theString, string expectedAttachment) + CommonName = "Mary Accepted", + Rsvp = true, + ParticipationStatus = EventParticipationStatus.Accepted, + SentBy = new Uri("mailto:someone@example.com"), + DirectoryEntry = new Uri(_ldapUri), + Type = "CuType", + Members = new List { "Group A", "Group B" }, + Role = ParticipationRole.Chair, + DelegatedTo = new List { "Peon A", "Peon B" }, + DelegatedFrom = new List { "Bigwig A", "Bigwig B" } + }; + yield return new TestCaseData(complex1).SetName("Complex attendee"); + + var simple = new Attendee("MAILTO:james@example.com") { - var asBytes = Encoding.UTF8.GetBytes(theString); - var binaryAttachment = new Attachment(asBytes); - - var calendar = new Calendar(); - var vEvent = GetSimpleEvent(); - vEvent.Attachments = new List { binaryAttachment }; - calendar.Events.Add(vEvent); - - var serialized = SerializeToString(calendar); - var unserialized = UnserializeCalendar(serialized); - var unserializedAttachment = unserialized - .Events - .First() - .Attachments - .Select(a => Encoding.UTF8.GetString(a.Data)) - .First(); - - Assert.Multiple(() => - { - Assert.That(unserializedAttachment, Is.EqualTo(expectedAttachment)); - Assert.That(unserialized.GetHashCode(), Is.EqualTo(calendar.GetHashCode())); - Assert.That(unserialized, Is.EqualTo(calendar)); - }); - } - - public static IEnumerable BinaryAttachment_TestCases() + CommonName = "James James", + Role = ParticipationRole.RequiredParticipant, + Rsvp = true, + ParticipationStatus = EventParticipationStatus.Tentative + }; + yield return new TestCaseData(simple).SetName("Simple attendee"); + } + + [Test, TestCaseSource(nameof(BinaryAttachment_TestCases))] + public void BinaryAttachment_Tests(string theString, string expectedAttachment) + { + var asBytes = Encoding.UTF8.GetBytes(theString); + var binaryAttachment = new Attachment(asBytes); + + var calendar = new Calendar(); + var vEvent = GetSimpleEvent(); + vEvent.Attachments = new List { binaryAttachment }; + calendar.Events.Add(vEvent); + + var serialized = SerializeToString(calendar); + var unserialized = UnserializeCalendar(serialized); + var unserializedAttachment = unserialized + .Events + .First() + .Attachments + .Select(a => Encoding.UTF8.GetString(a.Data)) + .First(); + + Assert.Multiple(() => { - const string shortString = "This is a string."; - yield return new TestCaseData(shortString, shortString) - .SetName("Short string"); + Assert.That(unserializedAttachment, Is.EqualTo(expectedAttachment)); + Assert.That(unserialized.GetHashCode(), Is.EqualTo(calendar.GetHashCode())); + Assert.That(unserialized, Is.EqualTo(calendar)); + }); + } - const string mediumString = "This is a stringThisThis is a"; - yield return new TestCaseData(mediumString, mediumString) - .SetName("Moderate string"); + public static IEnumerable BinaryAttachment_TestCases() + { + const string shortString = "This is a string."; + yield return new TestCaseData(shortString, shortString) + .SetName("Short string"); - const string longishString = "This is a song that never ends. It just goes on and on my friends. Some people started singing it not..."; - yield return new TestCaseData(longishString, longishString) - .SetName("Much longer string"); + const string mediumString = "This is a stringThisThis is a"; + yield return new TestCaseData(mediumString, mediumString) + .SetName("Moderate string"); - const string jsonString = - "{\"TheList\":[\"Foo\",\"Bar\",\"Baz\",\"Foo\",\"Bar\",\"Baz\",\"Foo\",\"Bar\",\"Baz\",\"Foo\",\"Bar\",\"Baz\",\"Foo\",\"Bar\",\"Baz\",\"Foo\",\"Bar\",\"Baz\"],\"TheNumber\":42,\"TheSet\":[\"Foo\",\"Bar\",\"Baz\"]}"; - yield return new TestCaseData(jsonString, jsonString).SetName("JSON-serialized text"); - } + const string longishString = "This is a song that never ends. It just goes on and on my friends. Some people started singing it not..."; + yield return new TestCaseData(longishString, longishString) + .SetName("Much longer string"); - [Test, TestCaseSource(nameof(UriAttachment_TestCases))] - public void UriAttachment_Tests(string uri, Uri expectedUri) - { - var attachment = new Attachment(uri); - - var calendar = new Calendar(); - var vEvent = GetSimpleEvent(); - vEvent.Attachments = new List { attachment }; - calendar.Events.Add(vEvent); - - var serialized = SerializeToString(calendar); - var unserialized = UnserializeCalendar(serialized); - var unserializedUri = unserialized - .Events - .First() - .Attachments - .Select(a => a.Uri) - .Single(); - - Assert.Multiple(() => - { - Assert.That(unserializedUri, Is.EqualTo(expectedUri)); - Assert.That(unserialized.GetHashCode(), Is.EqualTo(calendar.GetHashCode())); - Assert.That(unserialized, Is.EqualTo(calendar)); - }); - } - - public static IEnumerable UriAttachment_TestCases() - { - yield return new TestCaseData("http://www.google.com", new Uri("http://www.google.com")).SetName("HTTP URL"); - yield return new TestCaseData("mailto:rstockbower@gmail.com", new Uri("mailto:rstockbower@gmail.com")).SetName("mailto: URL"); - yield return new TestCaseData(_ldapUri, new Uri(_ldapUri)).SetName("ldap URL"); - yield return new TestCaseData("C:\\path\\to\\file.txt", new Uri("C:\\path\\to\\file.txt")).SetName("Local file path URL"); - yield return new TestCaseData("\\\\uncPath\\to\\resource.txt", new Uri("\\\\uncPath\\to\\resource.txt")).SetName("UNC path URL"); - } - - [Test, Ignore("TODO: Fix CATEGORIES multiple serializations")] - public void CategoriesTest() + const string jsonString = + "{\"TheList\":[\"Foo\",\"Bar\",\"Baz\",\"Foo\",\"Bar\",\"Baz\",\"Foo\",\"Bar\",\"Baz\",\"Foo\",\"Bar\",\"Baz\",\"Foo\",\"Bar\",\"Baz\",\"Foo\",\"Bar\",\"Baz\"],\"TheNumber\":42,\"TheSet\":[\"Foo\",\"Bar\",\"Baz\"]}"; + yield return new TestCaseData(jsonString, jsonString).SetName("JSON-serialized text"); + } + + [Test, TestCaseSource(nameof(UriAttachment_TestCases))] + public void UriAttachment_Tests(string uri, Uri expectedUri) + { + var attachment = new Attachment(uri); + + var calendar = new Calendar(); + var vEvent = GetSimpleEvent(); + vEvent.Attachments = new List { attachment }; + calendar.Events.Add(vEvent); + + var serialized = SerializeToString(calendar); + var unserialized = UnserializeCalendar(serialized); + var unserializedUri = unserialized + .Events + .First() + .Attachments + .Select(a => a.Uri) + .Single(); + + Assert.Multiple(() => { - var vEvent = GetSimpleEvent(); - vEvent.Categories = new List { "Foo", "Bar", "Baz" }; - var c = new Calendar(); - c.Events.Add(vEvent); - - var serialized = SerializeToString(c); - var categoriesCount = Regex.Matches(serialized, "CATEGORIES").Count; - Assert.That(categoriesCount, Is.EqualTo(1)); - - var deserialized = UnserializeCalendar(serialized); - Assert.That(deserialized, Is.EqualTo(c)); - } + Assert.That(unserializedUri, Is.EqualTo(expectedUri)); + Assert.That(unserialized.GetHashCode(), Is.EqualTo(calendar.GetHashCode())); + Assert.That(unserialized, Is.EqualTo(calendar)); + }); + } + + public static IEnumerable UriAttachment_TestCases() + { + yield return new TestCaseData("http://www.google.com", new Uri("http://www.google.com")).SetName("HTTP URL"); + yield return new TestCaseData("mailto:rstockbower@gmail.com", new Uri("mailto:rstockbower@gmail.com")).SetName("mailto: URL"); + yield return new TestCaseData(_ldapUri, new Uri(_ldapUri)).SetName("ldap URL"); + yield return new TestCaseData("C:\\path\\to\\file.txt", new Uri("C:\\path\\to\\file.txt")).SetName("Local file path URL"); + yield return new TestCaseData("\\\\uncPath\\to\\resource.txt", new Uri("\\\\uncPath\\to\\resource.txt")).SetName("UNC path URL"); + } + + [Test, Ignore("TODO: Fix CATEGORIES multiple serializations")] + public void CategoriesTest() + { + var vEvent = GetSimpleEvent(); + vEvent.Categories = new List { "Foo", "Bar", "Baz" }; + var c = new Calendar(); + c.Events.Add(vEvent); + + var serialized = SerializeToString(c); + var categoriesCount = Regex.Matches(serialized, "CATEGORIES").Count; + Assert.That(categoriesCount, Is.EqualTo(1)); + + var deserialized = UnserializeCalendar(serialized); + Assert.That(deserialized, Is.EqualTo(c)); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/TextUtilTests.cs b/Ical.Net.Tests/TextUtilTests.cs index bdbffe40a..89b02f327 100644 --- a/Ical.Net.Tests/TextUtilTests.cs +++ b/Ical.Net.Tests/TextUtilTests.cs @@ -1,45 +1,49 @@ -using System.Collections; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System.Collections; using Ical.Net.Utility; using NUnit.Framework; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +public class TextUtilTests { - public class TextUtilTests + [Test, TestCaseSource(nameof(FoldLines_TestCases))] + public string FoldLines_Tests(string incoming) => TextUtil.FoldLines(incoming); + + public static IEnumerable FoldLines_TestCases() { - [Test, TestCaseSource(nameof(FoldLines_TestCases))] - public string FoldLines_Tests(string incoming) => TextUtil.FoldLines(incoming); - - public static IEnumerable FoldLines_TestCases() - { - yield return new TestCaseData("Short") - .Returns("Short" + SerializationConstants.LineBreak) - .SetName("Short string remains unfolded"); - - const string moderatelyLongReturns = - "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHell" + SerializationConstants.LineBreak - + " oWorld" + SerializationConstants.LineBreak; - - yield return new TestCaseData("HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld") - .Returns(moderatelyLongReturns) - .SetName("Long string is folded"); - - yield return new TestCaseData(" HelloWorldHelloWorldHelloWorldHelloWorldHelloWorld ") - .Returns("HelloWorldHelloWorldHelloWorldHelloWorldHelloWorld" + SerializationConstants.LineBreak) - .SetName("Long string with front and rear whitespace is trimmed and fits in the allotted width"); - - const string reallyLong = - "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld"; - - const string reallyLongReturns = - "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHell" + SerializationConstants.LineBreak - + " oWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWo" + SerializationConstants.LineBreak - + " rldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld" + SerializationConstants.LineBreak - + " HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHel" + SerializationConstants.LineBreak - + " loWorldHelloWorld" + SerializationConstants.LineBreak; - - yield return new TestCaseData(reallyLong) - .Returns(reallyLongReturns) - .SetName("Really long string is split onto multiple lines at a width of 75 chars, prefixed with a space"); - } + yield return new TestCaseData("Short") + .Returns("Short" + SerializationConstants.LineBreak) + .SetName("Short string remains unfolded"); + + const string moderatelyLongReturns = + "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHell" + SerializationConstants.LineBreak + + " oWorld" + SerializationConstants.LineBreak; + + yield return new TestCaseData("HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld") + .Returns(moderatelyLongReturns) + .SetName("Long string is folded"); + + yield return new TestCaseData(" HelloWorldHelloWorldHelloWorldHelloWorldHelloWorld ") + .Returns("HelloWorldHelloWorldHelloWorldHelloWorldHelloWorld" + SerializationConstants.LineBreak) + .SetName("Long string with front and rear whitespace is trimmed and fits in the allotted width"); + + const string reallyLong = + "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld"; + + const string reallyLongReturns = + "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHell" + SerializationConstants.LineBreak + + " oWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWo" + SerializationConstants.LineBreak + + " rldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld" + SerializationConstants.LineBreak + + " HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHel" + SerializationConstants.LineBreak + + " loWorldHelloWorld" + SerializationConstants.LineBreak; + + yield return new TestCaseData(reallyLong) + .Returns(reallyLongReturns) + .SetName("Really long string is split onto multiple lines at a width of 75 chars, prefixed with a space"); } -} +} \ No newline at end of file diff --git a/Ical.Net.Tests/TodoTest.cs b/Ical.Net.Tests/TodoTest.cs index 4d103000c..664468e95 100644 --- a/Ical.Net.Tests/TodoTest.cs +++ b/Ical.Net.Tests/TodoTest.cs @@ -1,219 +1,223 @@ -using System.Collections; -using Ical.Net.DataTypes; -using NUnit.Framework; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System.Collections; using System.Collections.Generic; using System.Linq; +using Ical.Net.DataTypes; +using NUnit.Framework; + +namespace Ical.Net.Tests; -namespace Ical.Net.Tests +[TestFixture] +public class TodoTest { - [TestFixture] - public class TodoTest + private const string _tzid = "US-Eastern"; + + [Test, TestCaseSource(nameof(ActiveTodo_TestCases)), Category("Todo")] + public void ActiveTodo_Tests(string calendarString, IList> incoming) { - private const string _tzid = "US-Eastern"; + var iCal = Calendar.Load(calendarString); + ProgramTest.TestCal(iCal); + var todo = iCal.Todos; - [Test, TestCaseSource(nameof(ActiveTodo_TestCases)), Category("Todo")] - public void ActiveTodo_Tests(string calendarString, IList> incoming) + foreach (var calDateTime in incoming) { - var iCal = Calendar.Load(calendarString); - ProgramTest.TestCal(iCal); - var todo = iCal.Todos; - - foreach (var calDateTime in incoming) - { - var dt = calDateTime.Key; - dt.TzId = _tzid; - Assert.That(todo[0].IsActive(dt), Is.EqualTo(calDateTime.Value)); - } + var dt = calDateTime.Key; + dt.TzId = _tzid; + Assert.That(todo[0].IsActive(dt), Is.EqualTo(calDateTime.Value)); } + } - public static IEnumerable ActiveTodo_TestCases() + public static IEnumerable ActiveTodo_TestCases() + { + var testVals = new List> { - var testVals = new List> - { - new KeyValuePair(new CalDateTime(2200, 12, 31, 0, 0, 0), true) - }; - yield return new TestCaseData(IcsFiles.Todo1, testVals) - .SetName("Todo1"); - - testVals = new List> - { - new KeyValuePair(new CalDateTime(2006, 7, 28, 8, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 28, 8, 59, 59), false), - - new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2200, 12, 31, 0, 0, 0), true), - }; - yield return new TestCaseData(IcsFiles.Todo2, testVals) - .SetName("Todo2"); - - testVals = new List> - { - new KeyValuePair(new CalDateTime(2006, 7, 28, 8, 0, 0), false), - new KeyValuePair(new CalDateTime(2200, 12, 31, 0, 0, 0), false), - }; - yield return new TestCaseData(IcsFiles.Todo3, testVals).SetName("Todo3"); - - testVals = new List> - { - new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 29, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 30, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 31, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 1, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 2, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 3, 9, 0, 0), false), - - new KeyValuePair(new CalDateTime(2006, 8, 4, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 8, 5, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 8, 6, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 8, 7, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 8, 8, 9, 0, 0), true), - }; - yield return new TestCaseData(IcsFiles.Todo5, testVals).SetName("Todo5"); - - testVals = new List> - { - new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 29, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 30, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 31, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 1, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 2, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 3, 9, 0, 0), false), - - new KeyValuePair(new CalDateTime(2006, 8, 4, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 8, 5, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 8, 6, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 8, 7, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 8, 8, 9, 0, 0), true), - }; - yield return new TestCaseData(IcsFiles.Todo6, testVals).SetName("Todo6"); - - testVals = new List> - { - new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 29, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 30, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 31, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 1, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 2, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 3, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 4, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 5, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 6, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 30, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 31, 9, 0, 0), false), - - new KeyValuePair(new CalDateTime(2006, 9, 1, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 9, 2, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 9, 3, 9, 0, 0), true), - }; - yield return new TestCaseData(IcsFiles.Todo7, testVals).SetName("Todo7"); - - testVals = new List> - { - new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 29, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 30, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 31, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 1, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 2, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 3, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 4, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 5, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 6, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 30, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 31, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 9, 1, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 9, 2, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 9, 3, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 10, 10, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 11, 15, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 12, 5, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2007, 1, 3, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2007, 1, 4, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2007, 1, 5, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2007, 1, 6, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2007, 1, 7, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2007, 2, 1, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2007, 2, 2, 8, 59, 59), false), - - new KeyValuePair(new CalDateTime(2007, 2, 2, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2007, 2, 3, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2007, 2, 4, 9, 0, 0), true), - }; - yield return new TestCaseData(IcsFiles.Todo8, testVals).SetName("Todo8"); - - testVals = new List> - { - new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 29, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 7, 30, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 17, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 18, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 8, 19, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 9, 7, 9, 0, 0), false), - new KeyValuePair(new CalDateTime(2006, 9, 8, 8, 59, 59), false), - - new KeyValuePair(new CalDateTime(2006, 9, 8, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 9, 9, 9, 0, 0), true), - }; - yield return new TestCaseData(IcsFiles.Todo9, testVals).SetName("Todo9"); - } + new KeyValuePair(new CalDateTime(2200, 12, 31, 0, 0, 0), true) + }; + yield return new TestCaseData(IcsFiles.Todo1, testVals) + .SetName("Todo1"); - [Test, TestCaseSource(nameof(CompletedTodo_TestCases)), Category("Todo")] - public void CompletedTodo_Tests(string calendarString, IList> incoming) + testVals = new List> { - var iCal = Calendar.Load(calendarString); - ProgramTest.TestCal(iCal); - var todo = iCal.Todos; - - foreach (var calDateTime in incoming) - { - var dt = calDateTime.Key; - dt.TzId = _tzid; - Assert.That(todo[0].IsCompleted(dt), Is.EqualTo(calDateTime.Value)); - } - } + new KeyValuePair(new CalDateTime(2006, 7, 28, 8, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 28, 8, 59, 59), false), + + new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2200, 12, 31, 0, 0, 0), true), + }; + yield return new TestCaseData(IcsFiles.Todo2, testVals) + .SetName("Todo2"); - public static IEnumerable CompletedTodo_TestCases() + testVals = new List> { - var testVals = new List> - { - new KeyValuePair(new CalDateTime(2006, 07, 28, 8, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 07, 28, 9, 0, 0), true), - new KeyValuePair(new CalDateTime(2006, 8, 1, 0, 0, 0), true), - }; - yield return new TestCaseData(IcsFiles.Todo4, testVals).SetName("Todo4"); - } + new KeyValuePair(new CalDateTime(2006, 7, 28, 8, 0, 0), false), + new KeyValuePair(new CalDateTime(2200, 12, 31, 0, 0, 0), false), + }; + yield return new TestCaseData(IcsFiles.Todo3, testVals).SetName("Todo3"); - [Test, Category("Todo")] - public void Todo7_1() + testVals = new List> + { + new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 29, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 30, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 31, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 1, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 2, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 3, 9, 0, 0), false), + + new KeyValuePair(new CalDateTime(2006, 8, 4, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 8, 5, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 8, 6, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 8, 7, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 8, 8, 9, 0, 0), true), + }; + yield return new TestCaseData(IcsFiles.Todo5, testVals).SetName("Todo5"); + + testVals = new List> + { + new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 29, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 30, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 31, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 1, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 2, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 3, 9, 0, 0), false), + + new KeyValuePair(new CalDateTime(2006, 8, 4, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 8, 5, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 8, 6, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 8, 7, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 8, 8, 9, 0, 0), true), + }; + yield return new TestCaseData(IcsFiles.Todo6, testVals).SetName("Todo6"); + + testVals = new List> + { + new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 29, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 30, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 31, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 1, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 2, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 3, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 4, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 5, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 6, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 30, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 31, 9, 0, 0), false), + + new KeyValuePair(new CalDateTime(2006, 9, 1, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 9, 2, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 9, 3, 9, 0, 0), true), + }; + yield return new TestCaseData(IcsFiles.Todo7, testVals).SetName("Todo7"); + + testVals = new List> { - var iCal = Calendar.Load(IcsFiles.Todo7); - var todo = iCal.Todos; - - var items = new List - { - new CalDateTime(2006, 7, 28, 9, 0, 0, _tzid), - new CalDateTime(2006, 8, 4, 9, 0, 0, _tzid), - new CalDateTime(2006, 9, 1, 9, 0, 0, _tzid), - new CalDateTime(2006, 10, 6, 9, 0, 0, _tzid), - new CalDateTime(2006, 11, 3, 9, 0, 0, _tzid), - new CalDateTime(2006, 12, 1, 9, 0, 0, _tzid), - new CalDateTime(2007, 1, 5, 9, 0, 0, _tzid), - new CalDateTime(2007, 2, 2, 9, 0, 0, _tzid), - new CalDateTime(2007, 3, 2, 9, 0, 0, _tzid), - new CalDateTime(2007, 4, 6, 9, 0, 0, _tzid) - }; - - var occurrences = todo[0].GetOccurrences( - new CalDateTime(2006, 7, 1, 9, 0, 0), - new CalDateTime(2007, 7, 1, 9, 0, 0)).OrderBy(o => o.Period.StartTime).ToList(); - - Assert.That( - occurrences, -Has.Count.EqualTo(items.Count)); + new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 29, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 30, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 31, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 1, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 2, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 3, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 4, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 5, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 6, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 30, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 31, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 9, 1, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 9, 2, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 9, 3, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 10, 10, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 11, 15, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 12, 5, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2007, 1, 3, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2007, 1, 4, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2007, 1, 5, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2007, 1, 6, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2007, 1, 7, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2007, 2, 1, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2007, 2, 2, 8, 59, 59), false), + + new KeyValuePair(new CalDateTime(2007, 2, 2, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2007, 2, 3, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2007, 2, 4, 9, 0, 0), true), + }; + yield return new TestCaseData(IcsFiles.Todo8, testVals).SetName("Todo8"); + + testVals = new List> + { + new KeyValuePair(new CalDateTime(2006, 7, 28, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 29, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 7, 30, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 17, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 18, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 8, 19, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 9, 7, 9, 0, 0), false), + new KeyValuePair(new CalDateTime(2006, 9, 8, 8, 59, 59), false), + + new KeyValuePair(new CalDateTime(2006, 9, 8, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 9, 9, 9, 0, 0), true), + }; + yield return new TestCaseData(IcsFiles.Todo9, testVals).SetName("Todo9"); + } + + [Test, TestCaseSource(nameof(CompletedTodo_TestCases)), Category("Todo")] + public void CompletedTodo_Tests(string calendarString, IList> incoming) + { + var iCal = Calendar.Load(calendarString); + ProgramTest.TestCal(iCal); + var todo = iCal.Todos; + + foreach (var calDateTime in incoming) + { + var dt = calDateTime.Key; + dt.TzId = _tzid; + Assert.That(todo[0].IsCompleted(dt), Is.EqualTo(calDateTime.Value)); } } -} + + public static IEnumerable CompletedTodo_TestCases() + { + var testVals = new List> + { + new KeyValuePair(new CalDateTime(2006, 07, 28, 8, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 07, 28, 9, 0, 0), true), + new KeyValuePair(new CalDateTime(2006, 8, 1, 0, 0, 0), true), + }; + yield return new TestCaseData(IcsFiles.Todo4, testVals).SetName("Todo4"); + } + + [Test, Category("Todo")] + public void Todo7_1() + { + var iCal = Calendar.Load(IcsFiles.Todo7); + var todo = iCal.Todos; + + var items = new List + { + new CalDateTime(2006, 7, 28, 9, 0, 0, _tzid), + new CalDateTime(2006, 8, 4, 9, 0, 0, _tzid), + new CalDateTime(2006, 9, 1, 9, 0, 0, _tzid), + new CalDateTime(2006, 10, 6, 9, 0, 0, _tzid), + new CalDateTime(2006, 11, 3, 9, 0, 0, _tzid), + new CalDateTime(2006, 12, 1, 9, 0, 0, _tzid), + new CalDateTime(2007, 1, 5, 9, 0, 0, _tzid), + new CalDateTime(2007, 2, 2, 9, 0, 0, _tzid), + new CalDateTime(2007, 3, 2, 9, 0, 0, _tzid), + new CalDateTime(2007, 4, 6, 9, 0, 0, _tzid) + }; + + var occurrences = todo[0].GetOccurrences( + new CalDateTime(2006, 7, 1, 9, 0, 0), + new CalDateTime(2007, 7, 1, 9, 0, 0)).OrderBy(o => o.Period.StartTime).ToList(); + + Assert.That( + occurrences, + Has.Count.EqualTo(items.Count)); + } +} \ No newline at end of file diff --git a/Ical.Net.Tests/VTimeZoneTest.cs b/Ical.Net.Tests/VTimeZoneTest.cs index bc984b07e..ca14f1ecb 100644 --- a/Ical.Net.Tests/VTimeZoneTest.cs +++ b/Ical.Net.Tests/VTimeZoneTest.cs @@ -1,293 +1,297 @@ -using Ical.Net.CalendarComponents; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System; +using System.Collections.Generic; +using Ical.Net.CalendarComponents; using Ical.Net.DataTypes; using Ical.Net.Serialization; using NUnit.Framework; -using System; -using System.Collections.Generic; -namespace Ical.Net.Tests +namespace Ical.Net.Tests; + +public class VTimeZoneTest { - public class VTimeZoneTest + [Test, Category("VTimeZone")] + public void InvalidTzIdShouldThrowException() { - [Test, Category("VTimeZone")] - public void InvalidTzIdShouldThrowException() - { - Assert.Throws(() => new VTimeZone("shouldFail")); - } + Assert.Throws(() => new VTimeZone("shouldFail")); + } - [Test, Category("VTimeZone")] - public void VTimeZoneFromDateTimeZoneNullZoneShouldThrowException() - { - Assert.Throws(() => CreateTestCalendar("shouldFail")); - } + [Test, Category("VTimeZone")] + public void VTimeZoneFromDateTimeZoneNullZoneShouldThrowException() + { + Assert.Throws(() => CreateTestCalendar("shouldFail")); + } - [Test, Category("VTimeZone")] - public void VTimeZoneAmericaPhoenixShouldSerializeProperly() - { - var iCal = CreateTestCalendar("America/Phoenix"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); - - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZID:America/Phoenix"), Is.True, "Time zone not found in serialization"); - Assert.That(serialized.Contains("DTSTART:19670430T020000"), Is.True, "Daylight savings for Phoenix was not serialized properly."); - }); - } + [Test, Category("VTimeZone")] + public void VTimeZoneAmericaPhoenixShouldSerializeProperly() + { + var iCal = CreateTestCalendar("America/Phoenix"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); - [Test, Category("VTimeZone")] - public void VTimeZoneAmericaPhoenixShouldSerializeProperly2() + Assert.Multiple(() => { - var iCal = CreateTestCalendar("America/Phoenix", DateTime.Now, false); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); - - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZID:America/Phoenix"), Is.True, "Time zone not found in serialization"); - Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.False, "Daylight savings should not exist for Phoenix."); - }); - } + Assert.That(serialized.Contains("TZID:America/Phoenix"), Is.True, "Time zone not found in serialization"); + Assert.That(serialized.Contains("DTSTART:19670430T020000"), Is.True, "Daylight savings for Phoenix was not serialized properly."); + }); + } - [Test, Category("VTimeZone")] - public void VTimeZoneUsMountainStandardTimeShouldSerializeProperly() - { - var iCal = CreateTestCalendar("US Mountain Standard Time"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); - - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZID:US Mountain Standard Time"), Is.True, "Time zone not found in serialization"); - Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True); - Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True); - Assert.That(serialized.Contains("X-LIC-LOCATION"), Is.True, "X-LIC-LOCATION was not serialized"); - }); - } + [Test, Category("VTimeZone")] + public void VTimeZoneAmericaPhoenixShouldSerializeProperly2() + { + var iCal = CreateTestCalendar("America/Phoenix", DateTime.Now, false); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); - [Test, Category("VTimeZone")] - public void VTimeZonePacificKiritimatiShouldSerializeProperly() + Assert.Multiple(() => { - var iCal = CreateTestCalendar("Pacific/Kiritimati"); - var serializer = new CalendarSerializer(); - Assert.DoesNotThrow(() => serializer.SerializeToString(iCal)); - } + Assert.That(serialized.Contains("TZID:America/Phoenix"), Is.True, "Time zone not found in serialization"); + Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.False, "Daylight savings should not exist for Phoenix."); + }); + } - [Test, Category("VTimeZone")] - public void VTimeZoneCentralAmericaStandardTimeShouldSerializeProperly() + [Test, Category("VTimeZone")] + public void VTimeZoneUsMountainStandardTimeShouldSerializeProperly() + { + var iCal = CreateTestCalendar("US Mountain Standard Time"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); + + Assert.Multiple(() => { - var iCal = CreateTestCalendar("Central America Standard Time"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); + Assert.That(serialized.Contains("TZID:US Mountain Standard Time"), Is.True, "Time zone not found in serialization"); + Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True); + Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True); + Assert.That(serialized.Contains("X-LIC-LOCATION"), Is.True, "X-LIC-LOCATION was not serialized"); + }); + } - Assert.That(serialized.Contains("TZID:Central America Standard Time"), Is.True, "Time zone not found in serialization"); - } + [Test, Category("VTimeZone")] + public void VTimeZonePacificKiritimatiShouldSerializeProperly() + { + var iCal = CreateTestCalendar("Pacific/Kiritimati"); + var serializer = new CalendarSerializer(); + Assert.DoesNotThrow(() => serializer.SerializeToString(iCal)); + } + + [Test, Category("VTimeZone")] + public void VTimeZoneCentralAmericaStandardTimeShouldSerializeProperly() + { + var iCal = CreateTestCalendar("Central America Standard Time"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); + + Assert.That(serialized.Contains("TZID:Central America Standard Time"), Is.True, "Time zone not found in serialization"); + } + + [Test, Category("VTimeZone")] + public void VTimeZoneEasternStandardTimeShouldSerializeProperly() + { + var iCal = CreateTestCalendar("Eastern Standard Time"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); - [Test, Category("VTimeZone")] - public void VTimeZoneEasternStandardTimeShouldSerializeProperly() + Assert.That(serialized.Contains("TZID:Eastern Standard Time"), Is.True, "Time zone not found in serialization"); + } + + [Test, Category("VTimeZone")] + public void VTimeZoneEuropeMoscowShouldSerializeProperly() + { + var iCal = CreateTestCalendar("Europe/Moscow"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); + + Assert.Multiple(() => + { + Assert.That(serialized.Contains("TZID:Europe/Moscow"), Is.True, "Time zone not found in serialization"); + Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); + Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); + }); + Assert.Multiple(() => { - var iCal = CreateTestCalendar("Eastern Standard Time"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); + Assert.That(serialized.Contains("TZNAME:MSD"), Is.True, "MSD was not serialized"); + Assert.That(serialized.Contains("TZNAME:MSK"), Is.True, "MSK info was not serialized"); + Assert.That(serialized.Contains("TZNAME:MSD"), Is.True, "MSD was not serialized"); + Assert.That(serialized.Contains("TZNAME:MST"), Is.True, "MST was not serialized"); + Assert.That(serialized.Contains("TZNAME:MMT"), Is.True, "MMT was not serialized"); + Assert.That(serialized.Contains("TZOFFSETFROM:+023017"), Is.True, "TZOFFSETFROM:+023017 was not serialized"); + Assert.That(serialized.Contains("TZOFFSETTO:+023017"), Is.True, "TZOFFSETTO:+023017 was not serialized"); + Assert.That(serialized.Contains("DTSTART:19180916T010000"), Is.True, "DTSTART:19180916T010000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:19171228T000000"), Is.True, "DTSTART:19171228T000000 was not serialized"); + Assert.That(serialized.Contains("RDATE:19991031T030000"), Is.True, "RDATE:19991031T030000 was not serialized"); + }); + } - Assert.That(serialized.Contains("TZID:Eastern Standard Time"), Is.True, "Time zone not found in serialization"); - } + [Test, Category("VTimeZone")] + public void VTimeZoneAmericaChicagoShouldSerializeProperly() + { + var iCal = CreateTestCalendar("America/Chicago"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); - [Test, Category("VTimeZone")] - public void VTimeZoneEuropeMoscowShouldSerializeProperly() + Assert.Multiple(() => { - var iCal = CreateTestCalendar("Europe/Moscow"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); - - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZID:Europe/Moscow"), Is.True, "Time zone not found in serialization"); - Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); - Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); - }); - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZNAME:MSD"), Is.True, "MSD was not serialized"); - Assert.That(serialized.Contains("TZNAME:MSK"), Is.True, "MSK info was not serialized"); - Assert.That(serialized.Contains("TZNAME:MSD"), Is.True, "MSD was not serialized"); - Assert.That(serialized.Contains("TZNAME:MST"), Is.True, "MST was not serialized"); - Assert.That(serialized.Contains("TZNAME:MMT"), Is.True, "MMT was not serialized"); - Assert.That(serialized.Contains("TZOFFSETFROM:+023017"), Is.True, "TZOFFSETFROM:+023017 was not serialized"); - Assert.That(serialized.Contains("TZOFFSETTO:+023017"), Is.True, "TZOFFSETTO:+023017 was not serialized"); - Assert.That(serialized.Contains("DTSTART:19180916T010000"), Is.True, "DTSTART:19180916T010000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:19171228T000000"), Is.True, "DTSTART:19171228T000000 was not serialized"); - Assert.That(serialized.Contains("RDATE:19991031T030000"), Is.True, "RDATE:19991031T030000 was not serialized"); - }); - } + Assert.That(serialized.Contains("TZID:America/Chicago"), Is.True, "Time zone not found in serialization"); + Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); + Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); + Assert.That(serialized.Contains("TZNAME:CDT"), Is.True, "CDT was not serialized"); + Assert.That(serialized.Contains("TZNAME:CST"), Is.True, "CST was not serialized"); + Assert.That(serialized.Contains("TZNAME:EST"), Is.True, "EST was not serialized"); + Assert.That(serialized.Contains("TZNAME:CWT"), Is.True, "CWT was not serialized"); + Assert.That(serialized.Contains("TZNAME:CPT"), Is.True, "CPT was not serialized"); + Assert.That(serialized.Contains("DTSTART:19181027T020000"), Is.True, "DTSTART:19181027T020000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:19450814T180000"), Is.True, "DTSTART:19450814T180000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:19420209T020000"), Is.True, "DTSTART:19420209T020000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:19360301T020000"), Is.True, "DTSTART:19360301T020000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:20070311T020000"), Is.True, "DTSTART:20070311T020000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:20071104T020000"), Is.True, "DTSTART:20071104T020000 was not serialized"); + }); + } + + [Test, Category("VTimeZone")] + public void VTimeZoneAmericaLosAngelesShouldSerializeProperly() + { + var iCal = CreateTestCalendar("America/Los_Angeles"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); - [Test, Category("VTimeZone")] - public void VTimeZoneAmericaChicagoShouldSerializeProperly() + Assert.Multiple(() => { - var iCal = CreateTestCalendar("America/Chicago"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); - - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZID:America/Chicago"), Is.True, "Time zone not found in serialization"); - Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); - Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); - Assert.That(serialized.Contains("TZNAME:CDT"), Is.True, "CDT was not serialized"); - Assert.That(serialized.Contains("TZNAME:CST"), Is.True, "CST was not serialized"); - Assert.That(serialized.Contains("TZNAME:EST"), Is.True, "EST was not serialized"); - Assert.That(serialized.Contains("TZNAME:CWT"), Is.True, "CWT was not serialized"); - Assert.That(serialized.Contains("TZNAME:CPT"), Is.True, "CPT was not serialized"); - Assert.That(serialized.Contains("DTSTART:19181027T020000"), Is.True, "DTSTART:19181027T020000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:19450814T180000"), Is.True, "DTSTART:19450814T180000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:19420209T020000"), Is.True, "DTSTART:19420209T020000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:19360301T020000"), Is.True, "DTSTART:19360301T020000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:20070311T020000"), Is.True, "DTSTART:20070311T020000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:20071104T020000"), Is.True, "DTSTART:20071104T020000 was not serialized"); - }); - } + Assert.That(serialized.Contains("TZID:America/Los_Angeles"), Is.True, "Time zone not found in serialization"); + Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); + Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); + Assert.That(serialized.Contains("BYDAY=2SU"), Is.True, "BYDAY=2SU was not serialized"); + Assert.That(serialized.Contains("TZNAME:PDT"), Is.True, "PDT was not serialized"); + Assert.That(serialized.Contains("TZNAME:PST"), Is.True, "PST was not serialized"); + Assert.That(serialized.Contains("TZNAME:PPT"), Is.True, "PPT was not serialized"); + Assert.That(serialized.Contains("TZNAME:PWT"), Is.True, "PWT was not serialized"); + Assert.That(serialized.Contains("DTSTART:19180331T020000"), Is.True, "DTSTART:19180331T020000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:20071104T020000"), Is.True, "DTSTART:20071104T020000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:20070311T020000"), Is.True, "DTSTART:20070311T020000 was not serialized"); + }); + + //Assert.IsTrue(serialized.Contains("TZURL:http://tzurl.org/zoneinfo/America/Los_Angeles"), "TZURL:http://tzurl.org/zoneinfo/America/Los_Angeles was not serialized"); + //Assert.IsTrue(serialized.Contains("RDATE:19600424T010000"), "RDATE:19600424T010000 was not serialized"); // NodaTime doesn't match with what tzurl has + } + + [Test, Category("VTimeZone")] + public void VTimeZoneEuropeOsloShouldSerializeProperly() + { + var iCal = CreateTestCalendar("Europe/Oslo"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); - [Test, Category("VTimeZone")] - public void VTimeZoneAmericaLosAngelesShouldSerializeProperly() + Assert.Multiple(() => { - var iCal = CreateTestCalendar("America/Los_Angeles"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); - - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZID:America/Los_Angeles"), Is.True, "Time zone not found in serialization"); - Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); - Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); - Assert.That(serialized.Contains("BYDAY=2SU"), Is.True, "BYDAY=2SU was not serialized"); - Assert.That(serialized.Contains("TZNAME:PDT"), Is.True, "PDT was not serialized"); - Assert.That(serialized.Contains("TZNAME:PST"), Is.True, "PST was not serialized"); - Assert.That(serialized.Contains("TZNAME:PPT"), Is.True, "PPT was not serialized"); - Assert.That(serialized.Contains("TZNAME:PWT"), Is.True, "PWT was not serialized"); - Assert.That(serialized.Contains("DTSTART:19180331T020000"), Is.True, "DTSTART:19180331T020000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:20071104T020000"), Is.True, "DTSTART:20071104T020000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:20070311T020000"), Is.True, "DTSTART:20070311T020000 was not serialized"); - }); - - //Assert.IsTrue(serialized.Contains("TZURL:http://tzurl.org/zoneinfo/America/Los_Angeles"), "TZURL:http://tzurl.org/zoneinfo/America/Los_Angeles was not serialized"); - //Assert.IsTrue(serialized.Contains("RDATE:19600424T010000"), "RDATE:19600424T010000 was not serialized"); // NodaTime doesn't match with what tzurl has - } + Assert.That(serialized.Contains("TZID:Europe/Oslo"), Is.True, "Time zone not found in serialization"); + Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); + Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); + Assert.That(serialized.Contains("BYDAY=-1SU;BYMONTH=3"), Is.True, "BYDAY=-1SU;BYMONTH=3 was not serialized"); + Assert.That(serialized.Contains("BYDAY=-1SU;BYMONTH=10"), Is.True, "BYDAY=-1SU;BYMONTH=10 was not serialized"); + }); - [Test, Category("VTimeZone")] - public void VTimeZoneEuropeOsloShouldSerializeProperly() + } + + [Test, Category("VTimeZone")] + public void VTimeZoneAmericaAnchorageShouldSerializeProperly() + { + var iCal = CreateTestCalendar("America/Anchorage"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); + + Assert.Multiple(() => { - var iCal = CreateTestCalendar("Europe/Oslo"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); - - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZID:Europe/Oslo"), Is.True, "Time zone not found in serialization"); - Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); - Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); - Assert.That(serialized.Contains("BYDAY=-1SU;BYMONTH=3"), Is.True, "BYDAY=-1SU;BYMONTH=3 was not serialized"); - Assert.That(serialized.Contains("BYDAY=-1SU;BYMONTH=10"), Is.True, "BYDAY=-1SU;BYMONTH=10 was not serialized"); - }); + Assert.That(serialized.Contains("TZID:America/Anchorage"), Is.True, "Time zone not found in serialization"); + Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); + Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); + Assert.That(serialized.Contains("TZNAME:AHST"), Is.True, "AHST was not serialized"); + Assert.That(serialized.Contains("TZNAME:AHDT"), Is.True, "AHDT was not serialized"); + Assert.That(serialized.Contains("TZNAME:AKST"), Is.True, "AKST was not serialized"); + Assert.That(serialized.Contains("TZNAME:YST"), Is.True, "YST was not serialized"); + Assert.That(serialized.Contains("TZNAME:AHDT"), Is.True, "AHDT was not serialized"); + Assert.That(serialized.Contains("TZNAME:LMT"), Is.True, "LMT was not serialized"); + Assert.That(serialized.Contains("RDATE:19731028T020000"), Is.True, "RDATE:19731028T020000 was not serialized"); + Assert.That(serialized.Contains("RDATE:19801026T020000"), Is.True, "RDATE:19801026T020000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:19420209T020000"), Is.True, "DTSTART:19420209T020000 was not serialized"); + Assert.That(serialized.Contains("RDATE:19670401/P1D"), Is.False, "RDate was not properly serialized for vtimezone, should be RDATE:19670401T000000"); + }); + } - } + [Test, Category("VTimeZone")] + public void VTimeZoneAmericaEirunepeShouldSerializeProperly() + { + var iCal = CreateTestCalendar("America/Eirunepe"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); - [Test, Category("VTimeZone")] - public void VTimeZoneAmericaAnchorageShouldSerializeProperly() + Assert.Multiple(() => { - var iCal = CreateTestCalendar("America/Anchorage"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); - - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZID:America/Anchorage"), Is.True, "Time zone not found in serialization"); - Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); - Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); - Assert.That(serialized.Contains("TZNAME:AHST"), Is.True, "AHST was not serialized"); - Assert.That(serialized.Contains("TZNAME:AHDT"), Is.True, "AHDT was not serialized"); - Assert.That(serialized.Contains("TZNAME:AKST"), Is.True, "AKST was not serialized"); - Assert.That(serialized.Contains("TZNAME:YST"), Is.True, "YST was not serialized"); - Assert.That(serialized.Contains("TZNAME:AHDT"), Is.True, "AHDT was not serialized"); - Assert.That(serialized.Contains("TZNAME:LMT"), Is.True, "LMT was not serialized"); - Assert.That(serialized.Contains("RDATE:19731028T020000"), Is.True, "RDATE:19731028T020000 was not serialized"); - Assert.That(serialized.Contains("RDATE:19801026T020000"), Is.True, "RDATE:19801026T020000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:19420209T020000"), Is.True, "DTSTART:19420209T020000 was not serialized"); - Assert.That(serialized.Contains("RDATE:19670401/P1D"), Is.False, "RDate was not properly serialized for vtimezone, should be RDATE:19670401T000000"); - }); - } + Assert.That(serialized.Contains("TZID:America/Eirunepe"), Is.True, "Time zone not found in serialization"); + Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); + Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); + Assert.That(serialized.Contains("TZNAME:-04"), Is.True, "-04 was not serialized"); + Assert.That(serialized.Contains("TZNAME:-05"), Is.True, "-05 was not serialized"); + Assert.That(serialized.Contains("DTSTART:19311003T110000"), Is.True, "DTSTART:19311003T110000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:19320401T000000"), Is.True, "DTSTART:19320401T000000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:20080624T000000"), Is.True, "DTSTART:20080624T000000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:19501201T000000"), Is.True, "DTSTART:19501201T000000 was not serialized"); + }); + + // Should not contain the following + Assert.That(serialized.Contains("RDATE:19501201T000000/P1D"), Is.False, "The RDATE was not serialized correctly, should be RDATE:19501201T000000"); + } - [Test, Category("VTimeZone")] - public void VTimeZoneAmericaEirunepeShouldSerializeProperly() + [Test, Category("VTimeZone")] + public void VTimeZoneAmericaDetroitShouldSerializeProperly() + { + var iCal = CreateTestCalendar("America/Detroit"); + var serializer = new CalendarSerializer(); + var serialized = serializer.SerializeToString(iCal); + + Assert.Multiple(() => { - var iCal = CreateTestCalendar("America/Eirunepe"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); - - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZID:America/Eirunepe"), Is.True, "Time zone not found in serialization"); - Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); - Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); - Assert.That(serialized.Contains("TZNAME:-04"), Is.True, "-04 was not serialized"); - Assert.That(serialized.Contains("TZNAME:-05"), Is.True, "-05 was not serialized"); - Assert.That(serialized.Contains("DTSTART:19311003T110000"), Is.True, "DTSTART:19311003T110000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:19320401T000000"), Is.True, "DTSTART:19320401T000000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:20080624T000000"), Is.True, "DTSTART:20080624T000000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:19501201T000000"), Is.True, "DTSTART:19501201T000000 was not serialized"); - }); - - // Should not contain the following - Assert.That(serialized.Contains("RDATE:19501201T000000/P1D"), Is.False, "The RDATE was not serialized correctly, should be RDATE:19501201T000000"); - } + Assert.That(serialized.Contains("TZID:America/Detroit"), Is.True, "Time zone not found in serialization"); + Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); + Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); + Assert.That(serialized.Contains("TZNAME:EDT"), Is.True, "EDT was not serialized"); + Assert.That(serialized.Contains("TZNAME:EPT"), Is.True, "EPT was not serialized"); + Assert.That(serialized.Contains("TZNAME:EST"), Is.True, "EST was not serialized"); + Assert.That(serialized.Contains("DTSTART:20070311T020000"), Is.True, "DTSTART:20070311T020000 was not serialized"); + Assert.That(serialized.Contains("DTSTART:20071104T020000"), Is.True, "DTSTART:20071104T020000 was not serialized"); + }); + } - [Test, Category("VTimeZone")] - public void VTimeZoneAmericaDetroitShouldSerializeProperly() + private static Calendar CreateTestCalendar(string tzId, DateTime? earliestTime = null, bool includeHistoricalData = true) + { + var iCal = new Calendar(); + + if (earliestTime == null) { - var iCal = CreateTestCalendar("America/Detroit"); - var serializer = new CalendarSerializer(); - var serialized = serializer.SerializeToString(iCal); - - Assert.Multiple(() => - { - Assert.That(serialized.Contains("TZID:America/Detroit"), Is.True, "Time zone not found in serialization"); - Assert.That(serialized.Contains("BEGIN:STANDARD"), Is.True, "The standard timezone info was not serialized"); - Assert.That(serialized.Contains("BEGIN:DAYLIGHT"), Is.True, "The daylight timezone info was not serialized"); - Assert.That(serialized.Contains("TZNAME:EDT"), Is.True, "EDT was not serialized"); - Assert.That(serialized.Contains("TZNAME:EPT"), Is.True, "EPT was not serialized"); - Assert.That(serialized.Contains("TZNAME:EST"), Is.True, "EST was not serialized"); - Assert.That(serialized.Contains("DTSTART:20070311T020000"), Is.True, "DTSTART:20070311T020000 was not serialized"); - Assert.That(serialized.Contains("DTSTART:20071104T020000"), Is.True, "DTSTART:20071104T020000 was not serialized"); - }); + earliestTime = new DateTime(1900, 1, 1); } + iCal.AddTimeZone(tzId, earliestTime.Value, includeHistoricalData); - private static Calendar CreateTestCalendar(string tzId, DateTime? earliestTime = null, bool includeHistoricalData = true) + var calEvent = new CalendarEvent { - var iCal = new Calendar(); - - if (earliestTime == null) - { - earliestTime = new DateTime(1900, 1, 1); - } - iCal.AddTimeZone(tzId, earliestTime.Value, includeHistoricalData); - - var calEvent = new CalendarEvent - { - Description = "Test Recurring Event", - Start = new CalDateTime(DateTime.Now, tzId), - End = new CalDateTime(DateTime.Now.AddHours(1), tzId), - RecurrenceRules = new List { new RecurrencePattern(FrequencyType.Daily) } - }; - iCal.Events.Add(calEvent); - - var calEvent2 = new CalendarEvent - { - Description = "Test Recurring Event 2", - Start = new CalDateTime(DateTime.Now.AddHours(2), tzId), - End = new CalDateTime(DateTime.Now.AddHours(3), tzId), - RecurrenceRules = new List { new RecurrencePattern(FrequencyType.Daily) } - }; - iCal.Events.Add(calEvent2); - return iCal; - } + Description = "Test Recurring Event", + Start = new CalDateTime(DateTime.Now, tzId), + End = new CalDateTime(DateTime.Now.AddHours(1), tzId), + RecurrenceRules = new List { new RecurrencePattern(FrequencyType.Daily) } + }; + iCal.Events.Add(calEvent); + + var calEvent2 = new CalendarEvent + { + Description = "Test Recurring Event 2", + Start = new CalDateTime(DateTime.Now.AddHours(2), tzId), + End = new CalDateTime(DateTime.Now.AddHours(3), tzId), + RecurrenceRules = new List { new RecurrencePattern(FrequencyType.Daily) } + }; + iCal.Events.Add(calEvent2); + return iCal; } } \ No newline at end of file diff --git a/Ical.Net.Tests/contrib/libical/readme.txt b/Ical.Net.Tests/contrib/libical/readme.txt index 2544db28a..9141ed10e 100644 --- a/Ical.Net.Tests/contrib/libical/readme.txt +++ b/Ical.Net.Tests/contrib/libical/readme.txt @@ -1 +1 @@ -Files in this folder have originally been developed as part of [the libical project](https://github.com/libical/libical). The files may have been modified after copying from libical. To identify the modifications made since copying, refer to the Git history of the individual file or compare with the source file in the libical project. \ No newline at end of file +Files in this folder have originally been developed as part of [the libical project](https://github.com/libical/libical). The files may have been modified after copying from libical. To identify the modifications made since copying, refer to the Git history of the individual file or compare with the source file in the libical project. diff --git a/Ical.Net/Calendar.cs b/Ical.Net/Calendar.cs index 95623aafe..f91781fe2 100644 --- a/Ical.Net/Calendar.cs +++ b/Ical.Net/Calendar.cs @@ -1,404 +1,408 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Proxies; -using Ical.Net.Serialization; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization; using System.Text; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Proxies; +using Ical.Net.Serialization; +using Ical.Net.Utility; -namespace Ical.Net +namespace Ical.Net; + +public class Calendar : CalendarComponent, IGetOccurrencesTyped, IGetFreeBusy, IMergeable { - public class Calendar : CalendarComponent, IGetOccurrencesTyped, IGetFreeBusy, IMergeable + public static Calendar Load(string iCalendarString) + => CalendarCollection.Load(new StringReader(iCalendarString)).SingleOrDefault(); + + /// + /// Loads an from an open stream. + /// + /// The stream from which to load the object + /// An object + public static Calendar Load(Stream s) + => CalendarCollection.Load(new StreamReader(s, Encoding.UTF8)).SingleOrDefault(); + + public static Calendar Load(TextReader tr) + => CalendarCollection.Load(tr)?.SingleOrDefault(); + + public static IList Load(Stream s, Encoding e) + => Load(new StreamReader(s, e)); + + public static IList Load(TextReader tr) + => SimpleDeserializer.Default.Deserialize(tr).OfType().ToList(); + + public static IList Load(string ical) + => Load(new StringReader(ical)); + + private IUniqueComponentList _mUniqueComponents; + private IUniqueComponentList _mEvents; + private IUniqueComponentList _mTodos; + private ICalendarObjectList _mJournals; + private IUniqueComponentList _mFreeBusy; + private ICalendarObjectList _mTimeZones; + + /// + /// To load an existing an iCalendar object, use one of the provided LoadFromXXX methods. + /// + /// For example, use the following code to load an iCalendar object from a URL: + /// + /// IICalendar iCal = iCalendar.LoadFromUri(new Uri("http://somesite.com/calendar.ics")); + /// + /// + /// + public Calendar() { - public static Calendar Load(string iCalendarString) - => CalendarCollection.Load(new StringReader(iCalendarString)).SingleOrDefault(); - - /// - /// Loads an from an open stream. - /// - /// The stream from which to load the object - /// An object - public static Calendar Load(Stream s) - => CalendarCollection.Load(new StreamReader(s, Encoding.UTF8)).SingleOrDefault(); - - public static Calendar Load(TextReader tr) - => CalendarCollection.Load(tr)?.SingleOrDefault(); - - public static IList Load(Stream s, Encoding e) - => Load(new StreamReader(s, e)); - - public static IList Load(TextReader tr) - => SimpleDeserializer.Default.Deserialize(tr).OfType().ToList(); - - public static IList Load(string ical) - => Load(new StringReader(ical)); - - private IUniqueComponentList _mUniqueComponents; - private IUniqueComponentList _mEvents; - private IUniqueComponentList _mTodos; - private ICalendarObjectList _mJournals; - private IUniqueComponentList _mFreeBusy; - private ICalendarObjectList _mTimeZones; - - /// - /// To load an existing an iCalendar object, use one of the provided LoadFromXXX methods. - /// - /// For example, use the following code to load an iCalendar object from a URL: - /// - /// IICalendar iCal = iCalendar.LoadFromUri(new Uri("http://somesite.com/calendar.ics")); - /// - /// - /// - public Calendar() - { - Name = Components.Calendar; - Initialize(); - } + Name = Components.Calendar; + Initialize(); + } - private void Initialize() - { - _mUniqueComponents = new UniqueComponentListProxy(Children); - _mEvents = new UniqueComponentListProxy(Children); - _mTodos = new UniqueComponentListProxy(Children); - _mJournals = new CalendarObjectListProxy(Children); - _mFreeBusy = new UniqueComponentListProxy(Children); - _mTimeZones = new CalendarObjectListProxy(Children); - } + private void Initialize() + { + _mUniqueComponents = new UniqueComponentListProxy(Children); + _mEvents = new UniqueComponentListProxy(Children); + _mTodos = new UniqueComponentListProxy(Children); + _mJournals = new CalendarObjectListProxy(Children); + _mFreeBusy = new UniqueComponentListProxy(Children); + _mTimeZones = new CalendarObjectListProxy(Children); + } - protected override void OnDeserializing(StreamingContext context) - { - base.OnDeserializing(context); + protected override void OnDeserializing(StreamingContext context) + { + base.OnDeserializing(context); - Initialize(); - } + Initialize(); + } - protected bool Equals(Calendar other) - => string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) - && CollectionHelpers.Equals(UniqueComponents, other.UniqueComponents) - && CollectionHelpers.Equals(Events, other.Events) - && CollectionHelpers.Equals(Todos, other.Todos) - && CollectionHelpers.Equals(Journals, other.Journals) - && CollectionHelpers.Equals(FreeBusy, other.FreeBusy) - && CollectionHelpers.Equals(TimeZones, other.TimeZones); + protected bool Equals(Calendar other) + => string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) + && CollectionHelpers.Equals(UniqueComponents, other.UniqueComponents) + && CollectionHelpers.Equals(Events, other.Events) + && CollectionHelpers.Equals(Todos, other.Todos) + && CollectionHelpers.Equals(Journals, other.Journals) + && CollectionHelpers.Equals(FreeBusy, other.FreeBusy) + && CollectionHelpers.Equals(TimeZones, other.TimeZones); - public override bool Equals(object obj) + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, obj)) - { - return false; - } - if (ReferenceEquals(this, obj)) - { - return true; - } - return obj.GetType() == GetType() && Equals((Calendar)obj); + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; } + return obj.GetType() == GetType() && Equals((Calendar) obj); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = Name?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(UniqueComponents); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Events); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Todos); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Journals); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(FreeBusy); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(TimeZones); - return hashCode; - } + var hashCode = Name?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(UniqueComponents); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Events); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Todos); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Journals); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(FreeBusy); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(TimeZones); + return hashCode; } + } - public virtual IUniqueComponentList UniqueComponents => _mUniqueComponents; + public virtual IUniqueComponentList UniqueComponents => _mUniqueComponents; - public virtual IEnumerable RecurringItems => Children.OfType(); + public virtual IEnumerable RecurringItems => Children.OfType(); - /// - /// A collection of components in the iCalendar. - /// - public virtual IUniqueComponentList Events => _mEvents; + /// + /// A collection of components in the iCalendar. + /// + public virtual IUniqueComponentList Events => _mEvents; - /// - /// A collection of components in the iCalendar. - /// - public virtual IUniqueComponentList FreeBusy => _mFreeBusy; + /// + /// A collection of components in the iCalendar. + /// + public virtual IUniqueComponentList FreeBusy => _mFreeBusy; - /// - /// A collection of components in the iCalendar. - /// - public virtual ICalendarObjectList Journals => _mJournals; + /// + /// A collection of components in the iCalendar. + /// + public virtual ICalendarObjectList Journals => _mJournals; - /// - /// A collection of VTimeZone components in the iCalendar. - /// - public virtual ICalendarObjectList TimeZones => _mTimeZones; + /// + /// A collection of VTimeZone components in the iCalendar. + /// + public virtual ICalendarObjectList TimeZones => _mTimeZones; - /// - /// A collection of components in the iCalendar. - /// - public virtual IUniqueComponentList Todos => _mTodos; + /// + /// A collection of components in the iCalendar. + /// + public virtual IUniqueComponentList Todos => _mTodos; - public virtual string Version - { - get => Properties.Get("VERSION"); - set => Properties.Set("VERSION", value); - } + public virtual string Version + { + get => Properties.Get("VERSION"); + set => Properties.Set("VERSION", value); + } - public virtual string ProductId - { - get => Properties.Get("PRODID"); - set => Properties.Set("PRODID", value); - } + public virtual string ProductId + { + get => Properties.Get("PRODID"); + set => Properties.Set("PRODID", value); + } - public virtual string Scale - { - get => Properties.Get("CALSCALE"); - set => Properties.Set("CALSCALE", value); - } + public virtual string Scale + { + get => Properties.Get("CALSCALE"); + set => Properties.Set("CALSCALE", value); + } - public virtual string Method - { - get => Properties.Get("METHOD"); - set => Properties.Set("METHOD", value); - } + public virtual string Method + { + get => Properties.Get("METHOD"); + set => Properties.Set("METHOD", value); + } - [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] - public virtual RecurrenceRestrictionType RecurrenceRestriction - { - get => Properties.Get("X-DDAY-ICAL-RECURRENCE-RESTRICTION"); - set => Properties.Set("X-DDAY-ICAL-RECURRENCE-RESTRICTION", value); - } + [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] + public virtual RecurrenceRestrictionType RecurrenceRestriction + { + get => Properties.Get("X-DDAY-ICAL-RECURRENCE-RESTRICTION"); + set => Properties.Set("X-DDAY-ICAL-RECURRENCE-RESTRICTION", value); + } - [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] - public virtual RecurrenceEvaluationModeType RecurrenceEvaluationMode - { - get => Properties.Get("X-DDAY-ICAL-RECURRENCE-EVALUATION-MODE"); - set => Properties.Set("X-DDAY-ICAL-RECURRENCE-EVALUATION-MODE", value); - } + [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] + public virtual RecurrenceEvaluationModeType RecurrenceEvaluationMode + { + get => Properties.Get("X-DDAY-ICAL-RECURRENCE-EVALUATION-MODE"); + set => Properties.Set("X-DDAY-ICAL-RECURRENCE-EVALUATION-MODE", value); + } - /// - /// Adds a time zone to the iCalendar. This time zone may - /// then be used in date/time objects contained in the - /// calendar. - /// - /// The time zone added to the calendar. - public VTimeZone AddTimeZone(VTimeZone tz) + /// + /// Adds a time zone to the iCalendar. This time zone may + /// then be used in date/time objects contained in the + /// calendar. + /// + /// The time zone added to the calendar. + public VTimeZone AddTimeZone(VTimeZone tz) + { + this.AddChild(tz); + return tz; + } + + + /// + /// Clears recurrence evaluations for recurring components. + /// + public void ClearEvaluation() + { + foreach (var recurrable in RecurringItems) { - this.AddChild(tz); - return tz; + recurrable.ClearEvaluation(); } + } + /// + /// Returns a list of occurrences of each recurring component + /// for the date provided (). + /// + /// The date for which to return occurrences. Time is ignored on this parameter. + /// A list of occurrences that occur on the given date (). + public virtual HashSet GetOccurrences(IDateTime dt) + => GetOccurrences(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1))); + + /// + public virtual HashSet GetOccurrences(DateTime dt) + => GetOccurrences(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1))); + + /// + /// Returns a list of occurrences of each recurring component + /// that occur between and . + /// + /// The beginning date/time of the range. + /// The end date/time of the range. + /// A list of occurrences that fall between the date/time arguments provided. + public virtual HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) + => GetOccurrences(startTime, endTime); + + /// + public virtual HashSet GetOccurrences(DateTime startTime, DateTime endTime) + => GetOccurrences(new CalDateTime(startTime), new CalDateTime(endTime)); + + /// + /// Returns all occurrences of components of type T that start on the date provided. + /// All components starting between 12:00:00AM and 11:59:59 PM will be + /// returned. + /// + /// This will first Evaluate() the date range required in order to + /// determine the occurrences for the date provided, and then return + /// the occurrences. + /// + /// + /// The date for which to return occurrences. Time is ignored on this parameter. + /// A list of Periods representing the occurrences of this object. + public virtual HashSet GetOccurrences(IDateTime dt) where T : IRecurringComponent + => GetOccurrences(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddTicks(-1))); + + /// + public virtual HashSet GetOccurrences(DateTime dt) where T : IRecurringComponent + => GetOccurrences(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddTicks(-1))); + + /// + public virtual HashSet GetOccurrences(DateTime startTime, DateTime endTime) where T : IRecurringComponent + => GetOccurrences(new CalDateTime(startTime), new CalDateTime(endTime)); + + /// + /// Returns all occurrences of components of type T that start within the date range provided. + /// All components occurring between and + /// will be returned. + /// + /// The starting date range + /// The ending date range + public virtual HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) where T : IRecurringComponent + { + var occurrences = new HashSet(RecurringItems + .OfType() + .SelectMany(recurrable => recurrable.GetOccurrences(startTime, endTime))); + + var removeOccurrencesQuery = occurrences + .Where(o => o.Source is UniqueComponent) + .GroupBy(o => ((UniqueComponent) o.Source).Uid) + .SelectMany(group => group + .Where(o => o.Source.RecurrenceId != null) + .SelectMany(occurrence => group. + Where(o => o.Source.RecurrenceId == null && occurrence.Source.RecurrenceId.Date.Equals(o.Period.StartTime.Date)))); + + occurrences.ExceptWith(removeOccurrencesQuery); + return occurrences; + } - /// - /// Clears recurrence evaluations for recurring components. - /// - public void ClearEvaluation() + /// + /// Creates a typed object that is a direct child of the iCalendar itself. Generally, + /// you would invoke this method to create an Event, Todo, Journal, VTimeZone, FreeBusy, + /// or other base component type. + /// + /// + /// To create an event, use the following: + /// + /// IICalendar iCal = new iCalendar(); + /// + /// Event evt = iCal.Create<Event>(); + /// + /// + /// This creates the event, and adds it to the Events list of the iCalendar. + /// + /// The type of object to create + /// An object of the type specified + public T Create() where T : ICalendarComponent + { + var obj = Activator.CreateInstance(typeof(T)) as ICalendarObject; + if (obj is T) { - foreach (var recurrable in RecurringItems) - { - recurrable.ClearEvaluation(); - } + this.AddChild(obj); + return (T) obj; } + return default(T); + } - /// - /// Returns a list of occurrences of each recurring component - /// for the date provided (). - /// - /// The date for which to return occurrences. Time is ignored on this parameter. - /// A list of occurrences that occur on the given date (). - public virtual HashSet GetOccurrences(IDateTime dt) - => GetOccurrences(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1))); - - /// - public virtual HashSet GetOccurrences(DateTime dt) - => GetOccurrences(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1))); - - /// - /// Returns a list of occurrences of each recurring component - /// that occur between and . - /// - /// The beginning date/time of the range. - /// The end date/time of the range. - /// A list of occurrences that fall between the date/time arguments provided. - public virtual HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) - => GetOccurrences(startTime, endTime); - - /// - public virtual HashSet GetOccurrences(DateTime startTime, DateTime endTime) - => GetOccurrences(new CalDateTime(startTime), new CalDateTime(endTime)); - - /// - /// Returns all occurrences of components of type T that start on the date provided. - /// All components starting between 12:00:00AM and 11:59:59 PM will be - /// returned. - /// - /// This will first Evaluate() the date range required in order to - /// determine the occurrences for the date provided, and then return - /// the occurrences. - /// - /// - /// The date for which to return occurrences. Time is ignored on this parameter. - /// A list of Periods representing the occurrences of this object. - public virtual HashSet GetOccurrences(IDateTime dt) where T : IRecurringComponent - => GetOccurrences(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddTicks(-1))); - - /// - public virtual HashSet GetOccurrences(DateTime dt) where T : IRecurringComponent - => GetOccurrences(new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddTicks(-1))); - - /// - public virtual HashSet GetOccurrences(DateTime startTime, DateTime endTime) where T : IRecurringComponent - => GetOccurrences(new CalDateTime(startTime), new CalDateTime(endTime)); - - /// - /// Returns all occurrences of components of type T that start within the date range provided. - /// All components occurring between and - /// will be returned. - /// - /// The starting date range - /// The ending date range - public virtual HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) where T : IRecurringComponent + public void Dispose() + { + Children.Clear(); + } + + public virtual void MergeWith(IMergeable obj) + { + var c = obj as Calendar; + if (c == null) { - var occurrences = new HashSet(RecurringItems - .OfType() - .SelectMany(recurrable => recurrable.GetOccurrences(startTime, endTime))); - - var removeOccurrencesQuery = occurrences - .Where(o => o.Source is UniqueComponent) - .GroupBy(o => ((UniqueComponent)o.Source).Uid) - .SelectMany(group => group - .Where(o => o.Source.RecurrenceId != null) - .SelectMany(occurrence => group. - Where(o => o.Source.RecurrenceId == null && occurrence.Source.RecurrenceId.Date.Equals(o.Period.StartTime.Date)))); - - occurrences.ExceptWith(removeOccurrencesQuery); - return occurrences; + return; } - /// - /// Creates a typed object that is a direct child of the iCalendar itself. Generally, - /// you would invoke this method to create an Event, Todo, Journal, VTimeZone, FreeBusy, - /// or other base component type. - /// - /// - /// To create an event, use the following: - /// - /// IICalendar iCal = new iCalendar(); - /// - /// Event evt = iCal.Create<Event>(); - /// - /// - /// This creates the event, and adds it to the Events list of the iCalendar. - /// - /// The type of object to create - /// An object of the type specified - public T Create() where T : ICalendarComponent + if (Name == null) { - var obj = Activator.CreateInstance(typeof(T)) as ICalendarObject; - if (obj is T) - { - this.AddChild(obj); - return (T)obj; - } - return default(T); + Name = c.Name; } - public void Dispose() + Method = c.Method; + Version = c.Version; + ProductId = c.ProductId; + Scale = c.Scale; + + foreach (var p in c.Properties.Where(p => !Properties.ContainsKey(p.Name))) { - Children.Clear(); + Properties.Add(p); } - public virtual void MergeWith(IMergeable obj) + foreach (var child in c.Children) { - var c = obj as Calendar; - if (c == null) - { - return; - } - - if (Name == null) - { - Name = c.Name; - } - - Method = c.Method; - Version = c.Version; - ProductId = c.ProductId; - Scale = c.Scale; - - foreach (var p in c.Properties.Where(p => !Properties.ContainsKey(p.Name))) + if (child is IUniqueComponent) { - Properties.Add(p); - } - - foreach (var child in c.Children) - { - if (child is IUniqueComponent) - { - if (!UniqueComponents.ContainsKey(((IUniqueComponent)child).Uid)) - { - this.AddChild(child); - } - } - else + if (!UniqueComponents.ContainsKey(((IUniqueComponent) child).Uid)) { this.AddChild(child); } } + else + { + this.AddChild(child); + } } + } - public virtual FreeBusy GetFreeBusy(FreeBusy freeBusyRequest) => CalendarComponents.FreeBusy.Create(this, freeBusyRequest); + public virtual FreeBusy GetFreeBusy(FreeBusy freeBusyRequest) => CalendarComponents.FreeBusy.Create(this, freeBusyRequest); - public virtual FreeBusy GetFreeBusy(IDateTime fromInclusive, IDateTime toExclusive) - => CalendarComponents.FreeBusy.Create(this, CalendarComponents.FreeBusy.CreateRequest(fromInclusive, toExclusive, null, null)); + public virtual FreeBusy GetFreeBusy(IDateTime fromInclusive, IDateTime toExclusive) + => CalendarComponents.FreeBusy.Create(this, CalendarComponents.FreeBusy.CreateRequest(fromInclusive, toExclusive, null, null)); - public virtual FreeBusy GetFreeBusy(Organizer organizer, IEnumerable contacts, IDateTime fromInclusive, IDateTime toExclusive) - => CalendarComponents.FreeBusy.Create(this, CalendarComponents.FreeBusy.CreateRequest(fromInclusive, toExclusive, organizer, contacts)); + public virtual FreeBusy GetFreeBusy(Organizer organizer, IEnumerable contacts, IDateTime fromInclusive, IDateTime toExclusive) + => CalendarComponents.FreeBusy.Create(this, CalendarComponents.FreeBusy.CreateRequest(fromInclusive, toExclusive, organizer, contacts)); - /// - /// Adds a system time zone to the iCalendar. This time zone may - /// then be used in date/time objects contained in the - /// calendar. - /// - /// A System.TimeZoneInfo object to add to the calendar. - /// The time zone added to the calendar. - public VTimeZone AddTimeZone(TimeZoneInfo tzi) - { - var tz = VTimeZone.FromSystemTimeZone(tzi); - this.AddChild(tz); - return tz; - } + /// + /// Adds a system time zone to the iCalendar. This time zone may + /// then be used in date/time objects contained in the + /// calendar. + /// + /// A System.TimeZoneInfo object to add to the calendar. + /// The time zone added to the calendar. + public VTimeZone AddTimeZone(TimeZoneInfo tzi) + { + var tz = VTimeZone.FromSystemTimeZone(tzi); + this.AddChild(tz); + return tz; + } - public VTimeZone AddTimeZone(TimeZoneInfo tzi, DateTime earliestDateTimeToSupport, bool includeHistoricalData) - { - var tz = VTimeZone.FromSystemTimeZone(tzi, earliestDateTimeToSupport, includeHistoricalData); - this.AddChild(tz); - return tz; - } + public VTimeZone AddTimeZone(TimeZoneInfo tzi, DateTime earliestDateTimeToSupport, bool includeHistoricalData) + { + var tz = VTimeZone.FromSystemTimeZone(tzi, earliestDateTimeToSupport, includeHistoricalData); + this.AddChild(tz); + return tz; + } - public VTimeZone AddTimeZone(string tzId) - { - var tz = VTimeZone.FromDateTimeZone(tzId); - this.AddChild(tz); - return tz; - } + public VTimeZone AddTimeZone(string tzId) + { + var tz = VTimeZone.FromDateTimeZone(tzId); + this.AddChild(tz); + return tz; + } - public VTimeZone AddTimeZone(string tzId, DateTime earliestDateTimeToSupport, bool includeHistoricalData) - { - var tz = VTimeZone.FromDateTimeZone(tzId, earliestDateTimeToSupport, includeHistoricalData); - this.AddChild(tz); - return tz; - } + public VTimeZone AddTimeZone(string tzId, DateTime earliestDateTimeToSupport, bool includeHistoricalData) + { + var tz = VTimeZone.FromDateTimeZone(tzId, earliestDateTimeToSupport, includeHistoricalData); + this.AddChild(tz); + return tz; + } - public VTimeZone AddLocalTimeZone(DateTime earliestDateTimeToSupport, bool includeHistoricalData) - { - var tz = VTimeZone.FromLocalTimeZone(earliestDateTimeToSupport, includeHistoricalData); - this.AddChild(tz); - return tz; - } + public VTimeZone AddLocalTimeZone(DateTime earliestDateTimeToSupport, bool includeHistoricalData) + { + var tz = VTimeZone.FromLocalTimeZone(earliestDateTimeToSupport, includeHistoricalData); + this.AddChild(tz); + return tz; } } \ No newline at end of file diff --git a/Ical.Net/CalendarCollection.cs b/Ical.Net/CalendarCollection.cs index e096ec81b..77db05592 100644 --- a/Ical.Net/CalendarCollection.cs +++ b/Ical.Net/CalendarCollection.cs @@ -1,157 +1,161 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Serialization; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Serialization; +using Ical.Net.Utility; -namespace Ical.Net +namespace Ical.Net; + +/// +/// A list of iCalendars. +/// +public class CalendarCollection : List { + public static CalendarCollection Load(string iCalendarString) + => Load(new StringReader(iCalendarString)); + /// - /// A list of iCalendars. + /// Loads an from an open stream. /// - public class CalendarCollection : List + /// The stream from which to load the object + /// An object + public static CalendarCollection Load(Stream s) + => Load(new StreamReader(s, Encoding.UTF8)); + + public static CalendarCollection Load(TextReader tr) { - public static CalendarCollection Load(string iCalendarString) - => Load(new StringReader(iCalendarString)); - - /// - /// Loads an from an open stream. - /// - /// The stream from which to load the object - /// An object - public static CalendarCollection Load(Stream s) - => Load(new StreamReader(s, Encoding.UTF8)); - - public static CalendarCollection Load(TextReader tr) - { - var calendars = SimpleDeserializer.Default.Deserialize(tr).OfType(); - var collection = new CalendarCollection(); - collection.AddRange(calendars); - return collection; - } + var calendars = SimpleDeserializer.Default.Deserialize(tr).OfType(); + var collection = new CalendarCollection(); + collection.AddRange(calendars); + return collection; + } - public void ClearEvaluation() + public void ClearEvaluation() + { + foreach (var iCal in this) { - foreach (var iCal in this) - { - iCal.ClearEvaluation(); - } + iCal.ClearEvaluation(); } + } - public HashSet GetOccurrences(IDateTime dt) + public HashSet GetOccurrences(IDateTime dt) + { + var occurrences = new HashSet(); + foreach (var iCal in this) { - var occurrences = new HashSet(); - foreach (var iCal in this) - { - occurrences.UnionWith(iCal.GetOccurrences(dt)); - } - return occurrences; + occurrences.UnionWith(iCal.GetOccurrences(dt)); } + return occurrences; + } - public HashSet GetOccurrences(DateTime dt) + public HashSet GetOccurrences(DateTime dt) + { + var occurrences = new HashSet(); + foreach (var iCal in this) { - var occurrences = new HashSet(); - foreach (var iCal in this) - { - occurrences.UnionWith(iCal.GetOccurrences(dt)); - } - return occurrences; + occurrences.UnionWith(iCal.GetOccurrences(dt)); } + return occurrences; + } - public HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) + public HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) + { + var occurrences = new HashSet(); + foreach (var iCal in this) { - var occurrences = new HashSet(); - foreach (var iCal in this) - { - occurrences.UnionWith(iCal.GetOccurrences(startTime, endTime)); - } - return occurrences; + occurrences.UnionWith(iCal.GetOccurrences(startTime, endTime)); } + return occurrences; + } - public HashSet GetOccurrences(DateTime startTime, DateTime endTime) + public HashSet GetOccurrences(DateTime startTime, DateTime endTime) + { + var occurrences = new HashSet(); + foreach (var iCal in this) { - var occurrences = new HashSet(); - foreach (var iCal in this) - { - occurrences.UnionWith(iCal.GetOccurrences(startTime, endTime)); - } - return occurrences; + occurrences.UnionWith(iCal.GetOccurrences(startTime, endTime)); } + return occurrences; + } - public HashSet GetOccurrences(IDateTime dt) where T : IRecurringComponent + public HashSet GetOccurrences(IDateTime dt) where T : IRecurringComponent + { + var occurrences = new HashSet(); + foreach (var iCal in this) { - var occurrences = new HashSet(); - foreach (var iCal in this) - { - occurrences.UnionWith(iCal.GetOccurrences(dt)); - } - return occurrences; + occurrences.UnionWith(iCal.GetOccurrences(dt)); } + return occurrences; + } - public HashSet GetOccurrences(DateTime dt) where T : IRecurringComponent + public HashSet GetOccurrences(DateTime dt) where T : IRecurringComponent + { + var occurrences = new HashSet(); + foreach (var iCal in this) { - var occurrences = new HashSet(); - foreach (var iCal in this) - { - occurrences.UnionWith(iCal.GetOccurrences(dt)); - } - return occurrences; + occurrences.UnionWith(iCal.GetOccurrences(dt)); } + return occurrences; + } - public HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) where T : IRecurringComponent + public HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) where T : IRecurringComponent + { + var occurrences = new HashSet(); + foreach (var iCal in this) { - var occurrences = new HashSet(); - foreach (var iCal in this) - { - occurrences.UnionWith(iCal.GetOccurrences(startTime, endTime)); - } - return occurrences; + occurrences.UnionWith(iCal.GetOccurrences(startTime, endTime)); } + return occurrences; + } - public HashSet GetOccurrences(DateTime startTime, DateTime endTime) where T : IRecurringComponent + public HashSet GetOccurrences(DateTime startTime, DateTime endTime) where T : IRecurringComponent + { + var occurrences = new HashSet(); + foreach (var iCal in this) { - var occurrences = new HashSet(); - foreach (var iCal in this) - { - occurrences.UnionWith(iCal.GetOccurrences(startTime, endTime)); - } - return occurrences; + occurrences.UnionWith(iCal.GetOccurrences(startTime, endTime)); } + return occurrences; + } - private FreeBusy CombineFreeBusy(FreeBusy main, FreeBusy current) - { - main?.MergeWith(current); - return current; - } + private FreeBusy CombineFreeBusy(FreeBusy main, FreeBusy current) + { + main?.MergeWith(current); + return current; + } - public FreeBusy GetFreeBusy(FreeBusy freeBusyRequest) - { - return this.Aggregate(null, (current, iCal) => CombineFreeBusy(current, iCal.GetFreeBusy(freeBusyRequest))); - } + public FreeBusy GetFreeBusy(FreeBusy freeBusyRequest) + { + return this.Aggregate(null, (current, iCal) => CombineFreeBusy(current, iCal.GetFreeBusy(freeBusyRequest))); + } - public FreeBusy GetFreeBusy(IDateTime fromInclusive, IDateTime toExclusive) - { - return this.Aggregate(null, (current, iCal) => CombineFreeBusy(current, iCal.GetFreeBusy(fromInclusive, toExclusive))); - } + public FreeBusy GetFreeBusy(IDateTime fromInclusive, IDateTime toExclusive) + { + return this.Aggregate(null, (current, iCal) => CombineFreeBusy(current, iCal.GetFreeBusy(fromInclusive, toExclusive))); + } - public FreeBusy GetFreeBusy(Organizer organizer, IEnumerable contacts, IDateTime fromInclusive, IDateTime toExclusive) - { - return this.Aggregate(null, (current, iCal) => CombineFreeBusy(current, iCal.GetFreeBusy(organizer, contacts, fromInclusive, toExclusive))); - } + public FreeBusy GetFreeBusy(Organizer organizer, IEnumerable contacts, IDateTime fromInclusive, IDateTime toExclusive) + { + return this.Aggregate(null, (current, iCal) => CombineFreeBusy(current, iCal.GetFreeBusy(organizer, contacts, fromInclusive, toExclusive))); + } - public override int GetHashCode() => CollectionHelpers.GetHashCode(this); + public override int GetHashCode() => CollectionHelpers.GetHashCode(this); - protected bool Equals(CalendarCollection obj) => CollectionHelpers.Equals(this, obj); + protected bool Equals(CalendarCollection obj) => CollectionHelpers.Equals(this, obj); - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((CalendarEvent)obj); - } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((CalendarEvent) obj); } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/Alarm.cs b/Ical.Net/CalendarComponents/Alarm.cs index 4d8fda1fb..af83a0496 100644 --- a/Ical.Net/CalendarComponents/Alarm.cs +++ b/Ical.Net/CalendarComponents/Alarm.cs @@ -1,179 +1,183 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; +using Ical.Net.DataTypes; + +namespace Ical.Net.CalendarComponents; -namespace Ical.Net.CalendarComponents +/// +/// A class that represents an RFC 2445 VALARM component. +/// FIXME: move GetOccurrences() logic into an AlarmEvaluator. +/// +public class Alarm : CalendarComponent { - /// - /// A class that represents an RFC 2445 VALARM component. - /// FIXME: move GetOccurrences() logic into an AlarmEvaluator. - /// - public class Alarm : CalendarComponent + public virtual string Action { - public virtual string Action - { - get => Properties.Get(AlarmAction.Key); - set => Properties.Set(AlarmAction.Key, value); - } + get => Properties.Get(AlarmAction.Key); + set => Properties.Set(AlarmAction.Key, value); + } - public virtual Attachment Attachment - { - get => Properties.Get("ATTACH"); - set => Properties.Set("ATTACH", value); - } + public virtual Attachment Attachment + { + get => Properties.Get("ATTACH"); + set => Properties.Set("ATTACH", value); + } - public virtual IList Attendees - { - get => Properties.GetMany("ATTENDEE"); - set => Properties.Set("ATTENDEE", value); - } + public virtual IList Attendees + { + get => Properties.GetMany("ATTENDEE"); + set => Properties.Set("ATTENDEE", value); + } - public virtual string Description - { - get => Properties.Get("DESCRIPTION"); - set => Properties.Set("DESCRIPTION", value); - } + public virtual string Description + { + get => Properties.Get("DESCRIPTION"); + set => Properties.Set("DESCRIPTION", value); + } - public virtual TimeSpan Duration - { - get => Properties.Get("DURATION"); - set => Properties.Set("DURATION", value); - } + public virtual TimeSpan Duration + { + get => Properties.Get("DURATION"); + set => Properties.Set("DURATION", value); + } - public virtual int Repeat - { - get => Properties.Get("REPEAT"); - set => Properties.Set("REPEAT", value); - } + public virtual int Repeat + { + get => Properties.Get("REPEAT"); + set => Properties.Set("REPEAT", value); + } - public virtual string Summary - { - get => Properties.Get("SUMMARY"); - set => Properties.Set("SUMMARY", value); - } + public virtual string Summary + { + get => Properties.Get("SUMMARY"); + set => Properties.Set("SUMMARY", value); + } - public virtual Trigger Trigger - { - get => Properties.Get(TriggerRelation.Key); - set => Properties.Set(TriggerRelation.Key, value); - } + public virtual Trigger Trigger + { + get => Properties.Get(TriggerRelation.Key); + set => Properties.Set(TriggerRelation.Key, value); + } + + protected virtual IList Occurrences { get; set; } - protected virtual IList Occurrences { get; set; } + public Alarm() + { + Name = Components.Alarm; + Occurrences = new List(); + } + + /// + /// Gets a list of alarm occurrences for the given recurring component, + /// that occur between and . + /// + public virtual IList GetOccurrences(IRecurringComponent rc, IDateTime fromDate, IDateTime toDate) + { + Occurrences.Clear(); - public Alarm() + if (Trigger == null) { - Name = Components.Alarm; - Occurrences = new List(); + return Occurrences; } - /// - /// Gets a list of alarm occurrences for the given recurring component, - /// that occur between and . - /// - public virtual IList GetOccurrences(IRecurringComponent rc, IDateTime fromDate, IDateTime toDate) + // If the trigger is relative, it can recur right along with + // the recurring items, otherwise, it happens once and + // only once (at a precise time). + if (Trigger.IsRelative) { - Occurrences.Clear(); - - if (Trigger == null) + // Ensure that "FromDate" has already been set + if (fromDate == null) { - return Occurrences; + fromDate = rc.Start.Copy(); } - // If the trigger is relative, it can recur right along with - // the recurring items, otherwise, it happens once and - // only once (at a precise time). - if (Trigger.IsRelative) + var d = default(TimeSpan); + foreach (var o in rc.GetOccurrences(fromDate, toDate)) { - // Ensure that "FromDate" has already been set - if (fromDate == null) - { - fromDate = rc.Start.Copy(); - } - - var d = default(TimeSpan); - foreach (var o in rc.GetOccurrences(fromDate, toDate)) + var dt = o.Period.StartTime; + if (string.Equals(Trigger.Related, TriggerRelation.End, TriggerRelation.Comparison)) { - var dt = o.Period.StartTime; - if (string.Equals(Trigger.Related, TriggerRelation.End, TriggerRelation.Comparison)) + if (o.Period.EndTime != null) { - if (o.Period.EndTime != null) + dt = o.Period.EndTime; + if (d == default(TimeSpan)) { - dt = o.Period.EndTime; - if (d == default(TimeSpan)) - { - d = o.Period.Duration; - } - } - // Use the "last-found" duration as a reference point - else if (d != default(TimeSpan)) - { - dt = o.Period.StartTime.Add(d); - } - else - { - throw new ArgumentException( - "Alarm trigger is relative to the START of the occurrence; however, the occurence has no discernible end."); + d = o.Period.Duration; } } - - Occurrences.Add(new AlarmOccurrence(this, dt.Add(Trigger.Duration.Value), rc)); + // Use the "last-found" duration as a reference point + else if (d != default(TimeSpan)) + { + dt = o.Period.StartTime.Add(d); + } + else + { + throw new ArgumentException( + "Alarm trigger is relative to the START of the occurrence; however, the occurence has no discernible end."); + } } - } - else - { - var dt = Trigger.DateTime.Copy(); - dt.AssociatedObject = this; - Occurrences.Add(new AlarmOccurrence(this, dt, rc)); - } - - // If a REPEAT and DURATION value were specified, - // then handle those repetitions here. - AddRepeatedItems(); - return Occurrences; + Occurrences.Add(new AlarmOccurrence(this, dt.Add(Trigger.Duration.Value), rc)); + } } - - /// - /// Polls the component for alarms that have been triggered - /// since the provided date/time. If - /// is null, all triggered alarms will be returned. - /// - /// The earliest date/time to poll trigered alarms for. - /// A list of objects, each containing a triggered alarm. - public virtual IList Poll(IDateTime start, IDateTime end) + else { - var results = new List(); + var dt = Trigger.DateTime.Copy(); + dt.AssociatedObject = this; + Occurrences.Add(new AlarmOccurrence(this, dt, rc)); + } - // Evaluate the alarms to determine the recurrences - var rc = Parent as RecurringComponent; - if (rc == null) - { - return results; - } + // If a REPEAT and DURATION value were specified, + // then handle those repetitions here. + AddRepeatedItems(); + + return Occurrences; + } + + /// + /// Polls the component for alarms that have been triggered + /// since the provided date/time. If + /// is null, all triggered alarms will be returned. + /// + /// The earliest date/time to poll trigered alarms for. + /// A list of objects, each containing a triggered alarm. + public virtual IList Poll(IDateTime start, IDateTime end) + { + var results = new List(); - results.AddRange(GetOccurrences(rc, start, end)); + // Evaluate the alarms to determine the recurrences + var rc = Parent as RecurringComponent; + if (rc == null) + { return results; } - /// - /// Handles the repetitions that occur from the REPEAT and - /// DURATION properties. Each recurrence of the alarm will - /// have its own set of generated repetitions. - /// - protected virtual void AddRepeatedItems() + results.AddRange(GetOccurrences(rc, start, end)); + return results; + } + + /// + /// Handles the repetitions that occur from the REPEAT and + /// DURATION properties. Each recurrence of the alarm will + /// have its own set of generated repetitions. + /// + protected virtual void AddRepeatedItems() + { + var len = Occurrences.Count; + for (var i = 0; i < len; i++) { - var len = Occurrences.Count; - for (var i = 0; i < len; i++) - { - var ao = Occurrences[i]; - var alarmTime = ao.DateTime.Copy(); + var ao = Occurrences[i]; + var alarmTime = ao.DateTime.Copy(); - for (var j = 0; j < Repeat; j++) - { - alarmTime = alarmTime.Add(Duration); - Occurrences.Add(new AlarmOccurrence(this, alarmTime.Copy(), ao.Component)); - } + for (var j = 0; j < Repeat; j++) + { + alarmTime = alarmTime.Add(Duration); + Occurrences.Add(new AlarmOccurrence(this, alarmTime.Copy(), ao.Component)); } } } -} +} \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/CalendarComponent.cs b/Ical.Net/CalendarComponents/CalendarComponent.cs index a34e1e257..408bb4d03 100644 --- a/Ical.Net/CalendarComponents/CalendarComponent.cs +++ b/Ical.Net/CalendarComponents/CalendarComponent.cs @@ -1,77 +1,81 @@ -using System.Diagnostics; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System.Diagnostics; using System.Runtime.Serialization; -namespace Ical.Net.CalendarComponents +namespace Ical.Net.CalendarComponents; + +/// +/// This class is used by the parsing framework for iCalendar components. +/// Generally, you should not need to use this class directly. +/// +[DebuggerDisplay("Component: {Name}")] +public class CalendarComponent : CalendarObject, ICalendarComponent { /// - /// This class is used by the parsing framework for iCalendar components. - /// Generally, you should not need to use this class directly. + /// Returns a list of properties that are associated with the iCalendar object. /// - [DebuggerDisplay("Component: {Name}")] - public class CalendarComponent : CalendarObject, ICalendarComponent + public virtual CalendarPropertyList Properties { get; protected set; } + + public CalendarComponent() : base() { - /// - /// Returns a list of properties that are associated with the iCalendar object. - /// - public virtual CalendarPropertyList Properties { get; protected set; } + Initialize(); + } - public CalendarComponent() : base() - { - Initialize(); - } + public CalendarComponent(string name) : base(name) + { + Initialize(); + } - public CalendarComponent(string name) : base(name) - { - Initialize(); - } + private void Initialize() + { + Properties = new CalendarPropertyList(this); + } - private void Initialize() - { - Properties = new CalendarPropertyList(this); - } + protected override void OnDeserializing(StreamingContext context) + { + base.OnDeserializing(context); - protected override void OnDeserializing(StreamingContext context) - { - base.OnDeserializing(context); + Initialize(); + } - Initialize(); - } + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); - /// - public override void CopyFrom(ICopyable obj) + var c = obj as ICalendarComponent; + if (c == null) { - base.CopyFrom(obj); - - var c = obj as ICalendarComponent; - if (c == null) - { - return; - } - - Properties.Clear(); - foreach (var p in c.Properties) - { - // Uses CalendarObjectBase.Copy() for a deep copy - Properties.Add(p.Copy()); - } + return; } - /// - /// Adds a property to this component. - /// - public virtual void AddProperty(string name, string value) + Properties.Clear(); + foreach (var p in c.Properties) { - var p = new CalendarProperty(name, value); - AddProperty(p); + // Uses CalendarObjectBase.Copy() for a deep copy + Properties.Add(p.Copy()); } + } - /// - /// Adds a property to this component. - /// - public virtual void AddProperty(ICalendarProperty p) - { - p.Parent = this; - Properties.Set(p.Name, p.Value); - } + /// + /// Adds a property to this component. + /// + public virtual void AddProperty(string name, string value) + { + var p = new CalendarProperty(name, value); + AddProperty(p); + } + + /// + /// Adds a property to this component. + /// + public virtual void AddProperty(ICalendarProperty p) + { + p.Parent = this; + Properties.Set(p.Name, p.Value); } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/CalendarEvent.cs b/Ical.Net/CalendarComponents/CalendarEvent.cs index 272c9f9a0..2f13549f5 100644 --- a/Ical.Net/CalendarComponents/CalendarEvent.cs +++ b/Ical.Net/CalendarComponents/CalendarEvent.cs @@ -1,390 +1,394 @@ -using Ical.Net.DataTypes; -using Ical.Net.Evaluation; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using Ical.Net.DataTypes; +using Ical.Net.Evaluation; +using Ical.Net.Utility; -namespace Ical.Net.CalendarComponents +namespace Ical.Net.CalendarComponents; + +/// +/// A class that represents an RFC 5545 VEVENT component. +/// +/// +/// TODO: Add support for the following properties: +/// +/// Add support for the Organizer and Attendee properties +/// Add support for the Class property +/// Add support for the Geo property +/// Add support for the Priority property +/// Add support for the Related property +/// Create a TextCollection DataType for 'text' items separated by commas +/// +/// +public class CalendarEvent : RecurringComponent, IAlarmContainer, IComparable { + internal const string ComponentName = "VEVENT"; + /// - /// A class that represents an RFC 5545 VEVENT component. - /// + /// The start date/time of the event. /// - /// TODO: Add support for the following properties: - /// - /// Add support for the Organizer and Attendee properties - /// Add support for the Class property - /// Add support for the Geo property - /// Add support for the Priority property - /// Add support for the Related property - /// Create a TextCollection DataType for 'text' items separated by commas - /// + /// If the duration has not been set, but + /// the start/end time of the event is available, + /// the duration is automatically determined. + /// Likewise, if the end date/time has not been + /// set, but a start and duration are available, + /// the end date/time will be extrapolated. /// - public class CalendarEvent : RecurringComponent, IAlarmContainer, IComparable + /// + public override IDateTime DtStart { - internal const string ComponentName = "VEVENT"; - - /// - /// The start date/time of the event. - /// - /// If the duration has not been set, but - /// the start/end time of the event is available, - /// the duration is automatically determined. - /// Likewise, if the end date/time has not been - /// set, but a start and duration are available, - /// the end date/time will be extrapolated. - /// - /// - public override IDateTime DtStart + get => base.DtStart; + set { - get => base.DtStart; - set - { - base.DtStart = value; - ExtrapolateTimes(2); - } + base.DtStart = value; + ExtrapolateTimes(2); } + } - /// - /// The end date/time of the event. - /// - /// If the duration has not been set, but - /// the start/end time of the event is available, - /// the duration is automatically determined. - /// Likewise, if an end time and duration are available, - /// but a start time has not been set, the start time - /// will be extrapolated. - /// - /// - public virtual IDateTime DtEnd + /// + /// The end date/time of the event. + /// + /// If the duration has not been set, but + /// the start/end time of the event is available, + /// the duration is automatically determined. + /// Likewise, if an end time and duration are available, + /// but a start time has not been set, the start time + /// will be extrapolated. + /// + /// + public virtual IDateTime DtEnd + { + get => Properties.Get("DTEND"); + set { - get => Properties.Get("DTEND"); - set + if (!Equals(DtEnd, value)) { - if (!Equals(DtEnd, value)) - { - Properties.Set("DTEND", value); - ExtrapolateTimes(0); - } + Properties.Set("DTEND", value); + ExtrapolateTimes(0); } } + } - /// - /// The duration of the event. - /// - /// If a start time and duration is available, - /// the end time is automatically determined. - /// Likewise, if the end time and duration is - /// available, but a start time is not determined, - /// the start time will be extrapolated from - /// available information. - /// - /// - // NOTE: Duration is not supported by all systems, - // (i.e. iPhone) and cannot co-exist with DtEnd. - // RFC 5545 states: - // - // ; either 'dtend' or 'duration' may appear in - // ; a 'eventprop', but 'dtend' and 'duration' - // ; MUST NOT occur in the same 'eventprop' - // - // Therefore, Duration is not serialized, as DtEnd - // should always be extrapolated from the duration. - public virtual TimeSpan Duration + /// + /// The duration of the event. + /// + /// If a start time and duration is available, + /// the end time is automatically determined. + /// Likewise, if the end time and duration is + /// available, but a start time is not determined, + /// the start time will be extrapolated from + /// available information. + /// + /// + // NOTE: Duration is not supported by all systems, + // (i.e. iPhone) and cannot co-exist with DtEnd. + // RFC 5545 states: + // + // ; either 'dtend' or 'duration' may appear in + // ; a 'eventprop', but 'dtend' and 'duration' + // ; MUST NOT occur in the same 'eventprop' + // + // Therefore, Duration is not serialized, as DtEnd + // should always be extrapolated from the duration. + public virtual TimeSpan Duration + { + get => Properties.Get("DURATION"); + set { - get => Properties.Get("DURATION"); - set + if (!Equals(Duration, value)) { - if (!Equals(Duration, value)) - { - Properties.Set("DURATION", value); - ExtrapolateTimes(1); - } + Properties.Set("DURATION", value); + ExtrapolateTimes(1); } } + } - /// - /// An alias to the DtEnd field (i.e. end date/time). - /// - public virtual IDateTime End - { - get => DtEnd; - set => DtEnd = value; - } + /// + /// An alias to the DtEnd field (i.e. end date/time). + /// + public virtual IDateTime End + { + get => DtEnd; + set => DtEnd = value; + } - /// - /// Returns true if the event is an all-day event. - /// - public virtual bool IsAllDay + /// + /// Returns true if the event is an all-day event. + /// + public virtual bool IsAllDay + { + get => !Start.HasTime; + set { - get => !Start.HasTime; - set + // Set whether or not the start date/time + // has a time value. + if (Start != null) { - // Set whether or not the start date/time - // has a time value. - if (Start != null) - { - Start.HasTime = !value; - } - if (End != null) - { - End.HasTime = !value; - } - - if (value && Start != null && End != null && Equals(Start.Date, End.Date)) - { - Duration = default(TimeSpan); - End = Start.AddDays(1); - } + Start.HasTime = !value; + } + if (End != null) + { + End.HasTime = !value; } - } - /// - /// The geographic location (lat/long) of the event. - /// - public GeographicLocation GeographicLocation - { - get => Properties.Get("GEO"); - set => Properties.Set("GEO", value); + if (value && Start != null && End != null && Equals(Start.Date, End.Date)) + { + Duration = default(TimeSpan); + End = Start.AddDays(1); + } } + } - /// - /// The location of the event. - /// - public string Location - { - get => Properties.Get("LOCATION"); - set => Properties.Set("LOCATION", value); - } + /// + /// The geographic location (lat/long) of the event. + /// + public GeographicLocation GeographicLocation + { + get => Properties.Get("GEO"); + set => Properties.Set("GEO", value); + } - /// - /// Resources that will be used during the event. - /// To change existing values, assign a new . - /// Examples: - /// Conference room, Projector - /// - /// - public virtual IList Resources - { - get => Properties.GetMany("RESOURCES"); - set => Properties.Set("RESOURCES", value ?? new List()); - } + /// + /// The location of the event. + /// + public string Location + { + get => Properties.Get("LOCATION"); + set => Properties.Set("LOCATION", value); + } - /// - /// The status of the event. - /// - public string Status - { - get => Properties.Get("STATUS"); - set => Properties.Set("STATUS", value); - } + /// + /// Resources that will be used during the event. + /// To change existing values, assign a new . + /// Examples: + /// Conference room, Projector + /// + /// + public virtual IList Resources + { + get => Properties.GetMany("RESOURCES"); + set => Properties.Set("RESOURCES", value ?? new List()); + } - /// - /// The transparency of the event. In other words, - /// whether or not the period of time this event - /// occupies can contain other events (transparent), - /// or if the time cannot be scheduled for anything - /// else (opaque). - /// - public string Transparency - { - get => Properties.Get(TransparencyType.Key); - set => Properties.Set(TransparencyType.Key, value); - } + /// + /// The status of the event. + /// + public string Status + { + get => Properties.Get("STATUS"); + set => Properties.Set("STATUS", value); + } + + /// + /// The transparency of the event. In other words, + /// whether or not the period of time this event + /// occupies can contain other events (transparent), + /// or if the time cannot be scheduled for anything + /// else (opaque). + /// + public string Transparency + { + get => Properties.Get(TransparencyType.Key); + set => Properties.Set(TransparencyType.Key, value); + } + + private EventEvaluator _mEvaluator; + + /// + /// Constructs an Event object, with an iCalObject + /// (usually an iCalendar object) as its parent. + /// + public CalendarEvent() + { + Initialize(); + } + + private void Initialize() + { + Name = EventStatus.Name; + + _mEvaluator = new EventEvaluator(this); + SetService(_mEvaluator); + } + + /// + /// Use this method to determine if an event occurs on a given date. + /// + /// This event should be called only after the Evaluate + /// method has calculated the dates for which this event occurs. + /// + /// + /// The date to test. + /// True if the event occurs on the provided, False otherwise. + public virtual bool OccursOn(IDateTime dateTime) + { + return _mEvaluator.Periods.Any(p => p.StartTime.Date == dateTime.Date || // It's the start date OR + (p.StartTime.Date <= dateTime.Date && // It's after the start date AND + (p.EndTime.HasTime && p.EndTime.Date >= dateTime.Date || // an end time was specified, and it's after the test date + (!p.EndTime.HasTime && p.EndTime.Date > dateTime.Date)))); + } + + /// + /// Use this method to determine if an event begins at a given date and time. + /// + /// The date and time to test. + /// True if the event begins at the given date and time + public virtual bool OccursAt(IDateTime dateTime) + { + return _mEvaluator.Periods.Any(p => p.StartTime.Equals(dateTime)); + } + + /// + /// Determines whether or not the is actively displayed + /// as an upcoming or occurred event. + /// + /// True if the event has not been cancelled, False otherwise. + public virtual bool IsActive => !string.Equals(Status, EventStatus.Cancelled, EventStatus.Comparison); - private EventEvaluator _mEvaluator; + protected override bool EvaluationIncludesReferenceDate => true; - /// - /// Constructs an Event object, with an iCalObject - /// (usually an iCalendar object) as its parent. - /// - public CalendarEvent() + protected override void OnDeserializing(StreamingContext context) + { + base.OnDeserializing(context); + + Initialize(); + } + + protected override void OnDeserialized(StreamingContext context) + { + base.OnDeserialized(context); + + ExtrapolateTimes(-1); + } + + private void ExtrapolateTimes(int source) + { + /* + * Source values, a fix introduced to prevent stack overflow exceptions from occuring. + * -1 = Anybody, stack overflow could maybe still occur in this case? + * 0 = End + * 1 = Duration + * 2 = DtStart + */ + if (DtEnd == null && DtStart != null && Duration != default(TimeSpan) && source != 0) { - Initialize(); + DtEnd = DtStart.Add(Duration); } - - private void Initialize() + else if (Duration == default(TimeSpan) && DtStart != null && DtEnd != null && source != 1) { - Name = EventStatus.Name; - - _mEvaluator = new EventEvaluator(this); - SetService(_mEvaluator); + Duration = DtEnd.Subtract(DtStart); } - - /// - /// Use this method to determine if an event occurs on a given date. - /// - /// This event should be called only after the Evaluate - /// method has calculated the dates for which this event occurs. - /// - /// - /// The date to test. - /// True if the event occurs on the provided, False otherwise. - public virtual bool OccursOn(IDateTime dateTime) + else if (DtStart == null && Duration != default(TimeSpan) && DtEnd != null && source != 2) { - return _mEvaluator.Periods.Any(p => p.StartTime.Date == dateTime.Date || // It's the start date OR - (p.StartTime.Date <= dateTime.Date && // It's after the start date AND - (p.EndTime.HasTime && p.EndTime.Date >= dateTime.Date || // an end time was specified, and it's after the test date - (!p.EndTime.HasTime && p.EndTime.Date > dateTime.Date)))); + DtStart = DtEnd.Subtract(Duration); } + } - /// - /// Use this method to determine if an event begins at a given date and time. - /// - /// The date and time to test. - /// True if the event begins at the given date and time - public virtual bool OccursAt(IDateTime dateTime) + protected bool Equals(CalendarEvent other) + { + var resourcesSet = new HashSet(StringComparer.OrdinalIgnoreCase); + resourcesSet.UnionWith(Resources); + + var result = + Equals(DtStart, other.DtStart) + && string.Equals(Summary, other.Summary, StringComparison.OrdinalIgnoreCase) + && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) + && Equals(DtEnd, other.DtEnd) + && string.Equals(Location, other.Location, StringComparison.OrdinalIgnoreCase) + && resourcesSet.SetEquals(other.Resources) + && string.Equals(Status, other.Status, StringComparison.Ordinal) + && IsActive == other.IsActive + && string.Equals(Transparency, other.Transparency, TransparencyType.Comparison) + && EvaluationIncludesReferenceDate == other.EvaluationIncludesReferenceDate + && Attachments.SequenceEqual(other.Attachments) + && CollectionHelpers.Equals(ExceptionRules, other.ExceptionRules) + && CollectionHelpers.Equals(RecurrenceRules, other.RecurrenceRules); + + if (!result) { - return _mEvaluator.Periods.Any(p => p.StartTime.Equals(dateTime)); + return false; } - /// - /// Determines whether or not the is actively displayed - /// as an upcoming or occurred event. - /// - /// True if the event has not been cancelled, False otherwise. - public virtual bool IsActive => !string.Equals(Status, EventStatus.Cancelled, EventStatus.Comparison); + //RDATEs and EXDATEs are all List, because the spec allows for multiple declarations of collections. + //Consequently we have to contrive a normalized representation before we can determine whether two events are equal - protected override bool EvaluationIncludesReferenceDate => true; - - protected override void OnDeserializing(StreamingContext context) + var exDates = PeriodList.GetGroupedPeriods(ExceptionDates); + var otherExDates = PeriodList.GetGroupedPeriods(other.ExceptionDates); + if (exDates.Keys.Count != otherExDates.Keys.Count || !exDates.Keys.OrderBy(k => k).SequenceEqual(otherExDates.Keys.OrderBy(k => k))) { - base.OnDeserializing(context); - - Initialize(); + return false; } - protected override void OnDeserialized(StreamingContext context) + if (exDates.Any(exDate => !exDate.Value.OrderBy(d => d).SequenceEqual(otherExDates[exDate.Key].OrderBy(d => d)))) { - base.OnDeserialized(context); - - ExtrapolateTimes(-1); + return false; } - private void ExtrapolateTimes(int source) + var rDates = PeriodList.GetGroupedPeriods(RecurrenceDates); + var otherRDates = PeriodList.GetGroupedPeriods(other.RecurrenceDates); + if (rDates.Keys.Count != otherRDates.Keys.Count || !rDates.Keys.OrderBy(k => k).SequenceEqual(otherRDates.Keys.OrderBy(k => k))) { - /* - * Source values, a fix introduced to prevent stack overflow exceptions from occuring. - * -1 = Anybody, stack overflow could maybe still occur in this case? - * 0 = End - * 1 = Duration - * 2 = DtStart - */ - if (DtEnd == null && DtStart != null && Duration != default(TimeSpan) && source != 0) - { - DtEnd = DtStart.Add(Duration); - } - else if (Duration == default(TimeSpan) && DtStart != null && DtEnd != null && source != 1) - { - Duration = DtEnd.Subtract(DtStart); - } - else if (DtStart == null && Duration != default(TimeSpan) && DtEnd != null && source != 2) - { - DtStart = DtEnd.Subtract(Duration); - } + return false; } - protected bool Equals(CalendarEvent other) + if (rDates.Any(exDate => !exDate.Value.OrderBy(d => d).SequenceEqual(otherRDates[exDate.Key].OrderBy(d => d)))) { - var resourcesSet = new HashSet(StringComparer.OrdinalIgnoreCase); - resourcesSet.UnionWith(Resources); - - var result = - Equals(DtStart, other.DtStart) - && string.Equals(Summary, other.Summary, StringComparison.OrdinalIgnoreCase) - && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) - && Equals(DtEnd, other.DtEnd) - && string.Equals(Location, other.Location, StringComparison.OrdinalIgnoreCase) - && resourcesSet.SetEquals(other.Resources) - && string.Equals(Status, other.Status, StringComparison.Ordinal) - && IsActive == other.IsActive - && string.Equals(Transparency, other.Transparency, TransparencyType.Comparison) - && EvaluationIncludesReferenceDate == other.EvaluationIncludesReferenceDate - && Attachments.SequenceEqual(other.Attachments) - && CollectionHelpers.Equals(ExceptionRules, other.ExceptionRules) - && CollectionHelpers.Equals(RecurrenceRules, other.RecurrenceRules); - - if (!result) - { - return false; - } - - //RDATEs and EXDATEs are all List, because the spec allows for multiple declarations of collections. - //Consequently we have to contrive a normalized representation before we can determine whether two events are equal - - var exDates = PeriodList.GetGroupedPeriods(ExceptionDates); - var otherExDates = PeriodList.GetGroupedPeriods(other.ExceptionDates); - if (exDates.Keys.Count != otherExDates.Keys.Count || !exDates.Keys.OrderBy(k => k).SequenceEqual(otherExDates.Keys.OrderBy(k => k))) - { - return false; - } - - if (exDates.Any(exDate => !exDate.Value.OrderBy(d => d).SequenceEqual(otherExDates[exDate.Key].OrderBy(d => d)))) - { - return false; - } + return false; + } - var rDates = PeriodList.GetGroupedPeriods(RecurrenceDates); - var otherRDates = PeriodList.GetGroupedPeriods(other.RecurrenceDates); - if (rDates.Keys.Count != otherRDates.Keys.Count || !rDates.Keys.OrderBy(k => k).SequenceEqual(otherRDates.Keys.OrderBy(k => k))) - { - return false; - } + return true; + } - if (rDates.Any(exDate => !exDate.Value.OrderBy(d => d).SequenceEqual(otherRDates[exDate.Key].OrderBy(d => d)))) - { - return false; - } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((CalendarEvent) obj); + } - return true; + public override int GetHashCode() + { + unchecked + { + var hashCode = DtStart?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (Summary?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Description?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (DtEnd?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Location?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ Status?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ IsActive.GetHashCode(); + hashCode = (hashCode * 397) ^ Transparency?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Attachments); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Resources); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCodeForNestedCollection(ExceptionDates); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ExceptionRules); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCodeForNestedCollection(RecurrenceDates); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(RecurrenceRules); + return hashCode; } + } - public override bool Equals(object obj) + public int CompareTo(CalendarEvent other) + { + if (DtStart.Equals(other.DtStart)) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((CalendarEvent)obj); + return 0; } - - public override int GetHashCode() + if (DtStart.LessThan(other.DtStart)) { - unchecked - { - var hashCode = DtStart?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ (Summary?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (Description?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (DtEnd?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (Location?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ Status?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ IsActive.GetHashCode(); - hashCode = (hashCode * 397) ^ Transparency?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Attachments); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Resources); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCodeForNestedCollection(ExceptionDates); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ExceptionRules); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCodeForNestedCollection(RecurrenceDates); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(RecurrenceRules); - return hashCode; - } + return -1; } - - public int CompareTo(CalendarEvent other) + if (DtStart.GreaterThan(other.DtStart)) { - if (DtStart.Equals(other.DtStart)) - { - return 0; - } - if (DtStart.LessThan(other.DtStart)) - { - return -1; - } - if (DtStart.GreaterThan(other.DtStart)) - { - return 1; - } - throw new Exception("An error occurred while comparing two CalDateTimes."); + return 1; } + throw new Exception("An error occurred while comparing two CalDateTimes."); } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/FreeBusy.cs b/Ical.Net/CalendarComponents/FreeBusy.cs index 5e01d5de2..0f1d822dd 100644 --- a/Ical.Net/CalendarComponents/FreeBusy.cs +++ b/Ical.Net/CalendarComponents/FreeBusy.cs @@ -1,195 +1,199 @@ -using Ical.Net.DataTypes; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using Ical.Net.DataTypes; +using Ical.Net.Utility; -namespace Ical.Net.CalendarComponents +namespace Ical.Net.CalendarComponents; + +public class FreeBusy : UniqueComponent, IMergeable { - public class FreeBusy : UniqueComponent, IMergeable + public static FreeBusy Create(ICalendarObject obj, FreeBusy freeBusyRequest) { - public static FreeBusy Create(ICalendarObject obj, FreeBusy freeBusyRequest) + if (!(obj is IGetOccurrencesTyped)) { - if (!(obj is IGetOccurrencesTyped)) - { - return null; - } - var getOccurrences = (IGetOccurrencesTyped)obj; - var occurrences = getOccurrences.GetOccurrences(freeBusyRequest.Start, freeBusyRequest.End); - var contacts = new List(); - var isFilteredByAttendees = false; + return null; + } + var getOccurrences = (IGetOccurrencesTyped) obj; + var occurrences = getOccurrences.GetOccurrences(freeBusyRequest.Start, freeBusyRequest.End); + var contacts = new List(); + var isFilteredByAttendees = false; + + if (freeBusyRequest.Attendees != null && freeBusyRequest.Attendees.Count > 0) + { + isFilteredByAttendees = true; + var attendees = freeBusyRequest.Attendees + .Where(a => a.Value != null) + .Select(a => a.Value.OriginalString.Trim()); + contacts.AddRange(attendees); + } + + var fb = freeBusyRequest; + fb.Uid = Guid.NewGuid().ToString(); + fb.Entries.Clear(); + fb.DtStamp = CalDateTime.Now; - if (freeBusyRequest.Attendees != null && freeBusyRequest.Attendees.Count > 0) + foreach (var o in occurrences) + { + var uc = o.Source as IUniqueComponent; + + if (uc == null) { - isFilteredByAttendees = true; - var attendees = freeBusyRequest.Attendees - .Where(a => a.Value != null) - .Select(a => a.Value.OriginalString.Trim()); - contacts.AddRange(attendees); + continue; } - var fb = freeBusyRequest; - fb.Uid = Guid.NewGuid().ToString(); - fb.Entries.Clear(); - fb.DtStamp = CalDateTime.Now; + var evt = uc as CalendarEvent; + var accepted = false; + var type = FreeBusyStatus.Busy; - foreach (var o in occurrences) + // We only accept events, and only "opaque" events. + if (evt != null && evt.Transparency != TransparencyType.Transparent) { - var uc = o.Source as IUniqueComponent; - - if (uc == null) - { - continue; - } + accepted = true; + } - var evt = uc as CalendarEvent; - var accepted = false; - var type = FreeBusyStatus.Busy; + // If the result is filtered by attendees, then + // we won't accept it until we find an event + // that is being attended by this person. + if (accepted && isFilteredByAttendees) + { + accepted = false; - // We only accept events, and only "opaque" events. - if (evt != null && evt.Transparency != TransparencyType.Transparent) - { - accepted = true; - } + var participatingAttendeeQuery = uc.Attendees + .Where(attendee => + attendee.Value != null + && contacts.Contains(attendee.Value.OriginalString.Trim()) + && attendee.ParticipationStatus != null) + .Select(pa => pa.ParticipationStatus.ToUpperInvariant()); - // If the result is filtered by attendees, then - // we won't accept it until we find an event - // that is being attended by this person. - if (accepted && isFilteredByAttendees) + foreach (var participatingAttendee in participatingAttendeeQuery) { - accepted = false; - - var participatingAttendeeQuery = uc.Attendees - .Where(attendee => - attendee.Value != null - && contacts.Contains(attendee.Value.OriginalString.Trim()) - && attendee.ParticipationStatus != null) - .Select(pa => pa.ParticipationStatus.ToUpperInvariant()); - - foreach (var participatingAttendee in participatingAttendeeQuery) + switch (participatingAttendee) { - switch (participatingAttendee) - { - case EventParticipationStatus.Tentative: - accepted = true; - type = FreeBusyStatus.BusyTentative; - break; - case EventParticipationStatus.Accepted: - accepted = true; - type = FreeBusyStatus.Busy; - break; - } + case EventParticipationStatus.Tentative: + accepted = true; + type = FreeBusyStatus.BusyTentative; + break; + case EventParticipationStatus.Accepted: + accepted = true; + type = FreeBusyStatus.Busy; + break; } } - - if (accepted) - { - // If the entry was accepted, add it to our list! - fb.Entries.Add(new FreeBusyEntry(o.Period, type)); - } - } - - return fb; - } - - public static FreeBusy CreateRequest(IDateTime fromInclusive, IDateTime toExclusive, Organizer organizer, IEnumerable contacts) - { - var fb = new FreeBusy - { - DtStamp = CalDateTime.Now, - DtStart = fromInclusive, - DtEnd = toExclusive - }; - if (organizer != null) - { - fb.Organizer = organizer; } - if (contacts == null) + if (accepted) { - return fb; + // If the entry was accepted, add it to our list! + fb.Entries.Add(new FreeBusyEntry(o.Period, type)); } - foreach (var attendee in contacts) - { - fb.Attendees.Add(attendee); - } - - return fb; } - public FreeBusy() - { - Name = Components.Freebusy; - } + return fb; + } - public virtual IList Entries + public static FreeBusy CreateRequest(IDateTime fromInclusive, IDateTime toExclusive, Organizer organizer, IEnumerable contacts) + { + var fb = new FreeBusy { - get => Properties.GetMany("FREEBUSY"); - set => Properties.Set("FREEBUSY", value); + DtStamp = CalDateTime.Now, + DtStart = fromInclusive, + DtEnd = toExclusive + }; + if (organizer != null) + { + fb.Organizer = organizer; } - public virtual IDateTime DtStart + if (contacts == null) { - get => Properties.Get("DTSTART"); - set => Properties.Set("DTSTART", value); + return fb; } - - public virtual IDateTime DtEnd + foreach (var attendee in contacts) { - get => Properties.Get("DTEND"); - set => Properties.Set("DTEND", value); + fb.Attendees.Add(attendee); } - public virtual IDateTime Start + return fb; + } + + public FreeBusy() + { + Name = Components.Freebusy; + } + + public virtual IList Entries + { + get => Properties.GetMany("FREEBUSY"); + set => Properties.Set("FREEBUSY", value); + } + + public virtual IDateTime DtStart + { + get => Properties.Get("DTSTART"); + set => Properties.Set("DTSTART", value); + } + + public virtual IDateTime DtEnd + { + get => Properties.Get("DTEND"); + set => Properties.Set("DTEND", value); + } + + public virtual IDateTime Start + { + get => Properties.Get("DTSTART"); + set => Properties.Set("DTSTART", value); + } + + public virtual IDateTime End + { + get => Properties.Get("DTEND"); + set => Properties.Set("DTEND", value); + } + + public virtual FreeBusyStatus GetFreeBusyStatus(Period period) + { + var status = FreeBusyStatus.Free; + if (period == null) { - get => Properties.Get("DTSTART"); - set => Properties.Set("DTSTART", value); + return status; } - public virtual IDateTime End + foreach (var fbe in Entries.Where(fbe => fbe.CollidesWith(period) && status < fbe.Status)) { - get => Properties.Get("DTEND"); - set => Properties.Set("DTEND", value); + status = fbe.Status; } + return status; + } - public virtual FreeBusyStatus GetFreeBusyStatus(Period period) + public virtual FreeBusyStatus GetFreeBusyStatus(IDateTime dt) + { + var status = FreeBusyStatus.Free; + if (dt == null) { - var status = FreeBusyStatus.Free; - if (period == null) - { - return status; - } - - foreach (var fbe in Entries.Where(fbe => fbe.CollidesWith(period) && status < fbe.Status)) - { - status = fbe.Status; - } return status; } - public virtual FreeBusyStatus GetFreeBusyStatus(IDateTime dt) + foreach (var fbe in Entries.Where(fbe => fbe.Contains(dt) && status < fbe.Status)) { - var status = FreeBusyStatus.Free; - if (dt == null) - { - return status; - } - - foreach (var fbe in Entries.Where(fbe => fbe.Contains(dt) && status < fbe.Status)) - { - status = fbe.Status; - } - return status; + status = fbe.Status; } + return status; + } - public virtual void MergeWith(IMergeable obj) + public virtual void MergeWith(IMergeable obj) + { + if (!(obj is FreeBusy fb)) { - if (!(obj is FreeBusy fb)) - { - return; - } - - Entries.AddRange(fb.Entries.Where(entry => !Entries.Contains(entry))); + return; } + + Entries.AddRange(fb.Entries.Where(entry => !Entries.Contains(entry))); } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/IAlarmContainer.cs b/Ical.Net/CalendarComponents/IAlarmContainer.cs index 08b970ae3..8aa5f74d3 100644 --- a/Ical.Net/CalendarComponents/IAlarmContainer.cs +++ b/Ical.Net/CalendarComponents/IAlarmContainer.cs @@ -1,24 +1,28 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Collections.Generic; +using Ical.Net.DataTypes; + +namespace Ical.Net.CalendarComponents; -namespace Ical.Net.CalendarComponents +public interface IAlarmContainer { - public interface IAlarmContainer - { - /// - /// A list of s for this recurring component. - /// - ICalendarObjectList Alarms { get; } + /// + /// A list of s for this recurring component. + /// + ICalendarObjectList Alarms { get; } - /// - /// Polls s for occurrences within the d - /// time frame of this . For each evaluated - /// occurrence if this component, each is polled for its - /// corresponding alarm occurrences. - /// - /// The earliest allowable alarm occurrence to poll, or null. - /// - /// A List of objects, one for each occurrence of the . - IList PollAlarms(IDateTime startTime, IDateTime endTime); - } + /// + /// Polls s for occurrences within the d + /// time frame of this . For each evaluated + /// occurrence if this component, each is polled for its + /// corresponding alarm occurrences. + /// + /// The earliest allowable alarm occurrence to poll, or null. + /// + /// A List of objects, one for each occurrence of the . + IList PollAlarms(IDateTime startTime, IDateTime endTime); } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/ICalendarComponent.cs b/Ical.Net/CalendarComponents/ICalendarComponent.cs index fa44f68c6..7df4820f2 100644 --- a/Ical.Net/CalendarComponents/ICalendarComponent.cs +++ b/Ical.Net/CalendarComponents/ICalendarComponent.cs @@ -1,4 +1,8 @@ -namespace Ical.Net.CalendarComponents -{ - public interface ICalendarComponent : ICalendarPropertyListContainer { } -} \ No newline at end of file +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +namespace Ical.Net.CalendarComponents; + +public interface ICalendarComponent : ICalendarPropertyListContainer { } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/IRecurrable.cs b/Ical.Net/CalendarComponents/IRecurrable.cs index 21d80a604..31d242152 100644 --- a/Ical.Net/CalendarComponents/IRecurrable.cs +++ b/Ical.Net/CalendarComponents/IRecurrable.cs @@ -1,19 +1,23 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Collections.Generic; +using Ical.Net.DataTypes; + +namespace Ical.Net.CalendarComponents; -namespace Ical.Net.CalendarComponents +public interface IRecurrable : IGetOccurrences, IServiceProvider { - public interface IRecurrable : IGetOccurrences, IServiceProvider - { - /// - /// Gets/sets the start date/time of the component. - /// - IDateTime Start { get; set; } + /// + /// Gets/sets the start date/time of the component. + /// + IDateTime Start { get; set; } - IList ExceptionDates { get; set; } - IList ExceptionRules { get; set; } - IList RecurrenceDates { get; set; } - IList RecurrenceRules { get; set; } - IDateTime RecurrenceId { get; set; } - } + IList ExceptionDates { get; set; } + IList ExceptionRules { get; set; } + IList RecurrenceDates { get; set; } + IList RecurrenceRules { get; set; } + IDateTime RecurrenceId { get; set; } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/IRecurringComponent.cs b/Ical.Net/CalendarComponents/IRecurringComponent.cs index b75c4e992..eac21bfa2 100644 --- a/Ical.Net/CalendarComponents/IRecurringComponent.cs +++ b/Ical.Net/CalendarComponents/IRecurringComponent.cs @@ -1,20 +1,24 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Collections.Generic; +using Ical.Net.DataTypes; + +namespace Ical.Net.CalendarComponents; -namespace Ical.Net.CalendarComponents +public interface IRecurringComponent : IUniqueComponent, IRecurrable { - public interface IRecurringComponent : IUniqueComponent, IRecurrable - { - IList Attachments { get; set; } - IList Categories { get; set; } - string Class { get; set; } - IList Contacts { get; set; } - IDateTime Created { get; set; } - string Description { get; set; } - IDateTime LastModified { get; set; } - int Priority { get; set; } - IList RelatedComponents { get; set; } - int Sequence { get; set; } - string Summary { get; set; } - } + IList Attachments { get; set; } + IList Categories { get; set; } + string Class { get; set; } + IList Contacts { get; set; } + IDateTime Created { get; set; } + string Description { get; set; } + IDateTime LastModified { get; set; } + int Priority { get; set; } + IList RelatedComponents { get; set; } + int Sequence { get; set; } + string Summary { get; set; } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/IUniqueComponent.cs b/Ical.Net/CalendarComponents/IUniqueComponent.cs index 8a659917e..3536a5204 100644 --- a/Ical.Net/CalendarComponents/IUniqueComponent.cs +++ b/Ical.Net/CalendarComponents/IUniqueComponent.cs @@ -1,18 +1,22 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; +using Ical.Net.DataTypes; + +namespace Ical.Net.CalendarComponents; -namespace Ical.Net.CalendarComponents +public interface IUniqueComponent : ICalendarComponent { - public interface IUniqueComponent : ICalendarComponent - { - string Uid { get; set; } + string Uid { get; set; } - IList Attendees { get; set; } - IList Comments { get; set; } - IDateTime DtStamp { get; set; } - Organizer Organizer { get; set; } - IList RequestStatuses { get; set; } - Uri Url { get; set; } - } + IList Attendees { get; set; } + IList Comments { get; set; } + IDateTime DtStamp { get; set; } + Organizer Organizer { get; set; } + IList RequestStatuses { get; set; } + Uri Url { get; set; } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/Journal.cs b/Ical.Net/CalendarComponents/Journal.cs index 300eee450..0cedc9d4a 100644 --- a/Ical.Net/CalendarComponents/Journal.cs +++ b/Ical.Net/CalendarComponents/Journal.cs @@ -1,48 +1,52 @@ +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Runtime.Serialization; -namespace Ical.Net.CalendarComponents +namespace Ical.Net.CalendarComponents; + +/// +/// A class that represents an RFC 5545 VJOURNAL component. +/// +public class Journal : RecurringComponent { + public string Status + { + get => Properties.Get(JournalStatus.Key); + set => Properties.Set(JournalStatus.Key, value); + } + /// - /// A class that represents an RFC 5545 VJOURNAL component. + /// Constructs an Journal object, with an iCalObject + /// (usually an iCalendar object) as its parent. /// - public class Journal : RecurringComponent + public Journal() + { + Name = JournalStatus.Name; + } + + protected override bool EvaluationIncludesReferenceDate => true; + + protected override void OnDeserializing(StreamingContext context) + { + base.OnDeserializing(context); + } + + protected bool Equals(Journal other) => Start.Equals(other.Start) && Equals(other as RecurringComponent); + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((Journal) obj); + } + + public override int GetHashCode() { - public string Status - { - get => Properties.Get(JournalStatus.Key); - set => Properties.Set(JournalStatus.Key, value); - } - - /// - /// Constructs an Journal object, with an iCalObject - /// (usually an iCalendar object) as its parent. - /// - public Journal() - { - Name = JournalStatus.Name; - } - - protected override bool EvaluationIncludesReferenceDate => true; - - protected override void OnDeserializing(StreamingContext context) - { - base.OnDeserializing(context); - } - - protected bool Equals(Journal other) => Start.Equals(other.Start) && Equals(other as RecurringComponent); - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((Journal)obj); - } - - public override int GetHashCode() - { - var hashCode = Start?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ base.GetHashCode(); - return hashCode; - } + var hashCode = Start?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ base.GetHashCode(); + return hashCode; } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/RecurringComponent.cs b/Ical.Net/CalendarComponents/RecurringComponent.cs index a0e905d8d..b9e004d5d 100644 --- a/Ical.Net/CalendarComponents/RecurringComponent.cs +++ b/Ical.Net/CalendarComponents/RecurringComponent.cs @@ -1,242 +1,246 @@ -using Ical.Net.DataTypes; -using Ical.Net.Evaluation; -using Ical.Net.Proxies; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; +using Ical.Net.DataTypes; +using Ical.Net.Evaluation; +using Ical.Net.Proxies; +using Ical.Net.Utility; -namespace Ical.Net.CalendarComponents +namespace Ical.Net.CalendarComponents; + +/// +/// An iCalendar component that recurs. +/// +/// +/// This component automatically handles +/// RRULEs, RDATE, EXRULEs, and EXDATEs, as well as the DTSTART +/// for the recurring item (all recurring items must have a DTSTART). +/// +public class RecurringComponent : UniqueComponent, IRecurringComponent { - /// - /// An iCalendar component that recurs. - /// - /// - /// This component automatically handles - /// RRULEs, RDATE, EXRULEs, and EXDATEs, as well as the DTSTART - /// for the recurring item (all recurring items must have a DTSTART). - /// - public class RecurringComponent : UniqueComponent, IRecurringComponent - { - public static IEnumerable SortByDate(IEnumerable list) => SortByDate(list); + public static IEnumerable SortByDate(IEnumerable list) => SortByDate(list); - public static IEnumerable SortByDate(IEnumerable list) => list.OrderBy(d => d); + public static IEnumerable SortByDate(IEnumerable list) => list.OrderBy(d => d); - protected virtual bool EvaluationIncludesReferenceDate => false; + protected virtual bool EvaluationIncludesReferenceDate => false; - public virtual IList Attachments - { - get => Properties.GetMany("ATTACH"); - set => Properties.Set("ATTACH", value); - } + public virtual IList Attachments + { + get => Properties.GetMany("ATTACH"); + set => Properties.Set("ATTACH", value); + } - public virtual IList Categories - { - get => Properties.GetMany("CATEGORIES"); - set => Properties.Set("CATEGORIES", value); - } + public virtual IList Categories + { + get => Properties.GetMany("CATEGORIES"); + set => Properties.Set("CATEGORIES", value); + } - public virtual string Class - { - get => Properties.Get("CLASS"); - set => Properties.Set("CLASS", value); - } + public virtual string Class + { + get => Properties.Get("CLASS"); + set => Properties.Set("CLASS", value); + } - public virtual IList Contacts - { - get => Properties.GetMany("CONTACT"); - set => Properties.Set("CONTACT", value); - } + public virtual IList Contacts + { + get => Properties.GetMany("CONTACT"); + set => Properties.Set("CONTACT", value); + } - public virtual IDateTime Created - { - get => Properties.Get("CREATED"); - set => Properties.Set("CREATED", value); - } + public virtual IDateTime Created + { + get => Properties.Get("CREATED"); + set => Properties.Set("CREATED", value); + } - public virtual string Description - { - get => Properties.Get("DESCRIPTION"); - set => Properties.Set("DESCRIPTION", value); - } + public virtual string Description + { + get => Properties.Get("DESCRIPTION"); + set => Properties.Set("DESCRIPTION", value); + } - /// - /// The start date/time of the component. - /// - public virtual IDateTime DtStart - { - get => Properties.Get("DTSTART"); - set => Properties.Set("DTSTART", value); - } + /// + /// The start date/time of the component. + /// + public virtual IDateTime DtStart + { + get => Properties.Get("DTSTART"); + set => Properties.Set("DTSTART", value); + } - public virtual IList ExceptionDates - { - get => Properties.GetMany("EXDATE"); - set => Properties.Set("EXDATE", value); - } + public virtual IList ExceptionDates + { + get => Properties.GetMany("EXDATE"); + set => Properties.Set("EXDATE", value); + } - public virtual IList ExceptionRules - { - get => Properties.GetMany("EXRULE"); - set => Properties.Set("EXRULE", value); - } + public virtual IList ExceptionRules + { + get => Properties.GetMany("EXRULE"); + set => Properties.Set("EXRULE", value); + } - public virtual IDateTime LastModified - { - get => Properties.Get("LAST-MODIFIED"); - set => Properties.Set("LAST-MODIFIED", value); - } + public virtual IDateTime LastModified + { + get => Properties.Get("LAST-MODIFIED"); + set => Properties.Set("LAST-MODIFIED", value); + } - public virtual int Priority - { - get => Properties.Get("PRIORITY"); - set => Properties.Set("PRIORITY", value); - } + public virtual int Priority + { + get => Properties.Get("PRIORITY"); + set => Properties.Set("PRIORITY", value); + } - public virtual IList RecurrenceDates - { - get => Properties.GetMany("RDATE"); - set => Properties.Set("RDATE", value); - } + public virtual IList RecurrenceDates + { + get => Properties.GetMany("RDATE"); + set => Properties.Set("RDATE", value); + } - public virtual IList RecurrenceRules - { - get => Properties.GetMany("RRULE"); - set => Properties.Set("RRULE", value); - } + public virtual IList RecurrenceRules + { + get => Properties.GetMany("RRULE"); + set => Properties.Set("RRULE", value); + } - public virtual IDateTime RecurrenceId - { - get => Properties.Get("RECURRENCE-ID"); - set => Properties.Set("RECURRENCE-ID", value); - } + public virtual IDateTime RecurrenceId + { + get => Properties.Get("RECURRENCE-ID"); + set => Properties.Set("RECURRENCE-ID", value); + } - public virtual IList RelatedComponents - { - get => Properties.GetMany("RELATED-TO"); - set => Properties.Set("RELATED-TO", value); - } + public virtual IList RelatedComponents + { + get => Properties.GetMany("RELATED-TO"); + set => Properties.Set("RELATED-TO", value); + } - public virtual int Sequence - { - get => Properties.Get("SEQUENCE"); - set => Properties.Set("SEQUENCE", value); - } + public virtual int Sequence + { + get => Properties.Get("SEQUENCE"); + set => Properties.Set("SEQUENCE", value); + } - /// - /// An alias to the DTStart field (i.e. start date/time). - /// - public virtual IDateTime Start - { - get => DtStart; - set => DtStart = value; - } + /// + /// An alias to the DTStart field (i.e. start date/time). + /// + public virtual IDateTime Start + { + get => DtStart; + set => DtStart = value; + } - public virtual string Summary - { - get => Properties.Get("SUMMARY"); - set => Properties.Set("SUMMARY", value); - } + public virtual string Summary + { + get => Properties.Get("SUMMARY"); + set => Properties.Set("SUMMARY", value); + } - /// - /// A list of s for this recurring component. - /// - public virtual ICalendarObjectList Alarms => new CalendarObjectListProxy(Children); + /// + /// A list of s for this recurring component. + /// + public virtual ICalendarObjectList Alarms => new CalendarObjectListProxy(Children); - public RecurringComponent() - { - Initialize(); - EnsureProperties(); - } + public RecurringComponent() + { + Initialize(); + EnsureProperties(); + } - public RecurringComponent(string name) : base(name) - { - Initialize(); - EnsureProperties(); - } + public RecurringComponent(string name) : base(name) + { + Initialize(); + EnsureProperties(); + } - private void Initialize() => SetService(new RecurringEvaluator(this)); + private void Initialize() => SetService(new RecurringEvaluator(this)); - private void EnsureProperties() + private void EnsureProperties() + { + if (!Properties.ContainsKey("SEQUENCE")) { - if (!Properties.ContainsKey("SEQUENCE")) - { - Sequence = 0; - } + Sequence = 0; } + } - protected override void OnDeserializing(StreamingContext context) - { - base.OnDeserializing(context); + protected override void OnDeserializing(StreamingContext context) + { + base.OnDeserializing(context); - Initialize(); - } + Initialize(); + } - public virtual void ClearEvaluation() => RecurrenceUtil.ClearEvaluation(this); + public virtual void ClearEvaluation() => RecurrenceUtil.ClearEvaluation(this); - public virtual HashSet GetOccurrences(IDateTime dt) => RecurrenceUtil.GetOccurrences(this, dt, EvaluationIncludesReferenceDate); + public virtual HashSet GetOccurrences(IDateTime dt) => RecurrenceUtil.GetOccurrences(this, dt, EvaluationIncludesReferenceDate); - public virtual HashSet GetOccurrences(DateTime dt) - => RecurrenceUtil.GetOccurrences(this, new CalDateTime(dt), EvaluationIncludesReferenceDate); + public virtual HashSet GetOccurrences(DateTime dt) + => RecurrenceUtil.GetOccurrences(this, new CalDateTime(dt), EvaluationIncludesReferenceDate); - public virtual HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) - => RecurrenceUtil.GetOccurrences(this, startTime, endTime, EvaluationIncludesReferenceDate); + public virtual HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) + => RecurrenceUtil.GetOccurrences(this, startTime, endTime, EvaluationIncludesReferenceDate); - public virtual HashSet GetOccurrences(DateTime startTime, DateTime endTime) - => RecurrenceUtil.GetOccurrences(this, new CalDateTime(startTime), new CalDateTime(endTime), EvaluationIncludesReferenceDate); + public virtual HashSet GetOccurrences(DateTime startTime, DateTime endTime) + => RecurrenceUtil.GetOccurrences(this, new CalDateTime(startTime), new CalDateTime(endTime), EvaluationIncludesReferenceDate); - public virtual IList PollAlarms() => PollAlarms(null, null); + public virtual IList PollAlarms() => PollAlarms(null, null); - public virtual IList PollAlarms(IDateTime startTime, IDateTime endTime) - => Alarms?.SelectMany(a => a.Poll(startTime, endTime)).ToList() - ?? new List(); + public virtual IList PollAlarms(IDateTime startTime, IDateTime endTime) + => Alarms?.SelectMany(a => a.Poll(startTime, endTime)).ToList() + ?? new List(); - protected bool Equals(RecurringComponent other) - { - var result = Equals(DtStart, other.DtStart) - && Equals(Priority, other.Priority) - && string.Equals(Summary, other.Summary, StringComparison.OrdinalIgnoreCase) - && string.Equals(Class, other.Class, StringComparison.OrdinalIgnoreCase) - && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) - && Equals(RecurrenceId, other.RecurrenceId) - && Attachments.SequenceEqual(other.Attachments) - && CollectionHelpers.Equals(Categories, other.Categories) - && CollectionHelpers.Equals(Contacts, other.Contacts) - && CollectionHelpers.Equals(ExceptionDates, other.ExceptionDates) - && CollectionHelpers.Equals(ExceptionRules, other.ExceptionRules) - && CollectionHelpers.Equals(RecurrenceDates, other.RecurrenceDates, orderSignificant: true) - && CollectionHelpers.Equals(RecurrenceRules, other.RecurrenceRules, orderSignificant: true); - - return result; - } + protected bool Equals(RecurringComponent other) + { + var result = Equals(DtStart, other.DtStart) + && Equals(Priority, other.Priority) + && string.Equals(Summary, other.Summary, StringComparison.OrdinalIgnoreCase) + && string.Equals(Class, other.Class, StringComparison.OrdinalIgnoreCase) + && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) + && Equals(RecurrenceId, other.RecurrenceId) + && Attachments.SequenceEqual(other.Attachments) + && CollectionHelpers.Equals(Categories, other.Categories) + && CollectionHelpers.Equals(Contacts, other.Contacts) + && CollectionHelpers.Equals(ExceptionDates, other.ExceptionDates) + && CollectionHelpers.Equals(ExceptionRules, other.ExceptionRules) + && CollectionHelpers.Equals(RecurrenceDates, other.RecurrenceDates, orderSignificant: true) + && CollectionHelpers.Equals(RecurrenceRules, other.RecurrenceRules, orderSignificant: true); + + return result; + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((RecurringComponent)obj); - } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((RecurringComponent) obj); + } - public override int GetHashCode() - { - unchecked - { - var hashCode = DtStart?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ Priority.GetHashCode(); - hashCode = (hashCode * 397) ^ (Summary?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (Class?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (Description?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (RecurrenceId?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Attachments); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Categories); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Contacts); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ExceptionDates); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ExceptionRules); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(RecurrenceDates); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(RecurrenceRules); - return hashCode; - } + public override int GetHashCode() + { + unchecked + { + var hashCode = DtStart?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ Priority.GetHashCode(); + hashCode = (hashCode * 397) ^ (Summary?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Class?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Description?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (RecurrenceId?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Attachments); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Categories); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Contacts); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ExceptionDates); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ExceptionRules); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(RecurrenceDates); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(RecurrenceRules); + return hashCode; } } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/Todo.cs b/Ical.Net/CalendarComponents/Todo.cs index f5484bbb2..6d8544f33 100644 --- a/Ical.Net/CalendarComponents/Todo.cs +++ b/Ical.Net/CalendarComponents/Todo.cs @@ -1,211 +1,215 @@ -using Ical.Net.DataTypes; -using Ical.Net.Evaluation; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.Serialization; +using Ical.Net.DataTypes; +using Ical.Net.Evaluation; -namespace Ical.Net.CalendarComponents +namespace Ical.Net.CalendarComponents; + +/// +/// A class that represents an RFC 5545 VTODO component. +/// +[DebuggerDisplay("{Summary} - {Status}")] +public class Todo : RecurringComponent, IAlarmContainer { + private readonly TodoEvaluator _mEvaluator; + /// - /// A class that represents an RFC 5545 VTODO component. + /// The date/time the todo was completed. /// - [DebuggerDisplay("{Summary} - {Status}")] - public class Todo : RecurringComponent, IAlarmContainer + public virtual IDateTime Completed { - private readonly TodoEvaluator _mEvaluator; + get => Properties.Get("COMPLETED"); + set => Properties.Set("COMPLETED", value); + } - /// - /// The date/time the todo was completed. - /// - public virtual IDateTime Completed + /// + /// The start date/time of the todo item. + /// + public override IDateTime DtStart + { + get => base.DtStart; + set { - get => Properties.Get("COMPLETED"); - set => Properties.Set("COMPLETED", value); + base.DtStart = value; + ExtrapolateTimes(2); } + } - /// - /// The start date/time of the todo item. - /// - public override IDateTime DtStart + /// + /// The due date of the todo item. + /// + public virtual IDateTime Due + { + get => Properties.Get("DUE"); + set { - get => base.DtStart; - set - { - base.DtStart = value; - ExtrapolateTimes(2); - } + Properties.Set("DUE", value); + ExtrapolateTimes(0); } + } - /// - /// The due date of the todo item. - /// - public virtual IDateTime Due + /// + /// The duration of the todo item. + /// + // NOTE: Duration is not supported by all systems, + // (i.e. iPhone) and cannot co-exist with Due. + // RFC 5545 states: + // + // ; either 'due' or 'duration' may appear in + // ; a 'todoprop', but 'due' and 'duration' + // ; MUST NOT occur in the same 'todoprop' + // + // Therefore, Duration is not serialized, as Due + // should always be extrapolated from the duration. + public virtual TimeSpan Duration + { + get => Properties.Get("DURATION"); + set { - get => Properties.Get("DUE"); - set - { - Properties.Set("DUE", value); - ExtrapolateTimes(0); - } + Properties.Set("DURATION", value); + ExtrapolateTimes(1); } + } + + public virtual GeographicLocation GeographicLocation + { + get => Properties.Get("GEO"); + set => Properties.Set("GEO", value); + } + + public virtual string Location + { + get => Properties.Get("LOCATION"); + set => Properties.Set("LOCATION", value); + } - /// - /// The duration of the todo item. - /// - // NOTE: Duration is not supported by all systems, - // (i.e. iPhone) and cannot co-exist with Due. - // RFC 5545 states: - // - // ; either 'due' or 'duration' may appear in - // ; a 'todoprop', but 'due' and 'duration' - // ; MUST NOT occur in the same 'todoprop' - // - // Therefore, Duration is not serialized, as Due - // should always be extrapolated from the duration. - public virtual TimeSpan Duration + public virtual int PercentComplete + { + get => Properties.Get("PERCENT-COMPLETE"); + set => Properties.Set("PERCENT-COMPLETE", value); + } + + public virtual IList Resources + { + get => Properties.GetMany("RESOURCES"); + set => Properties.Set("RESOURCES", value ?? new List()); + } + + /// + /// The status of the todo item. + /// + public virtual string Status + { + get => Properties.Get(TodoStatus.Key); + set { - get => Properties.Get("DURATION"); - set + if (string.Equals(Status, value, TodoStatus.Comparison)) { - Properties.Set("DURATION", value); - ExtrapolateTimes(1); + return; } - } - public virtual GeographicLocation GeographicLocation - { - get => Properties.Get("GEO"); - set => Properties.Set("GEO", value); - } + // Automatically set/unset the Completed time, once the + // component is fully loaded (When deserializing, it shouldn't + // automatically set the completed time just because the + // status was changed). + if (IsLoaded) + { + Completed = string.Equals(value, TodoStatus.Completed, TodoStatus.Comparison) + ? CalDateTime.Now + : null; + } - public virtual string Location - { - get => Properties.Get("LOCATION"); - set => Properties.Set("LOCATION", value); + Properties.Set(TodoStatus.Key, value); } + } - public virtual int PercentComplete - { - get => Properties.Get("PERCENT-COMPLETE"); - set => Properties.Set("PERCENT-COMPLETE", value); - } + public Todo() + { + Name = TodoStatus.Name; - public virtual IList Resources - { - get => Properties.GetMany("RESOURCES"); - set => Properties.Set("RESOURCES", value ?? new List()); - } + _mEvaluator = new TodoEvaluator(this); + SetService(_mEvaluator); + } - /// - /// The status of the todo item. - /// - public virtual string Status + /// + /// Use this method to determine if a todo item has been completed. + /// This takes into account recurrence items and the previous date + /// of completion, if any. + /// + /// This method evaluates the recurrence pattern for this TODO + /// as necessary to ensure all relevant information is taken + /// into account to give the most accurate result possible. + /// + /// + /// True if the todo item has been completed + public virtual bool IsCompleted(IDateTime currDt) + { + if (Status == TodoStatus.Completed) { - get => Properties.Get(TodoStatus.Key); - set + if (Completed == null || Completed.GreaterThan(currDt)) { - if (string.Equals(Status, value, TodoStatus.Comparison)) - { - return; - } - - // Automatically set/unset the Completed time, once the - // component is fully loaded (When deserializing, it shouldn't - // automatically set the completed time just because the - // status was changed). - if (IsLoaded) - { - Completed = string.Equals(value, TodoStatus.Completed, TodoStatus.Comparison) - ? CalDateTime.Now - : null; - } - - Properties.Set(TodoStatus.Key, value); + return true; } - } - public Todo() - { - Name = TodoStatus.Name; + // Evaluate to the previous occurrence. + _mEvaluator.EvaluateToPreviousOccurrence(Completed, currDt); - _mEvaluator = new TodoEvaluator(this); - SetService(_mEvaluator); + return _mEvaluator.Periods.Cast().All(p => !p.StartTime.GreaterThan(Completed) || !currDt.GreaterThanOrEqual(p.StartTime)); } + return false; + } - /// - /// Use this method to determine if a todo item has been completed. - /// This takes into account recurrence items and the previous date - /// of completion, if any. - /// - /// This method evaluates the recurrence pattern for this TODO - /// as necessary to ensure all relevant information is taken - /// into account to give the most accurate result possible. - /// - /// - /// True if the todo item has been completed - public virtual bool IsCompleted(IDateTime currDt) - { - if (Status == TodoStatus.Completed) - { - if (Completed == null || Completed.GreaterThan(currDt)) - { - return true; - } + /// + /// Returns 'True' if the todo item is Active as of . + /// An item is Active if it requires action of some sort. + /// + /// The date and time to test. + /// True if the item is Active as of , False otherwise. + public virtual bool IsActive(IDateTime currDt) + => (DtStart == null || currDt.GreaterThanOrEqual(DtStart)) + && (!IsCompleted(currDt) && !IsCancelled); - // Evaluate to the previous occurrence. - _mEvaluator.EvaluateToPreviousOccurrence(Completed, currDt); + /// + /// Returns True if the todo item was cancelled. + /// + /// True if the todo was cancelled, False otherwise. + public virtual bool IsCancelled => string.Equals(Status, TodoStatus.Cancelled, TodoStatus.Comparison); - return _mEvaluator.Periods.Cast().All(p => !p.StartTime.GreaterThan(Completed) || !currDt.GreaterThanOrEqual(p.StartTime)); - } - return false; - } + protected override bool EvaluationIncludesReferenceDate => true; - /// - /// Returns 'True' if the todo item is Active as of . - /// An item is Active if it requires action of some sort. - /// - /// The date and time to test. - /// True if the item is Active as of , False otherwise. - public virtual bool IsActive(IDateTime currDt) - => (DtStart == null || currDt.GreaterThanOrEqual(DtStart)) - && (!IsCompleted(currDt) && !IsCancelled); - - /// - /// Returns True if the todo item was cancelled. - /// - /// True if the todo was cancelled, False otherwise. - public virtual bool IsCancelled => string.Equals(Status, TodoStatus.Cancelled, TodoStatus.Comparison); - - protected override bool EvaluationIncludesReferenceDate => true; - - protected override void OnDeserializing(StreamingContext context) + protected override void OnDeserializing(StreamingContext context) + { + //ToDo: a necessary evil, for now + base.OnDeserializing(context); + } + + private void ExtrapolateTimes(int source) + { + /* + * Source values, a fix introduced to prevent StackOverflow exceptions from occuring. + * 0 = Due + * 1 = Duration + * 2 = DtStart + */ + if (Due == null && DtStart != null && Duration != default(TimeSpan) && source != 0) { - //ToDo: a necessary evil, for now - base.OnDeserializing(context); + Due = DtStart.Add(Duration); } - - private void ExtrapolateTimes(int source) + else if (Duration == default(TimeSpan) && DtStart != null && Due != null && source != 1) { - /* - * Source values, a fix introduced to prevent StackOverflow exceptions from occuring. - * 0 = Due - * 1 = Duration - * 2 = DtStart - */ - if (Due == null && DtStart != null && Duration != default(TimeSpan) && source != 0) - { - Due = DtStart.Add(Duration); - } - else if (Duration == default(TimeSpan) && DtStart != null && Due != null && source != 1) - { - Duration = Due.Subtract(DtStart); - } - else if (DtStart == null && Duration != default(TimeSpan) && Due != null && source != 2) - { - DtStart = Due.Subtract(Duration); - } + Duration = Due.Subtract(DtStart); + } + else if (DtStart == null && Duration != default(TimeSpan) && Due != null && source != 2) + { + DtStart = Due.Subtract(Duration); } } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/UniqueComponent.cs b/Ical.Net/CalendarComponents/UniqueComponent.cs index c8b03ffec..ea355bd93 100644 --- a/Ical.Net/CalendarComponents/UniqueComponent.cs +++ b/Ical.Net/CalendarComponents/UniqueComponent.cs @@ -1,117 +1,121 @@ -using Ical.Net.DataTypes; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Runtime.Serialization; +using Ical.Net.DataTypes; +using Ical.Net.Utility; + +namespace Ical.Net.CalendarComponents; -namespace Ical.Net.CalendarComponents +/// +/// Represents a unique component, a component with a unique UID, +/// which can be used to uniquely identify the component. +/// +public class UniqueComponent : CalendarComponent, IUniqueComponent, IComparable { - /// - /// Represents a unique component, a component with a unique UID, - /// which can be used to uniquely identify the component. - /// - public class UniqueComponent : CalendarComponent, IUniqueComponent, IComparable + // TODO: Add AddRelationship() public method. + // This method will add the UID of a related component + // to the Related_To property, along with any "RELTYPE" + // parameter ("PARENT", "CHILD", "SIBLING", or other) + // TODO: Add RemoveRelationship() public method. + + public UniqueComponent() { - // TODO: Add AddRelationship() public method. - // This method will add the UID of a related component - // to the Related_To property, along with any "RELTYPE" - // parameter ("PARENT", "CHILD", "SIBLING", or other) - // TODO: Add RemoveRelationship() public method. + EnsureProperties(); + } - public UniqueComponent() - { - EnsureProperties(); - } + public UniqueComponent(string name) : base(name) + { + EnsureProperties(); + } - public UniqueComponent(string name) : base(name) + private void EnsureProperties() + { + if (string.IsNullOrEmpty(Uid)) { - EnsureProperties(); + // Create a new UID for the component + Uid = Guid.NewGuid().ToString(); } - private void EnsureProperties() + // NOTE: removed setting the 'CREATED' property here since it breaks serialization. + // See https://sourceforge.net/projects/dday-ical/forums/forum/656447/topic/3754354 + if (DtStamp == null) { - if (string.IsNullOrEmpty(Uid)) - { - // Create a new UID for the component - Uid = Guid.NewGuid().ToString(); - } - - // NOTE: removed setting the 'CREATED' property here since it breaks serialization. - // See https://sourceforge.net/projects/dday-ical/forums/forum/656447/topic/3754354 - if (DtStamp == null) - { - // icalendar RFC doesn't care about sub-second time resolution, so shave off everything smaller than seconds. - var utcNow = DateTime.UtcNow.Truncate(TimeSpan.FromSeconds(1)); - DtStamp = new CalDateTime(utcNow, "UTC"); - } + // icalendar RFC doesn't care about sub-second time resolution, so shave off everything smaller than seconds. + var utcNow = DateTime.UtcNow.Truncate(TimeSpan.FromSeconds(1)); + DtStamp = new CalDateTime(utcNow, "UTC"); } + } - public virtual IList Attendees - { - get => Properties.GetMany("ATTENDEE"); - set => Properties.Set("ATTENDEE", value); - } + public virtual IList Attendees + { + get => Properties.GetMany("ATTENDEE"); + set => Properties.Set("ATTENDEE", value); + } - public virtual IList Comments - { - get => Properties.GetMany("COMMENT"); - set => Properties.Set("COMMENT", value); - } + public virtual IList Comments + { + get => Properties.GetMany("COMMENT"); + set => Properties.Set("COMMENT", value); + } - public virtual IDateTime DtStamp - { - get => Properties.Get("DTSTAMP"); - set => Properties.Set("DTSTAMP", value); - } + public virtual IDateTime DtStamp + { + get => Properties.Get("DTSTAMP"); + set => Properties.Set("DTSTAMP", value); + } - public virtual Organizer Organizer - { - get => Properties.Get("ORGANIZER"); - set => Properties.Set("ORGANIZER", value); - } + public virtual Organizer Organizer + { + get => Properties.Get("ORGANIZER"); + set => Properties.Set("ORGANIZER", value); + } - public virtual IList RequestStatuses - { - get => Properties.GetMany("REQUEST-STATUS"); - set => Properties.Set("REQUEST-STATUS", value); - } + public virtual IList RequestStatuses + { + get => Properties.GetMany("REQUEST-STATUS"); + set => Properties.Set("REQUEST-STATUS", value); + } - public virtual Uri Url - { - get => Properties.Get("URL"); - set => Properties.Set("URL", value); - } + public virtual Uri Url + { + get => Properties.Get("URL"); + set => Properties.Set("URL", value); + } - protected override void OnDeserialized(StreamingContext context) - { - base.OnDeserialized(context); + protected override void OnDeserialized(StreamingContext context) + { + base.OnDeserialized(context); - EnsureProperties(); - } + EnsureProperties(); + } - public int CompareTo(UniqueComponent other) - => string.Compare(Uid, other.Uid, StringComparison.OrdinalIgnoreCase); + public int CompareTo(UniqueComponent other) + => string.Compare(Uid, other.Uid, StringComparison.OrdinalIgnoreCase); - public override bool Equals(object obj) + public override bool Equals(object obj) + { + if (obj is RecurringComponent && obj != this) { - if (obj is RecurringComponent && obj != this) + var r = (RecurringComponent) obj; + if (Uid != null) { - var r = (RecurringComponent)obj; - if (Uid != null) - { - return Uid.Equals(r.Uid); - } - return Uid == r.Uid; + return Uid.Equals(r.Uid); } - return base.Equals(obj); + return Uid == r.Uid; } + return base.Equals(obj); + } - public override int GetHashCode() => Uid?.GetHashCode() ?? base.GetHashCode(); + public override int GetHashCode() => Uid?.GetHashCode() ?? base.GetHashCode(); - public virtual string Uid - { - get => Properties.Get("UID"); - set => Properties.Set("UID", value); - } + public virtual string Uid + { + get => Properties.Get("UID"); + set => Properties.Set("UID", value); } } \ No newline at end of file diff --git a/Ical.Net/CalendarComponents/VTimeZone.cs b/Ical.Net/CalendarComponents/VTimeZone.cs index 24b3ca5f9..3ca05b2cc 100644 --- a/Ical.Net/CalendarComponents/VTimeZone.cs +++ b/Ical.Net/CalendarComponents/VTimeZone.cs @@ -1,385 +1,389 @@ +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System; +using System.Collections.Generic; +using System.Linq; using Ical.Net.DataTypes; using Ical.Net.Proxies; using Ical.Net.Utility; using NodaTime; using NodaTime.TimeZones; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Ical.Net.CalendarComponents -{ - /// - /// Represents an RFC 5545 VTIMEZONE component. - /// - public class VTimeZone : CalendarComponent - { - public static VTimeZone FromLocalTimeZone() - => FromDateTimeZone(DateUtil.LocalDateTimeZone.Id); - public static VTimeZone FromLocalTimeZone(DateTime earlistDateTimeToSupport, bool includeHistoricalData) - => FromDateTimeZone(DateUtil.LocalDateTimeZone.Id, earlistDateTimeToSupport, includeHistoricalData); +namespace Ical.Net.CalendarComponents; - public static VTimeZone FromSystemTimeZone(TimeZoneInfo tzinfo) - => FromSystemTimeZone(tzinfo, new DateTime(DateTime.Now.Year, 1, 1), false); +/// +/// Represents an RFC 5545 VTIMEZONE component. +/// +public class VTimeZone : CalendarComponent +{ + public static VTimeZone FromLocalTimeZone() + => FromDateTimeZone(DateUtil.LocalDateTimeZone.Id); - public static VTimeZone FromSystemTimeZone(TimeZoneInfo tzinfo, DateTime earlistDateTimeToSupport, bool includeHistoricalData) - => FromDateTimeZone(tzinfo.Id, earlistDateTimeToSupport, includeHistoricalData); + public static VTimeZone FromLocalTimeZone(DateTime earlistDateTimeToSupport, bool includeHistoricalData) + => FromDateTimeZone(DateUtil.LocalDateTimeZone.Id, earlistDateTimeToSupport, includeHistoricalData); - public static VTimeZone FromDateTimeZone(string tzId) - => FromDateTimeZone(tzId, new DateTime(DateTime.Now.Year, 1, 1), includeHistoricalData: false); + public static VTimeZone FromSystemTimeZone(TimeZoneInfo tzinfo) + => FromSystemTimeZone(tzinfo, new DateTime(DateTime.Now.Year, 1, 1), false); - public static VTimeZone FromDateTimeZone(string tzId, DateTime earlistDateTimeToSupport, bool includeHistoricalData) - { - var vTimeZone = new VTimeZone(tzId); + public static VTimeZone FromSystemTimeZone(TimeZoneInfo tzinfo, DateTime earlistDateTimeToSupport, bool includeHistoricalData) + => FromDateTimeZone(tzinfo.Id, earlistDateTimeToSupport, includeHistoricalData); - var earliestYear = 1900; - var earliestMonth = earlistDateTimeToSupport.Month; - var earliestDay = earlistDateTimeToSupport.Day; - // Support date/times for January 1st of the previous year by default. - if (earlistDateTimeToSupport.Year > 1900) - { - earliestYear = earlistDateTimeToSupport.Year - 1; - // Since we went back a year, we can't still be in a leap-year - if (earliestMonth == 2 && earliestDay == 29) - earliestDay = 28; - } - else - { - // Going back to 1900, which wasn't a leap year, so we need to switch to Feb 20 - if (earliestMonth == 2 && earliestDay == 29) - earliestDay = 28; - } - var earliest = Instant.FromUtc(earliestYear, earliestMonth, earliestDay, - earlistDateTimeToSupport.Hour, earlistDateTimeToSupport.Minute); + public static VTimeZone FromDateTimeZone(string tzId) + => FromDateTimeZone(tzId, new DateTime(DateTime.Now.Year, 1, 1), includeHistoricalData: false); - // Only include historical data if asked to do so. Otherwise, - // use only the most recent adjustment rules available. - var intervals = vTimeZone._nodaZone.GetZoneIntervals(earliest, Instant.FromDateTimeOffset(DateTimeOffset.Now)) - .Where(z => z.HasStart && z.Start != Instant.MinValue) - .ToList(); - - var matchingDaylightIntervals = new List(); - var matchingStandardIntervals = new List(); + public static VTimeZone FromDateTimeZone(string tzId, DateTime earlistDateTimeToSupport, bool includeHistoricalData) + { + var vTimeZone = new VTimeZone(tzId); - // if there are no intervals, create at least one standard interval - if (!intervals.Any()) - { - var start = new DateTimeOffset(new DateTime(earliestYear, 1, 1), new TimeSpan(vTimeZone._nodaZone.MaxOffset.Ticks)); - var interval = new ZoneInterval( - name: vTimeZone._nodaZone.Id, - start: Instant.FromDateTimeOffset(start), - end: Instant.FromDateTimeOffset(start) + Duration.FromHours(1), - wallOffset: vTimeZone._nodaZone.MinOffset, - savings: Offset.Zero); - intervals.Add(interval); - var zoneInfo = CreateTimeZoneInfo(intervals, new List(), true, true); - vTimeZone.AddChild(zoneInfo); - } - else - { - // first, get the latest standard and daylight intervals, find the oldest recurring date in both, set the RRULES for it, and create a VTimeZoneInfos out of them. - //standard - var standardIntervals = intervals.Where(x => x.Savings.ToTimeSpan() == new TimeSpan(0)).ToList(); - var latestStandardInterval = standardIntervals.OrderByDescending(x => x.Start).FirstOrDefault(); - matchingStandardIntervals = GetMatchingIntervals(standardIntervals, latestStandardInterval, true); - var latestStandardTimeZoneInfo = CreateTimeZoneInfo(matchingStandardIntervals, intervals); - vTimeZone.AddChild(latestStandardTimeZoneInfo); - - // check to see if there is no active, future daylight savings (ie, America/Phoenix) - if (latestStandardInterval != null && (latestStandardInterval.HasEnd ? latestStandardInterval.End : Instant.MaxValue) != Instant.MaxValue) - { - //daylight - var daylightIntervals = intervals.Where(x => x.Savings.ToTimeSpan() != new TimeSpan(0)).ToList(); - - if (daylightIntervals.Any()) - { - var latestDaylightInterval = daylightIntervals.OrderByDescending(x => x.Start).FirstOrDefault(); - matchingDaylightIntervals = GetMatchingIntervals(daylightIntervals, latestDaylightInterval, true); - var latestDaylightTimeZoneInfo = CreateTimeZoneInfo(matchingDaylightIntervals, intervals); - vTimeZone.AddChild(latestDaylightTimeZoneInfo); - } - } - } + var earliestYear = 1900; + var earliestMonth = earlistDateTimeToSupport.Month; + var earliestDay = earlistDateTimeToSupport.Day; + // Support date/times for January 1st of the previous year by default. + if (earlistDateTimeToSupport.Year > 1900) + { + earliestYear = earlistDateTimeToSupport.Year - 1; + // Since we went back a year, we can't still be in a leap-year + if (earliestMonth == 2 && earliestDay == 29) + earliestDay = 28; + } + else + { + // Going back to 1900, which wasn't a leap year, so we need to switch to Feb 20 + if (earliestMonth == 2 && earliestDay == 29) + earliestDay = 28; + } + var earliest = Instant.FromUtc(earliestYear, earliestMonth, earliestDay, + earlistDateTimeToSupport.Hour, earlistDateTimeToSupport.Minute); - if (!includeHistoricalData || intervals.Count == 1) - { - return vTimeZone; - } + // Only include historical data if asked to do so. Otherwise, + // use only the most recent adjustment rules available. + var intervals = vTimeZone._nodaZone.GetZoneIntervals(earliest, Instant.FromDateTimeOffset(DateTimeOffset.Now)) + .Where(z => z.HasStart && z.Start != Instant.MinValue) + .ToList(); - // then, do the historic intervals, using RDATE for them - var historicIntervals = intervals.Where(x => !matchingDaylightIntervals.Contains(x) && !matchingStandardIntervals.Contains(x)).ToList(); + var matchingDaylightIntervals = new List(); + var matchingStandardIntervals = new List(); - while (historicIntervals.Any(x => x.Start != Instant.MinValue)) + // if there are no intervals, create at least one standard interval + if (!intervals.Any()) + { + var start = new DateTimeOffset(new DateTime(earliestYear, 1, 1), new TimeSpan(vTimeZone._nodaZone.MaxOffset.Ticks)); + var interval = new ZoneInterval( + name: vTimeZone._nodaZone.Id, + start: Instant.FromDateTimeOffset(start), + end: Instant.FromDateTimeOffset(start) + Duration.FromHours(1), + wallOffset: vTimeZone._nodaZone.MinOffset, + savings: Offset.Zero); + intervals.Add(interval); + var zoneInfo = CreateTimeZoneInfo(intervals, new List(), true, true); + vTimeZone.AddChild(zoneInfo); + } + else + { + // first, get the latest standard and daylight intervals, find the oldest recurring date in both, set the RRULES for it, and create a VTimeZoneInfos out of them. + //standard + var standardIntervals = intervals.Where(x => x.Savings.ToTimeSpan() == new TimeSpan(0)).ToList(); + var latestStandardInterval = standardIntervals.OrderByDescending(x => x.Start).FirstOrDefault(); + matchingStandardIntervals = GetMatchingIntervals(standardIntervals, latestStandardInterval, true); + var latestStandardTimeZoneInfo = CreateTimeZoneInfo(matchingStandardIntervals, intervals); + vTimeZone.AddChild(latestStandardTimeZoneInfo); + + // check to see if there is no active, future daylight savings (ie, America/Phoenix) + if (latestStandardInterval != null && (latestStandardInterval.HasEnd ? latestStandardInterval.End : Instant.MaxValue) != Instant.MaxValue) { - var interval = historicIntervals.FirstOrDefault(x => x.Start != Instant.MinValue); + //daylight + var daylightIntervals = intervals.Where(x => x.Savings.ToTimeSpan() != new TimeSpan(0)).ToList(); - if (interval == null) + if (daylightIntervals.Any()) { - break; + var latestDaylightInterval = daylightIntervals.OrderByDescending(x => x.Start).FirstOrDefault(); + matchingDaylightIntervals = GetMatchingIntervals(daylightIntervals, latestDaylightInterval, true); + var latestDaylightTimeZoneInfo = CreateTimeZoneInfo(matchingDaylightIntervals, intervals); + vTimeZone.AddChild(latestDaylightTimeZoneInfo); } - - var matchedIntervals = GetMatchingIntervals(historicIntervals, interval); - var timeZoneInfo = CreateTimeZoneInfo(matchedIntervals, intervals, false); - vTimeZone.AddChild(timeZoneInfo); - historicIntervals = historicIntervals.Where(x => !matchedIntervals.Contains(x)).ToList(); } + } + if (!includeHistoricalData || intervals.Count == 1) + { return vTimeZone; } - private static VTimeZoneInfo CreateTimeZoneInfo(List matchedIntervals, List intervals, bool isRRule = true, - bool isOnlyInterval = false) + // then, do the historic intervals, using RDATE for them + var historicIntervals = intervals.Where(x => !matchingDaylightIntervals.Contains(x) && !matchingStandardIntervals.Contains(x)).ToList(); + + while (historicIntervals.Any(x => x.Start != Instant.MinValue)) { - if (matchedIntervals == null || !matchedIntervals.Any()) - { - throw new ArgumentException("No intervals found in matchedIntervals"); - } + var interval = historicIntervals.FirstOrDefault(x => x.Start != Instant.MinValue); - var oldestInterval = matchedIntervals.OrderBy(x => x.Start).FirstOrDefault(); - if (oldestInterval == null) + if (interval == null) { - throw new InvalidOperationException("oldestInterval was not found"); + break; } - var previousInterval = intervals.SingleOrDefault(x => (x.HasEnd ? x.End : Instant.MaxValue) == oldestInterval.Start); + var matchedIntervals = GetMatchingIntervals(historicIntervals, interval); + var timeZoneInfo = CreateTimeZoneInfo(matchedIntervals, intervals, false); + vTimeZone.AddChild(timeZoneInfo); + historicIntervals = historicIntervals.Where(x => !matchedIntervals.Contains(x)).ToList(); + } - var delta = new TimeSpan(1, 0, 0); + return vTimeZone; + } - if (previousInterval != null) - { - delta = new TimeSpan(0, 0, previousInterval.WallOffset.Seconds - oldestInterval.WallOffset.Seconds); - } - else if (isOnlyInterval) - { - delta = new TimeSpan(); - } + private static VTimeZoneInfo CreateTimeZoneInfo(List matchedIntervals, List intervals, bool isRRule = true, + bool isOnlyInterval = false) + { + if (matchedIntervals == null || !matchedIntervals.Any()) + { + throw new ArgumentException("No intervals found in matchedIntervals"); + } - var utcOffset = oldestInterval.StandardOffset.ToTimeSpan(); + var oldestInterval = matchedIntervals.OrderBy(x => x.Start).FirstOrDefault(); + if (oldestInterval == null) + { + throw new InvalidOperationException("oldestInterval was not found"); + } - var timeZoneInfo = new VTimeZoneInfo(); + var previousInterval = intervals.SingleOrDefault(x => (x.HasEnd ? x.End : Instant.MaxValue) == oldestInterval.Start); - var isDaylight = oldestInterval.Savings.Ticks > 0; + var delta = new TimeSpan(1, 0, 0); - if (isDaylight) - { - timeZoneInfo.Name = Components.Daylight; - timeZoneInfo.OffsetFrom = new UtcOffset(utcOffset); - timeZoneInfo.OffsetTo = new UtcOffset(utcOffset - delta); - } - else - { - timeZoneInfo.Name = Components.Standard; - timeZoneInfo.OffsetFrom = new UtcOffset(utcOffset + delta); - timeZoneInfo.OffsetTo = new UtcOffset(utcOffset); - } + if (previousInterval != null) + { + delta = new TimeSpan(0, 0, previousInterval.WallOffset.Seconds - oldestInterval.WallOffset.Seconds); + } + else if (isOnlyInterval) + { + delta = new TimeSpan(); + } - timeZoneInfo.TimeZoneName = oldestInterval.Name; + var utcOffset = oldestInterval.StandardOffset.ToTimeSpan(); - var start = oldestInterval.IsoLocalStart.ToDateTimeUnspecified() + delta; - timeZoneInfo.Start = new CalDateTime(start) { HasTime = true }; + var timeZoneInfo = new VTimeZoneInfo(); - if (isRRule) - { - PopulateTimeZoneInfoRecurrenceRules(timeZoneInfo, oldestInterval); - } - else - { - PopulateTimeZoneInfoRecurrenceDates(timeZoneInfo, matchedIntervals, delta); - } + var isDaylight = oldestInterval.Savings.Ticks > 0; - return timeZoneInfo; + if (isDaylight) + { + timeZoneInfo.Name = Components.Daylight; + timeZoneInfo.OffsetFrom = new UtcOffset(utcOffset); + timeZoneInfo.OffsetTo = new UtcOffset(utcOffset - delta); } + else + { + timeZoneInfo.Name = Components.Standard; + timeZoneInfo.OffsetFrom = new UtcOffset(utcOffset + delta); + timeZoneInfo.OffsetTo = new UtcOffset(utcOffset); + } + + timeZoneInfo.TimeZoneName = oldestInterval.Name; - private static List GetMatchingIntervals(List intervals, ZoneInterval intervalToMatch, bool consecutiveOnly = false) + var start = oldestInterval.IsoLocalStart.ToDateTimeUnspecified() + delta; + timeZoneInfo.Start = new CalDateTime(start) { HasTime = true }; + + if (isRRule) { - var matchedIntervals = intervals - .Where(x => x.Start != Instant.MinValue) - .Where(x => x.IsoLocalStart.Month == intervalToMatch.IsoLocalStart.Month + PopulateTimeZoneInfoRecurrenceRules(timeZoneInfo, oldestInterval); + } + else + { + PopulateTimeZoneInfoRecurrenceDates(timeZoneInfo, matchedIntervals, delta); + } + + return timeZoneInfo; + } + + private static List GetMatchingIntervals(List intervals, ZoneInterval intervalToMatch, bool consecutiveOnly = false) + { + var matchedIntervals = intervals + .Where(x => x.Start != Instant.MinValue) + .Where(x => x.IsoLocalStart.Month == intervalToMatch.IsoLocalStart.Month && x.IsoLocalStart.Hour == intervalToMatch.IsoLocalStart.Hour && x.IsoLocalStart.Minute == intervalToMatch.IsoLocalStart.Minute && x.IsoLocalStart.ToDateTimeUnspecified().DayOfWeek == intervalToMatch.IsoLocalStart.ToDateTimeUnspecified().DayOfWeek && x.WallOffset == intervalToMatch.WallOffset && x.Name == intervalToMatch.Name) - .ToList(); + .ToList(); - if (!consecutiveOnly) - { - return matchedIntervals; - } + if (!consecutiveOnly) + { + return matchedIntervals; + } - var consecutiveIntervals = new List(); + var consecutiveIntervals = new List(); - var currentYear = 0; + var currentYear = 0; - // return only the intervals where there are no gaps in years - foreach (var interval in matchedIntervals.OrderByDescending(x => x.IsoLocalStart.Year)) + // return only the intervals where there are no gaps in years + foreach (var interval in matchedIntervals.OrderByDescending(x => x.IsoLocalStart.Year)) + { + if (currentYear == 0) { - if (currentYear == 0) - { - currentYear = interval.IsoLocalStart.Year; - } - - if (currentYear != interval.IsoLocalStart.Year) - { - break; - } + currentYear = interval.IsoLocalStart.Year; + } - consecutiveIntervals.Add(interval); - currentYear--; + if (currentYear != interval.IsoLocalStart.Year) + { + break; } - return consecutiveIntervals; + consecutiveIntervals.Add(interval); + currentYear--; } - private static void PopulateTimeZoneInfoRecurrenceDates(VTimeZoneInfo tzi, List intervals, TimeSpan delta) + return consecutiveIntervals; + } + + private static void PopulateTimeZoneInfoRecurrenceDates(VTimeZoneInfo tzi, List intervals, TimeSpan delta) + { + foreach (var interval in intervals) { - foreach (var interval in intervals) + var periodList = new PeriodList(); + var time = interval.IsoLocalStart.ToDateTimeUnspecified(); + var date = new CalDateTime(time).Add(delta) as CalDateTime; + if (date == null) { - var periodList = new PeriodList(); - var time = interval.IsoLocalStart.ToDateTimeUnspecified(); - var date = new CalDateTime(time).Add(delta) as CalDateTime; - if (date == null) - { - continue; - } - - date.HasTime = true; - periodList.Add(date); - tzi.RecurrenceDates.Add(periodList); + continue; } - } - private static void PopulateTimeZoneInfoRecurrenceRules(VTimeZoneInfo tzi, ZoneInterval interval) - { - var recurrence = new IntervalRecurrencePattern(interval); - tzi.RecurrenceRules.Add(recurrence); + date.HasTime = true; + periodList.Add(date); + tzi.RecurrenceDates.Add(periodList); } + } + + private static void PopulateTimeZoneInfoRecurrenceRules(VTimeZoneInfo tzi, ZoneInterval interval) + { + var recurrence = new IntervalRecurrencePattern(interval); + tzi.RecurrenceRules.Add(recurrence); + } - private class IntervalRecurrencePattern : RecurrencePattern + private class IntervalRecurrencePattern : RecurrencePattern + { + public IntervalRecurrencePattern(ZoneInterval interval) { - public IntervalRecurrencePattern(ZoneInterval interval) - { - Frequency = FrequencyType.Yearly; - ByMonth.Add(interval.IsoLocalStart.Month); + Frequency = FrequencyType.Yearly; + ByMonth.Add(interval.IsoLocalStart.Month); - var date = interval.IsoLocalStart.ToDateTimeUnspecified(); - var weekday = date.DayOfWeek; - var weekNumber = DateUtil.WeekOfMonth(date); + var date = interval.IsoLocalStart.ToDateTimeUnspecified(); + var weekday = date.DayOfWeek; + var weekNumber = DateUtil.WeekOfMonth(date); - if (weekNumber >= 4) - ByDay.Add(new WeekDay(weekday, -1)); // Almost certainly likely last X-day of month. Avoid issues with 4/5 sundays in different year/months. Ideally, use the nodazone tz database rule for this interval instead. - else - ByDay.Add(new WeekDay(weekday, weekNumber)); - } + if (weekNumber >= 4) + ByDay.Add(new WeekDay(weekday, -1)); // Almost certainly likely last X-day of month. Avoid issues with 4/5 sundays in different year/months. Ideally, use the nodazone tz database rule for this interval instead. + else + ByDay.Add(new WeekDay(weekday, weekNumber)); } + } + + public VTimeZone() + { + Name = Components.Timezone; + } - public VTimeZone() + + public VTimeZone(string tzId) : this() + { + if (string.IsNullOrWhiteSpace(tzId)) { - Name = Components.Timezone; + return; } + TzId = tzId; + Location = _nodaZone.Id; + } - public VTimeZone(string tzId) : this() + private DateTimeZone _nodaZone; + private string _tzId; + public virtual string TzId + { + get { - if (string.IsNullOrWhiteSpace(tzId)) + if (string.IsNullOrWhiteSpace(_tzId)) { - return; + _tzId = Properties.Get("TZID"); } - - TzId = tzId; - Location = _nodaZone.Id; + return _tzId; } - - private DateTimeZone _nodaZone; - private string _tzId; - public virtual string TzId + set { - get + if (string.Equals(_tzId, value, StringComparison.OrdinalIgnoreCase)) { - if (string.IsNullOrWhiteSpace(_tzId)) - { - _tzId = Properties.Get("TZID"); - } - return _tzId; + return; } - set - { - if (string.Equals(_tzId, value, StringComparison.OrdinalIgnoreCase)) - { - return; - } - if (string.IsNullOrWhiteSpace(value)) - { - _tzId = null; - Properties.Remove("TZID"); - } - - _nodaZone = DateUtil.GetZone(value, useLocalIfNotFound: false); - var id = _nodaZone.Id; - if (string.IsNullOrWhiteSpace(id)) - { - throw new ArgumentException($"Unrecognized time zone id: {value}"); - } + if (string.IsNullOrWhiteSpace(value)) + { + _tzId = null; + Properties.Remove("TZID"); + } - if (!string.Equals(id, value, StringComparison.OrdinalIgnoreCase)) - { - //It was a BCL time zone, so we should use the original value - id = value; - } + _nodaZone = DateUtil.GetZone(value, useLocalIfNotFound: false); + var id = _nodaZone.Id; + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentException($"Unrecognized time zone id: {value}"); + } - _tzId = id; - Properties.Set("TZID", value); + if (!string.Equals(id, value, StringComparison.OrdinalIgnoreCase)) + { + //It was a BCL time zone, so we should use the original value + id = value; } + + _tzId = id; + Properties.Set("TZID", value); } + } - private Uri _url; - public virtual Uri Url + private Uri _url; + public virtual Uri Url + { + get => _url ?? (_url = Properties.Get("TZURL")); + set { - get => _url ?? (_url = Properties.Get("TZURL")); - set - { - _url = value; - Properties.Set("TZURL", _url); - } + _url = value; + Properties.Set("TZURL", _url); } + } - private string _location; - public string Location + private string _location; + public string Location + { + get => _location ?? (_location = Properties.Get("X-LIC-LOCATION")); + set { - get => _location ?? (_location = Properties.Get("X-LIC-LOCATION")); - set - { - _location = value; - Properties.Set("X-LIC-LOCATION", _location); - } + _location = value; + Properties.Set("X-LIC-LOCATION", _location); } + } - public ICalendarObjectList TimeZoneInfos => new CalendarObjectListProxy(Children); + public ICalendarObjectList TimeZoneInfos => new CalendarObjectListProxy(Children); - protected bool Equals(VTimeZone other) - => string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) - && string.Equals(TzId, other.TzId, StringComparison.OrdinalIgnoreCase) - && Equals(Url, other.Url); + protected bool Equals(VTimeZone other) + => string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) + && string.Equals(TzId, other.TzId, StringComparison.OrdinalIgnoreCase) + && Equals(Url, other.Url); - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != this.GetType()) return false; - return Equals((VTimeZone)obj); - } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((VTimeZone) obj); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = Name.GetHashCode(); - hashCode = (hashCode * 397) ^ (TzId?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (Url?.GetHashCode() ?? 0); - return hashCode; - } + var hashCode = Name.GetHashCode(); + hashCode = (hashCode * 397) ^ (TzId?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Url?.GetHashCode() ?? 0); + return hashCode; } } -} +} \ No newline at end of file diff --git a/Ical.Net/CalendarExtensions.cs b/Ical.Net/CalendarExtensions.cs index cf361c8a4..0f80956f1 100644 --- a/Ical.Net/CalendarExtensions.cs +++ b/Ical.Net/CalendarExtensions.cs @@ -1,26 +1,30 @@ +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Globalization; -namespace Ical.Net +namespace Ical.Net; + +public static class CalendarExtensions { - public static class CalendarExtensions + /// + /// https://blogs.msdn.microsoft.com/shawnste/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net/ + /// + public static int GetIso8601WeekOfYear(this System.Globalization.Calendar calendar, DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek) { - /// - /// https://blogs.msdn.microsoft.com/shawnste/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net/ - /// - public static int GetIso8601WeekOfYear(this System.Globalization.Calendar calendar, DateTime time, CalendarWeekRule rule, DayOfWeek firstDayOfWeek) + // Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll + // be the same week# as whatever Thursday, Friday or Saturday are, + // and we always get those right + var day = calendar.GetDayOfWeek(time); + if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday) { - // Seriously cheat. If its Monday, Tuesday or Wednesday, then it'll - // be the same week# as whatever Thursday, Friday or Saturday are, - // and we always get those right - var day = calendar.GetDayOfWeek(time); - if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday) - { - time = time.AddDays(3); - } - - // Return the week of our adjusted day - return calendar.GetWeekOfYear(time, rule, firstDayOfWeek); + time = time.AddDays(3); } + + // Return the week of our adjusted day + return calendar.GetWeekOfYear(time, rule, firstDayOfWeek); } } \ No newline at end of file diff --git a/Ical.Net/CalendarObject.cs b/Ical.Net/CalendarObject.cs index 5cf150db2..0dcfd8884 100644 --- a/Ical.Net/CalendarObject.cs +++ b/Ical.Net/CalendarObject.cs @@ -1,144 +1,148 @@ -using Ical.Net.Collections; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Runtime.Serialization; +using Ical.Net.Collections; + +namespace Ical.Net; -namespace Ical.Net +/// +/// The base class for all iCalendar objects and components. +/// +public class CalendarObject : CalendarObjectBase, ICalendarObject { - /// - /// The base class for all iCalendar objects and components. - /// - public class CalendarObject : CalendarObjectBase, ICalendarObject + private ICalendarObjectList _children; + private ServiceProvider _serviceProvider; + + internal CalendarObject() { - private ICalendarObjectList _children; - private ServiceProvider _serviceProvider; + Initialize(); + } - internal CalendarObject() - { - Initialize(); - } + public CalendarObject(string name) : this() + { + Name = name; + } - public CalendarObject(string name) : this() - { - Name = name; - } + public CalendarObject(int line, int col) : this() + { + Line = line; + Column = col; + } - public CalendarObject(int line, int col) : this() - { - Line = line; - Column = col; - } + private void Initialize() + { + _children = new CalendarObjectList(); + _serviceProvider = new ServiceProvider(); + _children.ItemAdded += Children_ItemAdded; + } - private void Initialize() - { - _children = new CalendarObjectList(); - _serviceProvider = new ServiceProvider(); - _children.ItemAdded += Children_ItemAdded; - } + [OnDeserializing] + internal void DeserializingInternal(StreamingContext context) => OnDeserializing(context); + + [OnDeserialized] + internal void DeserializedInternal(StreamingContext context) => OnDeserialized(context); - [OnDeserializing] - internal void DeserializingInternal(StreamingContext context) => OnDeserializing(context); + protected virtual void OnDeserializing(StreamingContext context) => Initialize(); - [OnDeserialized] - internal void DeserializedInternal(StreamingContext context) => OnDeserialized(context); + protected virtual void OnDeserialized(StreamingContext context) { } - protected virtual void OnDeserializing(StreamingContext context) => Initialize(); + private void Children_ItemAdded(object sender, ObjectEventArgs e) => e.First.Parent = this; - protected virtual void OnDeserialized(StreamingContext context) { } + protected bool Equals(CalendarObject other) => string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase); - private void Children_ItemAdded(object sender, ObjectEventArgs e) => e.First.Parent = this; + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((CalendarObject) obj); + } - protected bool Equals(CalendarObject other) => string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase); + public override int GetHashCode() => Name?.GetHashCode() ?? 0; - public override bool Equals(object obj) + /// + public override void CopyFrom(ICopyable c) + { + if (c is not ICalendarObject obj) { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((CalendarObject)obj); + return; } - public override int GetHashCode() => Name?.GetHashCode() ?? 0; + // Copy the name and basic information + Name = obj.Name; + Parent = obj.Parent; + Line = obj.Line; + Column = obj.Column; - /// - public override void CopyFrom(ICopyable c) + // Add each child + Children.Clear(); + foreach (var child in obj.Children) { - if (c is not ICalendarObject obj) - { - return; - } - - // Copy the name and basic information - Name = obj.Name; - Parent = obj.Parent; - Line = obj.Line; - Column = obj.Column; - - // Add each child - Children.Clear(); - foreach (var child in obj.Children) - { - // Add a deep copy of the child instead of the child itself - this.AddChild(child.Copy()); - } + // Add a deep copy of the child instead of the child itself + this.AddChild(child.Copy()); } + } + + /// + /// Returns the parent iCalObject that owns this one. + /// + public virtual ICalendarObject Parent { get; set; } + + /// + /// A collection of iCalObjects that are children of the current object. + /// + public virtual ICalendarObjectList Children => _children; - /// - /// Returns the parent iCalObject that owns this one. - /// - public virtual ICalendarObject Parent { get; set; } - - /// - /// A collection of iCalObjects that are children of the current object. - /// - public virtual ICalendarObjectList Children => _children; - - /// - /// Gets or sets the name of the iCalObject. For iCalendar components, this is the RFC 5545 name of the component. - /// - public virtual string Name { get; set; } - - /// - /// Gets the object. - /// The setter must be implemented in a derived class. - /// - public virtual Calendar Calendar + /// + /// Gets or sets the name of the iCalObject. For iCalendar components, this is the RFC 5545 name of the component. + /// + public virtual string Name { get; set; } + + /// + /// Gets the object. + /// The setter must be implemented in a derived class. + /// + public virtual Calendar Calendar + { + get { - get + ICalendarObject obj = this; + while (obj is not Net.Calendar && obj.Parent != null) { - ICalendarObject obj = this; - while (obj is not Net.Calendar && obj.Parent != null) - { - obj = obj.Parent; - } - - return obj as Calendar; + obj = obj.Parent; } - protected set => throw new NotSupportedException(); + + return obj as Calendar; } + protected set => throw new NotSupportedException(); + } - public virtual int Line { get; set; } + public virtual int Line { get; set; } - public virtual int Column { get; set; } + public virtual int Column { get; set; } - public virtual object GetService(Type serviceType) => _serviceProvider.GetService(serviceType); + public virtual object GetService(Type serviceType) => _serviceProvider.GetService(serviceType); - public virtual object GetService(string name) => _serviceProvider.GetService(name); + public virtual object GetService(string name) => _serviceProvider.GetService(name); - public virtual T GetService() => _serviceProvider.GetService(); + public virtual T GetService() => _serviceProvider.GetService(); - public virtual T GetService(string name) => _serviceProvider.GetService(name); + public virtual T GetService(string name) => _serviceProvider.GetService(name); - public virtual void SetService(string name, object obj) => _serviceProvider.SetService(name, obj); + public virtual void SetService(string name, object obj) => _serviceProvider.SetService(name, obj); - public virtual void SetService(object obj) => _serviceProvider.SetService(obj); + public virtual void SetService(object obj) => _serviceProvider.SetService(obj); - public virtual void RemoveService(Type type) => _serviceProvider.RemoveService(type); + public virtual void RemoveService(Type type) => _serviceProvider.RemoveService(type); - public virtual void RemoveService(string name) => _serviceProvider.RemoveService(name); + public virtual void RemoveService(string name) => _serviceProvider.RemoveService(name); - public virtual string Group - { - get => Name; - set => Name = value; - } + public virtual string Group + { + get => Name; + set => Name = value; } } \ No newline at end of file diff --git a/Ical.Net/CalendarObjectBase.cs b/Ical.Net/CalendarObjectBase.cs index b25d50c0d..8d3cbcda7 100644 --- a/Ical.Net/CalendarObjectBase.cs +++ b/Ical.Net/CalendarObjectBase.cs @@ -1,44 +1,48 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net +using System; + +namespace Ical.Net; + +// This class should be declared as abstract +public class CalendarObjectBase : ICopyable, ILoadable { - // This class should be declared as abstract - public class CalendarObjectBase : ICopyable, ILoadable + private bool _mIsLoaded = true; + + /// + /// Makes a deep copy of the source + /// to the current object. This method must be overridden in a derived class. + /// + public virtual void CopyFrom(ICopyable obj) + { + throw new NotImplementedException("Must be implemented in a derived class."); + } + + /// + /// Creates a deep copy of the object. + /// + /// The copy of the object. + public virtual T Copy() + { + var type = GetType(); + var obj = Activator.CreateInstance(type) as ICopyable; + + if (obj is not T objOfT) return default(T); + + obj.CopyFrom(this); + return objOfT; + } + + public virtual bool IsLoaded => _mIsLoaded; + + public event EventHandler Loaded; + + public virtual void OnLoaded() { - private bool _mIsLoaded = true; - - /// - /// Makes a deep copy of the source - /// to the current object. This method must be overridden in a derived class. - /// - public virtual void CopyFrom(ICopyable obj) - { - throw new NotImplementedException("Must be implemented in a derived class."); - } - - /// - /// Creates a deep copy of the object. - /// - /// The copy of the object. - public virtual T Copy() - { - var type = GetType(); - var obj = Activator.CreateInstance(type) as ICopyable; - - if (obj is not T objOfT) return default(T); - - obj.CopyFrom(this); - return objOfT; - } - - public virtual bool IsLoaded => _mIsLoaded; - - public event EventHandler Loaded; - - public virtual void OnLoaded() - { - _mIsLoaded = true; - Loaded?.Invoke(this, EventArgs.Empty); - } + _mIsLoaded = true; + Loaded?.Invoke(this, EventArgs.Empty); } } \ No newline at end of file diff --git a/Ical.Net/CalendarObjectExtensions.cs b/Ical.Net/CalendarObjectExtensions.cs index e6b266a4d..2c7d50cad 100644 --- a/Ical.Net/CalendarObjectExtensions.cs +++ b/Ical.Net/CalendarObjectExtensions.cs @@ -1,15 +1,19 @@ -namespace Ical.Net +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +namespace Ical.Net; + +public static class CalendarObjectExtensions { - public static class CalendarObjectExtensions + public static void AddChild(this ICalendarObject obj, TItem child) where TItem : ICalendarObject { - public static void AddChild(this ICalendarObject obj, TItem child) where TItem : ICalendarObject - { - obj.Children.Add(child); - } + obj.Children.Add(child); + } - public static void RemoveChild(this ICalendarObject obj, TItem child) where TItem : ICalendarObject - { - obj.Children.Remove(child); - } + public static void RemoveChild(this ICalendarObject obj, TItem child) where TItem : ICalendarObject + { + obj.Children.Remove(child); } } \ No newline at end of file diff --git a/Ical.Net/CalendarObjectList.cs b/Ical.Net/CalendarObjectList.cs index 8edcb2c35..165cc9b89 100644 --- a/Ical.Net/CalendarObjectList.cs +++ b/Ical.Net/CalendarObjectList.cs @@ -1,13 +1,17 @@ -using System.Diagnostics.CodeAnalysis; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System.Diagnostics.CodeAnalysis; using Ical.Net.Collections; -namespace Ical.Net +namespace Ical.Net; + +/// +/// A collection of calendar objects. +/// +[ExcludeFromCodeCoverage] +public class CalendarObjectList : GroupedList, ICalendarObjectList { - /// - /// A collection of calendar objects. - /// - [ExcludeFromCodeCoverage] - public class CalendarObjectList : GroupedList, ICalendarObjectList - { - } } \ No newline at end of file diff --git a/Ical.Net/CalendarParameter.cs b/Ical.Net/CalendarParameter.cs index 6b09314c3..db4e87a21 100644 --- a/Ical.Net/CalendarParameter.cs +++ b/Ical.Net/CalendarParameter.cs @@ -1,107 +1,111 @@ -using Ical.Net.Collections.Interfaces; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.Serialization; +using Ical.Net.Collections.Interfaces; + +namespace Ical.Net; -namespace Ical.Net +[DebuggerDisplay("{Name}={string.Join(\",\", Values)}")] +public class CalendarParameter : CalendarObject, IValueObject { - [DebuggerDisplay("{Name}={string.Join(\",\", Values)}")] - public class CalendarParameter : CalendarObject, IValueObject + private HashSet _values; + + public CalendarParameter() { - private HashSet _values; + Initialize(); + } - public CalendarParameter() - { - Initialize(); - } + public CalendarParameter(string name) : base(name) + { + Initialize(); + } - public CalendarParameter(string name) : base(name) - { - Initialize(); - } + public CalendarParameter(string name, string value) : base(name) + { + Initialize(); + AddValue(value); + } - public CalendarParameter(string name, string value) : base(name) + public CalendarParameter(string name, IEnumerable values) : base(name) + { + Initialize(); + foreach (var v in values) { - Initialize(); - AddValue(value); + AddValue(v); } + } - public CalendarParameter(string name, IEnumerable values) : base(name) - { - Initialize(); - foreach (var v in values) - { - AddValue(v); - } - } + private void Initialize() + { + _values = new HashSet(StringComparer.OrdinalIgnoreCase); + } - private void Initialize() - { - _values = new HashSet(StringComparer.OrdinalIgnoreCase); - } + protected override void OnDeserializing(StreamingContext context) + { + base.OnDeserializing(context); - protected override void OnDeserializing(StreamingContext context) - { - base.OnDeserializing(context); + Initialize(); + } - Initialize(); - } + /// + public override void CopyFrom(ICopyable c) + { + base.CopyFrom(c); - /// - public override void CopyFrom(ICopyable c) + var p = c as CalendarParameter; + if (p?.Values == null) { - base.CopyFrom(c); - - var p = c as CalendarParameter; - if (p?.Values == null) - { - return; - } - - _values = new HashSet(p.Values.Where(IsValidValue), StringComparer.OrdinalIgnoreCase); + return; } - public virtual IEnumerable Values => _values; + _values = new HashSet(p.Values.Where(IsValidValue), StringComparer.OrdinalIgnoreCase); + } - public virtual bool ContainsValue(string value) => _values.Contains(value); + public virtual IEnumerable Values => _values; - public virtual int ValueCount => _values?.Count ?? 0; + public virtual bool ContainsValue(string value) => _values.Contains(value); - public virtual void SetValue(string value) - { - _values.Clear(); - _values.Add(value); - } + public virtual int ValueCount => _values?.Count ?? 0; - public virtual void SetValue(IEnumerable values) - { - // Remove all previous values - _values.Clear(); - _values.UnionWith(values.Where(IsValidValue)); - } + public virtual void SetValue(string value) + { + _values.Clear(); + _values.Add(value); + } - private bool IsValidValue(string value) => !string.IsNullOrWhiteSpace(value); + public virtual void SetValue(IEnumerable values) + { + // Remove all previous values + _values.Clear(); + _values.UnionWith(values.Where(IsValidValue)); + } - public virtual void AddValue(string value) - { - if (!IsValidValue(value)) - { - return; - } - _values.Add(value); - } + private bool IsValidValue(string value) => !string.IsNullOrWhiteSpace(value); - public virtual void RemoveValue(string value) + public virtual void AddValue(string value) + { + if (!IsValidValue(value)) { - _values.Remove(value); + return; } + _values.Add(value); + } - public virtual string Value - { - get => Values?.FirstOrDefault(); - set => SetValue(value); - } + public virtual void RemoveValue(string value) + { + _values.Remove(value); + } + + public virtual string Value + { + get => Values?.FirstOrDefault(); + set => SetValue(value); } } \ No newline at end of file diff --git a/Ical.Net/CalendarProperty.cs b/Ical.Net/CalendarProperty.cs index 294293fc7..b389b3838 100644 --- a/Ical.Net/CalendarProperty.cs +++ b/Ical.Net/CalendarProperty.cs @@ -1,152 +1,156 @@ -using System.Collections.Generic; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System.Collections.Generic; using System.Diagnostics; using System.Linq; -namespace Ical.Net +namespace Ical.Net; + +/// +/// A class that represents a property of the +/// itself or one of its components. It can also represent non-standard +/// (X-) properties of an iCalendar component, as seen with many +/// applications, such as with Apple's iCal. +/// X-WR-CALNAME:US Holidays +/// +/// +/// Currently, the "known" properties for an iCalendar are as +/// follows: +/// +/// ProdID +/// Version +/// CalScale +/// Method +/// +/// There may be other, custom X-properties applied to the calendar, +/// and X-properties may be applied to calendar components. +/// +[DebuggerDisplay("{Name}:{Value}")] +public class CalendarProperty : CalendarObject, ICalendarProperty { + private List _values = new List(); + /// - /// A class that represents a property of the - /// itself or one of its components. It can also represent non-standard - /// (X-) properties of an iCalendar component, as seen with many - /// applications, such as with Apple's iCal. - /// X-WR-CALNAME:US Holidays + /// Returns a list of parameters that are associated with the iCalendar object. /// - /// - /// Currently, the "known" properties for an iCalendar are as - /// follows: - /// - /// ProdID - /// Version - /// CalScale - /// Method - /// - /// There may be other, custom X-properties applied to the calendar, - /// and X-properties may be applied to calendar components. - /// - [DebuggerDisplay("{Name}:{Value}")] - public class CalendarProperty : CalendarObject, ICalendarProperty - { - private List _values = new List(); - - /// - /// Returns a list of parameters that are associated with the iCalendar object. - /// - public virtual IParameterCollection Parameters { get; protected set; } = new ParameterList(); - - public CalendarProperty() { } - - public CalendarProperty(string name) : base(name) { } - - public CalendarProperty(string name, object value) : base(name) - { - _values.Add(value); - } + public virtual IParameterCollection Parameters { get; protected set; } = new ParameterList(); - public CalendarProperty(int line, int col) : base(line, col) { } + public CalendarProperty() { } - /// - /// Adds a parameter to the iCalendar object. - /// - public virtual void AddParameter(string name, string value) - { - var p = new CalendarParameter(name, value); - Parameters.Add(p); - } + public CalendarProperty(string name) : base(name) { } - /// - /// Adds a parameter to the iCalendar object. - /// - public virtual void AddParameter(CalendarParameter p) - { - Parameters.Add(p); - } + public CalendarProperty(string name, object value) : base(name) + { + _values.Add(value); + } - /// - public override void CopyFrom(ICopyable obj) - { - base.CopyFrom(obj); + public CalendarProperty(int line, int col) : base(line, col) { } - if (obj is not ICalendarProperty p) - { - return; - } + /// + /// Adds a parameter to the iCalendar object. + /// + public virtual void AddParameter(string name, string value) + { + var p = new CalendarParameter(name, value); + Parameters.Add(p); + } - SetValue(p.Values); - } + /// + /// Adds a parameter to the iCalendar object. + /// + public virtual void AddParameter(CalendarParameter p) + { + Parameters.Add(p); + } - public virtual IEnumerable Values => _values; + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); - public object Value + if (obj is not ICalendarProperty p) { - get => _values?.FirstOrDefault(); - set - { - if (value == null) - { - _values = null; - return; - } - - if (_values != null && _values.Count > 0) - { - _values[0] = value; - } - else - { - _values?.Clear(); - _values?.Add(value); - } - } + return; } - public virtual bool ContainsValue(object value) => _values.Contains(value); + SetValue(p.Values); + } - public virtual int ValueCount => _values?.Count ?? 0; + public virtual IEnumerable Values => _values; - public virtual void SetValue(object value) + public object Value + { + get => _values?.FirstOrDefault(); + set { - if (_values.Count == 0) + if (value == null) { - _values.Add(value); + _values = null; + return; } - else if (value != null) + + if (_values != null && _values.Count > 0) { - // Our list contains values. Let's set the first value! _values[0] = value; } else { - _values.Clear(); + _values?.Clear(); + _values?.Add(value); } } + } + + public virtual bool ContainsValue(object value) => _values.Contains(value); + + public virtual int ValueCount => _values?.Count ?? 0; - public virtual void SetValue(IEnumerable values) + public virtual void SetValue(object value) + { + if (_values.Count == 0) + { + _values.Add(value); + } + else if (value != null) + { + // Our list contains values. Let's set the first value! + _values[0] = value; + } + else { - // Remove all previous values _values.Clear(); - // If the values are ICopyable, create a deep copy of each value, - // otherwise just add the value - var toAdd = values?.Select(x => (x as ICopyable)?.Copy() ?? x) ?? Enumerable.Empty(); - _values.AddRange(toAdd); } + } - public virtual void AddValue(object value) - { - if (value == null) - { - return; - } + public virtual void SetValue(IEnumerable values) + { + // Remove all previous values + _values.Clear(); + // If the values are ICopyable, create a deep copy of each value, + // otherwise just add the value + var toAdd = values?.Select(x => (x as ICopyable)?.Copy() ?? x) ?? Enumerable.Empty(); + _values.AddRange(toAdd); + } - _values.Add(value); + public virtual void AddValue(object value) + { + if (value == null) + { + return; } - public virtual void RemoveValue(object value) + _values.Add(value); + } + + public virtual void RemoveValue(object value) + { + if (value == null) { - if (value == null) - { - return; - } - _values.Remove(value); + return; } + _values.Remove(value); } } \ No newline at end of file diff --git a/Ical.Net/CalendarPropertyList.cs b/Ical.Net/CalendarPropertyList.cs index 5717a7beb..c93cbb846 100644 --- a/Ical.Net/CalendarPropertyList.cs +++ b/Ical.Net/CalendarPropertyList.cs @@ -1,27 +1,31 @@ -using Ical.Net.Collections; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Linq; +using Ical.Net.Collections; -namespace Ical.Net -{ - public class CalendarPropertyList : GroupedValueList - { - private readonly ICalendarObject _mParent; +namespace Ical.Net; - public CalendarPropertyList() { } +public class CalendarPropertyList : GroupedValueList +{ + private readonly ICalendarObject _mParent; - public CalendarPropertyList(ICalendarObject parent) - { - _mParent = parent; - ItemAdded += CalendarPropertyList_ItemAdded; - } + public CalendarPropertyList() { } - private void CalendarPropertyList_ItemAdded(object sender, ObjectEventArgs e) - { - e.First.Parent = _mParent; - } + public CalendarPropertyList(ICalendarObject parent) + { + _mParent = parent; + ItemAdded += CalendarPropertyList_ItemAdded; + } - public ICalendarProperty this[string name] => ContainsKey(name) - ? AllOf(name).FirstOrDefault() - : null; + private void CalendarPropertyList_ItemAdded(object sender, ObjectEventArgs e) + { + e.First.Parent = _mParent; } + + public ICalendarProperty this[string name] => ContainsKey(name) + ? AllOf(name).FirstOrDefault() + : null; } \ No newline at end of file diff --git a/Ical.Net/Collections/GroupedList.cs b/Ical.Net/Collections/GroupedList.cs index 65c1df033..13f00fc28 100644 --- a/Ical.Net/Collections/GroupedList.cs +++ b/Ical.Net/Collections/GroupedList.cs @@ -1,186 +1,211 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System; using System.Collections; using System.Collections.Generic; using System.Linq; -namespace Ical.Net.Collections +namespace Ical.Net.Collections; + +/// +/// A list of objects that are keyed. +/// +public class GroupedList : + IGroupedList + where TItem : class, IGroupedObject { - /// - /// A list of objects that are keyed. - /// - public class GroupedList : - IGroupedList - where TItem : class, IGroupedObject - { - private readonly List> _lists = new List>(); - private readonly Dictionary> _dictionary = new Dictionary>(); + private readonly List> _lists = new List>(); + private readonly Dictionary> _dictionary = new Dictionary>(); - private IMultiLinkedList EnsureList(TGroup group) + private IMultiLinkedList EnsureList(TGroup group) + { + if (group == null) { - if (group == null) - { - return null; - } - - if (_dictionary.ContainsKey(group)) - { - return _dictionary[group]; - } - - var list = new MultiLinkedList(); - _dictionary[group] = list; - - _lists.Add(list); - return list; + return null; } - private IMultiLinkedList ListForIndex(int index, out int relativeIndex) + if (_dictionary.ContainsKey(group)) { - var list = _lists.FirstOrDefault(l => l.StartIndex <= index && l.ExclusiveEnd > index); - if (list != null) - { - relativeIndex = index - list.StartIndex; - return list; - } - relativeIndex = -1; - return null; + return _dictionary[group]; } - public event EventHandler> ItemAdded; + var list = new MultiLinkedList(); + _dictionary[group] = list; - protected void OnItemAdded(TItem obj, int index) + _lists.Add(list); + return list; + } + + private IMultiLinkedList ListForIndex(int index, out int relativeIndex) + { + var list = _lists.FirstOrDefault(l => l.StartIndex <= index && l.ExclusiveEnd > index); + if (list != null) { - ItemAdded?.Invoke(this, new ObjectEventArgs(obj, index)); + relativeIndex = index - list.StartIndex; + return list; } + relativeIndex = -1; + return null; + } - public virtual void Add(TItem item) - { - if (item == null) - { - return; - } + public event EventHandler> ItemAdded; - // Add a new list if necessary - var group = item.Group; - var list = EnsureList(group); - var index = list.Count; - list.Add(item); - OnItemAdded(item, list.StartIndex + index); - } + protected void OnItemAdded(TItem obj, int index) + { + ItemAdded?.Invoke(this, new ObjectEventArgs(obj, index)); + } - public virtual int IndexOf(TItem item) + public virtual void Add(TItem item) + { + if (item == null) { - var group = item.Group; - if (!_dictionary.ContainsKey(group)) - { - return -1; - } - - // Get the list associated with this object's group - var list = _dictionary[group]; + return; + } - // Find the object within the list. - var index = list.IndexOf(item); + // Add a new list if necessary + var group = item.Group; + var list = EnsureList(group); + var index = list.Count; + list.Add(item); + OnItemAdded(item, list.StartIndex + index); + } - // Return the index within the overall KeyedList - if (index >= 0) - return list.StartIndex + index; + public virtual int IndexOf(TItem item) + { + var group = item.Group; + if (!_dictionary.ContainsKey(group)) + { return -1; } - public virtual void Clear(TGroup group) - { - if (!_dictionary.ContainsKey(group)) - { - return; - } + // Get the list associated with this object's group + var list = _dictionary[group]; - // Clear the list (note that this also clears the list in the _Lists object). - _dictionary[group].Clear(); - } + // Find the object within the list. + var index = list.IndexOf(item); - public virtual void Clear() + // Return the index within the overall KeyedList + if (index >= 0) + return list.StartIndex + index; + return -1; + } + + public virtual void Clear(TGroup group) + { + if (!_dictionary.ContainsKey(group)) { - _dictionary.Clear(); - _lists.Clear(); + return; } - public virtual bool ContainsKey(TGroup group) => _dictionary.ContainsKey(@group); + // Clear the list (note that this also clears the list in the _Lists object). + _dictionary[group].Clear(); + } - public virtual int Count => _lists.Sum(list => list.Count); + public virtual void Clear() + { + _dictionary.Clear(); + _lists.Clear(); + } - public virtual int CountOf(TGroup group) => _dictionary.ContainsKey(group) - ? _dictionary[group].Count - : 0; + public virtual bool ContainsKey(TGroup group) => _dictionary.ContainsKey(@group); - public virtual IEnumerable Values() => _dictionary.Values.SelectMany(i => i); + public virtual int Count => _lists.Sum(list => list.Count); - public virtual IEnumerable AllOf(TGroup group) => _dictionary.ContainsKey(@group) - ? (IEnumerable)_dictionary[@group] - : new TItem[0]; + public virtual int CountOf(TGroup group) => _dictionary.ContainsKey(group) + ? _dictionary[group].Count + : 0; - public virtual bool Remove(TItem obj) - { - var group = obj.Group; - if (!_dictionary.ContainsKey(group)) - { - return false; - } + public virtual IEnumerable Values() => _dictionary.Values.SelectMany(i => i); - var items = _dictionary[group]; - var index = items.IndexOf(obj); + public virtual IEnumerable AllOf(TGroup group) => _dictionary.ContainsKey(@group) + ? (IEnumerable) _dictionary[@group] + : new TItem[0]; - if (index < 0) - { - return false; - } - - items.RemoveAt(index); - return true; + public virtual bool Remove(TItem obj) + { + var group = obj.Group; + if (!_dictionary.ContainsKey(group)) + { + return false; } - public virtual bool Remove(TGroup group) + var items = _dictionary[group]; + var index = items.IndexOf(obj); + + if (index < 0) { - if (!_dictionary.ContainsKey(group)) - { - return false; - } + return false; + } - var list = _dictionary[group]; - for (var i = list.Count - 1; i >= 0; i--) - { - list.RemoveAt(i); - } - return true; + items.RemoveAt(index); + return true; + } + + public virtual bool Remove(TGroup group) + { + if (!_dictionary.ContainsKey(group)) + { + return false; } - public virtual bool Contains(TItem item) + var list = _dictionary[group]; + for (var i = list.Count - 1; i >= 0; i--) { - var group = item.Group; - return _dictionary.ContainsKey(group) && _dictionary[group].Contains(item); + list.RemoveAt(i); } + return true; + } + + public virtual bool Contains(TItem item) + { + var group = item.Group; + return _dictionary.ContainsKey(group) && _dictionary[group].Contains(item); + } - public virtual void CopyTo(TItem[] array, int arrayIndex) + public virtual void CopyTo(TItem[] array, int arrayIndex) + { + _dictionary.SelectMany(kvp => kvp.Value).ToArray().CopyTo(array, arrayIndex); + } + + public virtual bool IsReadOnly => false; + + public virtual void Insert(int index, TItem item) + { + int relativeIndex; + var list = ListForIndex(index, out relativeIndex); + if (list == null) { - _dictionary.SelectMany(kvp => kvp.Value).ToArray().CopyTo(array, arrayIndex); + return; } - public virtual bool IsReadOnly => false; + list.Insert(relativeIndex, item); + OnItemAdded(item, index); + } - public virtual void Insert(int index, TItem item) + public virtual void RemoveAt(int index) + { + int relativeIndex; + var list = ListForIndex(index, out relativeIndex); + if (list == null) + { + return; + } + var item = list[relativeIndex]; + list.RemoveAt(relativeIndex); + } + + public virtual TItem this[int index] + { + get { int relativeIndex; var list = ListForIndex(index, out relativeIndex); - if (list == null) - { - return; - } - - list.Insert(relativeIndex, item); - OnItemAdded(item, index); + return list?[relativeIndex]; } - - public virtual void RemoveAt(int index) + set { int relativeIndex; var list = ListForIndex(index, out relativeIndex); @@ -188,37 +213,16 @@ public virtual void RemoveAt(int index) { return; } + + // Remove the item at that index and replace it var item = list[relativeIndex]; list.RemoveAt(relativeIndex); + list.Insert(relativeIndex, value); + OnItemAdded(item, index); } + } - public virtual TItem this[int index] - { - get - { - int relativeIndex; - var list = ListForIndex(index, out relativeIndex); - return list?[relativeIndex]; - } - set - { - int relativeIndex; - var list = ListForIndex(index, out relativeIndex); - if (list == null) - { - return; - } - - // Remove the item at that index and replace it - var item = list[relativeIndex]; - list.RemoveAt(relativeIndex); - list.Insert(relativeIndex, value); - OnItemAdded(item, index); - } - } - - public IEnumerator GetEnumerator() => new GroupedListEnumerator(_lists); + public IEnumerator GetEnumerator() => new GroupedListEnumerator(_lists); - IEnumerator IEnumerable.GetEnumerator() => new GroupedListEnumerator(_lists); - } -} + IEnumerator IEnumerable.GetEnumerator() => new GroupedListEnumerator(_lists); +} \ No newline at end of file diff --git a/Ical.Net/Collections/GroupedListEnumerator.cs b/Ical.Net/Collections/GroupedListEnumerator.cs index 6bcbd4ff1..e832e0ead 100644 --- a/Ical.Net/Collections/GroupedListEnumerator.cs +++ b/Ical.Net/Collections/GroupedListEnumerator.cs @@ -1,105 +1,109 @@ -using System.Collections; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System.Collections; using System.Collections.Generic; -namespace Ical.Net.Collections +namespace Ical.Net.Collections; + +public class GroupedListEnumerator : + IEnumerator { - public class GroupedListEnumerator : - IEnumerator - { - private readonly IList> _lists; - private IEnumerator> _listsEnumerator; - private IEnumerator _listEnumerator; + private readonly IList> _lists; + private IEnumerator> _listsEnumerator; + private IEnumerator _listEnumerator; - public GroupedListEnumerator(IList> lists) => _lists = lists; + public GroupedListEnumerator(IList> lists) => _lists = lists; - public virtual TType Current - => _listEnumerator == null - ? default(TType) - : _listEnumerator.Current; + public virtual TType Current + => _listEnumerator == null + ? default(TType) + : _listEnumerator.Current; + + public virtual void Dispose() + { + Reset(); + } - public virtual void Dispose() + private void DisposeListEnumerator() + { + if (_listEnumerator == null) { - Reset(); + return; } + _listEnumerator.Dispose(); + _listEnumerator = null; + } - private void DisposeListEnumerator() + object IEnumerator.Current + => _listEnumerator == null + ? default(TType) + : _listEnumerator.Current; + + private bool MoveNextList() + { + if (_listsEnumerator == null) { - if (_listEnumerator == null) - { - return; - } - _listEnumerator.Dispose(); - _listEnumerator = null; + _listsEnumerator = _lists.GetEnumerator(); } - object IEnumerator.Current - => _listEnumerator == null - ? default(TType) - : _listEnumerator.Current; + if (_listsEnumerator == null) + { + return false; + } - private bool MoveNextList() + if (!_listsEnumerator.MoveNext()) { - if (_listsEnumerator == null) - { - _listsEnumerator = _lists.GetEnumerator(); - } + return false; + } - if (_listsEnumerator == null) - { - return false; - } + DisposeListEnumerator(); + if (_listsEnumerator.Current == null) + { + return false; + } - if (!_listsEnumerator.MoveNext()) - { - return false; - } + _listEnumerator = _listsEnumerator.Current.GetEnumerator(); + return true; + } - DisposeListEnumerator(); - if (_listsEnumerator.Current == null) + public virtual bool MoveNext() + { + while (true) + { + if (_listEnumerator == null) { - return false; + if (MoveNextList()) + { + continue; + } } - - _listEnumerator = _listsEnumerator.Current.GetEnumerator(); - return true; - } - - public virtual bool MoveNext() - { - while (true) + else { - if (_listEnumerator == null) + if (_listEnumerator.MoveNext()) { - if (MoveNextList()) - { - continue; - } + return true; } - else + DisposeListEnumerator(); + if (MoveNextList()) { - if (_listEnumerator.MoveNext()) - { - return true; - } - DisposeListEnumerator(); - if (MoveNextList()) - { - continue; - } + continue; } - return false; } + return false; } + } - public virtual void Reset() + public virtual void Reset() + { + if (_listsEnumerator == null) { - if (_listsEnumerator == null) - { - return; - } - - _listsEnumerator.Dispose(); - _listsEnumerator = null; + return; } + + _listsEnumerator.Dispose(); + _listsEnumerator = null; } -} +} \ No newline at end of file diff --git a/Ical.Net/Collections/GroupedValueList.cs b/Ical.Net/Collections/GroupedValueList.cs index 19ce810b8..e50caa63f 100644 --- a/Ical.Net/Collections/GroupedValueList.cs +++ b/Ical.Net/Collections/GroupedValueList.cs @@ -1,49 +1,53 @@ -using Ical.Net.Collections.Interfaces; -using Ical.Net.Collections.Proxies; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using Ical.Net.Collections.Interfaces; +using Ical.Net.Collections.Proxies; + +namespace Ical.Net.Collections; -namespace Ical.Net.Collections +public class GroupedValueList : + GroupedList + where TInterface : class, IGroupedObject, IValueObject + where TItem : new() { - public class GroupedValueList : - GroupedList - where TInterface : class, IGroupedObject, IValueObject - where TItem : new() + public virtual void Set(TGroup group, TValueType value) { - public virtual void Set(TGroup group, TValueType value) - { - Set(group, new[] { value }); - } + Set(group, new[] { value }); + } - public virtual void Set(TGroup group, IEnumerable values) + public virtual void Set(TGroup group, IEnumerable values) + { + if (ContainsKey(group)) { - if (ContainsKey(group)) - { - AllOf(group)?.FirstOrDefault()?.SetValue(values); - return; - } - - // No matching item was found, add a new item to the list - var obj = Activator.CreateInstance(typeof(TItem)) as TInterface; - obj.Group = group; - obj.SetValue(values); - Add(obj); + AllOf(group)?.FirstOrDefault()?.SetValue(values); + return; } - public virtual TType Get(TGroup group) + // No matching item was found, add a new item to the list + var obj = Activator.CreateInstance(typeof(TItem)) as TInterface; + obj.Group = group; + obj.SetValue(values); + Add(obj); + } + + public virtual TType Get(TGroup group) + { + var firstItem = AllOf(group).FirstOrDefault(); + if (firstItem?.Values != null) { - var firstItem = AllOf(group).FirstOrDefault(); - if (firstItem?.Values != null) - { - return firstItem - .Values - .OfType() - .FirstOrDefault(); - } - return default(TType); + return firstItem + .Values + .OfType() + .FirstOrDefault(); } - - public virtual IList GetMany(TGroup group) => new GroupedValueListProxy(this, group); + return default(TType); } -} + + public virtual IList GetMany(TGroup group) => new GroupedValueListProxy(this, group); +} \ No newline at end of file diff --git a/Ical.Net/Collections/IGroupedCollection.cs b/Ical.Net/Collections/IGroupedCollection.cs index 4682f54f9..d41a17fbc 100644 --- a/Ical.Net/Collections/IGroupedCollection.cs +++ b/Ical.Net/Collections/IGroupedCollection.cs @@ -1,44 +1,48 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System; using System.Collections.Generic; -namespace Ical.Net.Collections +namespace Ical.Net.Collections; + +public interface IGroupedCollection : + ICollection + where TItem : class, IGroupedObject { - public interface IGroupedCollection : - ICollection - where TItem : class, IGroupedObject - { - /// - /// Fired after an item is added to the collection. - /// - event EventHandler> ItemAdded; - - /// - /// Removes all items with the matching group from the collection. - /// - /// True if the object was removed, false otherwise. - bool Remove(TGroup group); - - /// - /// Clears all items matching the specified group. - /// - void Clear(TGroup group); - - /// - /// Returns true if the list contains at least one - /// object with a matching group, false otherwise. - /// - bool ContainsKey(TGroup group); - - /// - /// Returns the number of objects in the list - /// with a matching group. - /// - int CountOf(TGroup group); - - /// - /// Returns a list of objects that - /// match the specified group. - /// - IEnumerable AllOf(TGroup group); - } -} + /// + /// Fired after an item is added to the collection. + /// + event EventHandler> ItemAdded; + + /// + /// Removes all items with the matching group from the collection. + /// + /// True if the object was removed, false otherwise. + bool Remove(TGroup group); + + /// + /// Clears all items matching the specified group. + /// + void Clear(TGroup group); + + /// + /// Returns true if the list contains at least one + /// object with a matching group, false otherwise. + /// + bool ContainsKey(TGroup group); + + /// + /// Returns the number of objects in the list + /// with a matching group. + /// + int CountOf(TGroup group); + + /// + /// Returns a list of objects that + /// match the specified group. + /// + IEnumerable AllOf(TGroup group); +} \ No newline at end of file diff --git a/Ical.Net/Collections/IGroupedList.cs b/Ical.Net/Collections/IGroupedList.cs index 7ce39921b..d38a5a584 100644 --- a/Ical.Net/Collections/IGroupedList.cs +++ b/Ical.Net/Collections/IGroupedList.cs @@ -1,22 +1,26 @@ -using System.Collections.Generic; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net.Collections +using System.Collections.Generic; + +namespace Ical.Net.Collections; + +public interface IGroupedList : + IGroupedCollection, + IList + where TItem : class, IGroupedObject { - public interface IGroupedList : - IGroupedCollection, - IList - where TItem : class, IGroupedObject - { - /// - /// Returns the index of the given item - /// within the list, or -1 if the item - /// is not found in the list. - /// - new int IndexOf(TItem obj); + /// + /// Returns the index of the given item + /// within the list, or -1 if the item + /// is not found in the list. + /// + new int IndexOf(TItem obj); - /// - /// Gets the object at the specified index. - /// - new TItem this[int index] { get; } - } -} + /// + /// Gets the object at the specified index. + /// + new TItem this[int index] { get; } +} \ No newline at end of file diff --git a/Ical.Net/Collections/IGroupedObject.cs b/Ical.Net/Collections/IGroupedObject.cs index 5ee400b63..17e491179 100644 --- a/Ical.Net/Collections/IGroupedObject.cs +++ b/Ical.Net/Collections/IGroupedObject.cs @@ -1,7 +1,11 @@ -namespace Ical.Net.Collections +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +namespace Ical.Net.Collections; + +public interface IGroupedObject { - public interface IGroupedObject - { - TGroup Group { get; set; } - } -} + TGroup Group { get; set; } +} \ No newline at end of file diff --git a/Ical.Net/Collections/IMultiLinkedList.cs b/Ical.Net/Collections/IMultiLinkedList.cs index 07bce5e03..c131dd3dc 100644 --- a/Ical.Net/Collections/IMultiLinkedList.cs +++ b/Ical.Net/Collections/IMultiLinkedList.cs @@ -1,11 +1,15 @@ -using System.Collections.Generic; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net.Collections +using System.Collections.Generic; + +namespace Ical.Net.Collections; + +public interface IMultiLinkedList : + IList { - public interface IMultiLinkedList : - IList - { - int StartIndex { get; } - int ExclusiveEnd { get; } - } -} + int StartIndex { get; } + int ExclusiveEnd { get; } +} \ No newline at end of file diff --git a/Ical.Net/Collections/Interfaces/IValueObject.cs b/Ical.Net/Collections/Interfaces/IValueObject.cs index d6c61e9d2..7e02f03f7 100644 --- a/Ical.Net/Collections/Interfaces/IValueObject.cs +++ b/Ical.Net/Collections/Interfaces/IValueObject.cs @@ -1,16 +1,20 @@ -using System.Collections.Generic; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net.Collections.Interfaces +using System.Collections.Generic; + +namespace Ical.Net.Collections.Interfaces; + +public interface IValueObject { - public interface IValueObject - { - IEnumerable Values { get; } + IEnumerable Values { get; } - bool ContainsValue(T value); - void SetValue(T value); - void SetValue(IEnumerable values); - void AddValue(T value); - void RemoveValue(T value); - int ValueCount { get; } - } -} + bool ContainsValue(T value); + void SetValue(T value); + void SetValue(IEnumerable values); + void AddValue(T value); + void RemoveValue(T value); + int ValueCount { get; } +} \ No newline at end of file diff --git a/Ical.Net/Collections/MultiLinkedList.cs b/Ical.Net/Collections/MultiLinkedList.cs index 9cee33596..3a768224b 100644 --- a/Ical.Net/Collections/MultiLinkedList.cs +++ b/Ical.Net/Collections/MultiLinkedList.cs @@ -1,26 +1,30 @@ -using System.Collections.Generic; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net.Collections -{ - public class MultiLinkedList : - List, - IMultiLinkedList - { - private IMultiLinkedList _previous; - private IMultiLinkedList _next; +using System.Collections.Generic; - public virtual void SetPrevious(IMultiLinkedList previous) - { - _previous = previous; - } +namespace Ical.Net.Collections; - public virtual void SetNext(IMultiLinkedList next) - { - _next = next; - } +public class MultiLinkedList : + List, + IMultiLinkedList +{ + private IMultiLinkedList _previous; + private IMultiLinkedList _next; - public virtual int StartIndex => _previous?.ExclusiveEnd ?? 0; + public virtual void SetPrevious(IMultiLinkedList previous) + { + _previous = previous; + } - public virtual int ExclusiveEnd => Count > 0 ? StartIndex + Count : StartIndex; + public virtual void SetNext(IMultiLinkedList next) + { + _next = next; } -} + + public virtual int StartIndex => _previous?.ExclusiveEnd ?? 0; + + public virtual int ExclusiveEnd => Count > 0 ? StartIndex + Count : StartIndex; +} \ No newline at end of file diff --git a/Ical.Net/Collections/ObjectEventArgs.cs b/Ical.Net/Collections/ObjectEventArgs.cs index c65b02d99..a47f7b314 100644 --- a/Ical.Net/Collections/ObjectEventArgs.cs +++ b/Ical.Net/Collections/ObjectEventArgs.cs @@ -1,17 +1,21 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net.Collections +using System; + +namespace Ical.Net.Collections; + +public class ObjectEventArgs : + EventArgs { - public class ObjectEventArgs : - EventArgs - { - public T First { get; set; } - public TU Second { get; set; } + public T First { get; set; } + public TU Second { get; set; } - public ObjectEventArgs(T first, TU second) - { - First = first; - Second = second; - } + public ObjectEventArgs(T first, TU second) + { + First = first; + Second = second; } -} +} \ No newline at end of file diff --git a/Ical.Net/Collections/Proxies/GroupedCollectionProxy.cs b/Ical.Net/Collections/Proxies/GroupedCollectionProxy.cs index cb75e0370..f6bf53966 100644 --- a/Ical.Net/Collections/Proxies/GroupedCollectionProxy.cs +++ b/Ical.Net/Collections/Proxies/GroupedCollectionProxy.cs @@ -1,107 +1,111 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System; using System.Collections; using System.Collections.Generic; using System.Linq; -namespace Ical.Net.Collections.Proxies +namespace Ical.Net.Collections.Proxies; + +/// +/// A proxy for a keyed list. +/// +public class GroupedCollectionProxy : + IGroupedCollection + where TOriginal : class, IGroupedObject + where TNew : class, TOriginal { - /// - /// A proxy for a keyed list. - /// - public class GroupedCollectionProxy : - IGroupedCollection - where TOriginal : class, IGroupedObject - where TNew : class, TOriginal + private readonly Func _predicate; + + public GroupedCollectionProxy(IGroupedCollection realObject, Func predicate = null) { - private readonly Func _predicate; + _predicate = predicate ?? (o => true); + SetProxiedObject(realObject); + } - public GroupedCollectionProxy(IGroupedCollection realObject, Func predicate = null) - { - _predicate = predicate ?? (o => true); - SetProxiedObject(realObject); - } + public virtual event EventHandler> ItemAdded; + public virtual event EventHandler> ItemRemoved; - public virtual event EventHandler> ItemAdded; - public virtual event EventHandler> ItemRemoved; + protected void OnItemAdded(TNew item, int index) + { + ItemAdded?.Invoke(this, new ObjectEventArgs(item, index)); + } - protected void OnItemAdded(TNew item, int index) - { - ItemAdded?.Invoke(this, new ObjectEventArgs(item, index)); - } + protected void OnItemRemoved(TNew item, int index) + { + ItemRemoved?.Invoke(this, new ObjectEventArgs(item, index)); + } - protected void OnItemRemoved(TNew item, int index) - { - ItemRemoved?.Invoke(this, new ObjectEventArgs(item, index)); - } + public virtual bool Remove(TGroup group) => RealObject.Remove(group); - public virtual bool Remove(TGroup group) => RealObject.Remove(group); + public virtual void Clear(TGroup group) + { + RealObject.Clear(group); + } - public virtual void Clear(TGroup group) - { - RealObject.Clear(group); - } + public virtual bool ContainsKey(TGroup group) => RealObject.ContainsKey(group); - public virtual bool ContainsKey(TGroup group) => RealObject.ContainsKey(group); + public virtual int CountOf(TGroup group) => RealObject.OfType().Count(); - public virtual int CountOf(TGroup group) => RealObject.OfType().Count(); + public virtual IEnumerable AllOf(TGroup group) => RealObject + .AllOf(group) + .OfType() + .Where(_predicate); - public virtual IEnumerable AllOf(TGroup group) => RealObject - .AllOf(group) - .OfType() - .Where(_predicate); - - public virtual void Add(TNew item) - { - RealObject.Add(item); - } + public virtual void Add(TNew item) + { + RealObject.Add(item); + } - public virtual void Clear() - { - // Only clear items of this type - // that match the predicate. + public virtual void Clear() + { + // Only clear items of this type + // that match the predicate. - var items = RealObject - .OfType() - .ToArray(); + var items = RealObject + .OfType() + .ToArray(); - foreach (var item in items) - { - RealObject.Remove(item); - } + foreach (var item in items) + { + RealObject.Remove(item); } + } - public virtual bool Contains(TNew item) => RealObject.Contains(item); + public virtual bool Contains(TNew item) => RealObject.Contains(item); - public virtual void CopyTo(TNew[] array, int arrayIndex) + public virtual void CopyTo(TNew[] array, int arrayIndex) + { + var i = 0; + foreach (var item in this) { - var i = 0; - foreach (var item in this) - { - array[arrayIndex + (i++)] = item; - } + array[arrayIndex + (i++)] = item; } + } - public virtual int Count => RealObject - .OfType() - .Count(); + public virtual int Count => RealObject + .OfType() + .Count(); - public virtual bool IsReadOnly => false; + public virtual bool IsReadOnly => false; - public virtual bool Remove(TNew item) => RealObject.Remove(item); + public virtual bool Remove(TNew item) => RealObject.Remove(item); - public virtual IEnumerator GetEnumerator() => RealObject - .OfType() - .GetEnumerator(); + public virtual IEnumerator GetEnumerator() => RealObject + .OfType() + .GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => RealObject - .OfType() - .GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => RealObject + .OfType() + .GetEnumerator(); - public IGroupedCollection RealObject { get; private set; } + public IGroupedCollection RealObject { get; private set; } - public virtual void SetProxiedObject(IGroupedCollection realObject) - { - RealObject = realObject; - } + public virtual void SetProxiedObject(IGroupedCollection realObject) + { + RealObject = realObject; } -} +} \ No newline at end of file diff --git a/Ical.Net/Collections/Proxies/GroupedValueListProxy.cs b/Ical.Net/Collections/Proxies/GroupedValueListProxy.cs index 349a3ae28..4b59948b3 100644 --- a/Ical.Net/Collections/Proxies/GroupedValueListProxy.cs +++ b/Ical.Net/Collections/Proxies/GroupedValueListProxy.cs @@ -1,232 +1,236 @@ -using Ical.Net.Collections.Interfaces; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using Ical.Net.Collections.Interfaces; + +namespace Ical.Net.Collections.Proxies; -namespace Ical.Net.Collections.Proxies +/// +/// A proxy for a keyed list. +/// +public class GroupedValueListProxy : IList + where TInterface : class, IGroupedObject, IValueObject + where TItem : new() { - /// - /// A proxy for a keyed list. - /// - public class GroupedValueListProxy : IList - where TInterface : class, IGroupedObject, IValueObject - where TItem : new() + private readonly GroupedValueList _realObject; + private readonly TGroup _group; + private TInterface _container; + + public GroupedValueListProxy(GroupedValueList realObject, TGroup group) { - private readonly GroupedValueList _realObject; - private readonly TGroup _group; - private TInterface _container; + _realObject = realObject; + _group = group; + } - public GroupedValueListProxy(GroupedValueList realObject, TGroup group) + private TInterface EnsureContainer() + { + if (_container != null) { - _realObject = realObject; - _group = group; + return _container; } - private TInterface EnsureContainer() - { - if (_container != null) - { - return _container; - } + // Find an item that matches our group + _container = Items.FirstOrDefault(); - // Find an item that matches our group - _container = Items.FirstOrDefault(); - - // If no item is found, create a new object and add it to the list - if (!Equals(_container, default(TInterface))) - { - return _container; - } - var container = new TItem(); - if (!(container is TInterface)) - { - throw new Exception("Could not create a container for the value - the container is not of type " + typeof(TInterface).Name); - } - - _container = (TInterface)(object)container; - _container.Group = _group; - _realObject.Add(_container); + // If no item is found, create a new object and add it to the list + if (!Equals(_container, default(TInterface))) + { return _container; } + var container = new TItem(); + if (!(container is TInterface)) + { + throw new Exception("Could not create a container for the value - the container is not of type " + typeof(TInterface).Name); + } - private void IterateValues(Func, int, int, bool> action) + _container = (TInterface) (object) container; + _container.Group = _group; + _realObject.Add(_container); + return _container; + } + + private void IterateValues(Func, int, int, bool> action) + { + var i = 0; + foreach (var obj in _realObject) { - var i = 0; - foreach (var obj in _realObject) - { - // Get the number of items of the target value i this object - var count = obj.Values?.OfType().Count() ?? 0; + // Get the number of items of the target value i this object + var count = obj.Values?.OfType().Count() ?? 0; - // Perform some action on this item - if (!action(obj, i, count)) - return; + // Perform some action on this item + if (!action(obj, i, count)) + return; - i += count; - } + i += count; } + } + + private IEnumerator GetEnumeratorInternal() + { + return Items + .Where(o => o.ValueCount > 0) + .SelectMany(o => o.Values.OfType()) + .GetEnumerator(); + } - private IEnumerator GetEnumeratorInternal() + public virtual void Add(TNewValue item) + { + // Add the value to the object + if (item is TOriginalValue) { - return Items - .Where(o => o.ValueCount > 0) - .SelectMany(o => o.Values.OfType()) - .GetEnumerator(); + var value = (TOriginalValue) (object) item; + EnsureContainer().AddValue(value); } + } - public virtual void Add(TNewValue item) + public virtual void Clear() + { + var items = Items.Where(o => o.Values != null); + + foreach (var original in items) { - // Add the value to the object - if (item is TOriginalValue) - { - var value = (TOriginalValue)(object)item; - EnsureContainer().AddValue(value); - } + // Clear all values from each matching object + original.SetValue(default(TOriginalValue)); } + } - public virtual void Clear() - { - var items = Items.Where(o => o.Values != null); + public virtual bool Contains(TNewValue item) => Items.Any(o => o.ContainsValue((TOriginalValue) (object) item)); - foreach (var original in items) - { - // Clear all values from each matching object - original.SetValue(default(TOriginalValue)); - } + public virtual void CopyTo(TNewValue[] array, int arrayIndex) + { + Items + .Where(o => o.Values != null) + .SelectMany(o => o.Values) + .ToArray() + .CopyTo(array, arrayIndex); + } + + public virtual int Count => Items.Sum(o => o.ValueCount); + + public virtual bool IsReadOnly => false; + + public virtual bool Remove(TNewValue item) + { + if (!(item is TOriginalValue)) + { + return false; } - public virtual bool Contains(TNewValue item) => Items.Any(o => o.ContainsValue((TOriginalValue)(object)item)); + var value = (TOriginalValue) (object) item; + var container = Items.FirstOrDefault(o => o.ContainsValue(value)); - public virtual void CopyTo(TNewValue[] array, int arrayIndex) + if (container == null) { - Items - .Where(o => o.Values != null) - .SelectMany(o => o.Values) - .ToArray() - .CopyTo(array, arrayIndex); + return false; } - public virtual int Count => Items.Sum(o => o.ValueCount); + container.RemoveValue(value); + return true; + } + + public virtual IEnumerator GetEnumerator() => GetEnumeratorInternal(); - public virtual bool IsReadOnly => false; + IEnumerator IEnumerable.GetEnumerator() => GetEnumeratorInternal(); - public virtual bool Remove(TNewValue item) - { - if (!(item is TOriginalValue)) - { - return false; - } + public virtual int IndexOf(TNewValue item) + { + var index = -1; - var value = (TOriginalValue)(object)item; - var container = Items.FirstOrDefault(o => o.ContainsValue(value)); + if (!(item is TOriginalValue)) + { + return index; + } - if (container == null) + var value = (TOriginalValue) (object) item; + IterateValues((o, i, count) => + { + if (o.Values != null && o.Values.Contains(value)) { + var list = o.Values.ToList(); + index = i + list.IndexOf(value); return false; } - - container.RemoveValue(value); return true; - } + }); - public virtual IEnumerator GetEnumerator() => GetEnumeratorInternal(); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumeratorInternal(); + return index; + } - public virtual int IndexOf(TNewValue item) + public virtual void Insert(int index, TNewValue item) + { + IterateValues((o, i, count) => { - var index = -1; - - if (!(item is TOriginalValue)) - { - return index; - } + var value = (TOriginalValue) (object) item; - var value = (TOriginalValue)(object)item; - IterateValues((o, i, count) => + // Determine if this index is found within this object + if (index < i || index >= count) { - if (o.Values != null && o.Values.Contains(value)) - { - var list = o.Values.ToList(); - index = i + list.IndexOf(value); - return false; - } return true; - }); + } - return index; - } + // Convert the items to a list + var items = o.Values.ToList(); + // Insert the item at the relative index within the list + items.Insert(index - i, value); + // Set the new list + o.SetValue(items); + return false; + }); + } - public virtual void Insert(int index, TNewValue item) + public virtual void RemoveAt(int index) + { + IterateValues((o, i, count) => { - IterateValues((o, i, count) => + // Determine if this index is found within this object + if (index >= i && index < count) { - var value = (TOriginalValue)(object)item; - - // Determine if this index is found within this object - if (index < i || index >= count) - { - return true; - } - // Convert the items to a list var items = o.Values.ToList(); - // Insert the item at the relative index within the list - items.Insert(index - i, value); + // Remove the item at the relative index within the list + items.RemoveAt(index - i); // Set the new list o.SetValue(items); return false; - }); - } + } + return true; + }); + } - public virtual void RemoveAt(int index) + public virtual TNewValue this[int index] + { + get { - IterateValues((o, i, count) => + if (index >= 0 && index < Count) { - // Determine if this index is found within this object - if (index >= i && index < count) - { - // Convert the items to a list - var items = o.Values.ToList(); - // Remove the item at the relative index within the list - items.RemoveAt(index - i); - // Set the new list - o.SetValue(items); - return false; - } - return true; - }); + return Items + .SelectMany(i => i.Values?.OfType()) + .Skip(index) + .FirstOrDefault(); + } + return default(TNewValue); } - - public virtual TNewValue this[int index] + set { - get + if (index >= 0 && index < Count) { - if (index >= 0 && index < Count) + if (!Equals(value, default(TNewValue))) { - return Items - .SelectMany(i => i.Values?.OfType()) - .Skip(index) - .FirstOrDefault(); - } - return default(TNewValue); - } - set - { - if (index >= 0 && index < Count) - { - if (!Equals(value, default(TNewValue))) - { - Insert(index, value); - index++; - } - RemoveAt(index); + Insert(index, value); + index++; } + RemoveAt(index); } } - - public virtual IEnumerable Items => _group == null - ? _realObject - : _realObject.AllOf(_group); } -} + + public virtual IEnumerable Items => _group == null + ? _realObject + : _realObject.AllOf(_group); +} \ No newline at end of file diff --git a/Ical.Net/Constants.cs b/Ical.Net/Constants.cs index 0485f61ea..7557dccd2 100644 --- a/Ical.Net/Constants.cs +++ b/Ical.Net/Constants.cs @@ -1,373 +1,377 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net +using System; + +namespace Ical.Net; + +public static class AlarmAction +{ + public const string Name = "ACTION"; + public const string Key = "ACTION"; + public static readonly StringComparison Comparison = StringComparison.Ordinal; + + public const string Audio = "AUDIO"; + public const string Display = "DISPLAY"; + public const string Email = "EMAIL"; + + [Obsolete("Procedure was deprecated by RFC-5545")] + public const string Procedure = "PROCEDURE"; +} + +public static class TriggerRelation +{ + public const string Name = "TRIGGER"; + public const string Key = "TRIGGER"; + public static readonly StringComparison Comparison = StringComparison.Ordinal; + + public const string Start = "START"; + public const string End = "END"; +} + +public static class Components +{ + public const string Alarm = "VALARM"; + public const string Calendar = "VCALENDAR"; + public const string Freebusy = "VFREEBUSY"; + public const string Timezone = "VTIMEZONE"; + public const string Daylight = "DAYLIGHT"; + public const string Standard = "STANDARD"; +} + +public static class EventParticipationStatus +{ + public const string Name = "PARTSTAT"; + public const string Key = "PARTSTAT"; + public static readonly StringComparison Comparison = StringComparison.Ordinal; + + /// Event needs action + public const string NeedsAction = "NEEDS-ACTION"; + /// Event accepted + public const string Accepted = "ACCEPTED"; + /// Event declined + public const string Declined = "DECLINED"; + /// Event tentatively accepted + public const string Tentative = "TENTATIVE"; + /// Event delegated + public const string Delegated = "DELEGATED"; + + public static string Default => NeedsAction; +} + +public static class ToDoParticipationStatus +{ + public const string Name = "PARTSTAT"; + public const string Key = "PARTSTAT"; + public static readonly StringComparison Comparison = StringComparison.Ordinal; + + /// To-do needs action + public const string NeedsAction = "NEEDS-ACTION"; + /// To-do accepted + public const string Accepted = "ACCEPTED"; + /// To-do declined + public const string Declined = "DECLINED"; + /// To-do tentatively accepted + public const string Tentative = "TENTATIVE"; + /// To-do delegated + public const string Delegated = "DELEGATED"; + /// To-do completed + public const string Completed = "COMPLETED"; + /// To-do in process + public const string InProcess = "IN-PROCESS"; + + public static string Default => NeedsAction; +} + +public static class JournalParticipationStatus +{ + public const string Name = "PARTSTAT"; + public const string Key = "PARTSTAT"; + public static readonly StringComparison Comparison = StringComparison.Ordinal; + + public const string NeedsAction = "NEEDS-ACTION"; + public const string Accepted = "ACCEPTED"; + public const string Declined = "DECLINED"; + + public static string Default => NeedsAction; +} + +public static class ParticipationRole +{ + public const string Role = "ROLE"; + + /// Indicates the chair of the calendar entity + public const string Chair = "CHAIR"; + + /// Indicates a participant whose participation is required + public const string RequiredParticipant = "REQ-PARTICIPANT"; + + /// Indicates a participant whose participation is optional + public const string OptionalParticipant = "OPT-PARTICIPANT"; + + /// Indicates a participant who is copied for information purposes only + public const string NonParticipant = "NON-PARTICIPANT"; + + public static string Default => RequiredParticipant; + public static string ParamName => Role; +} + +public class SerializationConstants +{ + public const string LineBreak = "\r\n"; +} + +/// +/// Status codes available to an item +/// +public static class EventStatus +{ + public const string Name = "VEVENT"; + public static readonly StringComparison Comparison = StringComparison.Ordinal; + + public const string Tentative = "TENTATIVE"; + public const string Confirmed = "CONFIRMED"; + public const string Cancelled = "CANCELLED"; +} + +/// +/// Status codes available to a item. +/// +public static class TodoStatus +{ + public const string Name = "VTODO"; + public const string Key = "STATUS"; + public static readonly StringComparison Comparison = StringComparison.Ordinal; + + public const string NeedsAction = "NEEDS-ACTION"; + public const string Completed = "COMPLETED"; + public const string InProcess = "IN-PROCESS"; + public const string Cancelled = "CANCELLED"; +} + +/// +/// Status codes available to a entry. +/// +public static class JournalStatus +{ + public const string Name = "VJOURNAL"; + public const string Key = "STATUS"; + public static readonly StringComparison Comparison = StringComparison.Ordinal; + + public const string Draft = "DRAFT"; + public const string Final = "FINAL"; + public const string Cancelled = "CANCELLED"; +} + +public enum FreeBusyStatus +{ + Free = 0, + BusyTentative = 1, + BusyUnavailable = 2, + Busy = 3 +} + +public enum FrequencyType +{ + None, + Secondly, + Minutely, + Hourly, + Daily, + Weekly, + Monthly, + Yearly +} + +/// +/// Indicates the occurrence of the specific day within a +/// MONTHLY or YEARLY recurrence frequency. For example, within +/// a MONTHLY frequency, consider the following: +/// +/// RecurrencePattern r = new RecurrencePattern(); +/// r.Frequency = FrequencyType.Monthly; +/// r.ByDay.Add(new WeekDay(DayOfWeek.Monday, FrequencyOccurrence.First)); +/// +/// The above example represents the first Monday within the month, +/// whereas if FrequencyOccurrence.Last were specified, it would +/// represent the last Monday of the month. +/// +/// For a YEARLY frequency, consider the following: +/// +/// Recur r = new Recur(); +/// r.Frequency = FrequencyType.Yearly; +/// r.ByDay.Add(new WeekDay(DayOfWeek.Monday, FrequencyOccurrence.Second)); +/// +/// The above example represents the second Monday of the year. This can +/// also be represented with the following code: +/// +/// r.ByDay.Add(new WeekDay(DayOfWeek.Monday, 2)); +/// +public enum FrequencyOccurrence +{ + None = int.MinValue, + Last = -1, + SecondToLast = -2, + ThirdToLast = -3, + FourthToLast = -4, + FifthToLast = -5, + First = 1, + Second = 2, + Third = 3, + Fourth = 4, + Fifth = 5 +} + +[Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] +public enum RecurrenceRestrictionType +{ + /// + /// Same as RestrictSecondly. + /// + Default, + + /// + /// Does not restrict recurrence evaluation - WARNING: this may cause very slow performance! + /// + NoRestriction, + + /// + /// Disallows use of the SECONDLY frequency for recurrence evaluation + /// + RestrictSecondly, + + /// + /// Disallows use of the MINUTELY and SECONDLY frequencies for recurrence evaluation + /// + RestrictMinutely, + + /// + /// Disallows use of the HOURLY, MINUTELY, and SECONDLY frequencies for recurrence evaluation + /// + RestrictHourly +} + +[Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] +public enum RecurrenceEvaluationModeType +{ + /// + /// Same as ThrowException. + /// + Default, + + /// + /// Automatically adjusts the evaluation to the next-best frequency based on the restriction type. + /// For example, if the restriction were IgnoreSeconds, and the frequency were SECONDLY, then + /// this would cause the frequency to be adjusted to MINUTELY, the next closest thing. + /// + AdjustAutomatically, + + /// + /// This will throw an exception if a recurrence rule is evaluated that does not meet the minimum + /// restrictions. For example, if the restriction were IgnoreSeconds, and a SECONDLY frequency + /// were evaluated, an exception would be thrown. + /// + ThrowException +} + +public static class TransparencyType { - public static class AlarmAction - { - public const string Name = "ACTION"; - public const string Key = "ACTION"; - public static readonly StringComparison Comparison = StringComparison.Ordinal; - - public const string Audio = "AUDIO"; - public const string Display = "DISPLAY"; - public const string Email = "EMAIL"; - - [Obsolete("Procedure was deprecated by RFC-5545")] - public const string Procedure = "PROCEDURE"; - } - - public static class TriggerRelation - { - public const string Name = "TRIGGER"; - public const string Key = "TRIGGER"; - public static readonly StringComparison Comparison = StringComparison.Ordinal; - - public const string Start = "START"; - public const string End = "END"; - } - - public static class Components - { - public const string Alarm = "VALARM"; - public const string Calendar = "VCALENDAR"; - public const string Freebusy = "VFREEBUSY"; - public const string Timezone = "VTIMEZONE"; - public const string Daylight = "DAYLIGHT"; - public const string Standard = "STANDARD"; - } - - public static class EventParticipationStatus - { - public const string Name = "PARTSTAT"; - public const string Key = "PARTSTAT"; - public static readonly StringComparison Comparison = StringComparison.Ordinal; - - /// Event needs action - public const string NeedsAction = "NEEDS-ACTION"; - /// Event accepted - public const string Accepted = "ACCEPTED"; - /// Event declined - public const string Declined = "DECLINED"; - /// Event tentatively accepted - public const string Tentative = "TENTATIVE"; - /// Event delegated - public const string Delegated = "DELEGATED"; - - public static string Default => NeedsAction; - } - - public static class ToDoParticipationStatus - { - public const string Name = "PARTSTAT"; - public const string Key = "PARTSTAT"; - public static readonly StringComparison Comparison = StringComparison.Ordinal; - - /// To-do needs action - public const string NeedsAction = "NEEDS-ACTION"; - /// To-do accepted - public const string Accepted = "ACCEPTED"; - /// To-do declined - public const string Declined = "DECLINED"; - /// To-do tentatively accepted - public const string Tentative = "TENTATIVE"; - /// To-do delegated - public const string Delegated = "DELEGATED"; - /// To-do completed - public const string Completed = "COMPLETED"; - /// To-do in process - public const string InProcess = "IN-PROCESS"; - - public static string Default => NeedsAction; - } - - public static class JournalParticipationStatus - { - public const string Name = "PARTSTAT"; - public const string Key = "PARTSTAT"; - public static readonly StringComparison Comparison = StringComparison.Ordinal; - - public const string NeedsAction = "NEEDS-ACTION"; - public const string Accepted = "ACCEPTED"; - public const string Declined = "DECLINED"; - - public static string Default => NeedsAction; - } - - public static class ParticipationRole - { - public const string Role = "ROLE"; - - /// Indicates the chair of the calendar entity - public const string Chair = "CHAIR"; - - /// Indicates a participant whose participation is required - public const string RequiredParticipant = "REQ-PARTICIPANT"; - - /// Indicates a participant whose participation is optional - public const string OptionalParticipant = "OPT-PARTICIPANT"; - - /// Indicates a participant who is copied for information purposes only - public const string NonParticipant = "NON-PARTICIPANT"; - - public static string Default => RequiredParticipant; - public static string ParamName => Role; - } - - public class SerializationConstants - { - public const string LineBreak = "\r\n"; - } + public const string Name = "TRANSP"; + public const string Key = "TRANSP"; + public static readonly StringComparison Comparison = StringComparison.Ordinal; + + public const string Opaque = "OPAQUE"; + public const string Transparent = "TRANSPARENT"; +} + +public static class LibraryMetadata +{ + public const string Version = "2.0"; + public static readonly string ProdId = "-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN"; +} + +public static class CalendarScales +{ + public const string Gregorian = "GREGORIAN"; +} + +public static class CalendarMethods +{ + /// + /// Used to publish an iCalendar object to one or + /// more "Calendar Users". There is no interactivity + /// between the publisher and any other "Calendar User". + /// An example might include a baseball team publishing + /// its schedule to the public. + /// + public const string Publish = "PUBLISH"; + + /// + /// Used to schedule an iCalendar object with other + /// "Calendar Users". Requests are interactive in + /// that they require the receiver to respond using + /// the reply methods. Meeting requests, busy-time + /// requests, and the assignment of tasks to other + /// "Calendar Users" are all examples. Requests are + /// also used by the Organizer to update the status + /// of an iCalendar object. + /// + public const string Request = "REQUEST"; + + /// + /// A reply is used in response to a request to + /// convey Attendee status to the Organizer. + /// Replies are commonly used to respond to meeting + /// and task requests. + /// + public const string Reply = "REPLY"; /// - /// Status codes available to an item + /// Add one or more new instances to an existing + /// recurring iCalendar object. /// - public static class EventStatus - { - public const string Name = "VEVENT"; - public static readonly StringComparison Comparison = StringComparison.Ordinal; + public const string Add = "ADD"; - public const string Tentative = "TENTATIVE"; - public const string Confirmed = "CONFIRMED"; - public const string Cancelled = "CANCELLED"; - } + /// + /// Cancel one or more instances of an existing + /// iCalendar object. + /// + public const string Cancel = "CANCEL"; /// - /// Status codes available to a item. + /// Used by an Attendee to request the latest + /// version of an iCalendar object. /// - public static class TodoStatus - { - public const string Name = "VTODO"; - public const string Key = "STATUS"; - public static readonly StringComparison Comparison = StringComparison.Ordinal; - - public const string NeedsAction = "NEEDS-ACTION"; - public const string Completed = "COMPLETED"; - public const string InProcess = "IN-PROCESS"; - public const string Cancelled = "CANCELLED"; - } + public const string Refresh = "REFRESH"; /// - /// Status codes available to a entry. + /// Used by an Attendee to negotiate a change in an + /// iCalendar object. Examples include the request + /// to change a proposed event time or change the + /// due date for a task. /// - public static class JournalStatus - { - public const string Name = "VJOURNAL"; - public const string Key = "STATUS"; - public static readonly StringComparison Comparison = StringComparison.Ordinal; - - public const string Draft = "DRAFT"; - public const string Final = "FINAL"; - public const string Cancelled = "CANCELLED"; - } - - public enum FreeBusyStatus - { - Free = 0, - BusyTentative = 1, - BusyUnavailable = 2, - Busy = 3 - } - - public enum FrequencyType - { - None, - Secondly, - Minutely, - Hourly, - Daily, - Weekly, - Monthly, - Yearly - } + public const string Counter = "COUNTER"; /// - /// Indicates the occurrence of the specific day within a - /// MONTHLY or YEARLY recurrence frequency. For example, within - /// a MONTHLY frequency, consider the following: - /// - /// RecurrencePattern r = new RecurrencePattern(); - /// r.Frequency = FrequencyType.Monthly; - /// r.ByDay.Add(new WeekDay(DayOfWeek.Monday, FrequencyOccurrence.First)); - /// - /// The above example represents the first Monday within the month, - /// whereas if FrequencyOccurrence.Last were specified, it would - /// represent the last Monday of the month. - /// - /// For a YEARLY frequency, consider the following: - /// - /// Recur r = new Recur(); - /// r.Frequency = FrequencyType.Yearly; - /// r.ByDay.Add(new WeekDay(DayOfWeek.Monday, FrequencyOccurrence.Second)); - /// - /// The above example represents the second Monday of the year. This can - /// also be represented with the following code: - /// - /// r.ByDay.Add(new WeekDay(DayOfWeek.Monday, 2)); + /// Used by the Organizer to decline the proposed + /// counter-proposal. /// - public enum FrequencyOccurrence - { - None = int.MinValue, - Last = -1, - SecondToLast = -2, - ThirdToLast = -3, - FourthToLast = -4, - FifthToLast = -5, - First = 1, - Second = 2, - Third = 3, - Fourth = 4, - Fifth = 5 - } - - [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] - public enum RecurrenceRestrictionType - { - /// - /// Same as RestrictSecondly. - /// - Default, - - /// - /// Does not restrict recurrence evaluation - WARNING: this may cause very slow performance! - /// - NoRestriction, - - /// - /// Disallows use of the SECONDLY frequency for recurrence evaluation - /// - RestrictSecondly, - - /// - /// Disallows use of the MINUTELY and SECONDLY frequencies for recurrence evaluation - /// - RestrictMinutely, - - /// - /// Disallows use of the HOURLY, MINUTELY, and SECONDLY frequencies for recurrence evaluation - /// - RestrictHourly - } - - [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] - public enum RecurrenceEvaluationModeType - { - /// - /// Same as ThrowException. - /// - Default, - - /// - /// Automatically adjusts the evaluation to the next-best frequency based on the restriction type. - /// For example, if the restriction were IgnoreSeconds, and the frequency were SECONDLY, then - /// this would cause the frequency to be adjusted to MINUTELY, the next closest thing. - /// - AdjustAutomatically, - - /// - /// This will throw an exception if a recurrence rule is evaluated that does not meet the minimum - /// restrictions. For example, if the restriction were IgnoreSeconds, and a SECONDLY frequency - /// were evaluated, an exception would be thrown. - /// - ThrowException - } - - public static class TransparencyType - { - public const string Name = "TRANSP"; - public const string Key = "TRANSP"; - public static readonly StringComparison Comparison = StringComparison.Ordinal; - - public const string Opaque = "OPAQUE"; - public const string Transparent = "TRANSPARENT"; - } - - public static class LibraryMetadata - { - public const string Version = "2.0"; - public static readonly string ProdId = "-//github.com/ical-org/ical.net//NONSGML ical.net 4.0//EN"; - } - - public static class CalendarScales - { - public const string Gregorian = "GREGORIAN"; - } - - public static class CalendarMethods - { - /// - /// Used to publish an iCalendar object to one or - /// more "Calendar Users". There is no interactivity - /// between the publisher and any other "Calendar User". - /// An example might include a baseball team publishing - /// its schedule to the public. - /// - public const string Publish = "PUBLISH"; - - /// - /// Used to schedule an iCalendar object with other - /// "Calendar Users". Requests are interactive in - /// that they require the receiver to respond using - /// the reply methods. Meeting requests, busy-time - /// requests, and the assignment of tasks to other - /// "Calendar Users" are all examples. Requests are - /// also used by the Organizer to update the status - /// of an iCalendar object. - /// - public const string Request = "REQUEST"; - - /// - /// A reply is used in response to a request to - /// convey Attendee status to the Organizer. - /// Replies are commonly used to respond to meeting - /// and task requests. - /// - public const string Reply = "REPLY"; - - /// - /// Add one or more new instances to an existing - /// recurring iCalendar object. - /// - public const string Add = "ADD"; - - /// - /// Cancel one or more instances of an existing - /// iCalendar object. - /// - public const string Cancel = "CANCEL"; - - /// - /// Used by an Attendee to request the latest - /// version of an iCalendar object. - /// - public const string Refresh = "REFRESH"; - - /// - /// Used by an Attendee to negotiate a change in an - /// iCalendar object. Examples include the request - /// to change a proposed event time or change the - /// due date for a task. - /// - public const string Counter = "COUNTER"; - - /// - /// Used by the Organizer to decline the proposed - /// counter-proposal. - /// - public const string DeclineCounter = "DECLINECOUNTER"; - } + public const string DeclineCounter = "DECLINECOUNTER"; +} +/// +/// The defaults used for regular expressions. +/// +public static class RegexDefaults +{ + /// + /// The default timeout for regular expressions in milliseconds. + /// + public const int TimeoutMilliseconds = 200; /// - /// The defaults used for regular expressions. + /// The default timeout for regular expressions. /// - public static class RegexDefaults - { - /// - /// The default timeout for regular expressions in milliseconds. - /// - public const int TimeoutMilliseconds = 200; - /// - /// The default timeout for regular expressions. - /// - public static readonly TimeSpan Timeout = TimeSpan.FromMilliseconds(TimeoutMilliseconds); - } + public static readonly TimeSpan Timeout = TimeSpan.FromMilliseconds(TimeoutMilliseconds); } \ No newline at end of file diff --git a/Ical.Net/DataTypes/AlarmOccurrence.cs b/Ical.Net/DataTypes/AlarmOccurrence.cs index 7b2de1517..bd60a65c4 100644 --- a/Ical.Net/DataTypes/AlarmOccurrence.cs +++ b/Ical.Net/DataTypes/AlarmOccurrence.cs @@ -1,68 +1,72 @@ -using Ical.Net.CalendarComponents; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; +using Ical.Net.CalendarComponents; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +/// +/// A class that represents a specific occurrence of an . +/// +/// +/// The contains the when +/// the alarm occurs, the that fired, and the +/// component on which the alarm fired. +/// +public class AlarmOccurrence : IComparable { - /// - /// A class that represents a specific occurrence of an . - /// - /// - /// The contains the when - /// the alarm occurs, the that fired, and the - /// component on which the alarm fired. - /// - public class AlarmOccurrence : IComparable - { - public Period Period { get; set; } + public Period Period { get; set; } - public IRecurringComponent Component { get; set; } + public IRecurringComponent Component { get; set; } - public Alarm Alarm { get; set; } + public Alarm Alarm { get; set; } - public IDateTime DateTime - { - get => Period.StartTime; - set => Period = new Period(value); - } + public IDateTime DateTime + { + get => Period.StartTime; + set => Period = new Period(value); + } - public AlarmOccurrence(AlarmOccurrence ao) - { - Period = ao.Period; - Component = ao.Component; - Alarm = ao.Alarm; - } + public AlarmOccurrence(AlarmOccurrence ao) + { + Period = ao.Period; + Component = ao.Component; + Alarm = ao.Alarm; + } - public AlarmOccurrence(Alarm a, IDateTime dt, IRecurringComponent rc) - { - Alarm = a; - Period = new Period(dt); - Component = rc; - } + public AlarmOccurrence(Alarm a, IDateTime dt, IRecurringComponent rc) + { + Alarm = a; + Period = new Period(dt); + Component = rc; + } - public int CompareTo(AlarmOccurrence other) => Period.CompareTo(other.Period); + public int CompareTo(AlarmOccurrence other) => Period.CompareTo(other.Period); - protected bool Equals(AlarmOccurrence other) - => Equals(Period, other.Period) - && Equals(Component, other.Component) - && Equals(Alarm, other.Alarm); + protected bool Equals(AlarmOccurrence other) + => Equals(Period, other.Period) + && Equals(Component, other.Component) + && Equals(Alarm, other.Alarm); - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((AlarmOccurrence)obj); - } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((AlarmOccurrence) obj); + } - public override int GetHashCode() + public override int GetHashCode() + { + // ToDo: Alarm doesn't implement Equals or GetHashCode() + unchecked { - // ToDo: Alarm doesn't implement Equals or GetHashCode() - unchecked - { - var hashCode = Period?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ (Component?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (Alarm?.GetHashCode() ?? 0); - return hashCode; - } + var hashCode = Period?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (Component?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Alarm?.GetHashCode() ?? 0); + return hashCode; } } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/Attachment.cs b/Ical.Net/DataTypes/Attachment.cs index d21cb90c5..7e277488f 100644 --- a/Ical.Net/DataTypes/Attachment.cs +++ b/Ical.Net/DataTypes/Attachment.cs @@ -1,117 +1,121 @@ -using Ical.Net.Serialization.DataTypes; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Linq; using System.Text; +using Ical.Net.Serialization.DataTypes; +using Ical.Net.Utility; -namespace Ical.Net.DataTypes +namespace Ical.Net.DataTypes; + +/// +/// Attachments represent the ATTACH element that can be associated with Alarms, Journals, Todos, and Events. There are two kinds of attachments: +/// 1) A string representing a URI which is typically human-readable, OR +/// 2) A base64-encoded string that can represent anything +/// +public class Attachment : EncodableDataType { - /// - /// Attachments represent the ATTACH element that can be associated with Alarms, Journals, Todos, and Events. There are two kinds of attachments: - /// 1) A string representing a URI which is typically human-readable, OR - /// 2) A base64-encoded string that can represent anything - /// - public class Attachment : EncodableDataType - { - public virtual Uri Uri { get; set; } - public virtual byte[] Data { get; private set; } // private set for CopyFrom + public virtual Uri Uri { get; set; } + public virtual byte[] Data { get; private set; } // private set for CopyFrom - private Encoding _valueEncoding = System.Text.Encoding.UTF8; - public virtual Encoding ValueEncoding + private Encoding _valueEncoding = System.Text.Encoding.UTF8; + public virtual Encoding ValueEncoding + { + get => _valueEncoding; + set { - get => _valueEncoding; - set + if (value == null) { - if (value == null) - { - return; - } - _valueEncoding = value; + return; } + _valueEncoding = value; } + } - public virtual string FormatType - { - get => Parameters.Get("FMTTYPE"); - set => Parameters.Set("FMTTYPE", value); - } + public virtual string FormatType + { + get => Parameters.Get("FMTTYPE"); + set => Parameters.Set("FMTTYPE", value); + } - public Attachment() { } + public Attachment() { } - public Attachment(byte[] value) : this() + public Attachment(byte[] value) : this() + { + if (value != null) { - if (value != null) - { - Data = value; - } + Data = value; } + } - public Attachment(string value) : this() + public Attachment(string value) : this() + { + if (string.IsNullOrEmpty(value)) { - if (string.IsNullOrEmpty(value)) - { - return; - } - - var serializer = new AttachmentSerializer(); - var a = serializer.Deserialize(value); - if (a == null) - { - throw new ArgumentException($"{value} is not a valid ATTACH component"); - } - - ValueEncoding = a.ValueEncoding; + return; + } - Data = a.Data; - Uri = a.Uri; + var serializer = new AttachmentSerializer(); + var a = serializer.Deserialize(value); + if (a == null) + { + throw new ArgumentException($"{value} is not a valid ATTACH component"); } - public override string ToString() - => Data == null - ? string.Empty - : ValueEncoding.GetString(Data); + ValueEncoding = a.ValueEncoding; - /// - public override void CopyFrom(ICopyable obj) - { - if (obj is not Attachment att) return; - base.CopyFrom(obj); + Data = a.Data; + Uri = a.Uri; + } - Uri = att.Uri != null ? new Uri(att.Uri.ToString()) : null; - if (att.Data != null) - { - Data = new byte[att.Data.Length]; - Array.Copy(att.Data, Data, att.Data.Length); - } + public override string ToString() + => Data == null + ? string.Empty + : ValueEncoding.GetString(Data); - ValueEncoding = att.ValueEncoding; - FormatType = att.FormatType; - } + /// + public override void CopyFrom(ICopyable obj) + { + if (obj is not Attachment att) return; + base.CopyFrom(obj); - protected bool Equals(Attachment other) + Uri = att.Uri != null ? new Uri(att.Uri.ToString()) : null; + if (att.Data != null) { - var firstPart = Equals(Uri, other.Uri) && ValueEncoding.Equals(other.ValueEncoding); - return Data == null - ? firstPart - : firstPart && Data.SequenceEqual(other.Data); + Data = new byte[att.Data.Length]; + Array.Copy(att.Data, Data, att.Data.Length); } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((Attachment)obj); - } + ValueEncoding = att.ValueEncoding; + FormatType = att.FormatType; + } + + protected bool Equals(Attachment other) + { + var firstPart = Equals(Uri, other.Uri) && ValueEncoding.Equals(other.ValueEncoding); + return Data == null + ? firstPart + : firstPart && Data.SequenceEqual(other.Data); + } - public override int GetHashCode() + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((Attachment) obj); + } + + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = Uri?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ (CollectionHelpers.GetHashCode(Data)); - hashCode = (hashCode * 397) ^ (ValueEncoding?.GetHashCode() ?? 0); - return hashCode; - } + var hashCode = Uri?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (CollectionHelpers.GetHashCode(Data)); + hashCode = (hashCode * 397) ^ (ValueEncoding?.GetHashCode() ?? 0); + return hashCode; } } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/Attendee.cs b/Ical.Net/DataTypes/Attendee.cs index 11c4c77fc..3410bea60 100644 --- a/Ical.Net/DataTypes/Attendee.cs +++ b/Ical.Net/DataTypes/Attendee.cs @@ -1,304 +1,308 @@ -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using Ical.Net.Utility; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +public class Attendee : EncodableDataType { - public class Attendee : EncodableDataType + private Uri _sentBy; + /// SENT-BY, to indicate who is acting on behalf of the ATTENDEE + public virtual Uri SentBy { - private Uri _sentBy; - /// SENT-BY, to indicate who is acting on behalf of the ATTENDEE - public virtual Uri SentBy + get { - get + if (_sentBy != null) { - if (_sentBy != null) - { - return _sentBy; - } - - var newUrl = Parameters.Get("SENT-BY"); - Uri.TryCreate(newUrl, UriKind.RelativeOrAbsolute, out _sentBy); return _sentBy; } - set + + var newUrl = Parameters.Get("SENT-BY"); + Uri.TryCreate(newUrl, UriKind.RelativeOrAbsolute, out _sentBy); + return _sentBy; + } + set + { + if (value == null || value == _sentBy) { - if (value == null || value == _sentBy) - { - return; - } - _sentBy = value; - Parameters.Set("SENT-BY", value.OriginalString); + return; } + _sentBy = value; + Parameters.Set("SENT-BY", value.OriginalString); } + } - private string _commonName; - /// CN: to show the common or displayable name associated with the calendar address - public virtual string CommonName + private string _commonName; + /// CN: to show the common or displayable name associated with the calendar address + public virtual string CommonName + { + get { - get + if (string.IsNullOrEmpty(_commonName)) { - if (string.IsNullOrEmpty(_commonName)) - { - _commonName = Parameters.Get("CN"); - } - return _commonName; + _commonName = Parameters.Get("CN"); } - set + return _commonName; + } + set + { + if (string.Equals(_commonName, value, StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(_commonName, value, StringComparison.OrdinalIgnoreCase)) - { - return; - } - _commonName = value; - Parameters.Set("CN", value); + return; } + _commonName = value; + Parameters.Set("CN", value); } + } - private Uri _directoryEntry; - /// DIR, to indicate the URI that points to the directory information corresponding to the attendee - public virtual Uri DirectoryEntry + private Uri _directoryEntry; + /// DIR, to indicate the URI that points to the directory information corresponding to the attendee + public virtual Uri DirectoryEntry + { + get { - get + if (_directoryEntry != null) { - if (_directoryEntry != null) - { - return _directoryEntry; - } - - var newUrl = Parameters.Get("SENT-BY"); - Uri.TryCreate(newUrl, UriKind.RelativeOrAbsolute, out _directoryEntry); return _directoryEntry; } - set + + var newUrl = Parameters.Get("SENT-BY"); + Uri.TryCreate(newUrl, UriKind.RelativeOrAbsolute, out _directoryEntry); + return _directoryEntry; + } + set + { + if (value == null || value == _directoryEntry) { - if (value == null || value == _directoryEntry) - { - return; - } - _directoryEntry = value; - Parameters.Set("DIR", value.OriginalString); + return; } + _directoryEntry = value; + Parameters.Set("DIR", value.OriginalString); } + } - private string _type; - /// CUTYPE: the type of calendar user - public virtual string Type + private string _type; + /// CUTYPE: the type of calendar user + public virtual string Type + { + get { - get + if (string.IsNullOrEmpty(_type)) { - if (string.IsNullOrEmpty(_type)) - { - _type = Parameters.Get("CUTYPE"); - } - return _type; + _type = Parameters.Get("CUTYPE"); } - set + return _type; + } + set + { + if (string.Equals(_type, value, StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(_type, value, StringComparison.OrdinalIgnoreCase)) - { - return; - } - - _type = value; - Parameters.Set("CUTYPE", value); + return; } + + _type = value; + Parameters.Set("CUTYPE", value); } + } - private List _members; - /// MEMBER: the groups the user belongs to - public virtual IList Members + private List _members; + /// MEMBER: the groups the user belongs to + public virtual IList Members + { + get => _members ?? (_members = new List(Parameters.GetMany("MEMBER"))); + set { - get => _members ?? (_members = new List(Parameters.GetMany("MEMBER"))); - set - { - _members = new List(value); - Parameters.Set("MEMBER", value); - } + _members = new List(value); + Parameters.Set("MEMBER", value); } + } - private string _role; - /// ROLE: the intended role the attendee will have - public virtual string Role + private string _role; + /// ROLE: the intended role the attendee will have + public virtual string Role + { + get { - get + if (string.IsNullOrEmpty(_role)) { - if (string.IsNullOrEmpty(_role)) - { - _role = Parameters.Get("ROLE"); - } - return _role; + _role = Parameters.Get("ROLE"); } - set + return _role; + } + set + { + if (string.Equals(_role, value, StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(_role, value, StringComparison.OrdinalIgnoreCase)) - { - return; - } - _role = value; - Parameters.Set("ROLE", value); + return; } + _role = value; + Parameters.Set("ROLE", value); } + } - private string _participationStatus; - public virtual string ParticipationStatus + private string _participationStatus; + public virtual string ParticipationStatus + { + get { - get + if (string.IsNullOrEmpty(_participationStatus)) { - if (string.IsNullOrEmpty(_participationStatus)) - { - _participationStatus = Parameters.Get(EventParticipationStatus.Key); - } - return _participationStatus; + _participationStatus = Parameters.Get(EventParticipationStatus.Key); } - set + return _participationStatus; + } + set + { + if (string.Equals(_participationStatus, value, EventParticipationStatus.Comparison)) { - if (string.Equals(_participationStatus, value, EventParticipationStatus.Comparison)) - { - return; - } - _participationStatus = value; - Parameters.Set(EventParticipationStatus.Key, value); + return; } + _participationStatus = value; + Parameters.Set(EventParticipationStatus.Key, value); } + } - private bool? _rsvp; - /// RSVP, to indicate whether a reply is requested - public virtual bool Rsvp + private bool? _rsvp; + /// RSVP, to indicate whether a reply is requested + public virtual bool Rsvp + { + get { - get + if (_rsvp != null) { - if (_rsvp != null) - { - return _rsvp.Value; - } - - bool val; - var rsvp = Parameters.Get("RSVP"); - if (rsvp != null && bool.TryParse(rsvp, out val)) - { - _rsvp = val; - return _rsvp.Value; - } - return false; + return _rsvp.Value; } - set + + bool val; + var rsvp = Parameters.Get("RSVP"); + if (rsvp != null && bool.TryParse(rsvp, out val)) { - _rsvp = value; - var val = value.ToString().ToUpperInvariant(); - Parameters.Set("RSVP", val); + _rsvp = val; + return _rsvp.Value; } + return false; + } + set + { + _rsvp = value; + var val = value.ToString().ToUpperInvariant(); + Parameters.Set("RSVP", val); } + } - private List _delegatedTo; - /// DELEGATED-TO, to indicate the calendar users that the original request was delegated to - public virtual IList DelegatedTo + private List _delegatedTo; + /// DELEGATED-TO, to indicate the calendar users that the original request was delegated to + public virtual IList DelegatedTo + { + get => _delegatedTo ?? (_delegatedTo = new List(Parameters.GetMany("DELEGATED-TO"))); + set { - get => _delegatedTo ?? (_delegatedTo = new List(Parameters.GetMany("DELEGATED-TO"))); - set + if (value == null) { - if (value == null) - { - return; - } - _delegatedTo = new List(value); - Parameters.Set("DELEGATED-TO", value); + return; } + _delegatedTo = new List(value); + Parameters.Set("DELEGATED-TO", value); } + } - private List _delegatedFrom; - /// DELEGATED-FROM, to indicate whom the request was delegated from - public virtual IList DelegatedFrom + private List _delegatedFrom; + /// DELEGATED-FROM, to indicate whom the request was delegated from + public virtual IList DelegatedFrom + { + get => _delegatedFrom ?? (_delegatedFrom = new List(Parameters.GetMany("DELEGATED-FROM"))); + set { - get => _delegatedFrom ?? (_delegatedFrom = new List(Parameters.GetMany("DELEGATED-FROM"))); - set + if (value == null) { - if (value == null) - { - return; - } - _delegatedFrom = new List(value); - Parameters.Set("DELEGATED-FROM", value); + return; } + _delegatedFrom = new List(value); + Parameters.Set("DELEGATED-FROM", value); } + } - /// Uri associated with the attendee, typically an email address - public virtual Uri Value { get; set; } + /// Uri associated with the attendee, typically an email address + public virtual Uri Value { get; set; } - public Attendee() { } + public Attendee() { } - public Attendee(Uri attendee) - { - Value = attendee; - } + public Attendee(Uri attendee) + { + Value = attendee; + } - public Attendee(string attendeeUri) + public Attendee(string attendeeUri) + { + if (!Uri.IsWellFormedUriString(attendeeUri, UriKind.Absolute)) { - if (!Uri.IsWellFormedUriString(attendeeUri, UriKind.Absolute)) - { - throw new ArgumentException("attendeeUri"); - } - Value = new Uri(attendeeUri); + throw new ArgumentException("attendeeUri"); } + Value = new Uri(attendeeUri); + } - /// - public override void CopyFrom(ICopyable obj) - { - if (obj is not Attendee atn) return; - base.CopyFrom(obj); + /// + public override void CopyFrom(ICopyable obj) + { + if (obj is not Attendee atn) return; + base.CopyFrom(obj); - Value = new Uri(atn.Value.ToString()); + Value = new Uri(atn.Value.ToString()); - // String assignments create new instances - CommonName = atn.CommonName; - ParticipationStatus = atn.ParticipationStatus; - Role = atn.Role; - Type = atn.Type; + // String assignments create new instances + CommonName = atn.CommonName; + ParticipationStatus = atn.ParticipationStatus; + Role = atn.Role; + Type = atn.Type; - Rsvp = atn.Rsvp; + Rsvp = atn.Rsvp; - SentBy = atn.SentBy != null ? new Uri(atn.SentBy.ToString()) : null; - DirectoryEntry = atn.DirectoryEntry != null ? new Uri(atn.DirectoryEntry.ToString()) : null; - } - - protected bool Equals(Attendee other) => Equals(SentBy, other.SentBy) - && string.Equals(CommonName, other.CommonName, StringComparison.OrdinalIgnoreCase) - && Equals(DirectoryEntry, other.DirectoryEntry) - && string.Equals(Type, other.Type, StringComparison.OrdinalIgnoreCase) - && string.Equals(Role, other.Role) - && string.Equals(ParticipationStatus, other.ParticipationStatus, StringComparison.OrdinalIgnoreCase) - && Rsvp == other.Rsvp - && Equals(Value, other.Value) - && Members.SequenceEqual(other.Members) - && DelegatedTo.SequenceEqual(other.DelegatedTo) - && DelegatedFrom.SequenceEqual(other.DelegatedFrom); + SentBy = atn.SentBy != null ? new Uri(atn.SentBy.ToString()) : null; + DirectoryEntry = atn.DirectoryEntry != null ? new Uri(atn.DirectoryEntry.ToString()) : null; + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals((Attendee)obj); - } + protected bool Equals(Attendee other) => Equals(SentBy, other.SentBy) + && string.Equals(CommonName, other.CommonName, StringComparison.OrdinalIgnoreCase) + && Equals(DirectoryEntry, other.DirectoryEntry) + && string.Equals(Type, other.Type, StringComparison.OrdinalIgnoreCase) + && string.Equals(Role, other.Role) + && string.Equals(ParticipationStatus, other.ParticipationStatus, StringComparison.OrdinalIgnoreCase) + && Rsvp == other.Rsvp + && Equals(Value, other.Value) + && Members.SequenceEqual(other.Members) + && DelegatedTo.SequenceEqual(other.DelegatedTo) + && DelegatedFrom.SequenceEqual(other.DelegatedFrom); + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != GetType()) return false; + return Equals((Attendee) obj); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = SentBy?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ (CommonName?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (DirectoryEntry?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (Type?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Members); - hashCode = (hashCode * 397) ^ (Role?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (ParticipationStatus?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ Rsvp.GetHashCode(); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(DelegatedTo); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(DelegatedFrom); - hashCode = (hashCode * 397) ^ (Value?.GetHashCode() ?? 0); - return hashCode; - } + var hashCode = SentBy?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (CommonName?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (DirectoryEntry?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (Type?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Members); + hashCode = (hashCode * 397) ^ (Role?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (ParticipationStatus?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ Rsvp.GetHashCode(); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(DelegatedTo); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(DelegatedFrom); + hashCode = (hashCode * 397) ^ (Value?.GetHashCode() ?? 0); + return hashCode; } } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/CalDateTime.cs b/Ical.Net/DataTypes/CalDateTime.cs index c149d779f..769419249 100644 --- a/Ical.Net/DataTypes/CalDateTime.cs +++ b/Ical.Net/DataTypes/CalDateTime.cs @@ -1,550 +1,554 @@ -using Ical.Net.Serialization.DataTypes; -using Ical.Net.Utility; -using NodaTime; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; +using Ical.Net.Serialization.DataTypes; +using Ical.Net.Utility; +using NodaTime; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +/// +/// The iCalendar equivalent of the .NET class. +/// +/// In addition to the features of the class, the +/// class handles time zone differences, and integrates seamlessly into the iCalendar framework. +/// +/// +public sealed class CalDateTime : EncodableDataType, IDateTime { + public static CalDateTime Now => new CalDateTime(DateTime.Now); + + public static CalDateTime Today => new CalDateTime(DateTime.Today); + + private bool _hasDate; + private bool _hasTime; + + public CalDateTime() { } + + public CalDateTime(IDateTime value) + { + Initialize(value.Value, value.TzId, null); + } + + public CalDateTime(DateTime value) : this(value, null) { } + /// - /// The iCalendar equivalent of the .NET class. - /// - /// In addition to the features of the class, the - /// class handles time zone differences, and integrates seamlessly into the iCalendar framework. - /// + /// Specifying a `tzId` value will override `value`'s `DateTimeKind` property. If the time zone specified is UTC, the underlying `DateTimeKind` will be + /// `Utc`. If a non-UTC time zone is specified, the underlying `DateTimeKind` property will be `Local`. If no time zone is specified, the `DateTimeKind` + /// property will be left untouched. /// - public sealed class CalDateTime : EncodableDataType, IDateTime + public CalDateTime(DateTime value, string tzId) { - public static CalDateTime Now => new CalDateTime(DateTime.Now); - - public static CalDateTime Today => new CalDateTime(DateTime.Today); + Initialize(value, tzId, null); + } - private bool _hasDate; - private bool _hasTime; + public CalDateTime(int year, int month, int day, int hour, int minute, int second) + { + Initialize(year, month, day, hour, minute, second, null, null); + HasTime = true; + } - public CalDateTime() { } + public CalDateTime(int year, int month, int day, int hour, int minute, int second, string tzId) + { + Initialize(year, month, day, hour, minute, second, tzId, null); + HasTime = true; + } - public CalDateTime(IDateTime value) - { - Initialize(value.Value, value.TzId, null); - } + public CalDateTime(int year, int month, int day, int hour, int minute, int second, string tzId, Calendar cal) + { + Initialize(year, month, day, hour, minute, second, tzId, cal); + HasTime = true; + } - public CalDateTime(DateTime value) : this(value, null) { } + public CalDateTime(int year, int month, int day) : this(year, month, day, 0, 0, 0) { } + public CalDateTime(int year, int month, int day, string tzId) : this(year, month, day, 0, 0, 0, tzId) { } - /// - /// Specifying a `tzId` value will override `value`'s `DateTimeKind` property. If the time zone specified is UTC, the underlying `DateTimeKind` will be - /// `Utc`. If a non-UTC time zone is specified, the underlying `DateTimeKind` property will be `Local`. If no time zone is specified, the `DateTimeKind` - /// property will be left untouched. - /// - public CalDateTime(DateTime value, string tzId) - { - Initialize(value, tzId, null); - } + public CalDateTime(string value) + { + var serializer = new DateTimeSerializer(); + CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + } - public CalDateTime(int year, int month, int day, int hour, int minute, int second) - { - Initialize(year, month, day, hour, minute, second, null, null); - HasTime = true; - } + private void Initialize(int year, int month, int day, int hour, int minute, int second, string tzId, Calendar cal) + { + Initialize(CoerceDateTime(year, month, day, hour, minute, second, DateTimeKind.Local), tzId, cal); + } - public CalDateTime(int year, int month, int day, int hour, int minute, int second, string tzId) + private void Initialize(DateTime value, string tzId, Calendar cal) + { + if (!string.IsNullOrWhiteSpace(tzId) && !tzId.Equals("UTC", StringComparison.OrdinalIgnoreCase)) { - Initialize(year, month, day, hour, minute, second, tzId, null); - HasTime = true; + // Definitely local + value = DateTime.SpecifyKind(value, DateTimeKind.Local); + TzId = tzId; } - - public CalDateTime(int year, int month, int day, int hour, int minute, int second, string tzId, Calendar cal) + else if (string.Equals("UTC", tzId, StringComparison.OrdinalIgnoreCase) || value.Kind == DateTimeKind.Utc) { - Initialize(year, month, day, hour, minute, second, tzId, cal); - HasTime = true; + // Probably UTC + value = DateTime.SpecifyKind(value, DateTimeKind.Utc); + TzId = "UTC"; } - public CalDateTime(int year, int month, int day) : this(year, month, day, 0, 0, 0) { } - public CalDateTime(int year, int month, int day, string tzId) : this(year, month, day, 0, 0, 0, tzId) { } - - public CalDateTime(string value) - { - var serializer = new DateTimeSerializer(); - CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); - } + Value = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second, value.Kind); + HasDate = true; + HasTime = value.Second != 0 || value.Minute != 0 || value.Hour != 0; + AssociatedObject = cal; + } - private void Initialize(int year, int month, int day, int hour, int minute, int second, string tzId, Calendar cal) - { - Initialize(CoerceDateTime(year, month, day, hour, minute, second, DateTimeKind.Local), tzId, cal); - } + private DateTime CoerceDateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) + { + var dt = DateTime.MinValue; - private void Initialize(DateTime value, string tzId, Calendar cal) + // NOTE: determine if a date/time value exceeds the representable date/time values in .NET. + // If so, let's automatically adjust the date/time to compensate. + // FIXME: should we have a parsing setting that will throw an exception + // instead of automatically adjusting the date/time value to the + // closest representable date/time? + try { - if (!string.IsNullOrWhiteSpace(tzId) && !tzId.Equals("UTC", StringComparison.OrdinalIgnoreCase)) + if (year > 9999) { - // Definitely local - value = DateTime.SpecifyKind(value, DateTimeKind.Local); - TzId = tzId; + dt = DateTime.MaxValue; } - else if (string.Equals("UTC", tzId, StringComparison.OrdinalIgnoreCase) || value.Kind == DateTimeKind.Utc) + else if (year > 0) { - // Probably UTC - value = DateTime.SpecifyKind(value, DateTimeKind.Utc); - TzId = "UTC"; + dt = new DateTime(year, month, day, hour, minute, second, kind); } - - Value = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second, value.Kind); - HasDate = true; - HasTime = value.Second != 0 || value.Minute != 0 || value.Hour != 0; - AssociatedObject = cal; } + catch { } - private DateTime CoerceDateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) - { - var dt = DateTime.MinValue; - - // NOTE: determine if a date/time value exceeds the representable date/time values in .NET. - // If so, let's automatically adjust the date/time to compensate. - // FIXME: should we have a parsing setting that will throw an exception - // instead of automatically adjusting the date/time value to the - // closest representable date/time? - try - { - if (year > 9999) - { - dt = DateTime.MaxValue; - } - else if (year > 0) - { - dt = new DateTime(year, month, day, hour, minute, second, kind); - } - } - catch { } - - return dt; - } + return dt; + } - public override ICalendarObject AssociatedObject + public override ICalendarObject AssociatedObject + { + get => base.AssociatedObject; + set { - get => base.AssociatedObject; - set + if (!Equals(AssociatedObject, value)) { - if (!Equals(AssociatedObject, value)) - { - base.AssociatedObject = value; - } + base.AssociatedObject = value; } } + } - /// - public override void CopyFrom(ICopyable obj) - { - base.CopyFrom(obj); + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); - var dt = obj as IDateTime; - if (dt == null) - { - return; - } + var dt = obj as IDateTime; + if (dt == null) + { + return; + } - _value = dt.Value; - _hasDate = dt.HasDate; - _hasTime = dt.HasTime; - // String assignments create new instances - TzId = dt.TzId; + _value = dt.Value; + _hasDate = dt.HasDate; + _hasTime = dt.HasTime; + // String assignments create new instances + TzId = dt.TzId; - AssociateWith(dt); - } + AssociateWith(dt); + } - public bool Equals(CalDateTime other) - => this == other; + public bool Equals(CalDateTime other) + => this == other; - public override bool Equals(object other) - => other is IDateTime && (CalDateTime)other == this; + public override bool Equals(object other) + => other is IDateTime && (CalDateTime) other == this; - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = Value.GetHashCode(); - hashCode = (hashCode * 397) ^ HasDate.GetHashCode(); - hashCode = (hashCode * 397) ^ AsUtc.GetHashCode(); - hashCode = (hashCode * 397) ^ (TzId != null ? TzId.GetHashCode() : 0); - return hashCode; - } + var hashCode = Value.GetHashCode(); + hashCode = (hashCode * 397) ^ HasDate.GetHashCode(); + hashCode = (hashCode * 397) ^ AsUtc.GetHashCode(); + hashCode = (hashCode * 397) ^ (TzId != null ? TzId.GetHashCode() : 0); + return hashCode; } + } - public static bool operator <(CalDateTime left, IDateTime right) - => left != null && right != null && left.AsUtc < right.AsUtc; + public static bool operator <(CalDateTime left, IDateTime right) + => left != null && right != null && left.AsUtc < right.AsUtc; - public static bool operator >(CalDateTime left, IDateTime right) - => left != null && right != null && left.AsUtc > right.AsUtc; + public static bool operator >(CalDateTime left, IDateTime right) + => left != null && right != null && left.AsUtc > right.AsUtc; - public static bool operator <=(CalDateTime left, IDateTime right) - => left != null && right != null && left.AsUtc <= right.AsUtc; + public static bool operator <=(CalDateTime left, IDateTime right) + => left != null && right != null && left.AsUtc <= right.AsUtc; - public static bool operator >=(CalDateTime left, IDateTime right) - => left != null && right != null && left.AsUtc >= right.AsUtc; + public static bool operator >=(CalDateTime left, IDateTime right) + => left != null && right != null && left.AsUtc >= right.AsUtc; - public static bool operator ==(CalDateTime left, IDateTime right) - { - return ReferenceEquals(left, null) || ReferenceEquals(right, null) - ? ReferenceEquals(left, right) - : right is CalDateTime - && left.Value.Equals(right.Value) - && left.HasDate == right.HasDate - && left.AsUtc.Equals(right.AsUtc) - && string.Equals(left.TzId, right.TzId, StringComparison.OrdinalIgnoreCase); - } + public static bool operator ==(CalDateTime left, IDateTime right) + { + return ReferenceEquals(left, null) || ReferenceEquals(right, null) + ? ReferenceEquals(left, right) + : right is CalDateTime + && left.Value.Equals(right.Value) + && left.HasDate == right.HasDate + && left.AsUtc.Equals(right.AsUtc) + && string.Equals(left.TzId, right.TzId, StringComparison.OrdinalIgnoreCase); + } - public static bool operator !=(CalDateTime left, IDateTime right) - => !(left == right); + public static bool operator !=(CalDateTime left, IDateTime right) + => !(left == right); - public static TimeSpan operator -(CalDateTime left, IDateTime right) - { - left.AssociateWith(right); - return left.AsUtc - right.AsUtc; - } + public static TimeSpan operator -(CalDateTime left, IDateTime right) + { + left.AssociateWith(right); + return left.AsUtc - right.AsUtc; + } - public static IDateTime operator -(CalDateTime left, TimeSpan right) - { - var copy = left.Copy(); - copy.Value -= right; - return copy; - } + public static IDateTime operator -(CalDateTime left, TimeSpan right) + { + var copy = left.Copy(); + copy.Value -= right; + return copy; + } - public static IDateTime operator +(CalDateTime left, TimeSpan right) - { - var copy = left.Copy(); - copy.Value += right; - return copy; - } + public static IDateTime operator +(CalDateTime left, TimeSpan right) + { + var copy = left.Copy(); + copy.Value += right; + return copy; + } - public static implicit operator CalDateTime(DateTime left) => new CalDateTime(left); + public static implicit operator CalDateTime(DateTime left) => new CalDateTime(left); - /// - /// Converts the date/time to the date/time of the computer running the program. If the DateTimeKind is Unspecified, it's assumed that the underlying - /// Value already represents the system's datetime. - /// - public DateTime AsSystemLocal + /// + /// Converts the date/time to the date/time of the computer running the program. If the DateTimeKind is Unspecified, it's assumed that the underlying + /// Value already represents the system's datetime. + /// + public DateTime AsSystemLocal + { + get { - get + if (Value.Kind == DateTimeKind.Unspecified) { - if (Value.Kind == DateTimeKind.Unspecified) - { - return HasTime - ? Value - : Value.Date; - } - return HasTime - ? Value.ToLocalTime() - : Value.ToLocalTime().Date; + ? Value + : Value.Date; } + + return HasTime + ? Value.ToLocalTime() + : Value.ToLocalTime().Date; } + } - private DateTime _asUtc = DateTime.MinValue; - /// - /// Returns a representation of the DateTime in Coordinated Universal Time (UTC) - /// - public DateTime AsUtc + private DateTime _asUtc = DateTime.MinValue; + /// + /// Returns a representation of the DateTime in Coordinated Universal Time (UTC) + /// + public DateTime AsUtc + { + get { - get + if (_asUtc == DateTime.MinValue) { - if (_asUtc == DateTime.MinValue) + // In order of weighting: + // 1) Specified TzId + // 2) Value having a DateTimeKind.Utc + // 3) Use the OS's time zone + + if (!string.IsNullOrWhiteSpace(TzId)) + { + var asLocal = DateUtil.ToZonedDateTimeLeniently(Value, TzId); + _asUtc = asLocal.ToDateTimeUtc(); + } + else if (IsUtc || Value.Kind == DateTimeKind.Utc) { - // In order of weighting: - // 1) Specified TzId - // 2) Value having a DateTimeKind.Utc - // 3) Use the OS's time zone - - if (!string.IsNullOrWhiteSpace(TzId)) - { - var asLocal = DateUtil.ToZonedDateTimeLeniently(Value, TzId); - _asUtc = asLocal.ToDateTimeUtc(); - } - else if (IsUtc || Value.Kind == DateTimeKind.Utc) - { - _asUtc = DateTime.SpecifyKind(Value, DateTimeKind.Utc); - } - else - { - _asUtc = DateTime.SpecifyKind(Value, DateTimeKind.Local).ToUniversalTime(); - } + _asUtc = DateTime.SpecifyKind(Value, DateTimeKind.Utc); + } + else + { + _asUtc = DateTime.SpecifyKind(Value, DateTimeKind.Local).ToUniversalTime(); } - return _asUtc; } + return _asUtc; } + } - private DateTime _value; - public DateTime Value + private DateTime _value; + public DateTime Value + { + get => _value; + set { - get => _value; - set + if (_value == value && _value.Kind == value.Kind) { - if (_value == value && _value.Kind == value.Kind) - { - return; - } - - _asUtc = DateTime.MinValue; - _value = value; + return; } + + _asUtc = DateTime.MinValue; + _value = value; } + } - public bool IsUtc => _value.Kind == DateTimeKind.Utc; + public bool IsUtc => _value.Kind == DateTimeKind.Utc; - public bool HasDate - { - get => _hasDate; - set => _hasDate = value; - } + public bool HasDate + { + get => _hasDate; + set => _hasDate = value; + } - public bool HasTime - { - get => _hasTime; - set => _hasTime = value; - } + public bool HasTime + { + get => _hasTime; + set => _hasTime = value; + } - private string _tzId = string.Empty; + private string _tzId = string.Empty; - /// - /// Setting the TzId to a local time zone will set Value.Kind to Local. Setting TzId to UTC will set Value.Kind to Utc. If the incoming value is null - /// or whitespace, Value.Kind will be set to Unspecified. Setting the TzId will NOT incur a UTC offset conversion under any circumstances. To convert - /// to another time zone, use the ToTimeZone() method. - /// - public string TzId + /// + /// Setting the TzId to a local time zone will set Value.Kind to Local. Setting TzId to UTC will set Value.Kind to Utc. If the incoming value is null + /// or whitespace, Value.Kind will be set to Unspecified. Setting the TzId will NOT incur a UTC offset conversion under any circumstances. To convert + /// to another time zone, use the ToTimeZone() method. + /// + public string TzId + { + get { - get + if (string.IsNullOrWhiteSpace(_tzId)) { - if (string.IsNullOrWhiteSpace(_tzId)) - { - _tzId = Parameters.Get("TZID"); - } - return _tzId; + _tzId = Parameters.Get("TZID"); } - set + return _tzId; + } + set + { + if (string.Equals(_tzId, value, StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(_tzId, value, StringComparison.OrdinalIgnoreCase)) - { - return; - } + return; + } - _asUtc = DateTime.MinValue; + _asUtc = DateTime.MinValue; - var isEmpty = string.IsNullOrWhiteSpace(value); - if (isEmpty) - { - Parameters.Remove("TZID"); - _tzId = null; - Value = DateTime.SpecifyKind(Value, DateTimeKind.Local); - return; - } + var isEmpty = string.IsNullOrWhiteSpace(value); + if (isEmpty) + { + Parameters.Remove("TZID"); + _tzId = null; + Value = DateTime.SpecifyKind(Value, DateTimeKind.Local); + return; + } - var kind = string.Equals(value, "UTC", StringComparison.OrdinalIgnoreCase) - ? DateTimeKind.Utc - : DateTimeKind.Local; + var kind = string.Equals(value, "UTC", StringComparison.OrdinalIgnoreCase) + ? DateTimeKind.Utc + : DateTimeKind.Local; - Value = DateTime.SpecifyKind(Value, kind); - Parameters.Set("TZID", value); - _tzId = value; - } + Value = DateTime.SpecifyKind(Value, kind); + Parameters.Set("TZID", value); + _tzId = value; } + } - public string TimeZoneName => TzId; + public string TimeZoneName => TzId; - public int Year => Value.Year; + public int Year => Value.Year; - public int Month => Value.Month; + public int Month => Value.Month; - public int Day => Value.Day; + public int Day => Value.Day; - public int Hour => Value.Hour; + public int Hour => Value.Hour; - public int Minute => Value.Minute; + public int Minute => Value.Minute; - public int Second => Value.Second; + public int Second => Value.Second; - public int Millisecond => Value.Millisecond; + public int Millisecond => Value.Millisecond; - public long Ticks => Value.Ticks; + public long Ticks => Value.Ticks; - public DayOfWeek DayOfWeek => Value.DayOfWeek; + public DayOfWeek DayOfWeek => Value.DayOfWeek; - public int DayOfYear => Value.DayOfYear; + public int DayOfYear => Value.DayOfYear; - public DateTime Date => Value.Date; + public DateTime Date => Value.Date; - public TimeSpan TimeOfDay => Value.TimeOfDay; + public TimeSpan TimeOfDay => Value.TimeOfDay; - /// - /// Returns a representation of the IDateTime in the specified time zone - /// - public IDateTime ToTimeZone(string tzId) + /// + /// Returns a representation of the IDateTime in the specified time zone + /// + public IDateTime ToTimeZone(string tzId) + { + if (string.IsNullOrWhiteSpace(tzId)) { - if (string.IsNullOrWhiteSpace(tzId)) - { - throw new ArgumentException("You must provide a valid time zone id", nameof(tzId)); - } - - // If TzId is empty, it's a system-local datetime, so we should use the system time zone as the starting point. - var originalTzId = string.IsNullOrWhiteSpace(TzId) - ? TimeZoneInfo.Local.Id - : TzId; + throw new ArgumentException("You must provide a valid time zone id", nameof(tzId)); + } - var zonedOriginal = DateUtil.ToZonedDateTimeLeniently(Value, originalTzId); - var converted = zonedOriginal.WithZone(DateUtil.GetZone(tzId)); + // If TzId is empty, it's a system-local datetime, so we should use the system time zone as the starting point. + var originalTzId = string.IsNullOrWhiteSpace(TzId) + ? TimeZoneInfo.Local.Id + : TzId; - return converted.Zone == DateTimeZone.Utc - ? new CalDateTime(converted.ToDateTimeUtc(), tzId) - : new CalDateTime(DateTime.SpecifyKind(converted.ToDateTimeUnspecified(), DateTimeKind.Local), tzId); - } + var zonedOriginal = DateUtil.ToZonedDateTimeLeniently(Value, originalTzId); + var converted = zonedOriginal.WithZone(DateUtil.GetZone(tzId)); - /// - /// Returns a DateTimeOffset representation of the Value. If a TzId is specified, it will use that time zone's UTC offset, otherwise it will use the - /// system-local time zone. - /// - public DateTimeOffset AsDateTimeOffset => - string.IsNullOrWhiteSpace(TzId) - ? new DateTimeOffset(AsSystemLocal) - : DateUtil.ToZonedDateTimeLeniently(Value, TzId).ToDateTimeOffset(); + return converted.Zone == DateTimeZone.Utc + ? new CalDateTime(converted.ToDateTimeUtc(), tzId) + : new CalDateTime(DateTime.SpecifyKind(converted.ToDateTimeUnspecified(), DateTimeKind.Local), tzId); + } - public IDateTime Add(TimeSpan ts) => this + ts; + /// + /// Returns a DateTimeOffset representation of the Value. If a TzId is specified, it will use that time zone's UTC offset, otherwise it will use the + /// system-local time zone. + /// + public DateTimeOffset AsDateTimeOffset => + string.IsNullOrWhiteSpace(TzId) + ? new DateTimeOffset(AsSystemLocal) + : DateUtil.ToZonedDateTimeLeniently(Value, TzId).ToDateTimeOffset(); - public IDateTime Subtract(TimeSpan ts) => this - ts; + public IDateTime Add(TimeSpan ts) => this + ts; - public TimeSpan Subtract(IDateTime dt) => this - dt; + public IDateTime Subtract(TimeSpan ts) => this - ts; - public IDateTime AddYears(int years) - { - var dt = Copy(); - dt.Value = Value.AddYears(years); - return dt; - } + public TimeSpan Subtract(IDateTime dt) => this - dt; - public IDateTime AddMonths(int months) - { - var dt = Copy(); - dt.Value = Value.AddMonths(months); - return dt; - } + public IDateTime AddYears(int years) + { + var dt = Copy(); + dt.Value = Value.AddYears(years); + return dt; + } - public IDateTime AddDays(int days) - { - var dt = Copy(); - dt.Value = Value.AddDays(days); - return dt; - } + public IDateTime AddMonths(int months) + { + var dt = Copy(); + dt.Value = Value.AddMonths(months); + return dt; + } - public IDateTime AddHours(int hours) - { - var dt = Copy(); - if (!dt.HasTime && hours % 24 > 0) - { - dt.HasTime = true; - } - dt.Value = Value.AddHours(hours); - return dt; - } + public IDateTime AddDays(int days) + { + var dt = Copy(); + dt.Value = Value.AddDays(days); + return dt; + } - public IDateTime AddMinutes(int minutes) + public IDateTime AddHours(int hours) + { + var dt = Copy(); + if (!dt.HasTime && hours % 24 > 0) { - var dt = Copy(); - if (!dt.HasTime && minutes % 1440 > 0) - { - dt.HasTime = true; - } - dt.Value = Value.AddMinutes(minutes); - return dt; + dt.HasTime = true; } + dt.Value = Value.AddHours(hours); + return dt; + } - public IDateTime AddSeconds(int seconds) + public IDateTime AddMinutes(int minutes) + { + var dt = Copy(); + if (!dt.HasTime && minutes % 1440 > 0) { - var dt = Copy(); - if (!dt.HasTime && seconds % 86400 > 0) - { - dt.HasTime = true; - } - dt.Value = Value.AddSeconds(seconds); - return dt; + dt.HasTime = true; } + dt.Value = Value.AddMinutes(minutes); + return dt; + } - public IDateTime AddMilliseconds(int milliseconds) + public IDateTime AddSeconds(int seconds) + { + var dt = Copy(); + if (!dt.HasTime && seconds % 86400 > 0) { - var dt = Copy(); - if (!dt.HasTime && milliseconds % 86400000 > 0) - { - dt.HasTime = true; - } - dt.Value = Value.AddMilliseconds(milliseconds); - return dt; + dt.HasTime = true; } + dt.Value = Value.AddSeconds(seconds); + return dt; + } - public IDateTime AddTicks(long ticks) + public IDateTime AddMilliseconds(int milliseconds) + { + var dt = Copy(); + if (!dt.HasTime && milliseconds % 86400000 > 0) { - var dt = Copy(); dt.HasTime = true; - dt.Value = Value.AddTicks(ticks); - return dt; } + dt.Value = Value.AddMilliseconds(milliseconds); + return dt; + } - public bool LessThan(IDateTime dt) => this < dt; + public IDateTime AddTicks(long ticks) + { + var dt = Copy(); + dt.HasTime = true; + dt.Value = Value.AddTicks(ticks); + return dt; + } - public bool GreaterThan(IDateTime dt) => this > dt; + public bool LessThan(IDateTime dt) => this < dt; - public bool LessThanOrEqual(IDateTime dt) => this <= dt; + public bool GreaterThan(IDateTime dt) => this > dt; - public bool GreaterThanOrEqual(IDateTime dt) => this >= dt; + public bool LessThanOrEqual(IDateTime dt) => this <= dt; - public void AssociateWith(IDateTime dt) + public bool GreaterThanOrEqual(IDateTime dt) => this >= dt; + + public void AssociateWith(IDateTime dt) + { + if (AssociatedObject == null && dt.AssociatedObject != null) { - if (AssociatedObject == null && dt.AssociatedObject != null) - { - AssociatedObject = dt.AssociatedObject; - } - else if (AssociatedObject != null && dt.AssociatedObject == null) - { - dt.AssociatedObject = AssociatedObject; - } + AssociatedObject = dt.AssociatedObject; } + else if (AssociatedObject != null && dt.AssociatedObject == null) + { + dt.AssociatedObject = AssociatedObject; + } + } - public int CompareTo(IDateTime dt) + public int CompareTo(IDateTime dt) + { + if (Equals(dt)) { - if (Equals(dt)) - { - return 0; - } - if (this < dt) - { - return -1; - } - if (this > dt) - { - return 1; - } - throw new Exception("An error occurred while comparing two IDateTime values."); + return 0; } + if (this < dt) + { + return -1; + } + if (this > dt) + { + return 1; + } + throw new Exception("An error occurred while comparing two IDateTime values."); + } - public override string ToString() => ToString(null, null); + public override string ToString() => ToString(null, null); - public string ToString(string format) => ToString(format, null); + public string ToString(string format) => ToString(format, null); - public string ToString(string format, IFormatProvider formatProvider) + public string ToString(string format, IFormatProvider formatProvider) + { + var tz = TimeZoneName; + if (!string.IsNullOrEmpty(tz)) { - var tz = TimeZoneName; - if (!string.IsNullOrEmpty(tz)) - { - tz = " " + tz; - } + tz = " " + tz; + } - if (format != null) - { - return Value.ToString(format, formatProvider) + tz; - } - if (HasTime && HasDate) - { - return Value + tz; - } - if (HasTime) - { - return Value.TimeOfDay + tz; - } - return Value.ToString("d") + tz; + if (format != null) + { + return Value.ToString(format, formatProvider) + tz; + } + if (HasTime && HasDate) + { + return Value + tz; + } + if (HasTime) + { + return Value.TimeOfDay + tz; } + return Value.ToString("d") + tz; } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/CalendarDataType.cs b/Ical.Net/DataTypes/CalendarDataType.cs index a888b67f1..a3889d877 100644 --- a/Ical.Net/DataTypes/CalendarDataType.cs +++ b/Ical.Net/DataTypes/CalendarDataType.cs @@ -1,178 +1,182 @@ -using Ical.Net.Proxies; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Runtime.Serialization; +using Ical.Net.Proxies; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +/// +/// An abstract class from which all iCalendar data types inherit. +/// +public abstract class CalendarDataType : ICalendarDataType { - /// - /// An abstract class from which all iCalendar data types inherit. - /// - public abstract class CalendarDataType : ICalendarDataType - { - private IParameterCollection _parameters; - private ParameterCollectionProxy _proxy; - private ServiceProvider _serviceProvider; + private IParameterCollection _parameters; + private ParameterCollectionProxy _proxy; + private ServiceProvider _serviceProvider; - private ICalendarObject _associatedObject; + private ICalendarObject _associatedObject; - protected CalendarDataType() - { - Initialize(); - } + protected CalendarDataType() + { + Initialize(); + } - private void Initialize() - { - _parameters = new ParameterList(); - _proxy = new ParameterCollectionProxy(_parameters); - _serviceProvider = new ServiceProvider(); - } + private void Initialize() + { + _parameters = new ParameterList(); + _proxy = new ParameterCollectionProxy(_parameters); + _serviceProvider = new ServiceProvider(); + } - [OnDeserializing] - internal void DeserializingInternal(StreamingContext context) - { - OnDeserializing(context); - } + [OnDeserializing] + internal void DeserializingInternal(StreamingContext context) + { + OnDeserializing(context); + } - [OnDeserialized] - internal void DeserializedInternal(StreamingContext context) - { - OnDeserialized(context); - } + [OnDeserialized] + internal void DeserializedInternal(StreamingContext context) + { + OnDeserialized(context); + } - protected virtual void OnDeserializing(StreamingContext context) - { - Initialize(); - } + protected virtual void OnDeserializing(StreamingContext context) + { + Initialize(); + } - protected virtual void OnDeserialized(StreamingContext context) { } + protected virtual void OnDeserialized(StreamingContext context) { } - public virtual Type GetValueType() + public virtual Type GetValueType() + { + // See RFC 5545 Section 3.2.20. + if (_proxy != null && _proxy.ContainsKey("VALUE")) { - // See RFC 5545 Section 3.2.20. - if (_proxy != null && _proxy.ContainsKey("VALUE")) + switch (_proxy.Get("VALUE")) { - switch (_proxy.Get("VALUE")) - { - case "BINARY": - return typeof(byte[]); - case "BOOLEAN": - return typeof(bool); - case "CAL-ADDRESS": - return typeof(Uri); - case "DATE": - return typeof(IDateTime); - case "DATE-TIME": - return typeof(IDateTime); - case "DURATION": - return typeof(TimeSpan); - case "FLOAT": - return typeof(double); - case "INTEGER": - return typeof(int); - case "PERIOD": - return typeof(Period); - case "RECUR": - return typeof(RecurrencePattern); - case "TEXT": - return typeof(string); - case "TIME": - // FIXME: implement ISO.8601.2004 - throw new NotImplementedException(); - case "URI": - return typeof(Uri); - case "UTC-OFFSET": - return typeof(UtcOffset); - default: - return null; - } + case "BINARY": + return typeof(byte[]); + case "BOOLEAN": + return typeof(bool); + case "CAL-ADDRESS": + return typeof(Uri); + case "DATE": + return typeof(IDateTime); + case "DATE-TIME": + return typeof(IDateTime); + case "DURATION": + return typeof(TimeSpan); + case "FLOAT": + return typeof(double); + case "INTEGER": + return typeof(int); + case "PERIOD": + return typeof(Period); + case "RECUR": + return typeof(RecurrencePattern); + case "TEXT": + return typeof(string); + case "TIME": + // FIXME: implement ISO.8601.2004 + throw new NotImplementedException(); + case "URI": + return typeof(Uri); + case "UTC-OFFSET": + return typeof(UtcOffset); + default: + return null; } - return null; } + return null; + } - public virtual void SetValueType(string type) - { - _proxy?.Set("VALUE", type?.ToUpper()); - } + public virtual void SetValueType(string type) + { + _proxy?.Set("VALUE", type?.ToUpper()); + } - public virtual ICalendarObject AssociatedObject + public virtual ICalendarObject AssociatedObject + { + get => _associatedObject; + set { - get => _associatedObject; - set + if (Equals(_associatedObject, value)) { - if (Equals(_associatedObject, value)) - { - return; - } + return; + } - _associatedObject = value; - if (_associatedObject != null) - { - _proxy.SetParent(_associatedObject); - if (_associatedObject is ICalendarParameterCollectionContainer) - { - _proxy.SetProxiedObject(((ICalendarParameterCollectionContainer)_associatedObject).Parameters); - } - } - else + _associatedObject = value; + if (_associatedObject != null) + { + _proxy.SetParent(_associatedObject); + if (_associatedObject is ICalendarParameterCollectionContainer) { - _proxy.SetParent(null); - _proxy.SetProxiedObject(_parameters); + _proxy.SetProxiedObject(((ICalendarParameterCollectionContainer) _associatedObject).Parameters); } } + else + { + _proxy.SetParent(null); + _proxy.SetProxiedObject(_parameters); + } } + } - public virtual Calendar Calendar => _associatedObject?.Calendar; + public virtual Calendar Calendar => _associatedObject?.Calendar; - public virtual string Language - { - get => Parameters.Get("LANGUAGE"); - set => Parameters.Set("LANGUAGE", value); - } + public virtual string Language + { + get => Parameters.Get("LANGUAGE"); + set => Parameters.Set("LANGUAGE", value); + } - /// - public virtual void CopyFrom(ICopyable obj) + /// + public virtual void CopyFrom(ICopyable obj) + { + if (obj is not ICalendarDataType dt) { - if (obj is not ICalendarDataType dt) - { - return; - } - - _associatedObject = dt.AssociatedObject; - _proxy.SetParent(_associatedObject); - _proxy.SetProxiedObject(dt.Parameters); + return; } - /// - /// Creates a deep copy of the object. - /// - /// The copy of the object. - public virtual T Copy() - { - var type = GetType(); - var obj = Activator.CreateInstance(type) as ICopyable; + _associatedObject = dt.AssociatedObject; + _proxy.SetParent(_associatedObject); + _proxy.SetProxiedObject(dt.Parameters); + } - if (obj is not T o) return default(T); + /// + /// Creates a deep copy of the object. + /// + /// The copy of the object. + public virtual T Copy() + { + var type = GetType(); + var obj = Activator.CreateInstance(type) as ICopyable; - obj.CopyFrom(this); - return o; - } + if (obj is not T o) return default(T); - public virtual IParameterCollection Parameters => _proxy; + obj.CopyFrom(this); + return o; + } - public virtual object GetService(Type serviceType) => _serviceProvider.GetService(serviceType); + public virtual IParameterCollection Parameters => _proxy; - public object GetService(string name) => _serviceProvider.GetService(name); + public virtual object GetService(Type serviceType) => _serviceProvider.GetService(serviceType); - public T GetService() => _serviceProvider.GetService(); + public object GetService(string name) => _serviceProvider.GetService(name); - public T GetService(string name) => _serviceProvider.GetService(name); + public T GetService() => _serviceProvider.GetService(); - public void SetService(string name, object obj) => _serviceProvider.SetService(name, obj); + public T GetService(string name) => _serviceProvider.GetService(name); - public void SetService(object obj) => _serviceProvider.SetService(obj); + public void SetService(string name, object obj) => _serviceProvider.SetService(name, obj); - public void RemoveService(Type type) => _serviceProvider.RemoveService(type); + public void SetService(object obj) => _serviceProvider.SetService(obj); - public void RemoveService(string name) => _serviceProvider.RemoveService(name); - } + public void RemoveService(Type type) => _serviceProvider.RemoveService(type); + + public void RemoveService(string name) => _serviceProvider.RemoveService(name); } \ No newline at end of file diff --git a/Ical.Net/DataTypes/EncodableDataType.cs b/Ical.Net/DataTypes/EncodableDataType.cs index 1658fe2d1..c07243041 100644 --- a/Ical.Net/DataTypes/EncodableDataType.cs +++ b/Ical.Net/DataTypes/EncodableDataType.cs @@ -1,14 +1,18 @@ -namespace Ical.Net.DataTypes +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +namespace Ical.Net.DataTypes; + +/// +/// An abstract class from which all iCalendar data types inherit. +/// +public class EncodableDataType : CalendarDataType, IEncodableDataType { - /// - /// An abstract class from which all iCalendar data types inherit. - /// - public class EncodableDataType : CalendarDataType, IEncodableDataType + public virtual string Encoding { - public virtual string Encoding - { - get => Parameters.Get("ENCODING"); - set => Parameters.Set("ENCODING", value); - } + get => Parameters.Get("ENCODING"); + set => Parameters.Set("ENCODING", value); } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/FreeBusyEntry.cs b/Ical.Net/DataTypes/FreeBusyEntry.cs index a73d53730..19fddf976 100644 --- a/Ical.Net/DataTypes/FreeBusyEntry.cs +++ b/Ical.Net/DataTypes/FreeBusyEntry.cs @@ -1,31 +1,35 @@ -namespace Ical.Net.DataTypes +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +namespace Ical.Net.DataTypes; + +public class FreeBusyEntry : Period { - public class FreeBusyEntry : Period + public virtual FreeBusyStatus Status { get; set; } + + public FreeBusyEntry() { - public virtual FreeBusyStatus Status { get; set; } + Status = FreeBusyStatus.Busy; + } - public FreeBusyEntry() - { - Status = FreeBusyStatus.Busy; - } + public FreeBusyEntry(Period period, FreeBusyStatus status) + { + //Sets the status associated with a given period, which requires copying the period values + //Probably the Period object should just have a FreeBusyStatus directly? + CopyFrom(period); + Status = status; + } - public FreeBusyEntry(Period period, FreeBusyStatus status) - { - //Sets the status associated with a given period, which requires copying the period values - //Probably the Period object should just have a FreeBusyStatus directly? - CopyFrom(period); - Status = status; - } + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); - /// - public override void CopyFrom(ICopyable obj) + if (obj is FreeBusyEntry fb) { - base.CopyFrom(obj); - - if (obj is FreeBusyEntry fb) - { - Status = fb.Status; - } + Status = fb.Status; } } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/GeographicLocation.cs b/Ical.Net/DataTypes/GeographicLocation.cs index 9e5698da8..6a6e3cad4 100644 --- a/Ical.Net/DataTypes/GeographicLocation.cs +++ b/Ical.Net/DataTypes/GeographicLocation.cs @@ -1,65 +1,69 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.Serialization.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Diagnostics; +using Ical.Net.CalendarComponents; +using Ical.Net.Serialization.DataTypes; -namespace Ical.Net.DataTypes +namespace Ical.Net.DataTypes; + +/// +/// A class that represents the geographical location of an +/// or item. +/// +[DebuggerDisplay("{Latitude};{Longitude}")] +public class GeographicLocation : EncodableDataType { - /// - /// A class that represents the geographical location of an - /// or item. - /// - [DebuggerDisplay("{Latitude};{Longitude}")] - public class GeographicLocation : EncodableDataType + public double Latitude { get; set; } + public double Longitude { get; set; } + + public GeographicLocation() { } + + public GeographicLocation(string value) : this() { - public double Latitude { get; set; } - public double Longitude { get; set; } + var serializer = new GeographicLocationSerializer(); + serializer.Deserialize(value); + } - public GeographicLocation() { } + public GeographicLocation(double latitude, double longitude) + { + Latitude = latitude; + Longitude = longitude; + } - public GeographicLocation(string value) : this() - { - var serializer = new GeographicLocationSerializer(); - serializer.Deserialize(value); - } + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); - public GeographicLocation(double latitude, double longitude) + var geo = obj as GeographicLocation; + if (geo == null) { - Latitude = latitude; - Longitude = longitude; + return; } - /// - public override void CopyFrom(ICopyable obj) - { - base.CopyFrom(obj); - - var geo = obj as GeographicLocation; - if (geo == null) - { - return; - } - - Latitude = geo.Latitude; - Longitude = geo.Longitude; - } + Latitude = geo.Latitude; + Longitude = geo.Longitude; + } - public override string ToString() => Latitude.ToString("0.000000") + ";" + Longitude.ToString("0.000000"); + public override string ToString() => Latitude.ToString("0.000000") + ";" + Longitude.ToString("0.000000"); - protected bool Equals(GeographicLocation other) => Latitude.Equals(other.Latitude) && Longitude.Equals(other.Longitude); + protected bool Equals(GeographicLocation other) => Latitude.Equals(other.Latitude) && Longitude.Equals(other.Longitude); - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((GeographicLocation)obj); - } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((GeographicLocation) obj); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - return (Latitude.GetHashCode() * 397) ^ Longitude.GetHashCode(); - } + return (Latitude.GetHashCode() * 397) ^ Longitude.GetHashCode(); } } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/ICalendarDataType.cs b/Ical.Net/DataTypes/ICalendarDataType.cs index 304c82544..8b99b439e 100644 --- a/Ical.Net/DataTypes/ICalendarDataType.cs +++ b/Ical.Net/DataTypes/ICalendarDataType.cs @@ -1,14 +1,18 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net.DataTypes +using System; + +namespace Ical.Net.DataTypes; + +public interface ICalendarDataType : ICalendarParameterCollectionContainer, ICopyable, IServiceProvider { - public interface ICalendarDataType : ICalendarParameterCollectionContainer, ICopyable, IServiceProvider - { - Type GetValueType(); - void SetValueType(string type); - ICalendarObject AssociatedObject { get; set; } - Calendar Calendar { get; } - - string Language { get; set; } - } + Type GetValueType(); + void SetValueType(string type); + ICalendarObject AssociatedObject { get; set; } + Calendar Calendar { get; } + + string Language { get; set; } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/ICalendarParameterCollectionContainer.cs b/Ical.Net/DataTypes/ICalendarParameterCollectionContainer.cs index 2f5b8dcf8..36ca6a57b 100644 --- a/Ical.Net/DataTypes/ICalendarParameterCollectionContainer.cs +++ b/Ical.Net/DataTypes/ICalendarParameterCollectionContainer.cs @@ -1,7 +1,11 @@ -namespace Ical.Net.DataTypes +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +namespace Ical.Net.DataTypes; + +public interface ICalendarParameterCollectionContainer { - public interface ICalendarParameterCollectionContainer - { - IParameterCollection Parameters { get; } - } + IParameterCollection Parameters { get; } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/IDateTime.cs b/Ical.Net/DataTypes/IDateTime.cs index bead53f51..155d374fd 100644 --- a/Ical.Net/DataTypes/IDateTime.cs +++ b/Ical.Net/DataTypes/IDateTime.cs @@ -1,133 +1,137 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net.DataTypes +using System; + +namespace Ical.Net.DataTypes; + +public interface IDateTime : IEncodableDataType, IComparable, IFormattable, ICalendarDataType { - public interface IDateTime : IEncodableDataType, IComparable, IFormattable, ICalendarDataType - { - /// - /// Converts the date/time to this computer's local date/time. - /// - DateTime AsSystemLocal { get; } - - /// - /// Converts the date/time to UTC (Coordinated Universal Time) - /// - DateTime AsUtc { get; } - - /// - /// Returns a DateTimeOffset representation of the Value. If a TzId is specified, it will use that time zone's UTC offset, otherwise it will use the - /// system-local time zone. - /// - DateTimeOffset AsDateTimeOffset { get; } - - /// - /// Gets/sets whether the Value of this date/time represents - /// a universal time. - /// - bool IsUtc { get; } - - /// - /// Gets the time zone name this time is in, if it references a time zone. - /// - string TimeZoneName { get; } - - /// - /// Gets/sets the underlying DateTime value stored. This should always - /// use DateTimeKind.Utc, regardless of its actual representation. - /// Use IsUtc along with the TZID to control how this - /// date/time is handled. - /// - DateTime Value { get; set; } - - /// - /// Gets/sets whether or not this date/time value contains a 'date' part. - /// - bool HasDate { get; set; } - - /// - /// Gets/sets whether or not this date/time value contains a 'time' part. - /// - bool HasTime { get; set; } - - /// - /// Gets/sets the time zone ID for this date/time value. - /// - string TzId { get; set; } - - /// - /// Gets the year for this date/time value. - /// - int Year { get; } - - /// - /// Gets the month for this date/time value. - /// - int Month { get; } - - /// - /// Gets the day for this date/time value. - /// - int Day { get; } - - /// - /// Gets the hour for this date/time value. - /// - int Hour { get; } - - /// - /// Gets the minute for this date/time value. - /// - int Minute { get; } - - /// - /// Gets the second for this date/time value. - /// - int Second { get; } - - /// - /// Gets the millisecond for this date/time value. - /// - int Millisecond { get; } - - /// - /// Gets the ticks for this date/time value. - /// - long Ticks { get; } - - /// - /// Gets the DayOfWeek for this date/time value. - /// - DayOfWeek DayOfWeek { get; } - - /// - /// Gets the date portion of the date/time value. - /// - DateTime Date { get; } - - /// - /// Converts the date/time value to a local time - /// within the specified time zone. - /// - IDateTime ToTimeZone(string tzId); - - IDateTime Add(TimeSpan ts); - IDateTime Subtract(TimeSpan ts); - TimeSpan Subtract(IDateTime dt); - - IDateTime AddYears(int years); - IDateTime AddMonths(int months); - IDateTime AddDays(int days); - IDateTime AddHours(int hours); - IDateTime AddMinutes(int minutes); - IDateTime AddSeconds(int seconds); - IDateTime AddMilliseconds(int milliseconds); - IDateTime AddTicks(long ticks); - - bool LessThan(IDateTime dt); - bool GreaterThan(IDateTime dt); - bool LessThanOrEqual(IDateTime dt); - bool GreaterThanOrEqual(IDateTime dt); - - void AssociateWith(IDateTime dt); - } + /// + /// Converts the date/time to this computer's local date/time. + /// + DateTime AsSystemLocal { get; } + + /// + /// Converts the date/time to UTC (Coordinated Universal Time) + /// + DateTime AsUtc { get; } + + /// + /// Returns a DateTimeOffset representation of the Value. If a TzId is specified, it will use that time zone's UTC offset, otherwise it will use the + /// system-local time zone. + /// + DateTimeOffset AsDateTimeOffset { get; } + + /// + /// Gets/sets whether the Value of this date/time represents + /// a universal time. + /// + bool IsUtc { get; } + + /// + /// Gets the time zone name this time is in, if it references a time zone. + /// + string TimeZoneName { get; } + + /// + /// Gets/sets the underlying DateTime value stored. This should always + /// use DateTimeKind.Utc, regardless of its actual representation. + /// Use IsUtc along with the TZID to control how this + /// date/time is handled. + /// + DateTime Value { get; set; } + + /// + /// Gets/sets whether or not this date/time value contains a 'date' part. + /// + bool HasDate { get; set; } + + /// + /// Gets/sets whether or not this date/time value contains a 'time' part. + /// + bool HasTime { get; set; } + + /// + /// Gets/sets the time zone ID for this date/time value. + /// + string TzId { get; set; } + + /// + /// Gets the year for this date/time value. + /// + int Year { get; } + + /// + /// Gets the month for this date/time value. + /// + int Month { get; } + + /// + /// Gets the day for this date/time value. + /// + int Day { get; } + + /// + /// Gets the hour for this date/time value. + /// + int Hour { get; } + + /// + /// Gets the minute for this date/time value. + /// + int Minute { get; } + + /// + /// Gets the second for this date/time value. + /// + int Second { get; } + + /// + /// Gets the millisecond for this date/time value. + /// + int Millisecond { get; } + + /// + /// Gets the ticks for this date/time value. + /// + long Ticks { get; } + + /// + /// Gets the DayOfWeek for this date/time value. + /// + DayOfWeek DayOfWeek { get; } + + /// + /// Gets the date portion of the date/time value. + /// + DateTime Date { get; } + + /// + /// Converts the date/time value to a local time + /// within the specified time zone. + /// + IDateTime ToTimeZone(string tzId); + + IDateTime Add(TimeSpan ts); + IDateTime Subtract(TimeSpan ts); + TimeSpan Subtract(IDateTime dt); + + IDateTime AddYears(int years); + IDateTime AddMonths(int months); + IDateTime AddDays(int days); + IDateTime AddHours(int hours); + IDateTime AddMinutes(int minutes); + IDateTime AddSeconds(int seconds); + IDateTime AddMilliseconds(int milliseconds); + IDateTime AddTicks(long ticks); + + bool LessThan(IDateTime dt); + bool GreaterThan(IDateTime dt); + bool LessThanOrEqual(IDateTime dt); + bool GreaterThanOrEqual(IDateTime dt); + + void AssociateWith(IDateTime dt); } \ No newline at end of file diff --git a/Ical.Net/DataTypes/IEncodableDataType.cs b/Ical.Net/DataTypes/IEncodableDataType.cs index 836c4e111..82cd0283f 100644 --- a/Ical.Net/DataTypes/IEncodableDataType.cs +++ b/Ical.Net/DataTypes/IEncodableDataType.cs @@ -1,7 +1,11 @@ -namespace Ical.Net.DataTypes +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +namespace Ical.Net.DataTypes; + +public interface IEncodableDataType { - public interface IEncodableDataType - { - string Encoding { get; set; } - } + string Encoding { get; set; } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/Occurrence.cs b/Ical.Net/DataTypes/Occurrence.cs index 492cdbb5c..afbbf18de 100644 --- a/Ical.Net/DataTypes/Occurrence.cs +++ b/Ical.Net/DataTypes/Occurrence.cs @@ -1,60 +1,64 @@ -using Ical.Net.CalendarComponents; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; +using Ical.Net.CalendarComponents; -namespace Ical.Net.DataTypes +namespace Ical.Net.DataTypes; + +public class Occurrence : IComparable { - public class Occurrence : IComparable + public Period Period { get; set; } + public IRecurrable Source { get; set; } + + public Occurrence(Occurrence ao) { - public Period Period { get; set; } - public IRecurrable Source { get; set; } + Period = ao.Period; + Source = ao.Source; + } - public Occurrence(Occurrence ao) - { - Period = ao.Period; - Source = ao.Source; - } + public Occurrence(IRecurrable recurrable, Period period) + { + Source = recurrable; + Period = period; + } + + public bool Equals(Occurrence other) => Equals(Period, other.Period) && Equals(Source, other.Source); - public Occurrence(IRecurrable recurrable, Period period) + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { - Source = recurrable; - Period = period; + return false; } + return obj is Occurrence && Equals((Occurrence) obj); + } - public bool Equals(Occurrence other) => Equals(Period, other.Period) && Equals(Source, other.Source); - - public override bool Equals(object obj) + public override int GetHashCode() + { + unchecked { - if (ReferenceEquals(null, obj)) - { - return false; - } - return obj is Occurrence && Equals((Occurrence)obj); + return ((Period?.GetHashCode() ?? 0) * 397) ^ (Source?.GetHashCode() ?? 0); } + } - public override int GetHashCode() + public override string ToString() + { + var s = "Occurrence"; + if (Source != null) { - unchecked - { - return ((Period?.GetHashCode() ?? 0) * 397) ^ (Source?.GetHashCode() ?? 0); - } + s = Source.GetType().Name + " "; } - public override string ToString() + if (Period != null) { - var s = "Occurrence"; - if (Source != null) - { - s = Source.GetType().Name + " "; - } - - if (Period != null) - { - s += "(" + Period.StartTime + ")"; - } - - return s; + s += "(" + Period.StartTime + ")"; } - public int CompareTo(Occurrence other) => Period.CompareTo(other.Period); + return s; } + + public int CompareTo(Occurrence other) => Period.CompareTo(other.Period); } \ No newline at end of file diff --git a/Ical.Net/DataTypes/Organizer.cs b/Ical.Net/DataTypes/Organizer.cs index 58374d07e..0eed39872 100644 --- a/Ical.Net/DataTypes/Organizer.cs +++ b/Ical.Net/DataTypes/Organizer.cs @@ -1,99 +1,103 @@ -using Ical.Net.Serialization.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Diagnostics; using System.IO; +using Ical.Net.Serialization.DataTypes; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +/// +/// A class that represents the organizer of an event/todo/journal. +/// +[DebuggerDisplay("{Value}")] +public class Organizer : EncodableDataType { - /// - /// A class that represents the organizer of an event/todo/journal. - /// - [DebuggerDisplay("{Value}")] - public class Organizer : EncodableDataType + public virtual Uri SentBy { - public virtual Uri SentBy + get => new Uri(Parameters.Get("SENT-BY")); + set { - get => new Uri(Parameters.Get("SENT-BY")); - set + if (value != null) { - if (value != null) - { - Parameters.Set("SENT-BY", value.OriginalString); - } - else - { - Parameters.Set("SENT-BY", (string)null); - } + Parameters.Set("SENT-BY", value.OriginalString); + } + else + { + Parameters.Set("SENT-BY", (string) null); } } + } - public virtual string CommonName - { - get => Parameters.Get("CN"); - set => Parameters.Set("CN", value); - } + public virtual string CommonName + { + get => Parameters.Get("CN"); + set => Parameters.Set("CN", value); + } - public virtual Uri DirectoryEntry + public virtual Uri DirectoryEntry + { + get => new Uri(Parameters.Get("DIR")); + set { - get => new Uri(Parameters.Get("DIR")); - set + if (value != null) + { + Parameters.Set("DIR", value.OriginalString); + } + else { - if (value != null) - { - Parameters.Set("DIR", value.OriginalString); - } - else - { - Parameters.Set("DIR", (string)null); - } + Parameters.Set("DIR", (string) null); } } + } - public virtual Uri Value { get; set; } + public virtual Uri Value { get; set; } - public Organizer() { } + public Organizer() { } - public Organizer(string value) : this() + public Organizer(string value) : this() + { + if (string.IsNullOrWhiteSpace(value)) { - if (string.IsNullOrWhiteSpace(value)) - { - return; - } - - var serializer = new OrganizerSerializer(); - CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + return; } - protected bool Equals(Organizer other) => Equals(Value, other.Value); + var serializer = new OrganizerSerializer(); + CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + } + + protected bool Equals(Organizer other) => Equals(Value, other.Value); - public override bool Equals(object obj) + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, obj)) - { - return false; - } - if (ReferenceEquals(this, obj)) - { - return true; - } - if (obj.GetType() != GetType()) - { - return false; - } - return Equals((Organizer)obj); + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != GetType()) + { + return false; } + return Equals((Organizer) obj); + } - public override int GetHashCode() => Value?.GetHashCode() ?? 0; + public override int GetHashCode() => Value?.GetHashCode() ?? 0; - /// - public override void CopyFrom(ICopyable obj) - { - base.CopyFrom(obj); + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); - if (obj is Organizer o) - { - Value = o.Value; - } + if (obj is Organizer o) + { + Value = o.Value; } } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/Period.cs b/Ical.Net/DataTypes/Period.cs index f5fefa331..c64bcd11b 100644 --- a/Ical.Net/DataTypes/Period.cs +++ b/Ical.Net/DataTypes/Period.cs @@ -1,189 +1,193 @@ -using Ical.Net.Serialization.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; +using Ical.Net.Serialization.DataTypes; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +/// Represents an iCalendar period of time. +public class Period : EncodableDataType, IComparable { - /// Represents an iCalendar period of time. - public class Period : EncodableDataType, IComparable - { - public Period() { } + public Period() { } - public Period(IDateTime occurs) - : this(occurs, default(TimeSpan)) { } + public Period(IDateTime occurs) + : this(occurs, default(TimeSpan)) { } - public Period(IDateTime start, IDateTime end) + public Period(IDateTime start, IDateTime end) + { + if (end != null && end.LessThanOrEqual(start)) { - if (end != null && end.LessThanOrEqual(start)) - { - throw new ArgumentException($"Start time ( {start} ) must come before the end time ( {end} )"); - } - - StartTime = start; - if (end == null) - { - return; - } - EndTime = end; - Duration = end.Subtract(start); + throw new ArgumentException($"Start time ( {start} ) must come before the end time ( {end} )"); } - public Period(IDateTime start, TimeSpan duration) + StartTime = start; + if (end == null) { - if (duration < TimeSpan.Zero) - { - throw new ArgumentException($"Duration ( ${duration} ) cannot be less than zero"); - } - - StartTime = start; - if (duration == default(TimeSpan)) - { - return; - } + return; + } + EndTime = end; + Duration = end.Subtract(start); + } - Duration = duration; - EndTime = start.Add(duration); + public Period(IDateTime start, TimeSpan duration) + { + if (duration < TimeSpan.Zero) + { + throw new ArgumentException($"Duration ( ${duration} ) cannot be less than zero"); } - /// - public override void CopyFrom(ICopyable obj) + StartTime = start; + if (duration == default(TimeSpan)) { - base.CopyFrom(obj); + return; + } - if (obj is not Period p) return; + Duration = duration; + EndTime = start.Add(duration); + } - StartTime = p.StartTime?.Copy(); - EndTime = p.EndTime?.Copy(); - Duration = p.Duration; - } + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); - protected bool Equals(Period other) => Equals(StartTime, other.StartTime) && Equals(EndTime, other.EndTime) && Duration.Equals(other.Duration); + if (obj is not Period p) return; - public override bool Equals(object obj) + StartTime = p.StartTime?.Copy(); + EndTime = p.EndTime?.Copy(); + Duration = p.Duration; + } + + protected bool Equals(Period other) => Equals(StartTime, other.StartTime) && Equals(EndTime, other.EndTime) && Duration.Equals(other.Duration); + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((Period) obj); + } + + public override int GetHashCode() + { + unchecked { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((Period)obj); + var hashCode = StartTime?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (EndTime?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ Duration.GetHashCode(); + return hashCode; } + } - public override int GetHashCode() + public override string ToString() + { + var periodSerializer = new PeriodSerializer(); + return periodSerializer.SerializeToString(this); + } + + private void ExtrapolateTimes() + { + if (EndTime == null && StartTime != null && Duration != default(TimeSpan)) { - unchecked - { - var hashCode = StartTime?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ (EndTime?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ Duration.GetHashCode(); - return hashCode; - } + EndTime = StartTime.Add(Duration); } - - public override string ToString() + else if (Duration == default(TimeSpan) && StartTime != null && EndTime != null) { - var periodSerializer = new PeriodSerializer(); - return periodSerializer.SerializeToString(this); + Duration = EndTime.Subtract(StartTime); } - - private void ExtrapolateTimes() + else if (StartTime == null && Duration != default(TimeSpan) && EndTime != null) { - if (EndTime == null && StartTime != null && Duration != default(TimeSpan)) - { - EndTime = StartTime.Add(Duration); - } - else if (Duration == default(TimeSpan) && StartTime != null && EndTime != null) - { - Duration = EndTime.Subtract(StartTime); - } - else if (StartTime == null && Duration != default(TimeSpan) && EndTime != null) - { - StartTime = EndTime.Subtract(Duration); - } + StartTime = EndTime.Subtract(Duration); } + } - private IDateTime _startTime; - public virtual IDateTime StartTime + private IDateTime _startTime; + public virtual IDateTime StartTime + { + get => _startTime.HasTime + ? _startTime + : new CalDateTime(new DateTime(_startTime.Value.Year, _startTime.Value.Month, _startTime.Value.Day, 0, 0, 0), _startTime.TzId); + set { - get => _startTime.HasTime - ? _startTime - : new CalDateTime(new DateTime(_startTime.Value.Year, _startTime.Value.Month, _startTime.Value.Day, 0, 0, 0), _startTime.TzId); - set + if (Equals(_startTime, value)) { - if (Equals(_startTime, value)) - { - return; - } - _startTime = value; - ExtrapolateTimes(); + return; } + _startTime = value; + ExtrapolateTimes(); } + } - private IDateTime _endTime; - public virtual IDateTime EndTime + private IDateTime _endTime; + public virtual IDateTime EndTime + { + get => _endTime; + set { - get => _endTime; - set + if (Equals(_endTime, value)) { - if (Equals(_endTime, value)) - { - return; - } - _endTime = value; - ExtrapolateTimes(); + return; } + _endTime = value; + ExtrapolateTimes(); } + } - private TimeSpan _duration; - public virtual TimeSpan Duration + private TimeSpan _duration; + public virtual TimeSpan Duration + { + get { - get - { - if (StartTime != null - && EndTime == null - && StartTime.Value.TimeOfDay == TimeSpan.Zero) - { - return TimeSpan.FromDays(1); - } - return _duration; - } - set + if (StartTime != null + && EndTime == null + && StartTime.Value.TimeOfDay == TimeSpan.Zero) { - if (Equals(_duration, value)) - { - return; - } - _duration = value; - ExtrapolateTimes(); + return TimeSpan.FromDays(1); } + return _duration; } - - public virtual bool Contains(IDateTime dt) + set { - // Start time is inclusive - if (dt == null || StartTime == null || !StartTime.LessThanOrEqual(dt)) + if (Equals(_duration, value)) { - return false; + return; } + _duration = value; + ExtrapolateTimes(); + } + } - // End time is exclusive - return EndTime == null || EndTime.GreaterThan(dt); + public virtual bool Contains(IDateTime dt) + { + // Start time is inclusive + if (dt == null || StartTime == null || !StartTime.LessThanOrEqual(dt)) + { + return false; } - public virtual bool CollidesWith(Period period) => period != null - && ((period.StartTime != null && Contains(period.StartTime)) || (period.EndTime != null && Contains(period.EndTime))); + // End time is exclusive + return EndTime == null || EndTime.GreaterThan(dt); + } + + public virtual bool CollidesWith(Period period) => period != null + && ((period.StartTime != null && Contains(period.StartTime)) || (period.EndTime != null && Contains(period.EndTime))); - public int CompareTo(Period other) + public int CompareTo(Period other) + { + if (StartTime.Equals(other.StartTime)) { - if (StartTime.Equals(other.StartTime)) - { - return 0; - } - if (StartTime.LessThan(other.StartTime)) - { - return -1; - } - if (StartTime.GreaterThan(other.StartTime)) - { - return 1; - } - throw new Exception("An error occurred while comparing two Periods."); + return 0; + } + if (StartTime.LessThan(other.StartTime)) + { + return -1; + } + if (StartTime.GreaterThan(other.StartTime)) + { + return 1; } + throw new Exception("An error occurred while comparing two Periods."); } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/PeriodList.cs b/Ical.Net/DataTypes/PeriodList.cs index 5379bb0f9..44fd59794 100644 --- a/Ical.Net/DataTypes/PeriodList.cs +++ b/Ical.Net/DataTypes/PeriodList.cs @@ -1,125 +1,129 @@ -using Ical.Net.Evaluation; -using Ical.Net.Serialization.DataTypes; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; +using Ical.Net.Evaluation; +using Ical.Net.Serialization.DataTypes; +using Ical.Net.Utility; -namespace Ical.Net.DataTypes +namespace Ical.Net.DataTypes; + +/// +/// An iCalendar list of recurring dates (or date exclusions) +/// +public class PeriodList : EncodableDataType, IList { - /// - /// An iCalendar list of recurring dates (or date exclusions) - /// - public class PeriodList : EncodableDataType, IList + public string TzId { get; set; } + public int Count => Periods.Count; + + protected IList Periods { get; set; } = new List(); + + public PeriodList() { - public string TzId { get; set; } - public int Count => Periods.Count; + SetService(new PeriodListEvaluator(this)); + } - protected IList Periods { get; set; } = new List(); + public PeriodList(string value) : this() + { + var serializer = new PeriodListSerializer(); + CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + } - public PeriodList() + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); + if (obj is not PeriodList list) { - SetService(new PeriodListEvaluator(this)); + return; } - public PeriodList(string value) : this() + foreach (var p in list) { - var serializer = new PeriodListSerializer(); - CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + Add(p.Copy()); } - /// - public override void CopyFrom(ICopyable obj) - { - base.CopyFrom(obj); - if (obj is not PeriodList list) - { - return; - } - - foreach (var p in list) - { - Add(p.Copy()); - } - - // String assignments create new instances - TzId = list.TzId; - } + // String assignments create new instances + TzId = list.TzId; + } - public override string ToString() => new PeriodListSerializer().SerializeToString(this); + public override string ToString() => new PeriodListSerializer().SerializeToString(this); - public void Add(IDateTime dt) => Periods.Add(new Period(dt)); + public void Add(IDateTime dt) => Periods.Add(new Period(dt)); - public static Dictionary> GetGroupedPeriods(IList periodLists) + public static Dictionary> GetGroupedPeriods(IList periodLists) + { + // In order to know if two events are equal, a semantic understanding of exdates, rdates, rrules, and exrules is required. This could be done by + // computing the complete recurrence set (expensive) while being time-zone sensitive, or by comparing each List in each IPeriodList. + + // For example, events containing these rules generate the same recurrence set, including having the same time zone for each occurrence, so + // they're the same: + // Event A: + // RDATE:20170302T060000Z,20170303T060000Z + // Event B: + // RDATE:20170302T060000Z + // RDATE:20170303T060000Z + + var grouped = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (var periodList in periodLists) { - // In order to know if two events are equal, a semantic understanding of exdates, rdates, rrules, and exrules is required. This could be done by - // computing the complete recurrence set (expensive) while being time-zone sensitive, or by comparing each List in each IPeriodList. - - // For example, events containing these rules generate the same recurrence set, including having the same time zone for each occurrence, so - // they're the same: - // Event A: - // RDATE:20170302T060000Z,20170303T060000Z - // Event B: - // RDATE:20170302T060000Z - // RDATE:20170303T060000Z - - var grouped = new Dictionary>(StringComparer.OrdinalIgnoreCase); - foreach (var periodList in periodLists) + var defaultBucket = string.IsNullOrWhiteSpace(periodList.TzId) ? "" : periodList.TzId; + + foreach (var period in periodList) { - var defaultBucket = string.IsNullOrWhiteSpace(periodList.TzId) ? "" : periodList.TzId; + var actualBucket = string.IsNullOrWhiteSpace(period.StartTime.TzId) ? defaultBucket : period.StartTime.TzId; - foreach (var period in periodList) + if (!grouped.ContainsKey(actualBucket)) { - var actualBucket = string.IsNullOrWhiteSpace(period.StartTime.TzId) ? defaultBucket : period.StartTime.TzId; - - if (!grouped.ContainsKey(actualBucket)) - { - grouped.Add(actualBucket, new HashSet()); - } - grouped[actualBucket].Add(period); + grouped.Add(actualBucket, new HashSet()); } + grouped[actualBucket].Add(period); } - return grouped.ToDictionary(k => k.Key, v => v.Value.OrderBy(d => d.StartTime).ToList()); } + return grouped.ToDictionary(k => k.Key, v => v.Value.OrderBy(d => d.StartTime).ToList()); + } - protected bool Equals(PeriodList other) => string.Equals(TzId, other.TzId, StringComparison.OrdinalIgnoreCase) - && CollectionHelpers.Equals(Periods, other.Periods); + protected bool Equals(PeriodList other) => string.Equals(TzId, other.TzId, StringComparison.OrdinalIgnoreCase) + && CollectionHelpers.Equals(Periods, other.Periods); - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((PeriodList)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = TzId?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Periods); - return hashCode; - } - } + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((PeriodList) obj); + } - public Period this[int index] + public override int GetHashCode() + { + unchecked { - get => Periods[index]; - set => Periods[index] = value; + var hashCode = TzId?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(Periods); + return hashCode; } + } - public bool Remove(Period item) => Periods.Remove(item); - public bool IsReadOnly => Periods.IsReadOnly; - public int IndexOf(Period item) => Periods.IndexOf(item); - public void Insert(int index, Period item) => Periods.Insert(index, item); - public void RemoveAt(int index) => Periods.RemoveAt(index); - public void Add(Period item) => Periods.Add(item); - public void Clear() => Periods.Clear(); - public bool Contains(Period item) => Periods.Contains(item); - public void CopyTo(Period[] array, int arrayIndex) => Periods.CopyTo(array, arrayIndex); - public IEnumerator GetEnumerator() => Periods.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => Periods.GetEnumerator(); + public Period this[int index] + { + get => Periods[index]; + set => Periods[index] = value; } -} + + public bool Remove(Period item) => Periods.Remove(item); + public bool IsReadOnly => Periods.IsReadOnly; + public int IndexOf(Period item) => Periods.IndexOf(item); + public void Insert(int index, Period item) => Periods.Insert(index, item); + public void RemoveAt(int index) => Periods.RemoveAt(index); + public void Add(Period item) => Periods.Add(item); + public void Clear() => Periods.Clear(); + public bool Contains(Period item) => Periods.Contains(item); + public void CopyTo(Period[] array, int arrayIndex) => Periods.CopyTo(array, arrayIndex); + public IEnumerator GetEnumerator() => Periods.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => Periods.GetEnumerator(); +} \ No newline at end of file diff --git a/Ical.Net/DataTypes/RecurrencePattern.cs b/Ical.Net/DataTypes/RecurrencePattern.cs index 86500cef8..79dc07fb1 100644 --- a/Ical.Net/DataTypes/RecurrencePattern.cs +++ b/Ical.Net/DataTypes/RecurrencePattern.cs @@ -1,236 +1,240 @@ -using Ical.Net.Evaluation; -using Ical.Net.Serialization.DataTypes; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Ical.Net.Evaluation; +using Ical.Net.Serialization.DataTypes; +using Ical.Net.Utility; -namespace Ical.Net.DataTypes +namespace Ical.Net.DataTypes; + +/// +/// An iCalendar representation of the RRULE property. +/// https://tools.ietf.org/html/rfc5545#section-3.3.10 +/// +public class RecurrencePattern : EncodableDataType { - /// - /// An iCalendar representation of the RRULE property. - /// https://tools.ietf.org/html/rfc5545#section-3.3.10 - /// - public class RecurrencePattern : EncodableDataType - { - private int _interval = int.MinValue; + private int _interval = int.MinValue; #pragma warning disable 0618 - private RecurrenceRestrictionType? _restrictionType; - private RecurrenceEvaluationModeType? _evaluationMode; + private RecurrenceRestrictionType? _restrictionType; + private RecurrenceEvaluationModeType? _evaluationMode; #pragma warning restore 0618 - public FrequencyType Frequency { get; set; } + public FrequencyType Frequency { get; set; } - private DateTime _until = DateTime.MinValue; - public DateTime Until + private DateTime _until = DateTime.MinValue; + public DateTime Until + { + get => _until; + set { - get => _until; - set + if (_until == value && _until.Kind == value.Kind) { - if (_until == value && _until.Kind == value.Kind) - { - return; - } - - _until = value; + return; } + + _until = value; } + } - public int Count { get; set; } = int.MinValue; + public int Count { get; set; } = int.MinValue; - /// - /// Specifies how often the recurrence should repeat. - /// - 1 = every - /// - 2 = every second - /// - 3 = every third - /// - public int Interval - { - get => _interval == int.MinValue - ? 1 - : _interval; - set => _interval = value; - } + /// + /// Specifies how often the recurrence should repeat. + /// - 1 = every + /// - 2 = every second + /// - 3 = every third + /// + public int Interval + { + get => _interval == int.MinValue + ? 1 + : _interval; + set => _interval = value; + } - public List BySecond { get; set; } = new List(); + public List BySecond { get; set; } = new List(); - /// The ordinal minutes of the hour associated with this recurrence pattern. Valid values are 0-59. - public List ByMinute { get; set; } = new List(); + /// The ordinal minutes of the hour associated with this recurrence pattern. Valid values are 0-59. + public List ByMinute { get; set; } = new List(); - public List ByHour { get; set; } = new List(); + public List ByHour { get; set; } = new List(); - public List ByDay { get; set; } = new List(); + public List ByDay { get; set; } = new List(); - /// The ordinal days of the month associated with this recurrence pattern. Valid values are 1-31. - public List ByMonthDay { get; set; } = new List(); + /// The ordinal days of the month associated with this recurrence pattern. Valid values are 1-31. + public List ByMonthDay { get; set; } = new List(); - /// - /// The ordinal days of the year associated with this recurrence pattern. Something recurring on the first day of the year would be a list containing - /// 1, and would also be New Year's Day. - /// - public List ByYearDay { get; set; } = new List(); + /// + /// The ordinal days of the year associated with this recurrence pattern. Something recurring on the first day of the year would be a list containing + /// 1, and would also be New Year's Day. + /// + public List ByYearDay { get; set; } = new List(); - /// - /// The ordinal week of the year. Valid values are -53 to +53. Negative values count backwards from the end of the specified year. - /// A week is defined by ISO.8601.2004 - /// - public List ByWeekNo { get; set; } = new List(); + /// + /// The ordinal week of the year. Valid values are -53 to +53. Negative values count backwards from the end of the specified year. + /// A week is defined by ISO.8601.2004 + /// + public List ByWeekNo { get; set; } = new List(); - /// - /// List of months in the year associated with this rule. Valid values are 1 through 12. - /// - public List ByMonth { get; set; } = new List(); + /// + /// List of months in the year associated with this rule. Valid values are 1 through 12. + /// + public List ByMonth { get; set; } = new List(); - public List BySetPosition { get; set; } = new List(); + public List BySetPosition { get; set; } = new List(); - public DayOfWeek FirstDayOfWeek { get; set; } = DayOfWeek.Monday; + public DayOfWeek FirstDayOfWeek { get; set; } = DayOfWeek.Monday; #pragma warning disable 0618 - /// - /// The type of restriction to apply to the evaluation of this recurrence pattern. - /// Returns if not set. - /// - [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] - public RecurrenceRestrictionType RestrictionType + /// + /// The type of restriction to apply to the evaluation of this recurrence pattern. + /// Returns if not set. + /// + [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] + public RecurrenceRestrictionType RestrictionType + { + get { - get + // NOTE: Fixes bug #1924358 - Cannot evaluate Secondly patterns + if (_restrictionType != null) { - // NOTE: Fixes bug #1924358 - Cannot evaluate Secondly patterns - if (_restrictionType != null) - { - return _restrictionType.Value; - } - return Calendar?.RecurrenceRestriction ?? RecurrenceRestrictionType.Default; + return _restrictionType.Value; } - set => _restrictionType = value; + return Calendar?.RecurrenceRestriction ?? RecurrenceRestrictionType.Default; } + set => _restrictionType = value; + } - [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] - public RecurrenceEvaluationModeType EvaluationMode + [Obsolete("Usage may cause undesired results or exceptions. Will be removed.", false)] + public RecurrenceEvaluationModeType EvaluationMode + { + get { - get + // NOTE: Fixes bug #1924358 - Cannot evaluate Secondly patterns + if (_evaluationMode != null) { - // NOTE: Fixes bug #1924358 - Cannot evaluate Secondly patterns - if (_evaluationMode != null) - { - return _evaluationMode.Value; - } - return Calendar?.RecurrenceEvaluationMode ?? RecurrenceEvaluationModeType.Default; + return _evaluationMode.Value; } - set => _evaluationMode = value; + return Calendar?.RecurrenceEvaluationMode ?? RecurrenceEvaluationModeType.Default; } + set => _evaluationMode = value; + } #pragma warning restore 0618 - public RecurrencePattern() - { - SetService(new RecurrencePatternEvaluator(this)); - } + public RecurrencePattern() + { + SetService(new RecurrencePatternEvaluator(this)); + } - public RecurrencePattern(FrequencyType frequency) : this(frequency, 1) { } + public RecurrencePattern(FrequencyType frequency) : this(frequency, 1) { } - public RecurrencePattern(FrequencyType frequency, int interval) : this() - { - Frequency = frequency; - Interval = interval; - } + public RecurrencePattern(FrequencyType frequency, int interval) : this() + { + Frequency = frequency; + Interval = interval; + } - public RecurrencePattern(string value) : this() + public RecurrencePattern(string value) : this() + { + if (string.IsNullOrWhiteSpace(value)) { - if (string.IsNullOrWhiteSpace(value)) - { - return; - } - var serializer = new RecurrencePatternSerializer(); - CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + return; } + var serializer = new RecurrencePatternSerializer(); + CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + } - public override string ToString() - { - var serializer = new RecurrencePatternSerializer(); - return serializer.SerializeToString(this); - } + public override string ToString() + { + var serializer = new RecurrencePatternSerializer(); + return serializer.SerializeToString(this); + } - protected bool Equals(RecurrencePattern other) => (Interval == other.Interval) + protected bool Equals(RecurrencePattern other) => (Interval == other.Interval) #pragma warning disable 0618 - && RestrictionType == other.RestrictionType - && EvaluationMode == other.EvaluationMode + && RestrictionType == other.RestrictionType + && EvaluationMode == other.EvaluationMode #pragma warning restore 0618 - && Frequency == other.Frequency - && Until.Equals(other.Until) - && Count == other.Count - && (FirstDayOfWeek == other.FirstDayOfWeek) - && CollectionEquals(BySecond, other.BySecond) - && CollectionEquals(ByMinute, other.ByMinute) - && CollectionEquals(ByHour, other.ByHour) - && CollectionEquals(ByDay, other.ByDay) - && CollectionEquals(ByMonthDay, other.ByMonthDay) - && CollectionEquals(ByYearDay, other.ByYearDay) - && CollectionEquals(ByWeekNo, other.ByWeekNo) - && CollectionEquals(ByMonth, other.ByMonth) - && CollectionEquals(BySetPosition, other.BySetPosition); - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return obj.GetType() == GetType() && Equals((RecurrencePattern)obj); - } + && Frequency == other.Frequency + && Until.Equals(other.Until) + && Count == other.Count + && (FirstDayOfWeek == other.FirstDayOfWeek) + && CollectionEquals(BySecond, other.BySecond) + && CollectionEquals(ByMinute, other.ByMinute) + && CollectionEquals(ByHour, other.ByHour) + && CollectionEquals(ByDay, other.ByDay) + && CollectionEquals(ByMonthDay, other.ByMonthDay) + && CollectionEquals(ByYearDay, other.ByYearDay) + && CollectionEquals(ByWeekNo, other.ByWeekNo) + && CollectionEquals(ByMonth, other.ByMonth) + && CollectionEquals(BySetPosition, other.BySetPosition); + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals((RecurrencePattern) obj); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = Interval.GetHashCode(); + var hashCode = Interval.GetHashCode(); #pragma warning disable 0618 - hashCode = (hashCode * 397) ^ RestrictionType.GetHashCode(); - hashCode = (hashCode * 397) ^ EvaluationMode.GetHashCode(); + hashCode = (hashCode * 397) ^ RestrictionType.GetHashCode(); + hashCode = (hashCode * 397) ^ EvaluationMode.GetHashCode(); #pragma warning restore 0618 - hashCode = (hashCode * 397) ^ (int)Frequency; - hashCode = (hashCode * 397) ^ Until.GetHashCode(); - hashCode = (hashCode * 397) ^ Count; - hashCode = (hashCode * 397) ^ (int)FirstDayOfWeek; - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(BySecond); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByMinute); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByHour); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByDay); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByMonthDay); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByYearDay); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByWeekNo); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByMonth); - hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(BySetPosition); - return hashCode; - } + hashCode = (hashCode * 397) ^ (int) Frequency; + hashCode = (hashCode * 397) ^ Until.GetHashCode(); + hashCode = (hashCode * 397) ^ Count; + hashCode = (hashCode * 397) ^ (int) FirstDayOfWeek; + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(BySecond); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByMinute); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByHour); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByDay); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByMonthDay); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByYearDay); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByWeekNo); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(ByMonth); + hashCode = (hashCode * 397) ^ CollectionHelpers.GetHashCode(BySetPosition); + return hashCode; } + } - /// - public override void CopyFrom(ICopyable obj) + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); + if (obj is not RecurrencePattern r) { - base.CopyFrom(obj); - if (obj is not RecurrencePattern r) - { - return; - } + return; + } - Frequency = r.Frequency; - Until = r.Until; - Count = r.Count; - Interval = r.Interval; - BySecond = new List(r.BySecond); - ByMinute = new List(r.ByMinute); - ByHour = new List(r.ByHour); - ByDay = new List(r.ByDay); - ByMonthDay = new List(r.ByMonthDay); - ByYearDay = new List(r.ByYearDay); - ByWeekNo = new List(r.ByWeekNo); - ByMonth = new List(r.ByMonth); - BySetPosition = new List(r.BySetPosition); - FirstDayOfWeek = r.FirstDayOfWeek; + Frequency = r.Frequency; + Until = r.Until; + Count = r.Count; + Interval = r.Interval; + BySecond = new List(r.BySecond); + ByMinute = new List(r.ByMinute); + ByHour = new List(r.ByHour); + ByDay = new List(r.ByDay); + ByMonthDay = new List(r.ByMonthDay); + ByYearDay = new List(r.ByYearDay); + ByWeekNo = new List(r.ByWeekNo); + ByMonth = new List(r.ByMonth); + BySetPosition = new List(r.BySetPosition); + FirstDayOfWeek = r.FirstDayOfWeek; #pragma warning disable 0618 - RestrictionType = r.RestrictionType; - EvaluationMode = r.EvaluationMode; + RestrictionType = r.RestrictionType; + EvaluationMode = r.EvaluationMode; #pragma warning restore 0618 - } - - private static bool CollectionEquals(IEnumerable c1, IEnumerable c2) => c1.SequenceEqual(c2); } + + private static bool CollectionEquals(IEnumerable c1, IEnumerable c2) => c1.SequenceEqual(c2); } \ No newline at end of file diff --git a/Ical.Net/DataTypes/RequestStatus.cs b/Ical.Net/DataTypes/RequestStatus.cs index 85671688b..d768fe486 100644 --- a/Ical.Net/DataTypes/RequestStatus.cs +++ b/Ical.Net/DataTypes/RequestStatus.cs @@ -1,95 +1,99 @@ -using Ical.Net.Serialization.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.IO; +using Ical.Net.Serialization.DataTypes; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +/// +/// A class that represents the return status of an iCalendar request. +/// +public class RequestStatus : EncodableDataType { - /// - /// A class that represents the return status of an iCalendar request. - /// - public class RequestStatus : EncodableDataType + private string _mDescription; + private string _mExtraData; + private StatusCode _mStatusCode; + + public virtual string Description { - private string _mDescription; - private string _mExtraData; - private StatusCode _mStatusCode; + get => _mDescription; + set => _mDescription = value; + } - public virtual string Description - { - get => _mDescription; - set => _mDescription = value; - } + public virtual string ExtraData + { + get => _mExtraData; + set => _mExtraData = value; + } + + public virtual StatusCode StatusCode + { + get => _mStatusCode; + set => _mStatusCode = value; + } + + public RequestStatus() { } + + public RequestStatus(string value) : this() + { + var serializer = new RequestStatusSerializer(); + CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + } - public virtual string ExtraData + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); + if (obj is not RequestStatus rs) { - get => _mExtraData; - set => _mExtraData = value; + return; } - public virtual StatusCode StatusCode + if (rs.StatusCode != null) { - get => _mStatusCode; - set => _mStatusCode = value; + StatusCode = rs.StatusCode; } + Description = rs.Description; + ExtraData = rs.ExtraData; + } - public RequestStatus() { } + public override string ToString() + { + var serializer = new RequestStatusSerializer(); + return serializer.SerializeToString(this); + } - public RequestStatus(string value) : this() - { - var serializer = new RequestStatusSerializer(); - CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); - } + protected bool Equals(RequestStatus other) => string.Equals(_mDescription, other._mDescription) && string.Equals(_mExtraData, other._mExtraData) && + Equals(_mStatusCode, other._mStatusCode); - /// - public override void CopyFrom(ICopyable obj) + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { - base.CopyFrom(obj); - if (obj is not RequestStatus rs) - { - return; - } - - if (rs.StatusCode != null) - { - StatusCode = rs.StatusCode; - } - Description = rs.Description; - ExtraData = rs.ExtraData; + return false; } - - public override string ToString() + if (ReferenceEquals(this, obj)) { - var serializer = new RequestStatusSerializer(); - return serializer.SerializeToString(this); + return true; } - - protected bool Equals(RequestStatus other) => string.Equals(_mDescription, other._mDescription) && string.Equals(_mExtraData, other._mExtraData) && - Equals(_mStatusCode, other._mStatusCode); - - public override bool Equals(object obj) + if (obj.GetType() != GetType()) { - if (ReferenceEquals(null, obj)) - { - return false; - } - if (ReferenceEquals(this, obj)) - { - return true; - } - if (obj.GetType() != GetType()) - { - return false; - } - return Equals((RequestStatus)obj); + return false; } + return Equals((RequestStatus) obj); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = _mDescription?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ (_mExtraData?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ (_mStatusCode?.GetHashCode() ?? 0); - return hashCode; - } + var hashCode = _mDescription?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ (_mExtraData?.GetHashCode() ?? 0); + hashCode = (hashCode * 397) ^ (_mStatusCode?.GetHashCode() ?? 0); + return hashCode; } } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/StatusCode.cs b/Ical.Net/DataTypes/StatusCode.cs index 2f55af6f5..ebecb3554 100644 --- a/Ical.Net/DataTypes/StatusCode.cs +++ b/Ical.Net/DataTypes/StatusCode.cs @@ -1,81 +1,85 @@ -using Ical.Net.Serialization.DataTypes; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.IO; using System.Linq; +using Ical.Net.Serialization.DataTypes; +using Ical.Net.Utility; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +/// +/// An iCalendar status code. +/// +public class StatusCode : EncodableDataType { - /// - /// An iCalendar status code. - /// - public class StatusCode : EncodableDataType - { - public int[] Parts { get; private set; } + public int[] Parts { get; private set; } - public int Primary + public int Primary + { + get { - get + if (Parts.Length > 0) { - if (Parts.Length > 0) - { - return Parts[0]; - } - return 0; + return Parts[0]; } + return 0; } + } - public int Secondary => Parts.Length > 1 - ? Parts[1] - : 0; + public int Secondary => Parts.Length > 1 + ? Parts[1] + : 0; - public int Tertiary => Parts.Length > 2 - ? Parts[2] - : 0; + public int Tertiary => Parts.Length > 2 + ? Parts[2] + : 0; - public StatusCode() { } + public StatusCode() { } - public StatusCode(int[] parts) - { - Parts = parts; - } + public StatusCode(int[] parts) + { + Parts = parts; + } - public StatusCode(string value) : this() - { - var serializer = new StatusCodeSerializer(); - CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); - } + public StatusCode(string value) : this() + { + var serializer = new StatusCodeSerializer(); + CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + } - /// - public override void CopyFrom(ICopyable obj) - { - base.CopyFrom(obj); - if (obj is not StatusCode statusCode) return; + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); + if (obj is not StatusCode statusCode) return; - Parts = new int[statusCode.Parts.Length]; - statusCode.Parts.CopyTo(Parts, 0); - } + Parts = new int[statusCode.Parts.Length]; + statusCode.Parts.CopyTo(Parts, 0); + } - public override string ToString() => new StatusCodeSerializer().SerializeToString(this); + public override string ToString() => new StatusCodeSerializer().SerializeToString(this); - protected bool Equals(StatusCode other) => Parts.SequenceEqual(other.Parts); + protected bool Equals(StatusCode other) => Parts.SequenceEqual(other.Parts); - public override bool Equals(object obj) + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { - if (ReferenceEquals(null, obj)) - { - return false; - } - if (ReferenceEquals(this, obj)) - { - return true; - } - if (obj.GetType() != GetType()) - { - return false; - } - return Equals((StatusCode)obj); + return false; } - - public override int GetHashCode() => CollectionHelpers.GetHashCode(Parts); + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != GetType()) + { + return false; + } + return Equals((StatusCode) obj); } + + public override int GetHashCode() => CollectionHelpers.GetHashCode(Parts); } \ No newline at end of file diff --git a/Ical.Net/DataTypes/Trigger.cs b/Ical.Net/DataTypes/Trigger.cs index ee9e8b666..904aceb8b 100644 --- a/Ical.Net/DataTypes/Trigger.cs +++ b/Ical.Net/DataTypes/Trigger.cs @@ -1,120 +1,124 @@ -using Ical.Net.Serialization.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; +using Ical.Net.Serialization.DataTypes; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +/// +/// A class that is used to specify exactly when an component will trigger. +/// Usually this date/time is relative to the component to which the Alarm is associated. +/// +public class Trigger : EncodableDataType { - /// - /// A class that is used to specify exactly when an component will trigger. - /// Usually this date/time is relative to the component to which the Alarm is associated. - /// - public class Trigger : EncodableDataType - { - private IDateTime _mDateTime; - private TimeSpan? _mDuration; - private string _mRelated = TriggerRelation.Start; + private IDateTime _mDateTime; + private TimeSpan? _mDuration; + private string _mRelated = TriggerRelation.Start; - public virtual IDateTime DateTime + public virtual IDateTime DateTime + { + get => _mDateTime; + set { - get => _mDateTime; - set + _mDateTime = value; + if (_mDateTime == null) { - _mDateTime = value; - if (_mDateTime == null) - { - return; - } + return; + } - // NOTE: this, along with the "Duration" setter, fixes the bug tested in - // TODO11(), as well as this thread: https://sourceforge.net/forum/forum.php?thread_id=1926742&forum_id=656447 + // NOTE: this, along with the "Duration" setter, fixes the bug tested in + // TODO11(), as well as this thread: https://sourceforge.net/forum/forum.php?thread_id=1926742&forum_id=656447 - // DateTime and Duration are mutually exclusive - Duration = null; + // DateTime and Duration are mutually exclusive + Duration = null; - // Do not allow timeless date/time values - _mDateTime.HasTime = true; - } + // Do not allow timeless date/time values + _mDateTime.HasTime = true; } + } - public virtual TimeSpan? Duration + public virtual TimeSpan? Duration + { + get => _mDuration; + set { - get => _mDuration; - set + _mDuration = value; + if (_mDuration != null) { - _mDuration = value; - if (_mDuration != null) - { - // NOTE: see above. - - // DateTime and Duration are mutually exclusive - DateTime = null; - } + // NOTE: see above. + + // DateTime and Duration are mutually exclusive + DateTime = null; } } + } - public virtual string Related - { - get => _mRelated; - set => _mRelated = value; - } + public virtual string Related + { + get => _mRelated; + set => _mRelated = value; + } - public virtual bool IsRelative => _mDuration != null; + public virtual bool IsRelative => _mDuration != null; - public Trigger() { } + public Trigger() { } + + public Trigger(TimeSpan ts) + { + Duration = ts; + } + + public Trigger(string value) : this() + { + var serializer = new TriggerSerializer(); + CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + } - public Trigger(TimeSpan ts) + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); + if (obj is not Trigger t) { - Duration = ts; + return; } - public Trigger(string value) : this() + DateTime = t.DateTime?.Copy(); + Duration = t.Duration; + Related = t.Related; + } + + protected bool Equals(Trigger other) => Equals(_mDateTime, other._mDateTime) && _mDuration.Equals(other._mDuration) && _mRelated == other._mRelated; + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) { - var serializer = new TriggerSerializer(); - CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + return false; } - - /// - public override void CopyFrom(ICopyable obj) + if (ReferenceEquals(this, obj)) { - base.CopyFrom(obj); - if (obj is not Trigger t) - { - return; - } - - DateTime = t.DateTime?.Copy(); - Duration = t.Duration; - Related = t.Related; + return true; } - - protected bool Equals(Trigger other) => Equals(_mDateTime, other._mDateTime) && _mDuration.Equals(other._mDuration) && _mRelated == other._mRelated; - - public override bool Equals(object obj) + if (obj.GetType() != GetType()) { - if (ReferenceEquals(null, obj)) - { - return false; - } - if (ReferenceEquals(this, obj)) - { - return true; - } - if (obj.GetType() != GetType()) - { - return false; - } - return Equals((Trigger)obj); + return false; } + return Equals((Trigger) obj); + } - public override int GetHashCode() + public override int GetHashCode() + { + unchecked { - unchecked - { - var hashCode = _mDateTime?.GetHashCode() ?? 0; - hashCode = (hashCode * 397) ^ _mDuration.GetHashCode(); - hashCode = (hashCode * 397) ^ _mRelated?.GetHashCode() ?? 0; - return hashCode; - } + var hashCode = _mDateTime?.GetHashCode() ?? 0; + hashCode = (hashCode * 397) ^ _mDuration.GetHashCode(); + hashCode = (hashCode * 397) ^ _mRelated?.GetHashCode() ?? 0; + return hashCode; } } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/UTCOffset.cs b/Ical.Net/DataTypes/UTCOffset.cs index b08eaa453..e9c2ad981 100644 --- a/Ical.Net/DataTypes/UTCOffset.cs +++ b/Ical.Net/DataTypes/UTCOffset.cs @@ -1,75 +1,79 @@ -using Ical.Net.Serialization.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; +using Ical.Net.Serialization.DataTypes; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +/// +/// Represents a time offset from UTC (Coordinated Universal Time). +/// +public class UtcOffset : EncodableDataType { - /// - /// Represents a time offset from UTC (Coordinated Universal Time). - /// - public class UtcOffset : EncodableDataType - { - public TimeSpan Offset { get; private set; } + public TimeSpan Offset { get; private set; } - public bool Positive => Offset >= TimeSpan.Zero; + public bool Positive => Offset >= TimeSpan.Zero; - public int Hours => Math.Abs(Offset.Hours); + public int Hours => Math.Abs(Offset.Hours); - public int Minutes => Math.Abs(Offset.Minutes); + public int Minutes => Math.Abs(Offset.Minutes); - public int Seconds => Math.Abs(Offset.Seconds); + public int Seconds => Math.Abs(Offset.Seconds); - public UtcOffset() { } + public UtcOffset() { } - public UtcOffset(string value) : this() - { - Offset = UtcOffsetSerializer.GetOffset(value); - } + public UtcOffset(string value) : this() + { + Offset = UtcOffsetSerializer.GetOffset(value); + } - public UtcOffset(TimeSpan ts) - { - Offset = ts; - } + public UtcOffset(TimeSpan ts) + { + Offset = ts; + } - public static implicit operator UtcOffset(TimeSpan ts) => new UtcOffset(ts); + public static implicit operator UtcOffset(TimeSpan ts) => new UtcOffset(ts); - public static explicit operator TimeSpan(UtcOffset o) => o.Offset; + public static explicit operator TimeSpan(UtcOffset o) => o.Offset; - public virtual DateTime ToUtc(DateTime dt) => DateTime.SpecifyKind(dt.Add(-Offset), DateTimeKind.Utc); + public virtual DateTime ToUtc(DateTime dt) => DateTime.SpecifyKind(dt.Add(-Offset), DateTimeKind.Utc); - public virtual DateTime ToLocal(DateTime dt) => DateTime.SpecifyKind(dt.Add(Offset), DateTimeKind.Local); + public virtual DateTime ToLocal(DateTime dt) => DateTime.SpecifyKind(dt.Add(Offset), DateTimeKind.Local); - protected bool Equals(UtcOffset other) => Offset == other.Offset; + protected bool Equals(UtcOffset other) => Offset == other.Offset; - public override bool Equals(object obj) + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + if (ReferenceEquals(this, obj)) + { + return true; + } + if (obj.GetType() != GetType()) { - if (ReferenceEquals(null, obj)) - { - return false; - } - if (ReferenceEquals(this, obj)) - { - return true; - } - if (obj.GetType() != GetType()) - { - return false; - } - return Equals((UtcOffset)obj); + return false; } + return Equals((UtcOffset) obj); + } - public override int GetHashCode() => Offset.GetHashCode(); + public override int GetHashCode() => Offset.GetHashCode(); - public override string ToString() => (Positive ? "+" : "-") + Hours.ToString("00") + Minutes.ToString("00") + (Seconds != 0 ? Seconds.ToString("00") : string.Empty); + public override string ToString() => (Positive ? "+" : "-") + Hours.ToString("00") + Minutes.ToString("00") + (Seconds != 0 ? Seconds.ToString("00") : string.Empty); - /// - public override void CopyFrom(ICopyable obj) - { - base.CopyFrom(obj); + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); - if (obj is UtcOffset o) - { - Offset = o.Offset; - } + if (obj is UtcOffset o) + { + Offset = o.Offset; } } } \ No newline at end of file diff --git a/Ical.Net/DataTypes/WeekDay.cs b/Ical.Net/DataTypes/WeekDay.cs index abb7f0738..eb59a3eaa 100644 --- a/Ical.Net/DataTypes/WeekDay.cs +++ b/Ical.Net/DataTypes/WeekDay.cs @@ -1,84 +1,88 @@ -using Ical.Net.Serialization.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; +using Ical.Net.Serialization.DataTypes; + +namespace Ical.Net.DataTypes; -namespace Ical.Net.DataTypes +/// +/// Represents an RFC 5545 "BYDAY" value. +/// +public class WeekDay : EncodableDataType { - /// - /// Represents an RFC 5545 "BYDAY" value. - /// - public class WeekDay : EncodableDataType - { - public virtual int Offset { get; set; } = int.MinValue; + public virtual int Offset { get; set; } = int.MinValue; - public virtual DayOfWeek DayOfWeek { get; set; } + public virtual DayOfWeek DayOfWeek { get; set; } - public WeekDay() - { - Offset = int.MinValue; - } + public WeekDay() + { + Offset = int.MinValue; + } - public WeekDay(DayOfWeek day) : this() - { - DayOfWeek = day; - } + public WeekDay(DayOfWeek day) : this() + { + DayOfWeek = day; + } - public WeekDay(DayOfWeek day, int num) : this(day) - { - Offset = num; - } + public WeekDay(DayOfWeek day, int num) : this(day) + { + Offset = num; + } + + public WeekDay(DayOfWeek day, FrequencyOccurrence type) : this(day, (int) type) { } - public WeekDay(DayOfWeek day, FrequencyOccurrence type) : this(day, (int)type) { } + public WeekDay(string value) + { + var serializer = new WeekDaySerializer(); + CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + } - public WeekDay(string value) + public override bool Equals(object obj) + { + if (!(obj is WeekDay)) { - var serializer = new WeekDaySerializer(); - CopyFrom(serializer.Deserialize(new StringReader(value)) as ICopyable); + return false; } - public override bool Equals(object obj) - { - if (!(obj is WeekDay)) - { - return false; - } + var ds = (WeekDay) obj; + return ds.Offset == Offset && ds.DayOfWeek == DayOfWeek; + } - var ds = (WeekDay)obj; - return ds.Offset == Offset && ds.DayOfWeek == DayOfWeek; - } + public override int GetHashCode() => Offset.GetHashCode() ^ DayOfWeek.GetHashCode(); + + /// + public override void CopyFrom(ICopyable obj) + { + base.CopyFrom(obj); + if (obj is not WeekDay weekday) return; - public override int GetHashCode() => Offset.GetHashCode() ^ DayOfWeek.GetHashCode(); + Offset = weekday.Offset; + DayOfWeek = weekday.DayOfWeek; + } - /// - public override void CopyFrom(ICopyable obj) + public int CompareTo(object obj) + { + var weekday = obj switch { - base.CopyFrom(obj); - if (obj is not WeekDay weekday) return; + string => new WeekDay(obj.ToString()), + WeekDay day => day, + _ => null + }; - Offset = weekday.Offset; - DayOfWeek = weekday.DayOfWeek; + if (weekday == null) + { + throw new ArgumentException(); } - public int CompareTo(object obj) + var compare = DayOfWeek.CompareTo(weekday.DayOfWeek); + if (compare == 0) { - var weekday = obj switch - { - string => new WeekDay(obj.ToString()), - WeekDay day => day, - _ => null - }; - - if (weekday == null) - { - throw new ArgumentException(); - } - - var compare = DayOfWeek.CompareTo(weekday.DayOfWeek); - if (compare == 0) - { - compare = Offset.CompareTo(weekday.Offset); - } - return compare; + compare = Offset.CompareTo(weekday.Offset); } + return compare; } } \ No newline at end of file diff --git a/Ical.Net/Evaluation/Evaluator.cs b/Ical.Net/Evaluation/Evaluator.cs index 54496b984..073251c3e 100644 --- a/Ical.Net/Evaluation/Evaluator.cs +++ b/Ical.Net/Evaluation/Evaluator.cs @@ -1,120 +1,124 @@ -using Ical.Net.DataTypes; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Globalization; +using Ical.Net.DataTypes; +using Ical.Net.Utility; -namespace Ical.Net.Evaluation +namespace Ical.Net.Evaluation; + +public abstract class Evaluator : IEvaluator { - public abstract class Evaluator : IEvaluator - { - private DateTime _mEvaluationStartBounds = DateTime.MaxValue; - private DateTime _mEvaluationEndBounds = DateTime.MinValue; + private DateTime _mEvaluationStartBounds = DateTime.MaxValue; + private DateTime _mEvaluationEndBounds = DateTime.MinValue; - private ICalendarObject _mAssociatedObject; - private readonly ICalendarDataType _mAssociatedDataType; + private ICalendarObject _mAssociatedObject; + private readonly ICalendarDataType _mAssociatedDataType; - protected HashSet MPeriods; + protected HashSet MPeriods; - protected Evaluator() - { - Initialize(); - } + protected Evaluator() + { + Initialize(); + } - protected Evaluator(ICalendarObject associatedObject) - { - _mAssociatedObject = associatedObject; + protected Evaluator(ICalendarObject associatedObject) + { + _mAssociatedObject = associatedObject; - Initialize(); - } + Initialize(); + } - protected Evaluator(ICalendarDataType dataType) - { - _mAssociatedDataType = dataType; + protected Evaluator(ICalendarDataType dataType) + { + _mAssociatedDataType = dataType; - Initialize(); - } + Initialize(); + } - private void Initialize() - { - Calendar = CultureInfo.CurrentCulture.Calendar; - MPeriods = new HashSet(); - } + private void Initialize() + { + Calendar = CultureInfo.CurrentCulture.Calendar; + MPeriods = new HashSet(); + } - protected IDateTime ConvertToIDateTime(DateTime dt, IDateTime referenceDate) + protected IDateTime ConvertToIDateTime(DateTime dt, IDateTime referenceDate) + { + IDateTime newDt = new CalDateTime(dt, referenceDate.TzId); + newDt.AssociateWith(referenceDate); + return newDt; + } + + protected void IncrementDate(ref DateTime dt, RecurrencePattern pattern, int interval) + { + // FIXME: use a more specific exception. + if (interval == 0) { - IDateTime newDt = new CalDateTime(dt, referenceDate.TzId); - newDt.AssociateWith(referenceDate); - return newDt; + throw new Exception("Cannot evaluate with an interval of zero. Please use an interval other than zero."); } - protected void IncrementDate(ref DateTime dt, RecurrencePattern pattern, int interval) + var old = dt; + switch (pattern.Frequency) { + case FrequencyType.Secondly: + dt = old.AddSeconds(interval); + break; + case FrequencyType.Minutely: + dt = old.AddMinutes(interval); + break; + case FrequencyType.Hourly: + dt = old.AddHours(interval); + break; + case FrequencyType.Daily: + dt = old.AddDays(interval); + break; + case FrequencyType.Weekly: + dt = DateUtil.AddWeeks(old, interval, pattern.FirstDayOfWeek); + break; + case FrequencyType.Monthly: + dt = old.AddDays(-old.Day + 1).AddMonths(interval); + break; + case FrequencyType.Yearly: + dt = old.AddDays(-old.DayOfYear + 1).AddYears(interval); + break; // FIXME: use a more specific exception. - if (interval == 0) - { - throw new Exception("Cannot evaluate with an interval of zero. Please use an interval other than zero."); - } - - var old = dt; - switch (pattern.Frequency) - { - case FrequencyType.Secondly: - dt = old.AddSeconds(interval); - break; - case FrequencyType.Minutely: - dt = old.AddMinutes(interval); - break; - case FrequencyType.Hourly: - dt = old.AddHours(interval); - break; - case FrequencyType.Daily: - dt = old.AddDays(interval); - break; - case FrequencyType.Weekly: - dt = DateUtil.AddWeeks(old, interval, pattern.FirstDayOfWeek); - break; - case FrequencyType.Monthly: - dt = old.AddDays(-old.Day + 1).AddMonths(interval); - break; - case FrequencyType.Yearly: - dt = old.AddDays(-old.DayOfYear + 1).AddYears(interval); - break; - // FIXME: use a more specific exception. - default: - throw new Exception("FrequencyType.NONE cannot be evaluated. Please specify a FrequencyType before evaluating the recurrence."); - } + default: + throw new Exception("FrequencyType.NONE cannot be evaluated. Please specify a FrequencyType before evaluating the recurrence."); } + } - public System.Globalization.Calendar Calendar { get; private set; } + public System.Globalization.Calendar Calendar { get; private set; } - public virtual DateTime EvaluationStartBounds - { - get => _mEvaluationStartBounds; - set => _mEvaluationStartBounds = value; - } - - public virtual DateTime EvaluationEndBounds - { - get => _mEvaluationEndBounds; - set => _mEvaluationEndBounds = value; - } + public virtual DateTime EvaluationStartBounds + { + get => _mEvaluationStartBounds; + set => _mEvaluationStartBounds = value; + } - public virtual ICalendarObject AssociatedObject - { - get => _mAssociatedObject ?? _mAssociatedDataType?.AssociatedObject; - protected set => _mAssociatedObject = value; - } + public virtual DateTime EvaluationEndBounds + { + get => _mEvaluationEndBounds; + set => _mEvaluationEndBounds = value; + } - public virtual HashSet Periods => MPeriods; + public virtual ICalendarObject AssociatedObject + { + get => _mAssociatedObject ?? _mAssociatedDataType?.AssociatedObject; + protected set => _mAssociatedObject = value; + } - public virtual void Clear() - { - _mEvaluationStartBounds = DateTime.MaxValue; - _mEvaluationEndBounds = DateTime.MinValue; - MPeriods.Clear(); - } + public virtual HashSet Periods => MPeriods; - public abstract HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults); + public virtual void Clear() + { + _mEvaluationStartBounds = DateTime.MaxValue; + _mEvaluationEndBounds = DateTime.MinValue; + MPeriods.Clear(); } + + public abstract HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults); } \ No newline at end of file diff --git a/Ical.Net/Evaluation/EventEvaluator.cs b/Ical.Net/Evaluation/EventEvaluator.cs index c55759070..d0e07c98f 100644 --- a/Ical.Net/Evaluation/EventEvaluator.cs +++ b/Ical.Net/Evaluation/EventEvaluator.cs @@ -1,61 +1,65 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; -namespace Ical.Net.Evaluation +namespace Ical.Net.Evaluation; + +public class EventEvaluator : RecurringEvaluator { - public class EventEvaluator : RecurringEvaluator + protected CalendarEvent CalendarEvent + { + get => Recurrable as CalendarEvent; + set => Recurrable = value; + } + + public EventEvaluator(CalendarEvent evt) : base(evt) { } + + /// + /// Evaluates this event to determine the dates and times for which the event occurs. + /// This method only evaluates events which occur between + /// and ; therefore, if you require a list of events which + /// occur outside of this range, you must specify a and + /// which encapsulate the date(s) of interest. + /// + /// For events with very complex recurrence rules, this method may be a bottleneck + /// during processing time, especially when this method in called for a large number + /// of events, in sequence, or for a very large time span. + /// + /// + /// + /// The beginning date of the range to evaluate. + /// The end date of the range to evaluate. + /// + /// + public override HashSet Evaluate(IDateTime referenceTime, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) { - protected CalendarEvent CalendarEvent + // Evaluate recurrences normally + base.Evaluate(referenceTime, periodStart, periodEnd, includeReferenceDateInResults); + + foreach (var period in Periods) { - get => Recurrable as CalendarEvent; - set => Recurrable = value; + period.Duration = CalendarEvent.Duration; + period.EndTime = period.Duration == default + ? period.StartTime + : period.StartTime.Add(CalendarEvent.Duration); } - public EventEvaluator(CalendarEvent evt) : base(evt) { } - - /// - /// Evaluates this event to determine the dates and times for which the event occurs. - /// This method only evaluates events which occur between - /// and ; therefore, if you require a list of events which - /// occur outside of this range, you must specify a and - /// which encapsulate the date(s) of interest. - /// - /// For events with very complex recurrence rules, this method may be a bottleneck - /// during processing time, especially when this method in called for a large number - /// of events, in sequence, or for a very large time span. - /// - /// - /// - /// The beginning date of the range to evaluate. - /// The end date of the range to evaluate. - /// - /// - public override HashSet Evaluate(IDateTime referenceTime, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + // Ensure each period has a duration + foreach (var period in Periods.Where(p => p.EndTime == null)) { - // Evaluate recurrences normally - base.Evaluate(referenceTime, periodStart, periodEnd, includeReferenceDateInResults); - - foreach (var period in Periods) - { - period.Duration = CalendarEvent.Duration; - period.EndTime = period.Duration == default - ? period.StartTime - : period.StartTime.Add(CalendarEvent.Duration); - } - - // Ensure each period has a duration - foreach (var period in Periods.Where(p => p.EndTime == null)) - { - period.Duration = CalendarEvent.Duration; - period.EndTime = period.Duration == default - ? period.StartTime - : period.StartTime.Add(CalendarEvent.Duration); - } - - return Periods; + period.Duration = CalendarEvent.Duration; + period.EndTime = period.Duration == default + ? period.StartTime + : period.StartTime.Add(CalendarEvent.Duration); } + + return Periods; } } \ No newline at end of file diff --git a/Ical.Net/Evaluation/IEvaluator.cs b/Ical.Net/Evaluation/IEvaluator.cs index 7abd8360f..9f1519359 100644 --- a/Ical.Net/Evaluation/IEvaluator.cs +++ b/Ical.Net/Evaluation/IEvaluator.cs @@ -1,73 +1,77 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; +using Ical.Net.DataTypes; + +namespace Ical.Net.Evaluation; -namespace Ical.Net.Evaluation +public interface IEvaluator { - public interface IEvaluator - { - /// - /// The system calendar that governs the evaluation rules. - /// - System.Globalization.Calendar Calendar { get; } + /// + /// The system calendar that governs the evaluation rules. + /// + System.Globalization.Calendar Calendar { get; } - /// - /// The start bounds of the evaluation. This gives - /// the first date/time that is covered by the evaluation. - /// This together with EvaluationEndBounds determines - /// what time frames have already been evaluated, so - /// duplicate evaluation doesn't occur. - /// - DateTime EvaluationStartBounds { get; } + /// + /// The start bounds of the evaluation. This gives + /// the first date/time that is covered by the evaluation. + /// This together with EvaluationEndBounds determines + /// what time frames have already been evaluated, so + /// duplicate evaluation doesn't occur. + /// + DateTime EvaluationStartBounds { get; } - /// - /// The end bounds of the evaluation. - /// See for more info. - /// - DateTime EvaluationEndBounds { get; } + /// + /// The end bounds of the evaluation. + /// See for more info. + /// + DateTime EvaluationEndBounds { get; } - /// - /// Gets a list of periods collected so far during - /// the evaluation process. - /// - HashSet Periods { get; } + /// + /// Gets a list of periods collected so far during + /// the evaluation process. + /// + HashSet Periods { get; } - /// - /// Gets the object associated with this evaluator. - /// - ICalendarObject AssociatedObject { get; } + /// + /// Gets the object associated with this evaluator. + /// + ICalendarObject AssociatedObject { get; } - /// - /// Clears the evaluation, eliminating all data that has - /// been collected up to this point. Since this data is cached - /// as needed, this method can be useful to gather information - /// that is guaranteed to not be out-of-date. - /// - void Clear(); + /// + /// Clears the evaluation, eliminating all data that has + /// been collected up to this point. Since this data is cached + /// as needed, this method can be useful to gather information + /// that is guaranteed to not be out-of-date. + /// + void Clear(); - /// - /// Evaluates this item to determine the dates and times for which it occurs/recurs. - /// This method only evaluates items which occur/recur between - /// and ; therefore, if you require a list of items which - /// occur outside of this range, you must specify a and - /// which encapsulate the date(s) of interest. - /// This method evaluates using the as the beginning - /// point. For example, for a WEEKLY occurrence, the - /// determines the day of week that this item will recur on. - /// - /// For events with very complex recurrence rules, this method may be a bottleneck - /// during processing time, especially when this method is called for a large number - /// of items, in sequence, or for a very large time span. - /// - /// - /// - /// - /// - /// - /// - /// A list of objects for - /// each date/time when this item occurs/recurs. - /// - HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults); - } + /// + /// Evaluates this item to determine the dates and times for which it occurs/recurs. + /// This method only evaluates items which occur/recur between + /// and ; therefore, if you require a list of items which + /// occur outside of this range, you must specify a and + /// which encapsulate the date(s) of interest. + /// This method evaluates using the as the beginning + /// point. For example, for a WEEKLY occurrence, the + /// determines the day of week that this item will recur on. + /// + /// For events with very complex recurrence rules, this method may be a bottleneck + /// during processing time, especially when this method is called for a large number + /// of items, in sequence, or for a very large time span. + /// + /// + /// + /// + /// + /// + /// + /// A list of objects for + /// each date/time when this item occurs/recurs. + /// + HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults); } \ No newline at end of file diff --git a/Ical.Net/Evaluation/PeriodListEvaluator.cs b/Ical.Net/Evaluation/PeriodListEvaluator.cs index caaf0ffc1..5d5b4f78a 100644 --- a/Ical.Net/Evaluation/PeriodListEvaluator.cs +++ b/Ical.Net/Evaluation/PeriodListEvaluator.cs @@ -1,35 +1,39 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; +using Ical.Net.DataTypes; + +namespace Ical.Net.Evaluation; -namespace Ical.Net.Evaluation +public class PeriodListEvaluator : Evaluator { - public class PeriodListEvaluator : Evaluator + private readonly PeriodList _mPeriodList; + + public PeriodListEvaluator(PeriodList rdt) + { + _mPeriodList = rdt; + } + + public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) { - private readonly PeriodList _mPeriodList; + var periods = new HashSet(); - public PeriodListEvaluator(PeriodList rdt) + if (includeReferenceDateInResults) { - _mPeriodList = rdt; + Period p = new Period(referenceDate); + periods.Add(p); } - public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + if (periodEnd < periodStart) { - var periods = new HashSet(); - - if (includeReferenceDateInResults) - { - Period p = new Period(referenceDate); - periods.Add(p); - } - - if (periodEnd < periodStart) - { - return periods; - } - - periods.UnionWith(_mPeriodList); return periods; } + + periods.UnionWith(_mPeriodList); + return periods; } } \ No newline at end of file diff --git a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs index c949f1c85..32b2967ba 100644 --- a/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs +++ b/Ical.Net/Evaluation/RecurrencePatternEvaluator.cs @@ -1,952 +1,956 @@ -using Ical.Net.DataTypes; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Ical.Net.DataTypes; +using Ical.Net.Utility; -namespace Ical.Net.Evaluation +namespace Ical.Net.Evaluation; + +/// +/// Much of this code comes from iCal4j, as Ben Fortuna has done an +/// excellent job with the recurrence pattern evaluation there. +/// +/// Here's the iCal4j license: +/// ================== +/// iCal4j - License +/// ================== +/// +/// Copyright (c) 2009, Ben Fortuna +/// All rights reserved. +/// +/// Redistribution and use in source and binary forms, with or without +/// modification, are permitted provided that the following conditions +/// are met: +/// +/// o Redistributions of source code must retain the above copyright +/// notice, this list of conditions and the following disclaimer. +/// +/// o Redistributions in binary form must reproduce the above copyright +/// notice, this list of conditions and the following disclaimer in the +/// documentation and/or other materials provided with the distribution. +/// +/// o Neither the name of Ben Fortuna nor the names of any other contributors +/// may be used to endorse or promote products derived from this software +/// without specific prior written permission. +/// +/// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +/// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +/// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +/// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +/// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +/// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +/// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +/// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +/// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +/// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +/// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +/// +public class RecurrencePatternEvaluator : Evaluator { - /// - /// Much of this code comes from iCal4j, as Ben Fortuna has done an - /// excellent job with the recurrence pattern evaluation there. - /// - /// Here's the iCal4j license: - /// ================== - /// iCal4j - License - /// ================== - /// - /// Copyright (c) 2009, Ben Fortuna - /// All rights reserved. - /// - /// Redistribution and use in source and binary forms, with or without - /// modification, are permitted provided that the following conditions - /// are met: - /// - /// o Redistributions of source code must retain the above copyright - /// notice, this list of conditions and the following disclaimer. - /// - /// o Redistributions in binary form must reproduce the above copyright - /// notice, this list of conditions and the following disclaimer in the - /// documentation and/or other materials provided with the distribution. - /// - /// o Neither the name of Ben Fortuna nor the names of any other contributors - /// may be used to endorse or promote products derived from this software - /// without specific prior written permission. - /// - /// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - /// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - /// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - /// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - /// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - /// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - /// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - /// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - /// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - /// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - /// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - /// - public class RecurrencePatternEvaluator : Evaluator + private const int _maxIncrementCount = 1000; + + protected RecurrencePattern Pattern { get; set; } + + public RecurrencePatternEvaluator(RecurrencePattern pattern) { - private const int _maxIncrementCount = 1000; + Pattern = pattern; + } - protected RecurrencePattern Pattern { get; set; } + private RecurrencePattern ProcessRecurrencePattern(IDateTime referenceDate) + { + var r = new RecurrencePattern(); + r.CopyFrom(Pattern); - public RecurrencePatternEvaluator(RecurrencePattern pattern) + // Convert the UNTIL value to one that matches the same time information as the reference date + if (r.Until != DateTime.MinValue) { - Pattern = pattern; + r.Until = DateUtil.MatchTimeZone(referenceDate, new CalDateTime(r.Until, referenceDate.TzId)).Value; } - private RecurrencePattern ProcessRecurrencePattern(IDateTime referenceDate) + if (referenceDate.HasTime) { - var r = new RecurrencePattern(); - r.CopyFrom(Pattern); - - // Convert the UNTIL value to one that matches the same time information as the reference date - if (r.Until != DateTime.MinValue) + if (r.Frequency > FrequencyType.Secondly && r.BySecond.Count == 0 && referenceDate.HasTime + /* NOTE: Fixes a bug where all-day events have BySecond/ByMinute/ByHour added incorrectly */) { - r.Until = DateUtil.MatchTimeZone(referenceDate, new CalDateTime(r.Until, referenceDate.TzId)).Value; + r.BySecond.Add(referenceDate.Second); } - - if (referenceDate.HasTime) + if (r.Frequency > FrequencyType.Minutely && r.ByMinute.Count == 0 && referenceDate.HasTime + /* NOTE: Fixes a bug where all-day events have BySecond/ByMinute/ByHour added incorrectly */) { - if (r.Frequency > FrequencyType.Secondly && r.BySecond.Count == 0 && referenceDate.HasTime - /* NOTE: Fixes a bug where all-day events have BySecond/ByMinute/ByHour added incorrectly */) - { - r.BySecond.Add(referenceDate.Second); - } - if (r.Frequency > FrequencyType.Minutely && r.ByMinute.Count == 0 && referenceDate.HasTime - /* NOTE: Fixes a bug where all-day events have BySecond/ByMinute/ByHour added incorrectly */) - { - r.ByMinute.Add(referenceDate.Minute); - } - if (r.Frequency > FrequencyType.Hourly && r.ByHour.Count == 0 && referenceDate.HasTime - /* NOTE: Fixes a bug where all-day events have BySecond/ByMinute/ByHour added incorrectly */) - { - r.ByHour.Add(referenceDate.Hour); - } + r.ByMinute.Add(referenceDate.Minute); } - else + if (r.Frequency > FrequencyType.Hourly && r.ByHour.Count == 0 && referenceDate.HasTime + /* NOTE: Fixes a bug where all-day events have BySecond/ByMinute/ByHour added incorrectly */) { - // The BYSECOND, BYMINUTE and BYHOUR rule parts MUST NOT be specified - // when the associated "DTSTART" property has a DATE value type. - // These rule parts MUST be ignored in RECUR value that violate the - // above requirement(e.g., generated by applications that pre - date - // this revision of iCalendar). - r.BySecond.Clear(); - r.BySecond.Add(0); - r.ByMinute.Clear(); - r.ByMinute.Add(0); - r.ByHour.Clear(); - r.ByHour.Add(0); - } - - // If BYDAY, BYYEARDAY, or BYWEEKNO is specified, then - // we don't default BYDAY, BYMONTH or BYMONTHDAY - if (r.ByDay.Count == 0) - { - // If the frequency is weekly, use the original date's day of week. - // NOTE: fixes WeeklyCount1() and WeeklyUntil1() handling - // If BYWEEKNO is specified and BYMONTHDAY/BYYEARDAY is not specified, - // then let's add BYDAY to BYWEEKNO. - // NOTE: fixes YearlyByWeekNoX() handling - if (r.Frequency == FrequencyType.Weekly || (r.ByWeekNo.Count > 0 && r.ByMonthDay.Count == 0 && r.ByYearDay.Count == 0)) - { - r.ByDay.Add(new WeekDay(referenceDate.DayOfWeek)); - } - - // If BYMONTHDAY is not specified, - // default to the current day of month. - // NOTE: fixes YearlyByMonth1() handling, added BYYEARDAY exclusion - // to fix YearlyCountByYearDay1() handling - if (r.Frequency > FrequencyType.Weekly && r.ByWeekNo.Count == 0 && r.ByYearDay.Count == 0 && r.ByMonthDay.Count == 0) - { - r.ByMonthDay.Add(referenceDate.Day); - } - - // If BYMONTH is not specified, default to - // the current month. - // NOTE: fixes YearlyCountByYearDay1() handling - if (r.Frequency > FrequencyType.Monthly && r.ByWeekNo.Count == 0 && r.ByYearDay.Count == 0 && r.ByMonth.Count == 0) - { - r.ByMonth.Add(referenceDate.Month); - } + r.ByHour.Add(referenceDate.Hour); } - - return r; } -#pragma warning disable 0618 - private void EnforceEvaluationRestrictions(RecurrencePattern pattern) + else { - RecurrenceEvaluationModeType? evaluationMode = pattern.EvaluationMode; - RecurrenceRestrictionType? evaluationRestriction = pattern.RestrictionType; + // The BYSECOND, BYMINUTE and BYHOUR rule parts MUST NOT be specified + // when the associated "DTSTART" property has a DATE value type. + // These rule parts MUST be ignored in RECUR value that violate the + // above requirement(e.g., generated by applications that pre - date + // this revision of iCalendar). + r.BySecond.Clear(); + r.BySecond.Add(0); + r.ByMinute.Clear(); + r.ByMinute.Add(0); + r.ByHour.Clear(); + r.ByHour.Add(0); + } - if (evaluationRestriction != RecurrenceRestrictionType.NoRestriction) + // If BYDAY, BYYEARDAY, or BYWEEKNO is specified, then + // we don't default BYDAY, BYMONTH or BYMONTHDAY + if (r.ByDay.Count == 0) + { + // If the frequency is weekly, use the original date's day of week. + // NOTE: fixes WeeklyCount1() and WeeklyUntil1() handling + // If BYWEEKNO is specified and BYMONTHDAY/BYYEARDAY is not specified, + // then let's add BYDAY to BYWEEKNO. + // NOTE: fixes YearlyByWeekNoX() handling + if (r.Frequency == FrequencyType.Weekly || (r.ByWeekNo.Count > 0 && r.ByMonthDay.Count == 0 && r.ByYearDay.Count == 0)) { - switch (evaluationMode) - { - case RecurrenceEvaluationModeType.AdjustAutomatically: - switch (pattern.Frequency) - { - case FrequencyType.Secondly: - { - switch (evaluationRestriction) - { - case RecurrenceRestrictionType.Default: - case RecurrenceRestrictionType.RestrictSecondly: - pattern.Frequency = FrequencyType.Minutely; - break; - case RecurrenceRestrictionType.RestrictMinutely: - pattern.Frequency = FrequencyType.Hourly; - break; - case RecurrenceRestrictionType.RestrictHourly: - pattern.Frequency = FrequencyType.Daily; - break; - } - } - break; - case FrequencyType.Minutely: - { - switch (evaluationRestriction) - { - case RecurrenceRestrictionType.RestrictMinutely: - pattern.Frequency = FrequencyType.Hourly; - break; - case RecurrenceRestrictionType.RestrictHourly: - pattern.Frequency = FrequencyType.Daily; - break; - } - } - break; - case FrequencyType.Hourly: - { - switch (evaluationRestriction) - { - case RecurrenceRestrictionType.RestrictHourly: - pattern.Frequency = FrequencyType.Daily; - break; - } - } - break; - } - break; - case RecurrenceEvaluationModeType.ThrowException: - case RecurrenceEvaluationModeType.Default: - switch (pattern.Frequency) - { - case FrequencyType.Secondly: - { - switch (evaluationRestriction) - { - case RecurrenceRestrictionType.Default: - case RecurrenceRestrictionType.RestrictSecondly: - case RecurrenceRestrictionType.RestrictMinutely: - case RecurrenceRestrictionType.RestrictHourly: - throw new ArgumentException(); - } - } - break; - case FrequencyType.Minutely: - { - switch (evaluationRestriction) - { - case RecurrenceRestrictionType.RestrictMinutely: - case RecurrenceRestrictionType.RestrictHourly: - throw new ArgumentException(); - } - } - break; - case FrequencyType.Hourly: - { - switch (evaluationRestriction) - { - case RecurrenceRestrictionType.RestrictHourly: - throw new ArgumentException(); - } - } - break; - } - break; - } - } - } -#pragma warning 0618 restore - /// - /// Returns a list of start dates in the specified period represented by this recurrence pattern. - /// This method includes a base date argument, which indicates the start of the first occurrence of this recurrence. - /// The base date is used to inject default values to return a set of dates in the correct format. - /// For example, if the search start date (start) is Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, - /// the start dates returned should all be at 9:00AM, and not 12:19PM. - /// - private HashSet GetDates(IDateTime seed, DateTime periodStart, DateTime periodEnd, int maxCount, RecurrencePattern pattern, - bool includeReferenceDateInResults) - { - var dates = new HashSet(); - var originalDate = DateUtil.GetSimpleDateTimeData(seed); - var seedCopy = DateUtil.GetSimpleDateTimeData(seed); - - // optimize the start time for selecting candidates - // (only applicable where a COUNT is not specified) - if (pattern.Count == int.MinValue) - { - var incremented = seedCopy; - while (incremented < periodStart) - { - seedCopy = incremented; - IncrementDate(ref incremented, pattern, pattern.Interval); - } + r.ByDay.Add(new WeekDay(referenceDate.DayOfWeek)); } - var expandBehavior = RecurrenceUtil.GetExpandBehaviorList(pattern); - - var noCandidateIncrementCount = 0; - var candidate = DateTime.MinValue; - while (maxCount < 0 || dates.Count < maxCount) + // If BYMONTHDAY is not specified, + // default to the current day of month. + // NOTE: fixes YearlyByMonth1() handling, added BYYEARDAY exclusion + // to fix YearlyCountByYearDay1() handling + if (r.Frequency > FrequencyType.Weekly && r.ByWeekNo.Count == 0 && r.ByYearDay.Count == 0 && r.ByMonthDay.Count == 0) { - if (pattern.Until != DateTime.MinValue && candidate != DateTime.MinValue && candidate > pattern.Until) - { - break; - } - - if (candidate != DateTime.MinValue && candidate > periodEnd) - { - break; - } - - if (pattern.Count >= 1 && dates.Count >= pattern.Count) - { - break; - } + r.ByMonthDay.Add(referenceDate.Day); + } - //No need to continue if the seed is after the periodEnd - if (seedCopy > periodEnd) - { - break; - } + // If BYMONTH is not specified, default to + // the current month. + // NOTE: fixes YearlyCountByYearDay1() handling + if (r.Frequency > FrequencyType.Monthly && r.ByWeekNo.Count == 0 && r.ByYearDay.Count == 0 && r.ByMonth.Count == 0) + { + r.ByMonth.Add(referenceDate.Month); + } + } - var candidates = GetCandidates(seedCopy, pattern, expandBehavior); - if (candidates.Count > 0) - { - noCandidateIncrementCount = 0; + return r; + } +#pragma warning disable 0618 + private void EnforceEvaluationRestrictions(RecurrencePattern pattern) + { + RecurrenceEvaluationModeType? evaluationMode = pattern.EvaluationMode; + RecurrenceRestrictionType? evaluationRestriction = pattern.RestrictionType; - foreach (var t in candidates.OrderBy(c => c).Where(t => t >= originalDate)) + if (evaluationRestriction != RecurrenceRestrictionType.NoRestriction) + { + switch (evaluationMode) + { + case RecurrenceEvaluationModeType.AdjustAutomatically: + switch (pattern.Frequency) { - candidate = t; - - // candidates MAY occur before periodStart - // For example, FREQ=YEARLY;BYWEEKNO=1 could return dates - // from the previous year. - // - // exclude candidates that start at the same moment as periodEnd if the period is a range but keep them if targeting a specific moment - if (pattern.Count >= 1 && dates.Count >= pattern.Count) + case FrequencyType.Secondly: { - break; + switch (evaluationRestriction) + { + case RecurrenceRestrictionType.Default: + case RecurrenceRestrictionType.RestrictSecondly: + pattern.Frequency = FrequencyType.Minutely; + break; + case RecurrenceRestrictionType.RestrictMinutely: + pattern.Frequency = FrequencyType.Hourly; + break; + case RecurrenceRestrictionType.RestrictHourly: + pattern.Frequency = FrequencyType.Daily; + break; + } } - - if ((candidate >= periodEnd && periodStart != periodEnd) || candidate > periodEnd && periodStart == periodEnd) + break; + case FrequencyType.Minutely: { - continue; + switch (evaluationRestriction) + { + case RecurrenceRestrictionType.RestrictMinutely: + pattern.Frequency = FrequencyType.Hourly; + break; + case RecurrenceRestrictionType.RestrictHourly: + pattern.Frequency = FrequencyType.Daily; + break; + } } - - if (pattern.Until == DateTime.MinValue || candidate <= pattern.Until) + break; + case FrequencyType.Hourly: { - dates.Add(candidate); + switch (evaluationRestriction) + { + case RecurrenceRestrictionType.RestrictHourly: + pattern.Frequency = FrequencyType.Daily; + break; + } } + break; } - } - else - { - noCandidateIncrementCount++; - if (_maxIncrementCount > 0 && noCandidateIncrementCount > _maxIncrementCount) + break; + case RecurrenceEvaluationModeType.ThrowException: + case RecurrenceEvaluationModeType.Default: + switch (pattern.Frequency) { - break; + case FrequencyType.Secondly: + { + switch (evaluationRestriction) + { + case RecurrenceRestrictionType.Default: + case RecurrenceRestrictionType.RestrictSecondly: + case RecurrenceRestrictionType.RestrictMinutely: + case RecurrenceRestrictionType.RestrictHourly: + throw new ArgumentException(); + } + } + break; + case FrequencyType.Minutely: + { + switch (evaluationRestriction) + { + case RecurrenceRestrictionType.RestrictMinutely: + case RecurrenceRestrictionType.RestrictHourly: + throw new ArgumentException(); + } + } + break; + case FrequencyType.Hourly: + { + switch (evaluationRestriction) + { + case RecurrenceRestrictionType.RestrictHourly: + throw new ArgumentException(); + } + } + break; } - } - - IncrementDate(ref seedCopy, pattern, pattern.Interval); + break; } - - return dates; - } - - /// - /// Returns a list of possible dates generated from the applicable BY* rules, using the specified date as a seed. - /// - /// The seed date. - /// - /// - /// A list of possible dates. - private List GetCandidates(DateTime date, RecurrencePattern pattern, bool?[] expandBehaviors) - { - var dates = new List { date }; - dates = GetMonthVariants(dates, pattern, expandBehaviors[0]); - dates = GetWeekNoVariants(dates, pattern, expandBehaviors[1]); - dates = GetYearDayVariants(dates, pattern, expandBehaviors[2]); - dates = GetMonthDayVariants(dates, pattern, expandBehaviors[3]); - dates = GetDayVariants(dates, pattern, expandBehaviors[4]); - dates = GetHourVariants(dates, pattern, expandBehaviors[5]); - dates = GetMinuteVariants(dates, pattern, expandBehaviors[6]); - dates = GetSecondVariants(dates, pattern, expandBehaviors[7]); - dates = ApplySetPosRules(dates, pattern); - return dates; } + } +#pragma warning 0618 restore + /// + /// Returns a list of start dates in the specified period represented by this recurrence pattern. + /// This method includes a base date argument, which indicates the start of the first occurrence of this recurrence. + /// The base date is used to inject default values to return a set of dates in the correct format. + /// For example, if the search start date (start) is Wed, Mar 23, 12:19PM, but the recurrence is Mon - Fri, 9:00AM - 5:00PM, + /// the start dates returned should all be at 9:00AM, and not 12:19PM. + /// + private HashSet GetDates(IDateTime seed, DateTime periodStart, DateTime periodEnd, int maxCount, RecurrencePattern pattern, + bool includeReferenceDateInResults) + { + var dates = new HashSet(); + var originalDate = DateUtil.GetSimpleDateTimeData(seed); + var seedCopy = DateUtil.GetSimpleDateTimeData(seed); - /// - /// Applies BYSETPOS rules to . Valid positions are from 1 to the size of the date list. Invalid - /// positions are ignored. - /// - /// The list of dates to which the BYSETPOS rules will be applied. - /// - private List ApplySetPosRules(List dates, RecurrencePattern pattern) + // optimize the start time for selecting candidates + // (only applicable where a COUNT is not specified) + if (pattern.Count == int.MinValue) { - // return if no SETPOS rules specified.. - if (pattern.BySetPosition.Count == 0) + var incremented = seedCopy; + while (incremented < periodStart) { - return dates; + seedCopy = incremented; + IncrementDate(ref incremented, pattern, pattern.Interval); } - - // sort the list before processing.. - dates.Sort(); - - var size = dates.Count; - var setPosDates = pattern.BySetPosition - .Where(p => p > 0 && p <= size || p < 0 && p >= -size) //Protect against out of range access - .Select(p => p > 0 && p <= size - ? dates[p - 1] - : dates[size + p]) - .ToList(); - return setPosDates; } - /// - /// Applies BYMONTH rules specified in this Recur instance to the specified date list. - /// If no BYMONTH rules are specified, the date list is returned unmodified. - /// - /// The list of dates to which the BYMONTH rules will be applied. - /// - /// - /// The modified list of dates after applying the BYMONTH rules. - private List GetMonthVariants(List dates, RecurrencePattern pattern, bool? expand) + var expandBehavior = RecurrenceUtil.GetExpandBehaviorList(pattern); + + var noCandidateIncrementCount = 0; + var candidate = DateTime.MinValue; + while (maxCount < 0 || dates.Count < maxCount) { - if (expand == null || pattern.ByMonth.Count == 0) + if (pattern.Until != DateTime.MinValue && candidate != DateTime.MinValue && candidate > pattern.Until) { - return dates; + break; } - if (expand.Value) + if (candidate != DateTime.MinValue && candidate > periodEnd) { - // Expand behavior - return dates - .SelectMany(d => pattern.ByMonth.Select(month => d.AddMonths(month - d.Month))) - .ToList(); + break; } - // Limit behavior - var dateSet = new HashSet(dates); - dateSet.ExceptWith(dates.Where(date => pattern.ByMonth.All(t => t != date.Month))); - return dateSet.ToList(); - } - - /// - /// Applies BYWEEKNO rules specified in this Recur instance to the specified date list. - /// If no BYWEEKNO rules are specified, the date list is returned unmodified. - /// - /// The list of dates to which the BYWEEKNO rules will be applied. - /// The modified list of dates after applying the BYWEEKNO rules. - private List GetWeekNoVariants(List dates, RecurrencePattern pattern, bool? expand) - { - if (expand == null || pattern.ByWeekNo.Count == 0) + if (pattern.Count >= 1 && dates.Count >= pattern.Count) { - return dates; + break; } - if (!expand.Value) + //No need to continue if the seed is after the periodEnd + if (seedCopy > periodEnd) { - return new List(); + break; } - // Expand behavior - var weekNoDates = new List(); - foreach (var t in dates) + var candidates = GetCandidates(seedCopy, pattern, expandBehavior); + if (candidates.Count > 0) { - foreach (var weekNo in pattern.ByWeekNo) + noCandidateIncrementCount = 0; + + foreach (var t in candidates.OrderBy(c => c).Where(t => t >= originalDate)) { - var date = t; - // Determine our current week number - var currWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); - while (currWeekNo > weekNo) + candidate = t; + + // candidates MAY occur before periodStart + // For example, FREQ=YEARLY;BYWEEKNO=1 could return dates + // from the previous year. + // + // exclude candidates that start at the same moment as periodEnd if the period is a range but keep them if targeting a specific moment + if (pattern.Count >= 1 && dates.Count >= pattern.Count) { - // If currWeekNo > weekNo, then we're likely at the start of a year - // where currWeekNo could be 52 or 53. If we simply step ahead 7 days - // we should be back to week 1, where we can easily make the calculation - // to move to weekNo. - date = date.AddDays(7); - currWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + break; } - // Move ahead to the correct week of the year - date = date.AddDays((weekNo - currWeekNo) * 7); - - // Step backward single days until we're at the correct DayOfWeek - while (date.DayOfWeek != pattern.FirstDayOfWeek) + if ((candidate >= periodEnd && periodStart != periodEnd) || candidate > periodEnd && periodStart == periodEnd) { - date = date.AddDays(-1); + continue; } - for (var k = 0; k < 7; k++) + if (pattern.Until == DateTime.MinValue || candidate <= pattern.Until) { - weekNoDates.Add(date); - date = date.AddDays(1); + dates.Add(candidate); } } } - return weekNoDates; + else + { + noCandidateIncrementCount++; + if (_maxIncrementCount > 0 && noCandidateIncrementCount > _maxIncrementCount) + { + break; + } + } + + IncrementDate(ref seedCopy, pattern, pattern.Interval); } - /// - /// Applies BYYEARDAY rules specified in this Recur instance to the specified date list. - /// If no BYYEARDAY rules are specified, the date list is returned unmodified. - /// - /// The list of dates to which the BYYEARDAY rules will be applied. - /// The modified list of dates after applying the BYYEARDAY rules. - private List GetYearDayVariants(List dates, RecurrencePattern pattern, bool? expand) + return dates; + } + + /// + /// Returns a list of possible dates generated from the applicable BY* rules, using the specified date as a seed. + /// + /// The seed date. + /// + /// + /// A list of possible dates. + private List GetCandidates(DateTime date, RecurrencePattern pattern, bool?[] expandBehaviors) + { + var dates = new List { date }; + dates = GetMonthVariants(dates, pattern, expandBehaviors[0]); + dates = GetWeekNoVariants(dates, pattern, expandBehaviors[1]); + dates = GetYearDayVariants(dates, pattern, expandBehaviors[2]); + dates = GetMonthDayVariants(dates, pattern, expandBehaviors[3]); + dates = GetDayVariants(dates, pattern, expandBehaviors[4]); + dates = GetHourVariants(dates, pattern, expandBehaviors[5]); + dates = GetMinuteVariants(dates, pattern, expandBehaviors[6]); + dates = GetSecondVariants(dates, pattern, expandBehaviors[7]); + dates = ApplySetPosRules(dates, pattern); + return dates; + } + + /// + /// Applies BYSETPOS rules to . Valid positions are from 1 to the size of the date list. Invalid + /// positions are ignored. + /// + /// The list of dates to which the BYSETPOS rules will be applied. + /// + private List ApplySetPosRules(List dates, RecurrencePattern pattern) + { + // return if no SETPOS rules specified.. + if (pattern.BySetPosition.Count == 0) { - if (expand == null || pattern.ByYearDay.Count == 0) - { - return dates; - } + return dates; + } + + // sort the list before processing.. + dates.Sort(); + + var size = dates.Count; + var setPosDates = pattern.BySetPosition + .Where(p => p > 0 && p <= size || p < 0 && p >= -size) //Protect against out of range access + .Select(p => p > 0 && p <= size + ? dates[p - 1] + : dates[size + p]) + .ToList(); + return setPosDates; + } + + /// + /// Applies BYMONTH rules specified in this Recur instance to the specified date list. + /// If no BYMONTH rules are specified, the date list is returned unmodified. + /// + /// The list of dates to which the BYMONTH rules will be applied. + /// + /// + /// The modified list of dates after applying the BYMONTH rules. + private List GetMonthVariants(List dates, RecurrencePattern pattern, bool? expand) + { + if (expand == null || pattern.ByMonth.Count == 0) + { + return dates; + } - if (expand.Value) + if (expand.Value) + { + // Expand behavior + return dates + .SelectMany(d => pattern.ByMonth.Select(month => d.AddMonths(month - d.Month))) + .ToList(); + } + + // Limit behavior + var dateSet = new HashSet(dates); + dateSet.ExceptWith(dates.Where(date => pattern.ByMonth.All(t => t != date.Month))); + return dateSet.ToList(); + } + + /// + /// Applies BYWEEKNO rules specified in this Recur instance to the specified date list. + /// If no BYWEEKNO rules are specified, the date list is returned unmodified. + /// + /// The list of dates to which the BYWEEKNO rules will be applied. + /// The modified list of dates after applying the BYWEEKNO rules. + private List GetWeekNoVariants(List dates, RecurrencePattern pattern, bool? expand) + { + if (expand == null || pattern.ByWeekNo.Count == 0) + { + return dates; + } + + if (!expand.Value) + { + return new List(); + } + + // Expand behavior + var weekNoDates = new List(); + foreach (var t in dates) + { + foreach (var weekNo in pattern.ByWeekNo) { - var yearDayDates = new List(dates.Count); - foreach (var date in dates) + var date = t; + // Determine our current week number + var currWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + while (currWeekNo > weekNo) { - var date1 = date; - yearDayDates.AddRange(pattern.ByYearDay.Select(yearDay => yearDay > 0 - ? date1.AddDays(-date1.DayOfYear + yearDay) - : date1.AddDays(-date1.DayOfYear + 1).AddYears(1).AddDays(yearDay)) - // Ignore the BY values that don't fit into the current year (i.e. +-366 in non-leap-years). - .Where(d => d.Year == date1.Year)); + // If currWeekNo > weekNo, then we're likely at the start of a year + // where currWeekNo could be 52 or 53. If we simply step ahead 7 days + // we should be back to week 1, where we can easily make the calculation + // to move to weekNo. + date = date.AddDays(7); + currWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); } - return yearDayDates; - } - // Limit behavior - for (var i = dates.Count - 1; i >= 0; i--) - { - var date = dates[i]; - for (var j = 0; j < pattern.ByYearDay.Count; j++) - { - var yearDay = pattern.ByYearDay[j]; - var newDate = yearDay > 0 - ? date.AddDays(-date.DayOfYear + yearDay) - : date.AddDays(-date.DayOfYear + 1).AddYears(1).AddDays(yearDay); + // Move ahead to the correct week of the year + date = date.AddDays((weekNo - currWeekNo) * 7); - if (newDate.Date == date.Date) - { - goto Next; - } + // Step backward single days until we're at the correct DayOfWeek + while (date.DayOfWeek != pattern.FirstDayOfWeek) + { + date = date.AddDays(-1); } - dates.RemoveAt(i); - Next: - ; + for (var k = 0; k < 7; k++) + { + weekNoDates.Add(date); + date = date.AddDays(1); + } } + } + return weekNoDates; + } + /// + /// Applies BYYEARDAY rules specified in this Recur instance to the specified date list. + /// If no BYYEARDAY rules are specified, the date list is returned unmodified. + /// + /// The list of dates to which the BYYEARDAY rules will be applied. + /// The modified list of dates after applying the BYYEARDAY rules. + private List GetYearDayVariants(List dates, RecurrencePattern pattern, bool? expand) + { + if (expand == null || pattern.ByYearDay.Count == 0) + { return dates; } - /// - /// Applies BYMONTHDAY rules specified in this Recur instance to the specified date list. - /// If no BYMONTHDAY rules are specified, the date list is returned unmodified. - /// - /// The list of dates to which the BYMONTHDAY rules will be applied. - /// The modified list of dates after applying the BYMONTHDAY rules. - private List GetMonthDayVariants(List dates, RecurrencePattern pattern, bool? expand) + if (expand.Value) { - if (expand == null || pattern.ByMonthDay.Count == 0) + var yearDayDates = new List(dates.Count); + foreach (var date in dates) { - return dates; + var date1 = date; + yearDayDates.AddRange(pattern.ByYearDay.Select(yearDay => yearDay > 0 + ? date1.AddDays(-date1.DayOfYear + yearDay) + : date1.AddDays(-date1.DayOfYear + 1).AddYears(1).AddDays(yearDay)) + // Ignore the BY values that don't fit into the current year (i.e. +-366 in non-leap-years). + .Where(d => d.Year == date1.Year)); } - - if (expand.Value) + return yearDayDates; + } + // Limit behavior + for (var i = dates.Count - 1; i >= 0; i--) + { + var date = dates[i]; + for (var j = 0; j < pattern.ByYearDay.Count; j++) { - var monthDayDates = new List(); - foreach (var date in dates) + var yearDay = pattern.ByYearDay[j]; + + var newDate = yearDay > 0 + ? date.AddDays(-date.DayOfYear + yearDay) + : date.AddDays(-date.DayOfYear + 1).AddYears(1).AddDays(yearDay); + + if (newDate.Date == date.Date) { - monthDayDates.AddRange( - from monthDay in pattern.ByMonthDay - let daysInMonth = Calendar.GetDaysInMonth(date.Year, date.Month) - where Math.Abs(monthDay) <= daysInMonth - select monthDay > 0 - ? date.AddDays(-date.Day + monthDay) - : date.AddDays(-date.Day + 1).AddMonths(1).AddDays(monthDay) - ); + goto Next; } - return monthDayDates; } - // Limit behavior - for (var i = dates.Count - 1; i >= 0; i--) - { - var date = dates[i]; - for (var j = 0; j < pattern.ByMonthDay.Count; j++) - { - var monthDay = pattern.ByMonthDay[j]; - - var daysInMonth = Calendar.GetDaysInMonth(date.Year, date.Month); - if (Math.Abs(monthDay) > daysInMonth) - { - throw new ArgumentException("Invalid day of month: " + date + " (day " + monthDay + ")"); - } - // Account for positive or negative numbers - var newDate = monthDay > 0 - ? date.AddDays(-date.Day + monthDay) - : date.AddDays(-date.Day + 1).AddMonths(1).AddDays(monthDay); - - if (newDate.Day.Equals(date.Day)) - { - goto Next; - } - } + dates.RemoveAt(i); + Next: + ; + } - Next: - dates.RemoveAt(i); - } + return dates; + } + /// + /// Applies BYMONTHDAY rules specified in this Recur instance to the specified date list. + /// If no BYMONTHDAY rules are specified, the date list is returned unmodified. + /// + /// The list of dates to which the BYMONTHDAY rules will be applied. + /// The modified list of dates after applying the BYMONTHDAY rules. + private List GetMonthDayVariants(List dates, RecurrencePattern pattern, bool? expand) + { + if (expand == null || pattern.ByMonthDay.Count == 0) + { return dates; } - /// - /// Applies BYDAY rules specified in this Recur instance to the specified date list. - /// If no BYDAY rules are specified, the date list is returned unmodified. - /// - /// The list of dates to which BYDAY rules will be applied. - /// The modified list of dates after applying BYDAY rules, or the original list if no BYDAY rules are specified. - private List GetDayVariants(List dates, RecurrencePattern pattern, bool? expand) + if (expand.Value) { - if (expand == null || pattern.ByDay.Count == 0) - { - return dates; + var monthDayDates = new List(); + foreach (var date in dates) + { + monthDayDates.AddRange( + from monthDay in pattern.ByMonthDay + let daysInMonth = Calendar.GetDaysInMonth(date.Year, date.Month) + where Math.Abs(monthDay) <= daysInMonth + select monthDay > 0 + ? date.AddDays(-date.Day + monthDay) + : date.AddDays(-date.Day + 1).AddMonths(1).AddDays(monthDay) + ); } - - if (expand.Value) + return monthDayDates; + } + // Limit behavior + for (var i = dates.Count - 1; i >= 0; i--) + { + var date = dates[i]; + for (var j = 0; j < pattern.ByMonthDay.Count; j++) { - // Expand behavior - var weekDayDates = new List(); - foreach (var date in dates) + var monthDay = pattern.ByMonthDay[j]; + + var daysInMonth = Calendar.GetDaysInMonth(date.Year, date.Month); + if (Math.Abs(monthDay) > daysInMonth) { - foreach (var day in pattern.ByDay) - { - weekDayDates.AddRange(GetAbsWeekDays(date, day, pattern)); - } + throw new ArgumentException("Invalid day of month: " + date + " (day " + monthDay + ")"); } - return weekDayDates; - } + // Account for positive or negative numbers + var newDate = monthDay > 0 + ? date.AddDays(-date.Day + monthDay) + : date.AddDays(-date.Day + 1).AddMonths(1).AddDays(monthDay); - // Limit behavior - for (var i = dates.Count - 1; i >= 0; i--) - { - var date = dates[i]; - for (var j = 0; j < pattern.ByDay.Count; j++) + if (newDate.Day.Equals(date.Day)) { - var weekDay = pattern.ByDay[j]; - if (weekDay.DayOfWeek.Equals(date.DayOfWeek)) - { - // If no offset is specified, simply test the day of week! - // FIXME: test with offset... - if (date.DayOfWeek.Equals(weekDay.DayOfWeek)) - { - goto Next; - } - } + goto Next; } - dates.RemoveAt(i); - Next: - ; } - return dates; + Next: + dates.RemoveAt(i); } - /// - /// Returns a list of applicable dates corresponding to the specified week day in accordance with the frequency - /// specified by this recurrence rule. - /// - /// The date to start the evaluation from. - /// The week day to evaluate. - /// A list of applicable dates. - private List GetAbsWeekDays(DateTime date, WeekDay weekDay, RecurrencePattern pattern) + return dates; + } + + /// + /// Applies BYDAY rules specified in this Recur instance to the specified date list. + /// If no BYDAY rules are specified, the date list is returned unmodified. + /// + /// The list of dates to which BYDAY rules will be applied. + /// The modified list of dates after applying BYDAY rules, or the original list if no BYDAY rules are specified. + private List GetDayVariants(List dates, RecurrencePattern pattern, bool? expand) + { + if (expand == null || pattern.ByDay.Count == 0) { - var days = new List(); + return dates; + } - var dayOfWeek = weekDay.DayOfWeek; - if (pattern.Frequency == FrequencyType.Daily) + if (expand.Value) + { + // Expand behavior + var weekDayDates = new List(); + foreach (var date in dates) { - if (date.DayOfWeek == dayOfWeek) + foreach (var day in pattern.ByDay) { - days.Add(date); + weekDayDates.AddRange(GetAbsWeekDays(date, day, pattern)); } } - else if (pattern.Frequency == FrequencyType.Weekly || pattern.ByWeekNo.Count > 0) - { - var weekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); - // construct a list of possible week days.. - while (date.DayOfWeek != dayOfWeek) - { - date = date.AddDays(1); - } - - var nextWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); - var currentWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + return weekDayDates; + } - //When we manage weekly recurring pattern and we have boundary case: - //Weekdays: Dec 31, Jan 1, Feb 1, Mar 1, Apr 1, May 1, June 1, Dec 31 - It's the 53th week of the year, but all another are 1st week number. - //So we need an EXRULE for this situation, but only for weekly events - while (currentWeekNo == weekNo || (nextWeekNo < weekNo && currentWeekNo == nextWeekNo && pattern.Frequency == FrequencyType.Weekly)) + // Limit behavior + for (var i = dates.Count - 1; i >= 0; i--) + { + var date = dates[i]; + for (var j = 0; j < pattern.ByDay.Count; j++) + { + var weekDay = pattern.ByDay[j]; + if (weekDay.DayOfWeek.Equals(date.DayOfWeek)) { - if ((pattern.ByWeekNo.Count == 0 || pattern.ByWeekNo.Contains(currentWeekNo)) - && (pattern.ByMonth.Count == 0 || pattern.ByMonth.Contains(date.Month))) + // If no offset is specified, simply test the day of week! + // FIXME: test with offset... + if (date.DayOfWeek.Equals(weekDay.DayOfWeek)) { - days.Add(date); + goto Next; } - - date = date.AddDays(7); - currentWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); } } - else if (pattern.Frequency == FrequencyType.Monthly || pattern.ByMonth.Count > 0) - { - var month = date.Month; + dates.RemoveAt(i); + Next: + ; + } - // construct a list of possible month days.. - date = date.AddDays(-date.Day + 1); - while (date.DayOfWeek != dayOfWeek) - { - date = date.AddDays(1); - } + return dates; + } - while (date.Month == month) - { - var currentWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + /// + /// Returns a list of applicable dates corresponding to the specified week day in accordance with the frequency + /// specified by this recurrence rule. + /// + /// The date to start the evaluation from. + /// The week day to evaluate. + /// A list of applicable dates. + private List GetAbsWeekDays(DateTime date, WeekDay weekDay, RecurrencePattern pattern) + { + var days = new List(); - if ((pattern.ByWeekNo.Count == 0 || pattern.ByWeekNo.Contains(currentWeekNo)) - && (pattern.ByMonth.Count == 0 || pattern.ByMonth.Contains(date.Month))) - { - days.Add(date); - } - date = date.AddDays(7); - } + var dayOfWeek = weekDay.DayOfWeek; + if (pattern.Frequency == FrequencyType.Daily) + { + if (date.DayOfWeek == dayOfWeek) + { + days.Add(date); } - else if (pattern.Frequency == FrequencyType.Yearly) + } + else if (pattern.Frequency == FrequencyType.Weekly || pattern.ByWeekNo.Count > 0) + { + var weekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + + // construct a list of possible week days.. + while (date.DayOfWeek != dayOfWeek) { - var year = date.Year; + date = date.AddDays(1); + } - // construct a list of possible year days.. - date = date.AddDays(-date.DayOfYear + 1); - while (date.DayOfWeek != dayOfWeek) - { - date = date.AddDays(1); - } + var nextWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + var currentWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); - while (date.Year == year) + //When we manage weekly recurring pattern and we have boundary case: + //Weekdays: Dec 31, Jan 1, Feb 1, Mar 1, Apr 1, May 1, June 1, Dec 31 - It's the 53th week of the year, but all another are 1st week number. + //So we need an EXRULE for this situation, but only for weekly events + while (currentWeekNo == weekNo || (nextWeekNo < weekNo && currentWeekNo == nextWeekNo && pattern.Frequency == FrequencyType.Weekly)) + { + if ((pattern.ByWeekNo.Count == 0 || pattern.ByWeekNo.Contains(currentWeekNo)) + && (pattern.ByMonth.Count == 0 || pattern.ByMonth.Contains(date.Month))) { days.Add(date); - date = date.AddDays(7); } + + date = date.AddDays(7); + currentWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); } - return GetOffsetDates(days, weekDay.Offset); } - - /// - /// Returns a single-element sublist containing the element of at . - /// Valid offsets are from 1 to the size of the list. If an invalid offset is supplied, all elements from - /// are added to result. - /// - /// The list from which to extract the element. - /// The position of the element to extract. - private List GetOffsetDates(List dates, int offset) + else if (pattern.Frequency == FrequencyType.Monthly || pattern.ByMonth.Count > 0) { - if (offset == int.MinValue) - { - return dates; - } + var month = date.Month; - var offsetDates = new List(); - var size = dates.Count; - if (offset < 0 && offset >= -size) + // construct a list of possible month days.. + date = date.AddDays(-date.Day + 1); + while (date.DayOfWeek != dayOfWeek) { - offsetDates.Add(dates[size + offset]); + date = date.AddDays(1); } - else if (offset > 0 && offset <= size) + + while (date.Month == month) { - offsetDates.Add(dates[offset - 1]); + var currentWeekNo = Calendar.GetIso8601WeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); + + if ((pattern.ByWeekNo.Count == 0 || pattern.ByWeekNo.Contains(currentWeekNo)) + && (pattern.ByMonth.Count == 0 || pattern.ByMonth.Contains(date.Month))) + { + days.Add(date); + } + date = date.AddDays(7); } - return offsetDates; } - - /// - /// Applies BYHOUR rules specified in this Recur instance to the specified date list. - /// If no BYHOUR rules are specified, the date list is returned unmodified. - /// - /// The list of dates to which the BYHOUR rules will be applied. - /// - /// - /// The modified list of dates after applying the BYHOUR rules. - private List GetHourVariants(List dates, RecurrencePattern pattern, bool? expand) + else if (pattern.Frequency == FrequencyType.Yearly) { - if (expand == null || pattern.ByHour.Count == 0) + var year = date.Year; + + // construct a list of possible year days.. + date = date.AddDays(-date.DayOfYear + 1); + while (date.DayOfWeek != dayOfWeek) { - return dates; + date = date.AddDays(1); } - if (expand.Value) + while (date.Year == year) { - // Expand behavior - var hourlyDates = new List(); - for (var i = 0; i < dates.Count; i++) - { - var date = dates[i]; - for (var j = 0; j < pattern.ByHour.Count; j++) - { - var hour = pattern.ByHour[j]; - date = date.AddHours(-date.Hour + hour); - hourlyDates.Add(date); - } - } - return hourlyDates; + days.Add(date); + date = date.AddDays(7); } - // Limit behavior - for (var i = dates.Count - 1; i >= 0; i--) + } + return GetOffsetDates(days, weekDay.Offset); + } + + /// + /// Returns a single-element sublist containing the element of at . + /// Valid offsets are from 1 to the size of the list. If an invalid offset is supplied, all elements from + /// are added to result. + /// + /// The list from which to extract the element. + /// The position of the element to extract. + private List GetOffsetDates(List dates, int offset) + { + if (offset == int.MinValue) + { + return dates; + } + + var offsetDates = new List(); + var size = dates.Count; + if (offset < 0 && offset >= -size) + { + offsetDates.Add(dates[size + offset]); + } + else if (offset > 0 && offset <= size) + { + offsetDates.Add(dates[offset - 1]); + } + return offsetDates; + } + + /// + /// Applies BYHOUR rules specified in this Recur instance to the specified date list. + /// If no BYHOUR rules are specified, the date list is returned unmodified. + /// + /// The list of dates to which the BYHOUR rules will be applied. + /// + /// + /// The modified list of dates after applying the BYHOUR rules. + private List GetHourVariants(List dates, RecurrencePattern pattern, bool? expand) + { + if (expand == null || pattern.ByHour.Count == 0) + { + return dates; + } + + if (expand.Value) + { + // Expand behavior + var hourlyDates = new List(); + for (var i = 0; i < dates.Count; i++) { var date = dates[i]; for (var j = 0; j < pattern.ByHour.Count; j++) { var hour = pattern.ByHour[j]; - if (date.Hour == hour) - { - goto Next; - } + date = date.AddHours(-date.Hour + hour); + hourlyDates.Add(date); } - // Remove unmatched dates - dates.RemoveAt(i); - Next: - ; } - return dates; + return hourlyDates; } - - /// - /// Applies BYMINUTE rules specified in this Recur instance to the specified date list. - /// If no BYMINUTE rules are specified, the date list is returned unmodified. - /// - /// The list of dates to which the BYMINUTE rules will be applied. - /// - /// - /// The modified list of dates after applying the BYMINUTE rules. - private List GetMinuteVariants(List dates, RecurrencePattern pattern, bool? expand) + // Limit behavior + for (var i = dates.Count - 1; i >= 0; i--) { - if (expand == null || pattern.ByMinute.Count == 0) + var date = dates[i]; + for (var j = 0; j < pattern.ByHour.Count; j++) { - return dates; - } - - if (expand.Value) - { - // Expand behavior - var minutelyDates = new List(); - for (var i = 0; i < dates.Count; i++) + var hour = pattern.ByHour[j]; + if (date.Hour == hour) { - var date = dates[i]; - for (var j = 0; j < pattern.ByMinute.Count; j++) - { - var minute = pattern.ByMinute[j]; - date = date.AddMinutes(-date.Minute + minute); - minutelyDates.Add(date); - } + goto Next; } - return minutelyDates; } - // Limit behavior - for (var i = dates.Count - 1; i >= 0; i--) + // Remove unmatched dates + dates.RemoveAt(i); + Next: + ; + } + return dates; + } + + /// + /// Applies BYMINUTE rules specified in this Recur instance to the specified date list. + /// If no BYMINUTE rules are specified, the date list is returned unmodified. + /// + /// The list of dates to which the BYMINUTE rules will be applied. + /// + /// + /// The modified list of dates after applying the BYMINUTE rules. + private List GetMinuteVariants(List dates, RecurrencePattern pattern, bool? expand) + { + if (expand == null || pattern.ByMinute.Count == 0) + { + return dates; + } + + if (expand.Value) + { + // Expand behavior + var minutelyDates = new List(); + for (var i = 0; i < dates.Count; i++) { var date = dates[i]; for (var j = 0; j < pattern.ByMinute.Count; j++) { var minute = pattern.ByMinute[j]; - if (date.Minute == minute) - { - goto Next; - } + date = date.AddMinutes(-date.Minute + minute); + minutelyDates.Add(date); } - // Remove unmatched dates - dates.RemoveAt(i); - Next: - ; } - return dates; + return minutelyDates; } - - /// - /// Applies BYSECOND rules specified in this Recur instance to the specified date list. - /// If no BYSECOND rules are specified, the date list is returned unmodified. - /// - /// The list of dates to which the BYSECOND rules will be applied. - /// - /// - /// The modified list of dates after applying the BYSECOND rules. - private List GetSecondVariants(List dates, RecurrencePattern pattern, bool? expand) + // Limit behavior + for (var i = dates.Count - 1; i >= 0; i--) { - if (expand == null || pattern.BySecond.Count == 0) + var date = dates[i]; + for (var j = 0; j < pattern.ByMinute.Count; j++) { - return dates; - } - - if (expand.Value) - { - // Expand behavior - var secondlyDates = new List(); - for (var i = 0; i < dates.Count; i++) + var minute = pattern.ByMinute[j]; + if (date.Minute == minute) { - var date = dates[i]; - for (var j = 0; j < pattern.BySecond.Count; j++) - { - var second = pattern.BySecond[j]; - date = date.AddSeconds(-date.Second + second); - secondlyDates.Add(date); - } + goto Next; } - return secondlyDates; } - // Limit behavior - for (var i = dates.Count - 1; i >= 0; i--) + // Remove unmatched dates + dates.RemoveAt(i); + Next: + ; + } + return dates; + } + + /// + /// Applies BYSECOND rules specified in this Recur instance to the specified date list. + /// If no BYSECOND rules are specified, the date list is returned unmodified. + /// + /// The list of dates to which the BYSECOND rules will be applied. + /// + /// + /// The modified list of dates after applying the BYSECOND rules. + private List GetSecondVariants(List dates, RecurrencePattern pattern, bool? expand) + { + if (expand == null || pattern.BySecond.Count == 0) + { + return dates; + } + + if (expand.Value) + { + // Expand behavior + var secondlyDates = new List(); + for (var i = 0; i < dates.Count; i++) { var date = dates[i]; for (var j = 0; j < pattern.BySecond.Count; j++) { var second = pattern.BySecond[j]; - if (date.Second == second) - { - goto Next; - } + date = date.AddSeconds(-date.Second + second); + secondlyDates.Add(date); } - // Remove unmatched dates - dates.RemoveAt(i); - Next: - ; } - return dates; + return secondlyDates; } - - private Period CreatePeriod(DateTime dt, IDateTime referenceDate) + // Limit behavior + for (var i = dates.Count - 1; i >= 0; i--) { - // Turn each resulting date/time into an IDateTime and associate it - // with the reference date. - IDateTime newDt = new CalDateTime(dt, referenceDate.TzId); + var date = dates[i]; + for (var j = 0; j < pattern.BySecond.Count; j++) + { + var second = pattern.BySecond[j]; + if (date.Second == second) + { + goto Next; + } + } + // Remove unmatched dates + dates.RemoveAt(i); + Next: + ; + } + return dates; + } - // NOTE: fixes bug #2938007 - hasTime missing - newDt.HasTime = referenceDate.HasTime; + private Period CreatePeriod(DateTime dt, IDateTime referenceDate) + { + // Turn each resulting date/time into an IDateTime and associate it + // with the reference date. + IDateTime newDt = new CalDateTime(dt, referenceDate.TzId); - newDt.AssociateWith(referenceDate); + // NOTE: fixes bug #2938007 - hasTime missing + newDt.HasTime = referenceDate.HasTime; - // Create a period from the new date/time. - return new Period(newDt); - } + newDt.AssociateWith(referenceDate); - /// - /// Evaluate the occurrences of this recurrence pattern. - /// - /// The reference date, i.e. DTSTART. - /// Start (incl.) of the period occurrences are generated for. - /// End (excl.) of the period occurrences are generated for. - /// Whether the referenceDate itself should be returned. Ignored as the reference data MUST equal the first occurrence of an RRULE. - /// - public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + // Create a period from the new date/time. + return new Period(newDt); + } + + /// + /// Evaluate the occurrences of this recurrence pattern. + /// + /// The reference date, i.e. DTSTART. + /// Start (incl.) of the period occurrences are generated for. + /// End (excl.) of the period occurrences are generated for. + /// Whether the referenceDate itself should be returned. Ignored as the reference data MUST equal the first occurrence of an RRULE. + /// + public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + { + if (Pattern.Frequency != FrequencyType.None && Pattern.Frequency < FrequencyType.Daily && !referenceDate.HasTime) { - if (Pattern.Frequency != FrequencyType.None && Pattern.Frequency < FrequencyType.Daily && !referenceDate.HasTime) - { - // This case is not defined by RFC 5545. We handle it by evaluating the rule - // as if referenceDate had a time (i.e. set to midnight). + // This case is not defined by RFC 5545. We handle it by evaluating the rule + // as if referenceDate had a time (i.e. set to midnight). - referenceDate = referenceDate.Copy(); - referenceDate.HasTime = true; - } + referenceDate = referenceDate.Copy(); + referenceDate.HasTime = true; + } - // Create a recurrence pattern suitable for use during evaluation. - var pattern = ProcessRecurrencePattern(referenceDate); + // Create a recurrence pattern suitable for use during evaluation. + var pattern = ProcessRecurrencePattern(referenceDate); - // Enforce evaluation restrictions on the pattern. - EnforceEvaluationRestrictions(pattern); - Periods.Clear(); + // Enforce evaluation restrictions on the pattern. + EnforceEvaluationRestrictions(pattern); + Periods.Clear(); - var periodQuery = GetDates(referenceDate, periodStart, periodEnd, -1, pattern, includeReferenceDateInResults) - .Select(dt => CreatePeriod(dt, referenceDate)); + var periodQuery = GetDates(referenceDate, periodStart, periodEnd, -1, pattern, includeReferenceDateInResults) + .Select(dt => CreatePeriod(dt, referenceDate)); - Periods.UnionWith(periodQuery); + Periods.UnionWith(periodQuery); - return Periods; - } + return Periods; } } \ No newline at end of file diff --git a/Ical.Net/Evaluation/RecurrenceUtil.cs b/Ical.Net/Evaluation/RecurrenceUtil.cs index 6d15c7131..2423389b2 100644 --- a/Ical.Net/Evaluation/RecurrenceUtil.cs +++ b/Ical.Net/Evaluation/RecurrenceUtil.cs @@ -1,98 +1,102 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Collections.Generic; using System.Linq; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Utility; + +namespace Ical.Net.Evaluation; -namespace Ical.Net.Evaluation +internal class RecurrenceUtil { - internal class RecurrenceUtil + public static void ClearEvaluation(IRecurrable recurrable) { - public static void ClearEvaluation(IRecurrable recurrable) - { - var evaluator = recurrable.GetService(typeof(IEvaluator)) as IEvaluator; - evaluator?.Clear(); - } + var evaluator = recurrable.GetService(typeof(IEvaluator)) as IEvaluator; + evaluator?.Clear(); + } - public static HashSet GetOccurrences(IRecurrable recurrable, IDateTime dt, bool includeReferenceDateInResults) => GetOccurrences(recurrable, - new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1)), includeReferenceDateInResults); + public static HashSet GetOccurrences(IRecurrable recurrable, IDateTime dt, bool includeReferenceDateInResults) => GetOccurrences(recurrable, + new CalDateTime(dt.Date), new CalDateTime(dt.Date.AddDays(1).AddSeconds(-1)), includeReferenceDateInResults); - public static HashSet GetOccurrences(IRecurrable recurrable, IDateTime periodStart, IDateTime periodEnd, bool includeReferenceDateInResults) + public static HashSet GetOccurrences(IRecurrable recurrable, IDateTime periodStart, IDateTime periodEnd, bool includeReferenceDateInResults) + { + var evaluator = recurrable.GetService(typeof(IEvaluator)) as IEvaluator; + if (evaluator == null || recurrable.Start == null) { - var evaluator = recurrable.GetService(typeof(IEvaluator)) as IEvaluator; - if (evaluator == null || recurrable.Start == null) - { - return new HashSet(); - } + return new HashSet(); + } - // Ensure the start time is associated with the object being queried - var start = recurrable.Start; - start.AssociatedObject = recurrable as ICalendarObject; + // Ensure the start time is associated with the object being queried + var start = recurrable.Start; + start.AssociatedObject = recurrable as ICalendarObject; - // Change the time zone of periodStart/periodEnd as needed - // so they can be used during the evaluation process. + // Change the time zone of periodStart/periodEnd as needed + // so they can be used during the evaluation process. - periodStart.TzId = start.TzId; - periodEnd.TzId = start.TzId; + periodStart.TzId = start.TzId; + periodEnd.TzId = start.TzId; - var periods = evaluator.Evaluate(start, DateUtil.GetSimpleDateTimeData(periodStart), DateUtil.GetSimpleDateTimeData(periodEnd), - includeReferenceDateInResults); + var periods = evaluator.Evaluate(start, DateUtil.GetSimpleDateTimeData(periodStart), DateUtil.GetSimpleDateTimeData(periodEnd), + includeReferenceDateInResults); - var otherOccurrences = from p in periods - let endTime = p.EndTime ?? p.StartTime - where - (endTime.GreaterThan(periodStart) && p.StartTime.LessThan(periodEnd) || - (periodStart.Equals(periodEnd) && p.StartTime.LessThanOrEqual(periodStart) && endTime.GreaterThan(periodEnd))) || //A period that starts at the same time it ends - (p.StartTime.Equals(endTime) && periodStart.Equals(p.StartTime)) //An event that starts at the same time it ends - select new Occurrence(recurrable, p); + var otherOccurrences = from p in periods + let endTime = p.EndTime ?? p.StartTime + where + (endTime.GreaterThan(periodStart) && p.StartTime.LessThan(periodEnd) || + (periodStart.Equals(periodEnd) && p.StartTime.LessThanOrEqual(periodStart) && endTime.GreaterThan(periodEnd))) || //A period that starts at the same time it ends + (p.StartTime.Equals(endTime) && periodStart.Equals(p.StartTime)) //An event that starts at the same time it ends + select new Occurrence(recurrable, p); - var occurrences = new HashSet(otherOccurrences); - return occurrences; - } + var occurrences = new HashSet(otherOccurrences); + return occurrences; + } - public static bool?[] GetExpandBehaviorList(RecurrencePattern p) + public static bool?[] GetExpandBehaviorList(RecurrencePattern p) + { + // See the table in RFC 5545 Section 3.3.10 (Page 43). + switch (p.Frequency) { - // See the table in RFC 5545 Section 3.3.10 (Page 43). - switch (p.Frequency) + case FrequencyType.Minutely: + return new bool?[] { false, null, false, false, false, false, false, true, false }; + case FrequencyType.Hourly: + return new bool?[] { false, null, false, false, false, false, true, true, false }; + case FrequencyType.Daily: + return new bool?[] { false, null, null, false, false, true, true, true, false }; + case FrequencyType.Weekly: + return new bool?[] { false, null, null, null, true, true, true, true, false }; + case FrequencyType.Monthly: { - case FrequencyType.Minutely: - return new bool?[] { false, null, false, false, false, false, false, true, false }; - case FrequencyType.Hourly: - return new bool?[] { false, null, false, false, false, false, true, true, false }; - case FrequencyType.Daily: - return new bool?[] { false, null, null, false, false, true, true, true, false }; - case FrequencyType.Weekly: - return new bool?[] { false, null, null, null, true, true, true, true, false }; - case FrequencyType.Monthly: - { - var row = new bool?[] { false, null, null, true, true, true, true, true, false }; - - // Limit if BYMONTHDAY is present; otherwise, special expand for MONTHLY. - if (p.ByMonthDay.Count > 0) - { - row[4] = false; - } - - return row; - } - case FrequencyType.Yearly: - { - var row = new bool?[] { true, true, true, true, true, true, true, true, false }; - - // Limit if BYYEARDAY or BYMONTHDAY is present; otherwise, - // special expand for WEEKLY if BYWEEKNO present; otherwise, - // special expand for MONTHLY if BYMONTH present; otherwise, - // special expand for YEARLY. - if (p.ByYearDay.Count > 0 || p.ByMonthDay.Count > 0) - { - row[4] = false; - } - - return row; - } - default: - return new bool?[] { false, null, false, false, false, false, false, false, false }; + var row = new bool?[] { false, null, null, true, true, true, true, true, false }; + + // Limit if BYMONTHDAY is present; otherwise, special expand for MONTHLY. + if (p.ByMonthDay.Count > 0) + { + row[4] = false; + } + + return row; + } + case FrequencyType.Yearly: + { + var row = new bool?[] { true, true, true, true, true, true, true, true, false }; + + // Limit if BYYEARDAY or BYMONTHDAY is present; otherwise, + // special expand for WEEKLY if BYWEEKNO present; otherwise, + // special expand for MONTHLY if BYMONTH present; otherwise, + // special expand for YEARLY. + if (p.ByYearDay.Count > 0 || p.ByMonthDay.Count > 0) + { + row[4] = false; + } + + return row; } + default: + return new bool?[] { false, null, false, false, false, false, false, false, false }; } } } \ No newline at end of file diff --git a/Ical.Net/Evaluation/RecurringEvaluator.cs b/Ical.Net/Evaluation/RecurringEvaluator.cs index 1460f562b..0604346a6 100644 --- a/Ical.Net/Evaluation/RecurringEvaluator.cs +++ b/Ical.Net/Evaluation/RecurringEvaluator.cs @@ -1,165 +1,169 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; + +namespace Ical.Net.Evaluation; -namespace Ical.Net.Evaluation +public class RecurringEvaluator : Evaluator { - public class RecurringEvaluator : Evaluator + protected IRecurrable Recurrable { get; set; } + + public RecurringEvaluator(IRecurrable obj) { - protected IRecurrable Recurrable { get; set; } + Recurrable = obj; - public RecurringEvaluator(IRecurrable obj) + // We're not sure if the object is a calendar object + // or a calendar data type, so we need to assign + // the associated object manually + if (obj is ICalendarObject) { - Recurrable = obj; - - // We're not sure if the object is a calendar object - // or a calendar data type, so we need to assign - // the associated object manually - if (obj is ICalendarObject) - { - AssociatedObject = (ICalendarObject)obj; - } - if (obj is ICalendarDataType) - { - var dt = (ICalendarDataType)obj; - AssociatedObject = dt.AssociatedObject; - } + AssociatedObject = (ICalendarObject) obj; } - - /// - /// Evaluates the RRule component, and adds each specified Period to the Periods collection. - /// - /// - /// The beginning date of the range to evaluate. - /// The end date of the range to evaluate. - /// - protected HashSet EvaluateRRule(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + if (obj is ICalendarDataType) { - if (Recurrable.RecurrenceRules == null || !Recurrable.RecurrenceRules.Any()) - { - return new HashSet(); - } + var dt = (ICalendarDataType) obj; + AssociatedObject = dt.AssociatedObject; + } + } - var periodsQuery = Recurrable.RecurrenceRules.SelectMany(rule => - { - var ruleEvaluator = rule.GetService(typeof(IEvaluator)) as IEvaluator; - if (ruleEvaluator == null) - { - return Enumerable.Empty(); - } - return ruleEvaluator.Evaluate(referenceDate, periodStart, periodEnd, includeReferenceDateInResults); - }); - - var periods = new HashSet(periodsQuery); - - //Only add referenceDate if there are no RecurrenceRules defined - if (includeReferenceDateInResults && (Recurrable.RecurrenceRules == null || !Recurrable.RecurrenceRules.Any())) - { - periods.UnionWith(new[] { new Period(referenceDate) }); - } - return periods; + /// + /// Evaluates the RRule component, and adds each specified Period to the Periods collection. + /// + /// + /// The beginning date of the range to evaluate. + /// The end date of the range to evaluate. + /// + protected HashSet EvaluateRRule(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + { + if (Recurrable.RecurrenceRules == null || !Recurrable.RecurrenceRules.Any()) + { + return new HashSet(); } - /// Evaluates the RDate component, and adds each specified DateTime or Period to the Periods collection. - protected HashSet EvaluateRDate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd) + var periodsQuery = Recurrable.RecurrenceRules.SelectMany(rule => { - if (Recurrable.RecurrenceDates == null || !Recurrable.RecurrenceDates.Any()) + var ruleEvaluator = rule.GetService(typeof(IEvaluator)) as IEvaluator; + if (ruleEvaluator == null) { - return new HashSet(); + return Enumerable.Empty(); } + return ruleEvaluator.Evaluate(referenceDate, periodStart, periodEnd, includeReferenceDateInResults); + }); + + var periods = new HashSet(periodsQuery); - var recurrences = new HashSet(Recurrable.RecurrenceDates.SelectMany(rdate => rdate)); - return recurrences; + //Only add referenceDate if there are no RecurrenceRules defined + if (includeReferenceDateInResults && (Recurrable.RecurrenceRules == null || !Recurrable.RecurrenceRules.Any())) + { + periods.UnionWith(new[] { new Period(referenceDate) }); } + return periods; + } - /// - /// Evaluates the ExRule component, and excludes each specified DateTime from the Periods collection. - /// - /// - /// The beginning date of the range to evaluate. - /// The end date of the range to evaluate. - protected HashSet EvaluateExRule(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd) + /// Evaluates the RDate component, and adds each specified DateTime or Period to the Periods collection. + protected HashSet EvaluateRDate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd) + { + if (Recurrable.RecurrenceDates == null || !Recurrable.RecurrenceDates.Any()) { - if (Recurrable.ExceptionRules == null || !Recurrable.ExceptionRules.Any()) - { - return new HashSet(); - } + return new HashSet(); + } - var exRuleEvaluatorQuery = Recurrable.ExceptionRules.SelectMany(exRule => - { - var exRuleEvaluator = exRule.GetService(typeof(IEvaluator)) as IEvaluator; - if (exRuleEvaluator == null) - { - return Enumerable.Empty(); - } - return exRuleEvaluator.Evaluate(referenceDate, periodStart, periodEnd, false); - }); - - var exRuleExclusions = new HashSet(exRuleEvaluatorQuery); - return exRuleExclusions; + var recurrences = new HashSet(Recurrable.RecurrenceDates.SelectMany(rdate => rdate)); + return recurrences; + } + + /// + /// Evaluates the ExRule component, and excludes each specified DateTime from the Periods collection. + /// + /// + /// The beginning date of the range to evaluate. + /// The end date of the range to evaluate. + protected HashSet EvaluateExRule(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd) + { + if (Recurrable.ExceptionRules == null || !Recurrable.ExceptionRules.Any()) + { + return new HashSet(); } - /// - /// Evaluates the ExDate component, and excludes each specified DateTime or Period from the Periods collection. - /// - /// - /// The beginning date of the range to evaluate. - /// The end date of the range to evaluate. - protected HashSet EvaluateExDate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd) + var exRuleEvaluatorQuery = Recurrable.ExceptionRules.SelectMany(exRule => { - if (Recurrable.ExceptionDates == null || !Recurrable.ExceptionDates.Any()) + var exRuleEvaluator = exRule.GetService(typeof(IEvaluator)) as IEvaluator; + if (exRuleEvaluator == null) { - return new HashSet(); + return Enumerable.Empty(); } + return exRuleEvaluator.Evaluate(referenceDate, periodStart, periodEnd, false); + }); - var exDates = new HashSet(Recurrable.ExceptionDates.SelectMany(exDate => exDate)); - return exDates; - } + var exRuleExclusions = new HashSet(exRuleEvaluatorQuery); + return exRuleExclusions; + } - public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + /// + /// Evaluates the ExDate component, and excludes each specified DateTime or Period from the Periods collection. + /// + /// + /// The beginning date of the range to evaluate. + /// The end date of the range to evaluate. + protected HashSet EvaluateExDate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd) + { + if (Recurrable.ExceptionDates == null || !Recurrable.ExceptionDates.Any()) { - Periods.Clear(); + return new HashSet(); + } - var rruleOccurrences = EvaluateRRule(referenceDate, periodStart, periodEnd, includeReferenceDateInResults); - //Only add referenceDate if there are no RecurrenceRules defined - if (includeReferenceDateInResults && (Recurrable.RecurrenceRules == null || !Recurrable.RecurrenceRules.Any())) - { - rruleOccurrences.UnionWith(new[] { new Period(referenceDate), }); - } + var exDates = new HashSet(Recurrable.ExceptionDates.SelectMany(exDate => exDate)); + return exDates; + } + + public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + { + Periods.Clear(); - var rdateOccurrences = EvaluateRDate(referenceDate, periodStart, periodEnd); + var rruleOccurrences = EvaluateRRule(referenceDate, periodStart, periodEnd, includeReferenceDateInResults); + //Only add referenceDate if there are no RecurrenceRules defined + if (includeReferenceDateInResults && (Recurrable.RecurrenceRules == null || !Recurrable.RecurrenceRules.Any())) + { + rruleOccurrences.UnionWith(new[] { new Period(referenceDate), }); + } - var exRuleExclusions = EvaluateExRule(referenceDate, periodStart, periodEnd); - var exDateExclusions = EvaluateExDate(referenceDate, periodStart, periodEnd); + var rdateOccurrences = EvaluateRDate(referenceDate, periodStart, periodEnd); - //Exclusions trump inclusions - Periods.UnionWith(rruleOccurrences); - Periods.UnionWith(rdateOccurrences); - Periods.ExceptWith(exRuleExclusions); - Periods.ExceptWith(exDateExclusions); + var exRuleExclusions = EvaluateExRule(referenceDate, periodStart, periodEnd); + var exDateExclusions = EvaluateExDate(referenceDate, periodStart, periodEnd); - var dateOverlaps = FindDateOverlaps(exDateExclusions); - Periods.ExceptWith(dateOverlaps); + //Exclusions trump inclusions + Periods.UnionWith(rruleOccurrences); + Periods.UnionWith(rdateOccurrences); + Periods.ExceptWith(exRuleExclusions); + Periods.ExceptWith(exDateExclusions); - if (EvaluationStartBounds == DateTime.MaxValue || EvaluationStartBounds > periodStart) - { - EvaluationStartBounds = periodStart; - } - if (EvaluationEndBounds == DateTime.MinValue || EvaluationEndBounds < periodEnd) - { - EvaluationEndBounds = periodEnd; - } + var dateOverlaps = FindDateOverlaps(exDateExclusions); + Periods.ExceptWith(dateOverlaps); - return Periods; + if (EvaluationStartBounds == DateTime.MaxValue || EvaluationStartBounds > periodStart) + { + EvaluationStartBounds = periodStart; } - - private HashSet FindDateOverlaps(HashSet dates) + if (EvaluationEndBounds == DateTime.MinValue || EvaluationEndBounds < periodEnd) { - var datesWithoutTimes = new HashSet(dates.Where(d => d.StartTime.Value.TimeOfDay == TimeSpan.Zero).Select(d => d.StartTime.Value)); - var overlaps = new HashSet(Periods.Where(p => datesWithoutTimes.Contains(p.StartTime.Value.Date))); - return overlaps; + EvaluationEndBounds = periodEnd; } + + return Periods; + } + + private HashSet FindDateOverlaps(HashSet dates) + { + var datesWithoutTimes = new HashSet(dates.Where(d => d.StartTime.Value.TimeOfDay == TimeSpan.Zero).Select(d => d.StartTime.Value)); + var overlaps = new HashSet(Periods.Where(p => datesWithoutTimes.Contains(p.StartTime.Value.Date))); + return overlaps; } -} +} \ No newline at end of file diff --git a/Ical.Net/Evaluation/TimeZoneEvaluator.cs b/Ical.Net/Evaluation/TimeZoneEvaluator.cs index 2bd5e179b..bf117039e 100644 --- a/Ical.Net/Evaluation/TimeZoneEvaluator.cs +++ b/Ical.Net/Evaluation/TimeZoneEvaluator.cs @@ -1,137 +1,140 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Diagnostics; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; -namespace Ical.Net.Evaluation +namespace Ical.Net.Evaluation; + +public class TimeZoneEvaluator : Evaluator { - public class TimeZoneEvaluator : Evaluator - { - protected VTimeZone TimeZone { get; set; } + protected VTimeZone TimeZone { get; set; } - private List _occurrences; - public virtual List Occurrences - { - get => _occurrences; - set => _occurrences = value; - } + private List _occurrences; + public virtual List Occurrences + { + get => _occurrences; + set => _occurrences = value; + } - public TimeZoneEvaluator(VTimeZone tz) - { - TimeZone = tz; - _occurrences = new List(); - } + public TimeZoneEvaluator(VTimeZone tz) + { + TimeZone = tz; + _occurrences = new List(); + } - private void ProcessOccurrences(IDateTime referenceDate) - { - // Sort the occurrences by start time - _occurrences.Sort( - delegate (Occurrence o1, Occurrence o2) + private void ProcessOccurrences(IDateTime referenceDate) + { + // Sort the occurrences by start time + _occurrences.Sort( + delegate (Occurrence o1, Occurrence o2) { + if (o1.Period?.StartTime == null) { - if (o1.Period?.StartTime == null) - { - return -1; - } - return o2.Period?.StartTime == null - ? 1 - : o1.Period.StartTime.CompareTo(o2.Period.StartTime); + return -1; } - ); - - for (var i = 0; i < _occurrences.Count; i++) - { - var curr = _occurrences[i]; - var next = i < _occurrences.Count - 1 ? _occurrences[i + 1] : null; - - // Determine end times for our periods, overwriting previously calculated end times. - // This is important because we don't want to overcalculate our time zone information, - // but simply calculate enough to be accurate. When date/time ranges that are out of - // normal working bounds are encountered, then occurrences are processed again, and - // new end times are determined. - curr.Period.EndTime = next != null - ? next.Period.StartTime.AddTicks(-1) - : ConvertToIDateTime(EvaluationEndBounds, referenceDate); + return o2.Period?.StartTime == null + ? 1 + : o1.Period.StartTime.CompareTo(o2.Period.StartTime); } - } + ); - public override void Clear() + for (var i = 0; i < _occurrences.Count; i++) { - base.Clear(); - _occurrences.Clear(); + var curr = _occurrences[i]; + var next = i < _occurrences.Count - 1 ? _occurrences[i + 1] : null; + + // Determine end times for our periods, overwriting previously calculated end times. + // This is important because we don't want to overcalculate our time zone information, + // but simply calculate enough to be accurate. When date/time ranges that are out of + // normal working bounds are encountered, then occurrences are processed again, and + // new end times are determined. + curr.Period.EndTime = next != null + ? next.Period.StartTime.AddTicks(-1) + : ConvertToIDateTime(EvaluationEndBounds, referenceDate); } + } - public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) - { - // Ensure the reference date is associated with the time zone - if (referenceDate.AssociatedObject == null) - referenceDate.AssociatedObject = TimeZone; + public override void Clear() + { + base.Clear(); + _occurrences.Clear(); + } + + public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + { + // Ensure the reference date is associated with the time zone + if (referenceDate.AssociatedObject == null) + referenceDate.AssociatedObject = TimeZone; - var infos = new List(TimeZone.TimeZoneInfos); + var infos = new List(TimeZone.TimeZoneInfos); - // Evaluate extra time periods, without re-evaluating ones that were already evaluated - if ((EvaluationStartBounds == DateTime.MaxValue && EvaluationEndBounds == DateTime.MinValue) - || periodEnd.Equals(EvaluationStartBounds) - || periodStart.Equals(EvaluationEndBounds)) + // Evaluate extra time periods, without re-evaluating ones that were already evaluated + if ((EvaluationStartBounds == DateTime.MaxValue && EvaluationEndBounds == DateTime.MinValue) + || periodEnd.Equals(EvaluationStartBounds) + || periodStart.Equals(EvaluationEndBounds)) + { + foreach (var curr in infos) { - foreach (var curr in infos) + var evaluator = curr.GetService(typeof(IEvaluator)) as IEvaluator; + Debug.Assert(curr.Start != null, "TimeZoneInfo.Start must not be null."); + Debug.Assert(curr.Start.TzId == null, "TimeZoneInfo.Start must not have a time zone reference."); + Debug.Assert(evaluator != null, "TimeZoneInfo.GetService(typeof(IEvaluator)) must not be null."); + + // Time zones must include an effective start date/time + // and must provide an evaluator. + if (evaluator == null) { - var evaluator = curr.GetService(typeof(IEvaluator)) as IEvaluator; - Debug.Assert(curr.Start != null, "TimeZoneInfo.Start must not be null."); - Debug.Assert(curr.Start.TzId == null, "TimeZoneInfo.Start must not have a time zone reference."); - Debug.Assert(evaluator != null, "TimeZoneInfo.GetService(typeof(IEvaluator)) must not be null."); - - // Time zones must include an effective start date/time - // and must provide an evaluator. - if (evaluator == null) - { - continue; - } - - // Set the start bounds - if (EvaluationStartBounds > periodStart) - { - EvaluationStartBounds = periodStart; - } + continue; + } - // FIXME: 5 years is an arbitrary number, to eliminate the need - // to recalculate time zone information as much as possible. - var offsetEnd = periodEnd.AddYears(5); + // Set the start bounds + if (EvaluationStartBounds > periodStart) + { + EvaluationStartBounds = periodStart; + } - // Determine the UTC occurrences of the Time Zone observances - var periods = evaluator.Evaluate( - referenceDate, - periodStart, - offsetEnd, - includeReferenceDateInResults); + // FIXME: 5 years is an arbitrary number, to eliminate the need + // to recalculate time zone information as much as possible. + var offsetEnd = periodEnd.AddYears(5); - foreach (var period in periods) - { - Periods.Add(period); - var o = new Occurrence(curr, period); - if (!_occurrences.Contains(o)) - { - _occurrences.Add(o); - } - } + // Determine the UTC occurrences of the Time Zone observances + var periods = evaluator.Evaluate( + referenceDate, + periodStart, + offsetEnd, + includeReferenceDateInResults); - if (EvaluationEndBounds == DateTime.MinValue || EvaluationEndBounds < offsetEnd) + foreach (var period in periods) + { + Periods.Add(period); + var o = new Occurrence(curr, period); + if (!_occurrences.Contains(o)) { - EvaluationEndBounds = offsetEnd; + _occurrences.Add(o); } } - ProcessOccurrences(referenceDate); - } - else - { - if (EvaluationEndBounds != DateTime.MinValue && periodEnd > EvaluationEndBounds) + if (EvaluationEndBounds == DateTime.MinValue || EvaluationEndBounds < offsetEnd) { - Evaluate(referenceDate, EvaluationEndBounds, periodEnd, includeReferenceDateInResults); + EvaluationEndBounds = offsetEnd; } } - return Periods; + ProcessOccurrences(referenceDate); } + else + { + if (EvaluationEndBounds != DateTime.MinValue && periodEnd > EvaluationEndBounds) + { + Evaluate(referenceDate, EvaluationEndBounds, periodEnd, includeReferenceDateInResults); + } + } + + return Periods; } } \ No newline at end of file diff --git a/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs b/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs index 9a227f4e2..651c0f7a0 100644 --- a/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs +++ b/Ical.Net/Evaluation/TimeZoneInfoEvaluator.cs @@ -1,32 +1,36 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; -namespace Ical.Net.Evaluation +namespace Ical.Net.Evaluation; + +public class TimeZoneInfoEvaluator : RecurringEvaluator { - public class TimeZoneInfoEvaluator : RecurringEvaluator + protected VTimeZoneInfo TimeZoneInfo { - protected VTimeZoneInfo TimeZoneInfo - { - get => Recurrable as VTimeZoneInfo; - set => Recurrable = value; - } + get => Recurrable as VTimeZoneInfo; + set => Recurrable = value; + } - public TimeZoneInfoEvaluator(IRecurrable tzi) : base(tzi) { } + public TimeZoneInfoEvaluator(IRecurrable tzi) : base(tzi) { } - public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + { + // Time zones must include an effective start date/time + // and must provide an evaluator. + if (TimeZoneInfo == null) { - // Time zones must include an effective start date/time - // and must provide an evaluator. - if (TimeZoneInfo == null) - { - return new HashSet(); - } - - // Always include the reference date in the results - var periods = base.Evaluate(referenceDate, periodStart, periodEnd, true); - return periods; + return new HashSet(); } + + // Always include the reference date in the results + var periods = base.Evaluate(referenceDate, periodStart, periodEnd, true); + return periods; } } \ No newline at end of file diff --git a/Ical.Net/Evaluation/TodoEvaluator.cs b/Ical.Net/Evaluation/TodoEvaluator.cs index 871780a9e..db63d2a8b 100644 --- a/Ical.Net/Evaluation/TodoEvaluator.cs +++ b/Ical.Net/Evaluation/TodoEvaluator.cs @@ -1,103 +1,107 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; +using Ical.Net.Utility; -namespace Ical.Net.Evaluation +namespace Ical.Net.Evaluation; + +public class TodoEvaluator : RecurringEvaluator { - public class TodoEvaluator : RecurringEvaluator - { - protected Todo Todo => Recurrable as Todo; + protected Todo Todo => Recurrable as Todo; - public TodoEvaluator(Todo todo) : base(todo) { } + public TodoEvaluator(Todo todo) : base(todo) { } - public void EvaluateToPreviousOccurrence(IDateTime completedDate, IDateTime currDt) - { - var beginningDate = completedDate.Copy(); + public void EvaluateToPreviousOccurrence(IDateTime completedDate, IDateTime currDt) + { + var beginningDate = completedDate.Copy(); - if (Todo.RecurrenceRules != null) + if (Todo.RecurrenceRules != null) + { + foreach (var rrule in Todo.RecurrenceRules) { - foreach (var rrule in Todo.RecurrenceRules) - { - DetermineStartingRecurrence(rrule, ref beginningDate); - } + DetermineStartingRecurrence(rrule, ref beginningDate); } - if (Todo.RecurrenceDates != null) + } + if (Todo.RecurrenceDates != null) + { + foreach (var rdate in Todo.RecurrenceDates) { - foreach (var rdate in Todo.RecurrenceDates) - { - DetermineStartingRecurrence(rdate, ref beginningDate); - } + DetermineStartingRecurrence(rdate, ref beginningDate); } - if (Todo.ExceptionRules != null) + } + if (Todo.ExceptionRules != null) + { + foreach (var exrule in Todo.ExceptionRules) { - foreach (var exrule in Todo.ExceptionRules) - { - DetermineStartingRecurrence(exrule, ref beginningDate); - } + DetermineStartingRecurrence(exrule, ref beginningDate); } - if (Todo.ExceptionDates != null) + } + if (Todo.ExceptionDates != null) + { + foreach (var exdate in Todo.ExceptionDates) { - foreach (var exdate in Todo.ExceptionDates) - { - DetermineStartingRecurrence(exdate, ref beginningDate); - } + DetermineStartingRecurrence(exdate, ref beginningDate); } - - Evaluate(Todo.Start, DateUtil.GetSimpleDateTimeData(beginningDate), DateUtil.GetSimpleDateTimeData(currDt).AddTicks(1), true); } - public void DetermineStartingRecurrence(PeriodList rdate, ref IDateTime referenceDateTime) + Evaluate(Todo.Start, DateUtil.GetSimpleDateTimeData(beginningDate), DateUtil.GetSimpleDateTimeData(currDt).AddTicks(1), true); + } + + public void DetermineStartingRecurrence(PeriodList rdate, ref IDateTime referenceDateTime) + { + var evaluator = rdate.GetService(); + + var dt2 = referenceDateTime; + foreach (var p in evaluator.Periods.Where(p => p.StartTime.LessThan(dt2))) { - var evaluator = rdate.GetService(); + referenceDateTime = p.StartTime; + } + } - var dt2 = referenceDateTime; - foreach (var p in evaluator.Periods.Where(p => p.StartTime.LessThan(dt2))) - { - referenceDateTime = p.StartTime; - } + public void DetermineStartingRecurrence(RecurrencePattern recur, ref IDateTime referenceDateTime) + { + if (recur.Count != int.MinValue) + { + referenceDateTime = Todo.Start.Copy(); } + else + { + var dtVal = referenceDateTime.Value; + IncrementDate(ref dtVal, recur, -recur.Interval); + referenceDateTime.Value = dtVal; + } + } - public void DetermineStartingRecurrence(RecurrencePattern recur, ref IDateTime referenceDateTime) + public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + { + // TODO items can only recur if a start date is specified + if (Todo.Start == null) { - if (recur.Count != int.MinValue) - { - referenceDateTime = Todo.Start.Copy(); - } - else - { - var dtVal = referenceDateTime.Value; - IncrementDate(ref dtVal, recur, -recur.Interval); - referenceDateTime.Value = dtVal; - } + return new HashSet(); } - public override HashSet Evaluate(IDateTime referenceDate, DateTime periodStart, DateTime periodEnd, bool includeReferenceDateInResults) + base.Evaluate(referenceDate, periodStart, periodEnd, includeReferenceDateInResults); + + // Ensure each period has a duration + foreach (var period in Periods.Where(period => period.EndTime == null)) { - // TODO items can only recur if a start date is specified - if (Todo.Start == null) + period.Duration = Todo.Duration; + if (period.Duration != default) { - return new HashSet(); + period.EndTime = period.StartTime.Add(Todo.Duration); } - - base.Evaluate(referenceDate, periodStart, periodEnd, includeReferenceDateInResults); - - // Ensure each period has a duration - foreach (var period in Periods.Where(period => period.EndTime == null)) + else { period.Duration = Todo.Duration; - if (period.Duration != default) - { - period.EndTime = period.StartTime.Add(Todo.Duration); - } - else - { - period.Duration = Todo.Duration; - } } - return Periods; } + return Periods; } } \ No newline at end of file diff --git a/Ical.Net/ICalendarObject.cs b/Ical.Net/ICalendarObject.cs index 91b9ece8a..b82686169 100644 --- a/Ical.Net/ICalendarObject.cs +++ b/Ical.Net/ICalendarObject.cs @@ -1,42 +1,46 @@ -using Ical.Net.Collections; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net +using Ical.Net.Collections; + +namespace Ical.Net; + +public interface ICalendarObject : IGroupedObject, ILoadable, ICopyable, IServiceProvider { - public interface ICalendarObject : IGroupedObject, ILoadable, ICopyable, IServiceProvider - { - /// - /// The name of the calendar object. - /// Every calendar object can be assigned - /// a name. - /// - string Name { get; set; } - - /// - /// Returns the parent of this object. - /// - ICalendarObject Parent { get; set; } - - /// - /// Returns a collection of children of this object. - /// - ICalendarObjectList Children { get; } - - /// - /// Returns the iCalendar that this object - /// is associated with. - /// - Calendar Calendar { get; } - - /// - /// Returns the line number where this calendar - /// object was found during parsing. - /// - int Line { get; set; } - - /// - /// Returns the column number where this calendar - /// object was found during parsing. - /// - int Column { get; set; } - } + /// + /// The name of the calendar object. + /// Every calendar object can be assigned + /// a name. + /// + string Name { get; set; } + + /// + /// Returns the parent of this object. + /// + ICalendarObject Parent { get; set; } + + /// + /// Returns a collection of children of this object. + /// + ICalendarObjectList Children { get; } + + /// + /// Returns the iCalendar that this object + /// is associated with. + /// + Calendar Calendar { get; } + + /// + /// Returns the line number where this calendar + /// object was found during parsing. + /// + int Line { get; set; } + + /// + /// Returns the column number where this calendar + /// object was found during parsing. + /// + int Column { get; set; } } \ No newline at end of file diff --git a/Ical.Net/ICalendarObjectList.cs b/Ical.Net/ICalendarObjectList.cs index 5ccedc323..26f775a5c 100644 --- a/Ical.Net/ICalendarObjectList.cs +++ b/Ical.Net/ICalendarObjectList.cs @@ -1,10 +1,14 @@ -using Ical.Net.Collections; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net +using Ical.Net.Collections; + +namespace Ical.Net; + +public interface ICalendarObjectList : + IGroupedCollection where TType : class, ICalendarObject { - public interface ICalendarObjectList : - IGroupedCollection where TType : class, ICalendarObject - { - TType this[int index] { get; } - } + TType this[int index] { get; } } \ No newline at end of file diff --git a/Ical.Net/ICalendarProperty.cs b/Ical.Net/ICalendarProperty.cs index 804515ecb..968962d6f 100644 --- a/Ical.Net/ICalendarProperty.cs +++ b/Ical.Net/ICalendarProperty.cs @@ -1,10 +1,14 @@ -using Ical.Net.Collections.Interfaces; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using Ical.Net.Collections.Interfaces; using Ical.Net.DataTypes; -namespace Ical.Net +namespace Ical.Net; + +public interface ICalendarProperty : ICalendarParameterCollectionContainer, ICalendarObject, IValueObject { - public interface ICalendarProperty : ICalendarParameterCollectionContainer, ICalendarObject, IValueObject - { - object Value { get; set; } - } + object Value { get; set; } } \ No newline at end of file diff --git a/Ical.Net/ICalendarPropertyListContainer.cs b/Ical.Net/ICalendarPropertyListContainer.cs index c3d2937cc..cfa4e6f15 100644 --- a/Ical.Net/ICalendarPropertyListContainer.cs +++ b/Ical.Net/ICalendarPropertyListContainer.cs @@ -1,7 +1,11 @@ -namespace Ical.Net +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +namespace Ical.Net; + +public interface ICalendarPropertyListContainer : ICalendarObject { - public interface ICalendarPropertyListContainer : ICalendarObject - { - CalendarPropertyList Properties { get; } - } + CalendarPropertyList Properties { get; } } \ No newline at end of file diff --git a/Ical.Net/ICopyable.cs b/Ical.Net/ICopyable.cs index e20c7d55a..f93dc3661 100644 --- a/Ical.Net/ICopyable.cs +++ b/Ical.Net/ICopyable.cs @@ -1,21 +1,25 @@ -namespace Ical.Net +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +namespace Ical.Net; + +public interface ICopyable { - public interface ICopyable - { - /// - /// (Deep) copies all relevant members from - /// the source object to the current one. - /// - /// If an object cannot use the implementation of a base class, - /// it must override this method and implement the copy logic itself. - /// - void CopyFrom(ICopyable obj); + /// + /// (Deep) copies all relevant members from + /// the source object to the current one. + /// + /// If an object cannot use the implementation of a base class, + /// it must override this method and implement the copy logic itself. + /// + void CopyFrom(ICopyable obj); - /// - /// Returns a deep copy of the current object, mostly by using the method - /// of the object when it is overridden, otherwise is used the implementation of the base class. - /// This is necessary when working with mutable reference types. - /// - T Copy(); - } + /// + /// Returns a deep copy of the current object, mostly by using the method + /// of the object when it is overridden, otherwise is used the implementation of the base class. + /// This is necessary when working with mutable reference types. + /// + T Copy(); } \ No newline at end of file diff --git a/Ical.Net/IGetFreeBusy.cs b/Ical.Net/IGetFreeBusy.cs index df45840c3..1d4fc711d 100644 --- a/Ical.Net/IGetFreeBusy.cs +++ b/Ical.Net/IGetFreeBusy.cs @@ -1,13 +1,17 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Collections.Generic; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; + +namespace Ical.Net; -namespace Ical.Net +public interface IGetFreeBusy { - public interface IGetFreeBusy - { - FreeBusy GetFreeBusy(FreeBusy freeBusyRequest); - FreeBusy GetFreeBusy(IDateTime fromInclusive, IDateTime toExclusive); - FreeBusy GetFreeBusy(Organizer organizer, IEnumerable contacts, IDateTime fromInclusive, IDateTime toExclusive); - } + FreeBusy GetFreeBusy(FreeBusy freeBusyRequest); + FreeBusy GetFreeBusy(IDateTime fromInclusive, IDateTime toExclusive); + FreeBusy GetFreeBusy(Organizer organizer, IEnumerable contacts, IDateTime fromInclusive, IDateTime toExclusive); } \ No newline at end of file diff --git a/Ical.Net/IGetOccurrences.cs b/Ical.Net/IGetOccurrences.cs index 5694298b3..9207612fb 100644 --- a/Ical.Net/IGetOccurrences.cs +++ b/Ical.Net/IGetOccurrences.cs @@ -1,73 +1,77 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; -namespace Ical.Net +namespace Ical.Net; + +public interface IGetOccurrences { - public interface IGetOccurrences - { - /// - /// Clears a previous evaluation, usually because one of the - /// key elements used for evaluation has changed - /// (Start, End, Duration, recurrence rules, exceptions, etc.). - /// - void ClearEvaluation(); + /// + /// Clears a previous evaluation, usually because one of the + /// key elements used for evaluation has changed + /// (Start, End, Duration, recurrence rules, exceptions, etc.). + /// + void ClearEvaluation(); - /// - /// Returns all occurrences of this component that start on the date provided. - /// All components starting between 12:00:00AM and 11:59:59 PM will be - /// returned. - /// - /// This will first Evaluate() the date range required in order to - /// determine the occurrences for the date provided, and then return - /// the occurrences. - /// - /// - /// The date for which to return occurrences. - /// A list of Periods representing the occurrences of this object. - HashSet GetOccurrences(IDateTime dt); + /// + /// Returns all occurrences of this component that start on the date provided. + /// All components starting between 12:00:00AM and 11:59:59 PM will be + /// returned. + /// + /// This will first Evaluate() the date range required in order to + /// determine the occurrences for the date provided, and then return + /// the occurrences. + /// + /// + /// The date for which to return occurrences. + /// A list of Periods representing the occurrences of this object. + HashSet GetOccurrences(IDateTime dt); - HashSet GetOccurrences(DateTime dt); + HashSet GetOccurrences(DateTime dt); - /// - /// Returns all occurrences of this component that overlap with the date range provided. - /// All components that overlap with the time range between and will be returned. - /// - /// The starting date range - /// The ending date range - HashSet GetOccurrences(IDateTime startTime, IDateTime endTime); + /// + /// Returns all occurrences of this component that overlap with the date range provided. + /// All components that overlap with the time range between and will be returned. + /// + /// The starting date range + /// The ending date range + HashSet GetOccurrences(IDateTime startTime, IDateTime endTime); - HashSet GetOccurrences(DateTime startTime, DateTime endTime); - } + HashSet GetOccurrences(DateTime startTime, DateTime endTime); +} - public interface IGetOccurrencesTyped : IGetOccurrences - { - /// - /// Returns all occurrences of components of type T that start on the date provided. - /// All components starting between 12:00:00AM and 11:59:59 PM will be - /// returned. - /// - /// This will first Evaluate() the date range required in order to - /// determine the occurrences for the date provided, and then return - /// the occurrences. - /// - /// - /// The date for which to return occurrences. - /// A list of Periods representing the occurrences of this object. - HashSet GetOccurrences(IDateTime dt) where T : IRecurringComponent; +public interface IGetOccurrencesTyped : IGetOccurrences +{ + /// + /// Returns all occurrences of components of type T that start on the date provided. + /// All components starting between 12:00:00AM and 11:59:59 PM will be + /// returned. + /// + /// This will first Evaluate() the date range required in order to + /// determine the occurrences for the date provided, and then return + /// the occurrences. + /// + /// + /// The date for which to return occurrences. + /// A list of Periods representing the occurrences of this object. + HashSet GetOccurrences(IDateTime dt) where T : IRecurringComponent; - HashSet GetOccurrences(DateTime dt) where T : IRecurringComponent; + HashSet GetOccurrences(DateTime dt) where T : IRecurringComponent; - /// - /// Returns all occurrences of components of type T that start within the date range provided. - /// All components occurring between and - /// will be returned. - /// - /// The starting date range - /// The ending date range - HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) where T : IRecurringComponent; + /// + /// Returns all occurrences of components of type T that start within the date range provided. + /// All components occurring between and + /// will be returned. + /// + /// The starting date range + /// The ending date range + HashSet GetOccurrences(IDateTime startTime, IDateTime endTime) where T : IRecurringComponent; - HashSet GetOccurrences(DateTime startTime, DateTime endTime) where T : IRecurringComponent; - } + HashSet GetOccurrences(DateTime startTime, DateTime endTime) where T : IRecurringComponent; } \ No newline at end of file diff --git a/Ical.Net/ILoadable.cs b/Ical.Net/ILoadable.cs index c238d584e..37b90ac2f 100644 --- a/Ical.Net/ILoadable.cs +++ b/Ical.Net/ILoadable.cs @@ -1,22 +1,26 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net +using System; + +namespace Ical.Net; + +public interface ILoadable { - public interface ILoadable - { - /// - /// Gets whether or not the object has been loaded. - /// - bool IsLoaded { get; } + /// + /// Gets whether or not the object has been loaded. + /// + bool IsLoaded { get; } - /// - /// An event that fires when the object has been loaded. - /// - event EventHandler Loaded; + /// + /// An event that fires when the object has been loaded. + /// + event EventHandler Loaded; - /// - /// Fires the Loaded event. - /// - void OnLoaded(); - } + /// + /// Fires the Loaded event. + /// + void OnLoaded(); } \ No newline at end of file diff --git a/Ical.Net/IMergeable.cs b/Ical.Net/IMergeable.cs index 6ace17705..61aab5beb 100644 --- a/Ical.Net/IMergeable.cs +++ b/Ical.Net/IMergeable.cs @@ -1,10 +1,14 @@ -namespace Ical.Net +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +namespace Ical.Net; + +public interface IMergeable { - public interface IMergeable - { - /// - /// Merges this object with another. - /// - void MergeWith(IMergeable obj); - } + /// + /// Merges this object with another. + /// + void MergeWith(IMergeable obj); } \ No newline at end of file diff --git a/Ical.Net/IParameterCollection.cs b/Ical.Net/IParameterCollection.cs index f5aaff105..f23edc942 100644 --- a/Ical.Net/IParameterCollection.cs +++ b/Ical.Net/IParameterCollection.cs @@ -1,15 +1,19 @@ -using Ical.Net.Collections; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Collections.Generic; +using Ical.Net.Collections; + +namespace Ical.Net; -namespace Ical.Net +public interface IParameterCollection : IGroupedList { - public interface IParameterCollection : IGroupedList - { - void SetParent(ICalendarObject parent); - void Add(string name, string value); - string Get(string name); - IList GetMany(string name); - void Set(string name, string value); - void Set(string name, IEnumerable values); - } + void SetParent(ICalendarObject parent); + void Add(string name, string value); + string Get(string name); + IList GetMany(string name); + void Set(string name, string value); + void Set(string name, IEnumerable values); } \ No newline at end of file diff --git a/Ical.Net/IServiceProvider.cs b/Ical.Net/IServiceProvider.cs index 26793921b..03ee1db78 100644 --- a/Ical.Net/IServiceProvider.cs +++ b/Ical.Net/IServiceProvider.cs @@ -1,16 +1,20 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net +using System; + +namespace Ical.Net; + +public interface IServiceProvider { - public interface IServiceProvider - { - object GetService(string name); - object GetService(Type type); - T GetService(); - T GetService(string name); - void SetService(string name, object obj); - void SetService(object obj); - void RemoveService(Type type); - void RemoveService(string name); - } + object GetService(string name); + object GetService(Type type); + T GetService(); + T GetService(string name); + void SetService(string name, object obj); + void SetService(object obj); + void RemoveService(Type type); + void RemoveService(string name); } \ No newline at end of file diff --git a/Ical.Net/ParameterList.cs b/Ical.Net/ParameterList.cs index c7b8112bc..b9afb90d3 100644 --- a/Ical.Net/ParameterList.cs +++ b/Ical.Net/ParameterList.cs @@ -1,25 +1,29 @@ -using Ical.Net.Collections; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Collections.Generic; +using Ical.Net.Collections; + +namespace Ical.Net; -namespace Ical.Net +public class ParameterList : GroupedValueList, IParameterCollection { - public class ParameterList : GroupedValueList, IParameterCollection + public virtual void SetParent(ICalendarObject parent) { - public virtual void SetParent(ICalendarObject parent) + foreach (var parameter in this) { - foreach (var parameter in this) - { - parameter.Parent = parent; - } + parameter.Parent = parent; } + } - public virtual void Add(string name, string value) - { - Add(new CalendarParameter(name, value)); - } + public virtual void Add(string name, string value) + { + Add(new CalendarParameter(name, value)); + } - public virtual string Get(string name) => Get(name); + public virtual string Get(string name) => Get(name); - public virtual IList GetMany(string name) => GetMany(name); - } + public virtual IList GetMany(string name) => GetMany(name); } \ No newline at end of file diff --git a/Ical.Net/Proxies/CalendarObjectListProxy.cs b/Ical.Net/Proxies/CalendarObjectListProxy.cs index 7e89ead85..be7e35f69 100644 --- a/Ical.Net/Proxies/CalendarObjectListProxy.cs +++ b/Ical.Net/Proxies/CalendarObjectListProxy.cs @@ -1,14 +1,18 @@ -using Ical.Net.Collections; -using Ical.Net.Collections.Proxies; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Linq; +using Ical.Net.Collections; +using Ical.Net.Collections.Proxies; + +namespace Ical.Net.Proxies; -namespace Ical.Net.Proxies +public class CalendarObjectListProxy : GroupedCollectionProxy, ICalendarObjectList + where TType : class, ICalendarObject { - public class CalendarObjectListProxy : GroupedCollectionProxy, ICalendarObjectList - where TType : class, ICalendarObject - { - public CalendarObjectListProxy(IGroupedCollection list) : base(list) { } + public CalendarObjectListProxy(IGroupedCollection list) : base(list) { } - public virtual TType this[int index] => this.Skip(index).FirstOrDefault(); - } + public virtual TType this[int index] => this.Skip(index).FirstOrDefault(); } \ No newline at end of file diff --git a/Ical.Net/Proxies/IUniqueComponentList.cs b/Ical.Net/Proxies/IUniqueComponentList.cs index 8962daa12..5b9ed832a 100644 --- a/Ical.Net/Proxies/IUniqueComponentList.cs +++ b/Ical.Net/Proxies/IUniqueComponentList.cs @@ -1,12 +1,16 @@ -using Ical.Net.CalendarComponents; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System.Collections.Generic; +using Ical.Net.CalendarComponents; + +namespace Ical.Net.Proxies; -namespace Ical.Net.Proxies +public interface IUniqueComponentList : + ICalendarObjectList where TComponentType : class, IUniqueComponent { - public interface IUniqueComponentList : - ICalendarObjectList where TComponentType : class, IUniqueComponent - { - TComponentType this[string uid] { get; set; } - void AddRange(IEnumerable collection); - } + TComponentType this[string uid] { get; set; } + void AddRange(IEnumerable collection); } \ No newline at end of file diff --git a/Ical.Net/Proxies/ParameterCollectionProxy.cs b/Ical.Net/Proxies/ParameterCollectionProxy.cs index 573335c04..5bb553fea 100644 --- a/Ical.Net/Proxies/ParameterCollectionProxy.cs +++ b/Ical.Net/Proxies/ParameterCollectionProxy.cs @@ -1,78 +1,82 @@ -using Ical.Net.Collections; -using Ical.Net.Collections.Proxies; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using Ical.Net.Collections; +using Ical.Net.Collections.Proxies; + +namespace Ical.Net.Proxies; -namespace Ical.Net.Proxies +public class ParameterCollectionProxy : GroupedCollectionProxy, IParameterCollection { - public class ParameterCollectionProxy : GroupedCollectionProxy, IParameterCollection - { - protected GroupedValueList Parameters - => RealObject as GroupedValueList; + protected GroupedValueList Parameters + => RealObject as GroupedValueList; - public ParameterCollectionProxy(IGroupedList realObject) : base(realObject) { } + public ParameterCollectionProxy(IGroupedList realObject) : base(realObject) { } - public virtual void SetParent(ICalendarObject parent) + public virtual void SetParent(ICalendarObject parent) + { + foreach (var parameter in this) { - foreach (var parameter in this) - { - parameter.Parent = parent; - } + parameter.Parent = parent; } + } + + public virtual void Add(string name, string value) + { + RealObject.Add(new CalendarParameter(name, value)); + } + + public virtual string Get(string name) + { + var parameter = RealObject.FirstOrDefault(o => string.Equals(o.Name, name, StringComparison.Ordinal)); + + return parameter?.Value; + } + + public virtual IList GetMany(string name) => new GroupedValueListProxy(Parameters, name); + + public virtual void Set(string name, string value) + { + var parameter = RealObject.FirstOrDefault(o => string.Equals(o.Name, name, StringComparison.Ordinal)); - public virtual void Add(string name, string value) + if (parameter == null) { RealObject.Add(new CalendarParameter(name, value)); } - - public virtual string Get(string name) + else { - var parameter = RealObject.FirstOrDefault(o => string.Equals(o.Name, name, StringComparison.Ordinal)); - - return parameter?.Value; + parameter.SetValue(value); } + } - public virtual IList GetMany(string name) => new GroupedValueListProxy(Parameters, name); + public virtual void Set(string name, IEnumerable values) + { + var parameter = RealObject.FirstOrDefault(o => string.Equals(o.Name, name, StringComparison.Ordinal)); - public virtual void Set(string name, string value) + if (parameter == null) { - var parameter = RealObject.FirstOrDefault(o => string.Equals(o.Name, name, StringComparison.Ordinal)); - - if (parameter == null) - { - RealObject.Add(new CalendarParameter(name, value)); - } - else - { - parameter.SetValue(value); - } + RealObject.Add(new CalendarParameter(name, values)); } - - public virtual void Set(string name, IEnumerable values) + else { - var parameter = RealObject.FirstOrDefault(o => string.Equals(o.Name, name, StringComparison.Ordinal)); - - if (parameter == null) - { - RealObject.Add(new CalendarParameter(name, values)); - } - else - { - parameter.SetValue(values); - } + parameter.SetValue(values); } + } - public virtual int IndexOf(CalendarParameter obj) => 0; + public virtual int IndexOf(CalendarParameter obj) => 0; - public virtual void Insert(int index, CalendarParameter item) { } + public virtual void Insert(int index, CalendarParameter item) { } - public virtual void RemoveAt(int index) { } + public virtual void RemoveAt(int index) { } - public virtual CalendarParameter this[int index] - { - get { return Parameters[index]; } - set { } - } + public virtual CalendarParameter this[int index] + { + get { return Parameters[index]; } + set { } } } \ No newline at end of file diff --git a/Ical.Net/Proxies/UniqueComponentListProxy.cs b/Ical.Net/Proxies/UniqueComponentListProxy.cs index c2360f19d..4805de742 100644 --- a/Ical.Net/Proxies/UniqueComponentListProxy.cs +++ b/Ical.Net/Proxies/UniqueComponentListProxy.cs @@ -1,67 +1,71 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.Collections; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.Linq; +using Ical.Net.CalendarComponents; +using Ical.Net.Collections; -namespace Ical.Net.Proxies +namespace Ical.Net.Proxies; + +public class UniqueComponentListProxy : + CalendarObjectListProxy, + IUniqueComponentList + where TComponentType : class, IUniqueComponent { - public class UniqueComponentListProxy : - CalendarObjectListProxy, - IUniqueComponentList - where TComponentType : class, IUniqueComponent + private readonly Dictionary _lookup; + + public UniqueComponentListProxy(IGroupedCollection children) : base(children) { - private readonly Dictionary _lookup; + _lookup = new Dictionary(); + } - public UniqueComponentListProxy(IGroupedCollection children) : base(children) + private TComponentType Search(string uid) + { + if (_lookup.TryGetValue(uid, out var componentType)) { - _lookup = new Dictionary(); + return componentType; } - private TComponentType Search(string uid) + var item = this.FirstOrDefault(c => string.Equals(c.Uid, uid, StringComparison.OrdinalIgnoreCase)); + + if (item == null) { - if (_lookup.TryGetValue(uid, out var componentType)) - { - return componentType; - } + return default(TComponentType); + } + + _lookup[uid] = item; + return item; + } - var item = this.FirstOrDefault(c => string.Equals(c.Uid, uid, StringComparison.OrdinalIgnoreCase)); + public virtual TComponentType this[string uid] + { + get => Search(uid); + set + { + // Find the item matching the UID + var item = Search(uid); - if (item == null) + if (item != null) { - return default(TComponentType); + Remove(item); } - _lookup[uid] = item; - return item; - } - - public virtual TComponentType this[string uid] - { - get => Search(uid); - set + if (value != null) { - // Find the item matching the UID - var item = Search(uid); - - if (item != null) - { - Remove(item); - } - - if (value != null) - { - Add(value); - } + Add(value); } } + } - public void AddRange(IEnumerable collection) + public void AddRange(IEnumerable collection) + { + foreach (var element in collection) { - foreach (var element in collection) - { - Add(element); - } + Add(element); } } } \ No newline at end of file diff --git a/Ical.Net/Serialization/CalendarComponentFactory.cs b/Ical.Net/Serialization/CalendarComponentFactory.cs index 6f68ef680..a399a90fd 100644 --- a/Ical.Net/Serialization/CalendarComponentFactory.cs +++ b/Ical.Net/Serialization/CalendarComponentFactory.cs @@ -1,47 +1,51 @@ -using Ical.Net.CalendarComponents; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net.Serialization +using Ical.Net.CalendarComponents; + +namespace Ical.Net.Serialization; + +public class CalendarComponentFactory { - public class CalendarComponentFactory + public virtual ICalendarComponent Build(string objectName) { - public virtual ICalendarComponent Build(string objectName) - { - ICalendarComponent c; - var name = objectName.ToUpper(); + ICalendarComponent c; + var name = objectName.ToUpper(); - switch (name) - { - case Components.Alarm: - c = new Alarm(); - break; - case EventStatus.Name: - c = new CalendarEvent(); - break; - case Components.Freebusy: - c = new FreeBusy(); - break; - case JournalStatus.Name: - c = new Journal(); - break; - case Components.Timezone: - c = new VTimeZone(); - break; - case TodoStatus.Name: - c = new Todo(); - break; - case Components.Calendar: - c = new Calendar(); - break; - case Components.Daylight: - case Components.Standard: - c = new VTimeZoneInfo(); - break; - default: - c = new CalendarComponent(); - break; - } - c.Name = name; - return c; + switch (name) + { + case Components.Alarm: + c = new Alarm(); + break; + case EventStatus.Name: + c = new CalendarEvent(); + break; + case Components.Freebusy: + c = new FreeBusy(); + break; + case JournalStatus.Name: + c = new Journal(); + break; + case Components.Timezone: + c = new VTimeZone(); + break; + case TodoStatus.Name: + c = new Todo(); + break; + case Components.Calendar: + c = new Calendar(); + break; + case Components.Daylight: + case Components.Standard: + c = new VTimeZoneInfo(); + break; + default: + c = new CalendarComponent(); + break; } + c.Name = name; + return c; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/CalendarSerializer.cs b/Ical.Net/Serialization/CalendarSerializer.cs index 7bc701d25..53d105578 100644 --- a/Ical.Net/Serialization/CalendarSerializer.cs +++ b/Ical.Net/Serialization/CalendarSerializer.cs @@ -1,69 +1,73 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System; using System.Collections.Generic; using System.IO; -namespace Ical.Net.Serialization +namespace Ical.Net.Serialization; + +public class CalendarSerializer : ComponentSerializer { - public class CalendarSerializer : ComponentSerializer - { - private readonly Calendar _calendar; + private readonly Calendar _calendar; - public CalendarSerializer() - : this(new SerializationContext()) { } + public CalendarSerializer() + : this(new SerializationContext()) { } - public CalendarSerializer(Calendar cal) - { - _calendar = cal; - } + public CalendarSerializer(Calendar cal) + { + _calendar = cal; + } - public CalendarSerializer(SerializationContext ctx) : base(ctx) { } + public CalendarSerializer(SerializationContext ctx) : base(ctx) { } - public virtual string SerializeToString() => SerializeToString(_calendar); + public virtual string SerializeToString() => SerializeToString(_calendar); - protected override IComparer PropertySorter => new CalendarPropertySorter(); + protected override IComparer PropertySorter => new CalendarPropertySorter(); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + if (obj is Calendar) { - if (obj is Calendar) - { - // If we're serializing a calendar, we should indicate that we're using ical.net to do the work - var calendar = (Calendar)obj; - calendar.Version = LibraryMetadata.Version; - calendar.ProductId = LibraryMetadata.ProdId; - - return base.SerializeToString(calendar); - } + // If we're serializing a calendar, we should indicate that we're using ical.net to do the work + var calendar = (Calendar) obj; + calendar.Version = LibraryMetadata.Version; + calendar.ProductId = LibraryMetadata.ProdId; - return base.SerializeToString(obj); + return base.SerializeToString(calendar); } - public override object Deserialize(TextReader tr) => null; + return base.SerializeToString(obj); + } + + public override object Deserialize(TextReader tr) => null; - private class CalendarPropertySorter : IComparer + private class CalendarPropertySorter : IComparer + { + public int Compare(ICalendarProperty x, ICalendarProperty y) { - public int Compare(ICalendarProperty x, ICalendarProperty y) + if (x == y) + { + return 0; + } + if (x == null) + { + return -1; + } + if (y == null) + { + return 1; + } + // Alphabetize all properties except VERSION, which should appear first. + if (string.Equals("VERSION", x.Name, StringComparison.OrdinalIgnoreCase)) { - if (x == y) - { - return 0; - } - if (x == null) - { - return -1; - } - if (y == null) - { - return 1; - } - // Alphabetize all properties except VERSION, which should appear first. - if (string.Equals("VERSION", x.Name, StringComparison.OrdinalIgnoreCase)) - { - return -1; - } - return string.Equals("VERSION", y.Name, StringComparison.OrdinalIgnoreCase) - ? 1 - : string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); + return -1; } + return string.Equals("VERSION", y.Name, StringComparison.OrdinalIgnoreCase) + ? 1 + : string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); } } } \ No newline at end of file diff --git a/Ical.Net/Serialization/ComponentSerializer.cs b/Ical.Net/Serialization/ComponentSerializer.cs index 1d4a04028..e41af4322 100644 --- a/Ical.Net/Serialization/ComponentSerializer.cs +++ b/Ical.Net/Serialization/ComponentSerializer.cs @@ -1,78 +1,82 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.Utility; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using Ical.Net.CalendarComponents; +using Ical.Net.Utility; -namespace Ical.Net.Serialization +namespace Ical.Net.Serialization; + +public class ComponentSerializer : SerializerBase { - public class ComponentSerializer : SerializerBase - { - protected virtual IComparer PropertySorter => new PropertyAlphabetizer(); + protected virtual IComparer PropertySorter => new PropertyAlphabetizer(); - public ComponentSerializer() { } + public ComponentSerializer() { } - public ComponentSerializer(SerializationContext ctx) : base(ctx) { } + public ComponentSerializer(SerializationContext ctx) : base(ctx) { } - public override Type TargetType => typeof(CalendarComponent); + public override Type TargetType => typeof(CalendarComponent); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + if (!(obj is ICalendarComponent c)) { - if (!(obj is ICalendarComponent c)) - { - return null; - } - - var sb = new StringBuilder(); - var upperName = c.Name.ToUpperInvariant(); - sb.Append(TextUtil.FoldLines($"BEGIN:{upperName}")); + return null; + } - // Get a serializer factory - var sf = GetService(); + var sb = new StringBuilder(); + var upperName = c.Name.ToUpperInvariant(); + sb.Append(TextUtil.FoldLines($"BEGIN:{upperName}")); - // Sort the calendar properties in alphabetical order before serializing them! - var properties = c.Properties.OrderBy(p => p.Name).ToList(); + // Get a serializer factory + var sf = GetService(); - // Serialize properties - foreach (var p in properties) - { - // Get a serializer for each property. - var serializer = sf.Build(p.GetType(), SerializationContext) as IStringSerializer; - sb.Append(serializer.SerializeToString(p)); - } + // Sort the calendar properties in alphabetical order before serializing them! + var properties = c.Properties.OrderBy(p => p.Name).ToList(); - // Serialize child objects - foreach (var child in c.Children) - { - // Get a serializer for each child object. - var serializer = sf.Build(child.GetType(), SerializationContext) as IStringSerializer; - sb.Append(serializer.SerializeToString(child)); - } + // Serialize properties + foreach (var p in properties) + { + // Get a serializer for each property. + var serializer = sf.Build(p.GetType(), SerializationContext) as IStringSerializer; + sb.Append(serializer.SerializeToString(p)); + } - sb.Append(TextUtil.FoldLines($"END:{upperName}")); - return sb.ToString(); + // Serialize child objects + foreach (var child in c.Children) + { + // Get a serializer for each child object. + var serializer = sf.Build(child.GetType(), SerializationContext) as IStringSerializer; + sb.Append(serializer.SerializeToString(child)); } - public override object Deserialize(TextReader tr) => null; + sb.Append(TextUtil.FoldLines($"END:{upperName}")); + return sb.ToString(); + } + + public override object Deserialize(TextReader tr) => null; - public class PropertyAlphabetizer : IComparer + public class PropertyAlphabetizer : IComparer + { + public int Compare(ICalendarProperty x, ICalendarProperty y) { - public int Compare(ICalendarProperty x, ICalendarProperty y) + if (x == y) + { + return 0; + } + if (x == null) { - if (x == y) - { - return 0; - } - if (x == null) - { - return -1; - } - return y == null - ? 1 - : string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); + return -1; } + return y == null + ? 1 + : string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase); } } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataMapSerializer.cs b/Ical.Net/Serialization/DataMapSerializer.cs index e6aa780c3..05824ea81 100644 --- a/Ical.Net/Serialization/DataMapSerializer.cs +++ b/Ical.Net/Serialization/DataMapSerializer.cs @@ -1,66 +1,70 @@ -using Ical.Net.Serialization.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; +using Ical.Net.Serialization.DataTypes; + +namespace Ical.Net.Serialization; -namespace Ical.Net.Serialization +public class DataMapSerializer : SerializerBase { - public class DataMapSerializer : SerializerBase - { - public DataMapSerializer() { } + public DataMapSerializer() { } - public DataMapSerializer(SerializationContext ctx) : base(ctx) { } + public DataMapSerializer(SerializationContext ctx) : base(ctx) { } - protected IStringSerializer GetMappedSerializer() + protected IStringSerializer GetMappedSerializer() + { + var sf = GetService(); + var mapper = GetService(); + if (sf == null || mapper == null) { - var sf = GetService(); - var mapper = GetService(); - if (sf == null || mapper == null) - { - return null; - } + return null; + } - var obj = SerializationContext.Peek(); + var obj = SerializationContext.Peek(); - // Get the data type for this object - var type = mapper.GetPropertyMapping(obj); + // Get the data type for this object + var type = mapper.GetPropertyMapping(obj); - return type == null - ? new StringSerializer(SerializationContext) - : sf.Build(type, SerializationContext) as IStringSerializer; - } + return type == null + ? new StringSerializer(SerializationContext) + : sf.Build(type, SerializationContext) as IStringSerializer; + } - public override Type TargetType + public override Type TargetType + { + get { - get - { - ISerializer serializer = GetMappedSerializer(); - return serializer?.TargetType; - } + ISerializer serializer = GetMappedSerializer(); + return serializer?.TargetType; } + } - public override string SerializeToString(object obj) - { - var serializer = GetMappedSerializer(); - return serializer?.SerializeToString(obj); - } + public override string SerializeToString(object obj) + { + var serializer = GetMappedSerializer(); + return serializer?.SerializeToString(obj); + } - public override object Deserialize(TextReader tr) + public override object Deserialize(TextReader tr) + { + var serializer = GetMappedSerializer(); + if (serializer == null) { - var serializer = GetMappedSerializer(); - if (serializer == null) - { - return null; - } + return null; + } - var value = tr.ReadToEnd(); - var returnValue = serializer.Deserialize(new StringReader(value)); + var value = tr.ReadToEnd(); + var returnValue = serializer.Deserialize(new StringReader(value)); - // Default to returning the string representation of the value - // if the value wasn't formatted correctly. - // FIXME: should this be a try/catch? Should serializers be throwing - // an InvalidFormatException? This may have some performance issues - // as try/catch is much slower than other means. - return returnValue ?? value; - } + // Default to returning the string representation of the value + // if the value wasn't formatted correctly. + // FIXME: should this be a try/catch? Should serializers be throwing + // an InvalidFormatException? This may have some performance issues + // as try/catch is much slower than other means. + return returnValue ?? value; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypeMapper.cs b/Ical.Net/Serialization/DataTypeMapper.cs index 0c4ecdc1a..872faedc7 100644 --- a/Ical.Net/Serialization/DataTypeMapper.cs +++ b/Ical.Net/Serialization/DataTypeMapper.cs @@ -1,148 +1,152 @@ -using Ical.Net.CalendarComponents; -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; +using Ical.Net.CalendarComponents; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization; + +public delegate Type TypeResolverDelegate(object context); -namespace Ical.Net.Serialization +internal class DataTypeMapper { - public delegate Type TypeResolverDelegate(object context); + private class PropertyMapping + { + public Type ObjectType { get; set; } + public TypeResolverDelegate Resolver { get; set; } + public bool AllowsMultipleValuesPerProperty { get; set; } + } - internal class DataTypeMapper + private readonly IDictionary _propertyMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public DataTypeMapper() { - private class PropertyMapping + AddPropertyMapping(AlarmAction.Name, typeof(AlarmAction), false); + AddPropertyMapping("ATTACH", typeof(Attachment), false); + AddPropertyMapping("ATTENDEE", typeof(Attendee), false); + AddPropertyMapping("CATEGORIES", typeof(string), true); + AddPropertyMapping("COMMENT", typeof(string), false); + AddPropertyMapping("COMPLETED", typeof(IDateTime), false); + AddPropertyMapping("CONTACT", typeof(string), false); + AddPropertyMapping("CREATED", typeof(IDateTime), false); + AddPropertyMapping("DTEND", typeof(IDateTime), false); + AddPropertyMapping("DTSTAMP", typeof(IDateTime), false); + AddPropertyMapping("DTSTART", typeof(IDateTime), false); + AddPropertyMapping("DUE", typeof(IDateTime), false); + AddPropertyMapping("DURATION", typeof(TimeSpan), false); + AddPropertyMapping("EXDATE", typeof(PeriodList), false); + AddPropertyMapping("EXRULE", typeof(RecurrencePattern), false); + AddPropertyMapping("FREEBUSY", typeof(FreeBusyEntry), true); + AddPropertyMapping("GEO", typeof(GeographicLocation), false); + AddPropertyMapping("LAST-MODIFIED", typeof(IDateTime), false); + AddPropertyMapping("ORGANIZER", typeof(Organizer), false); + AddPropertyMapping("PERCENT-COMPLETE", typeof(int), false); + AddPropertyMapping("PRIORITY", typeof(int), false); + AddPropertyMapping("RDATE", typeof(PeriodList), false); + AddPropertyMapping("RECURRENCE-ID", typeof(IDateTime), false); + AddPropertyMapping("RELATED-TO", typeof(string), false); + AddPropertyMapping("REQUEST-STATUS", typeof(RequestStatus), false); + AddPropertyMapping("REPEAT", typeof(int), false); + AddPropertyMapping("RESOURCES", typeof(string), true); + AddPropertyMapping("RRULE", typeof(RecurrencePattern), false); + AddPropertyMapping("SEQUENCE", typeof(int), false); + AddPropertyMapping("STATUS", ResolveStatusProperty, false); + AddPropertyMapping("TRANSP", typeof(TransparencyType), false); + AddPropertyMapping(TriggerRelation.Name, typeof(Trigger), false); + AddPropertyMapping("TZNAME", typeof(string), false); + AddPropertyMapping("TZOFFSETFROM", typeof(UtcOffset), false); + AddPropertyMapping("TZOFFSETTO", typeof(UtcOffset), false); + AddPropertyMapping("TZURL", typeof(Uri), false); + AddPropertyMapping("URL", typeof(Uri), false); + } + + protected Type ResolveStatusProperty(object context) + { + if (!(context is ICalendarObject obj)) + { + return null; + } + + switch (obj.Parent) { - public Type ObjectType { get; set; } - public TypeResolverDelegate Resolver { get; set; } - public bool AllowsMultipleValuesPerProperty { get; set; } + case CalendarEvent _: + return typeof(EventStatus); + case Todo _: + return typeof(TodoStatus); + case Journal _: + return typeof(JournalStatus); } - private readonly IDictionary _propertyMap = new Dictionary(StringComparer.OrdinalIgnoreCase); + return null; + } - public DataTypeMapper() + public void AddPropertyMapping(string name, Type objectType, bool allowsMultipleValues) + { + if (name == null || objectType == null) { - AddPropertyMapping(AlarmAction.Name, typeof(AlarmAction), false); - AddPropertyMapping("ATTACH", typeof(Attachment), false); - AddPropertyMapping("ATTENDEE", typeof(Attendee), false); - AddPropertyMapping("CATEGORIES", typeof(string), true); - AddPropertyMapping("COMMENT", typeof(string), false); - AddPropertyMapping("COMPLETED", typeof(IDateTime), false); - AddPropertyMapping("CONTACT", typeof(string), false); - AddPropertyMapping("CREATED", typeof(IDateTime), false); - AddPropertyMapping("DTEND", typeof(IDateTime), false); - AddPropertyMapping("DTSTAMP", typeof(IDateTime), false); - AddPropertyMapping("DTSTART", typeof(IDateTime), false); - AddPropertyMapping("DUE", typeof(IDateTime), false); - AddPropertyMapping("DURATION", typeof(TimeSpan), false); - AddPropertyMapping("EXDATE", typeof(PeriodList), false); - AddPropertyMapping("EXRULE", typeof(RecurrencePattern), false); - AddPropertyMapping("FREEBUSY", typeof(FreeBusyEntry), true); - AddPropertyMapping("GEO", typeof(GeographicLocation), false); - AddPropertyMapping("LAST-MODIFIED", typeof(IDateTime), false); - AddPropertyMapping("ORGANIZER", typeof(Organizer), false); - AddPropertyMapping("PERCENT-COMPLETE", typeof(int), false); - AddPropertyMapping("PRIORITY", typeof(int), false); - AddPropertyMapping("RDATE", typeof(PeriodList), false); - AddPropertyMapping("RECURRENCE-ID", typeof(IDateTime), false); - AddPropertyMapping("RELATED-TO", typeof(string), false); - AddPropertyMapping("REQUEST-STATUS", typeof(RequestStatus), false); - AddPropertyMapping("REPEAT", typeof(int), false); - AddPropertyMapping("RESOURCES", typeof(string), true); - AddPropertyMapping("RRULE", typeof(RecurrencePattern), false); - AddPropertyMapping("SEQUENCE", typeof(int), false); - AddPropertyMapping("STATUS", ResolveStatusProperty, false); - AddPropertyMapping("TRANSP", typeof(TransparencyType), false); - AddPropertyMapping(TriggerRelation.Name, typeof(Trigger), false); - AddPropertyMapping("TZNAME", typeof(string), false); - AddPropertyMapping("TZOFFSETFROM", typeof(UtcOffset), false); - AddPropertyMapping("TZOFFSETTO", typeof(UtcOffset), false); - AddPropertyMapping("TZURL", typeof(Uri), false); - AddPropertyMapping("URL", typeof(Uri), false); + return; } - protected Type ResolveStatusProperty(object context) + var m = new PropertyMapping { - if (!(context is ICalendarObject obj)) - { - return null; - } - - switch (obj.Parent) - { - case CalendarEvent _: - return typeof(EventStatus); - case Todo _: - return typeof(TodoStatus); - case Journal _: - return typeof(JournalStatus); - } + ObjectType = objectType, + AllowsMultipleValuesPerProperty = allowsMultipleValues + }; - return null; - } + _propertyMap[name] = m; + } - public void AddPropertyMapping(string name, Type objectType, bool allowsMultipleValues) + public void AddPropertyMapping(string name, TypeResolverDelegate resolver, bool allowsMultipleValues) + { + if (name == null || resolver == null) { - if (name == null || objectType == null) - { - return; - } - - var m = new PropertyMapping - { - ObjectType = objectType, - AllowsMultipleValuesPerProperty = allowsMultipleValues - }; - - _propertyMap[name] = m; + return; } - public void AddPropertyMapping(string name, TypeResolverDelegate resolver, bool allowsMultipleValues) + var m = new PropertyMapping { - if (name == null || resolver == null) - { - return; - } - - var m = new PropertyMapping - { - Resolver = resolver, - AllowsMultipleValuesPerProperty = allowsMultipleValues - }; - - _propertyMap[name] = m; - } + Resolver = resolver, + AllowsMultipleValuesPerProperty = allowsMultipleValues + }; + + _propertyMap[name] = m; + } - public void RemovePropertyMapping(string name) + public void RemovePropertyMapping(string name) + { + if (name != null && _propertyMap.ContainsKey(name)) { - if (name != null && _propertyMap.ContainsKey(name)) - { - _propertyMap.Remove(name); - } + _propertyMap.Remove(name); } + } + + public virtual bool GetPropertyAllowsMultipleValues(object obj) + { + var p = obj as ICalendarProperty; + return !string.IsNullOrWhiteSpace(p?.Name) + && _propertyMap.TryGetValue(p.Name, out var m) + && m.AllowsMultipleValuesPerProperty; + } - public virtual bool GetPropertyAllowsMultipleValues(object obj) + public virtual Type GetPropertyMapping(object obj) + { + var p = obj as ICalendarProperty; + if (p?.Name == null) { - var p = obj as ICalendarProperty; - return !string.IsNullOrWhiteSpace(p?.Name) - && _propertyMap.TryGetValue(p.Name, out var m) - && m.AllowsMultipleValuesPerProperty; + return null; } - public virtual Type GetPropertyMapping(object obj) + if (!_propertyMap.TryGetValue(p.Name, out var m)) { - var p = obj as ICalendarProperty; - if (p?.Name == null) - { - return null; - } - - if (!_propertyMap.TryGetValue(p.Name, out var m)) - { - return null; - } - - return m.Resolver == null - ? m.ObjectType - : m.Resolver(p); + return null; } + + return m.Resolver == null + ? m.ObjectType + : m.Resolver(p); } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypeSerializerFactory.cs b/Ical.Net/Serialization/DataTypeSerializerFactory.cs index b95e3de6a..8fdcc9267 100644 --- a/Ical.Net/Serialization/DataTypeSerializerFactory.cs +++ b/Ical.Net/Serialization/DataTypeSerializerFactory.cs @@ -1,92 +1,96 @@ -using Ical.Net.DataTypes; -using Ical.Net.Serialization.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; +using Ical.Net.DataTypes; +using Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization +namespace Ical.Net.Serialization; + +public class DataTypeSerializerFactory : ISerializerFactory { - public class DataTypeSerializerFactory : ISerializerFactory + /// + /// Returns a serializer that can be used to serialize and object + /// of type . + /// + /// TODO: Add support for caching. + /// + /// + /// The type of object to be serialized. + /// The serialization context. + public virtual ISerializer Build(Type objectType, SerializationContext ctx) { - /// - /// Returns a serializer that can be used to serialize and object - /// of type . - /// - /// TODO: Add support for caching. - /// - /// - /// The type of object to be serialized. - /// The serialization context. - public virtual ISerializer Build(Type objectType, SerializationContext ctx) + if (objectType != null) { - if (objectType != null) - { - ISerializer s; + ISerializer s; - if (typeof(Attachment).IsAssignableFrom(objectType)) - { - s = new AttachmentSerializer(ctx); - } - else if (typeof(Attendee).IsAssignableFrom(objectType)) - { - s = new AttendeeSerializer(ctx); - } - else if (typeof(IDateTime).IsAssignableFrom(objectType)) - { - s = new DateTimeSerializer(ctx); - } - else if (typeof(FreeBusyEntry).IsAssignableFrom(objectType)) - { - s = new FreeBusyEntrySerializer(ctx); - } - else if (typeof(GeographicLocation).IsAssignableFrom(objectType)) - { - s = new GeographicLocationSerializer(ctx); - } - else if (typeof(Organizer).IsAssignableFrom(objectType)) - { - s = new OrganizerSerializer(ctx); - } - else if (typeof(Period).IsAssignableFrom(objectType)) - { - s = new PeriodSerializer(ctx); - } - else if (typeof(PeriodList).IsAssignableFrom(objectType)) - { - s = new PeriodListSerializer(ctx); - } - else if (typeof(RecurrencePattern).IsAssignableFrom(objectType)) - { - s = new RecurrencePatternSerializer(ctx); - } - else if (typeof(RequestStatus).IsAssignableFrom(objectType)) - { - s = new RequestStatusSerializer(ctx); - } - else if (typeof(StatusCode).IsAssignableFrom(objectType)) - { - s = new StatusCodeSerializer(ctx); - } - else if (typeof(Trigger).IsAssignableFrom(objectType)) - { - s = new TriggerSerializer(ctx); - } - else if (typeof(UtcOffset).IsAssignableFrom(objectType)) - { - s = new UtcOffsetSerializer(ctx); - } - else if (typeof(WeekDay).IsAssignableFrom(objectType)) - { - s = new WeekDaySerializer(ctx); - } - // Default to a string serializer, which simply calls - // ToString() on the value to serialize it. - else - { - s = new StringSerializer(ctx); - } - - return s; + if (typeof(Attachment).IsAssignableFrom(objectType)) + { + s = new AttachmentSerializer(ctx); + } + else if (typeof(Attendee).IsAssignableFrom(objectType)) + { + s = new AttendeeSerializer(ctx); + } + else if (typeof(IDateTime).IsAssignableFrom(objectType)) + { + s = new DateTimeSerializer(ctx); + } + else if (typeof(FreeBusyEntry).IsAssignableFrom(objectType)) + { + s = new FreeBusyEntrySerializer(ctx); + } + else if (typeof(GeographicLocation).IsAssignableFrom(objectType)) + { + s = new GeographicLocationSerializer(ctx); + } + else if (typeof(Organizer).IsAssignableFrom(objectType)) + { + s = new OrganizerSerializer(ctx); + } + else if (typeof(Period).IsAssignableFrom(objectType)) + { + s = new PeriodSerializer(ctx); + } + else if (typeof(PeriodList).IsAssignableFrom(objectType)) + { + s = new PeriodListSerializer(ctx); + } + else if (typeof(RecurrencePattern).IsAssignableFrom(objectType)) + { + s = new RecurrencePatternSerializer(ctx); + } + else if (typeof(RequestStatus).IsAssignableFrom(objectType)) + { + s = new RequestStatusSerializer(ctx); + } + else if (typeof(StatusCode).IsAssignableFrom(objectType)) + { + s = new StatusCodeSerializer(ctx); } - return null; + else if (typeof(Trigger).IsAssignableFrom(objectType)) + { + s = new TriggerSerializer(ctx); + } + else if (typeof(UtcOffset).IsAssignableFrom(objectType)) + { + s = new UtcOffsetSerializer(ctx); + } + else if (typeof(WeekDay).IsAssignableFrom(objectType)) + { + s = new WeekDaySerializer(ctx); + } + // Default to a string serializer, which simply calls + // ToString() on the value to serialize it. + else + { + s = new StringSerializer(ctx); + } + + return s; } + return null; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/AttachmentSerializer.cs b/Ical.Net/Serialization/DataTypes/AttachmentSerializer.cs index 6d455b16f..49c5d2e5b 100644 --- a/Ical.Net/Serialization/DataTypes/AttachmentSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/AttachmentSerializer.cs @@ -1,90 +1,94 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class AttachmentSerializer : EncodableDataTypeSerializer { - public class AttachmentSerializer : EncodableDataTypeSerializer - { - public AttachmentSerializer() { } + public AttachmentSerializer() { } - public AttachmentSerializer(SerializationContext ctx) : base(ctx) { } + public AttachmentSerializer(SerializationContext ctx) : base(ctx) { } - public override Type TargetType => typeof(Attachment); + public override Type TargetType => typeof(Attachment); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + var a = obj as Attachment; + if (a == null) { - var a = obj as Attachment; - if (a == null) - { - return null; - } - - if (a.Uri != null) - { - if (a.Parameters.ContainsKey("VALUE")) - { - // Ensure no VALUE type is provided - a.Parameters.Remove("VALUE"); - } + return null; + } - return Encode(a, a.Uri.OriginalString); - } - if (a.Data == null) + if (a.Uri != null) + { + if (a.Parameters.ContainsKey("VALUE")) { - return null; + // Ensure no VALUE type is provided + a.Parameters.Remove("VALUE"); } - // Ensure the VALUE type is set to BINARY - a.SetValueType("BINARY"); + return Encode(a, a.Uri.OriginalString); + } + if (a.Data == null) + { + return null; + } - // BASE64 encoding for BINARY inline attachments. - a.Parameters.Set("ENCODING", "BASE64"); + // Ensure the VALUE type is set to BINARY + a.SetValueType("BINARY"); - return Encode(a, a.Data); - } + // BASE64 encoding for BINARY inline attachments. + a.Parameters.Set("ENCODING", "BASE64"); + + return Encode(a, a.Data); + } - public Attachment Deserialize(string attachment) + public Attachment Deserialize(string attachment) + { + try { - try - { - var a = CreateAndAssociate() as Attachment; - // Decode the value, if necessary - var data = DecodeData(a, attachment); + var a = CreateAndAssociate() as Attachment; + // Decode the value, if necessary + var data = DecodeData(a, attachment); - // Get the currently-used encoding off the encoding stack. - var encodingStack = GetService(); - a.ValueEncoding = encodingStack.Current; + // Get the currently-used encoding off the encoding stack. + var encodingStack = GetService(); + a.ValueEncoding = encodingStack.Current; - // Get the format of the attachment - var valueType = a.GetValueType(); - if (valueType == typeof(byte[])) - { - // If the VALUE type is specifically set to BINARY, - // then set the Data property instead. - return new Attachment(data) - { - ValueEncoding = a.ValueEncoding, - AssociatedObject = a.AssociatedObject, - }; - } - - // The default VALUE type for attachments is URI. So, let's - // grab the URI by default. - var uriValue = Decode(a, attachment); - a.Uri = new Uri(uriValue, UriKind.RelativeOrAbsolute); - - - return a; - } - catch + // Get the format of the attachment + var valueType = a.GetValueType(); + if (valueType == typeof(byte[])) { - // ignored + // If the VALUE type is specifically set to BINARY, + // then set the Data property instead. + return new Attachment(data) + { + ValueEncoding = a.ValueEncoding, + AssociatedObject = a.AssociatedObject, + }; } - return null; + // The default VALUE type for attachments is URI. So, let's + // grab the URI by default. + var uriValue = Decode(a, attachment); + a.Uri = new Uri(uriValue, UriKind.RelativeOrAbsolute); + + + return a; + } + catch + { + // ignored } - public override object Deserialize(TextReader tr) => Deserialize(tr.ReadToEnd()); + return null; } + + public override object Deserialize(TextReader tr) => Deserialize(tr.ReadToEnd()); } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/AttendeeSerializer.cs b/Ical.Net/Serialization/DataTypes/AttendeeSerializer.cs index 732647384..780d13c9e 100644 --- a/Ical.Net/Serialization/DataTypes/AttendeeSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/AttendeeSerializer.cs @@ -1,49 +1,53 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class AttendeeSerializer : StringSerializer { - public class AttendeeSerializer : StringSerializer - { - public AttendeeSerializer() { } + public AttendeeSerializer() { } - public AttendeeSerializer(SerializationContext ctx) : base(ctx) { } + public AttendeeSerializer(SerializationContext ctx) : base(ctx) { } - public override Type TargetType => typeof(Attendee); + public override Type TargetType => typeof(Attendee); - public override string SerializeToString(object obj) - { - var a = obj as Attendee; - return a?.Value == null - ? null - : Encode(a, a.Value.OriginalString); - } + public override string SerializeToString(object obj) + { + var a = obj as Attendee; + return a?.Value == null + ? null + : Encode(a, a.Value.OriginalString); + } - public Attendee Deserialize(string attendee) + public Attendee Deserialize(string attendee) + { + try { - try - { - var a = CreateAndAssociate() as Attendee; - var uriString = Unescape(Decode(a, attendee)); + var a = CreateAndAssociate() as Attendee; + var uriString = Unescape(Decode(a, attendee)); - // Prepend "mailto:" if necessary - if (!uriString.StartsWith("mailto:", StringComparison.OrdinalIgnoreCase)) - { - uriString = "mailto:" + uriString; - } - - a.Value = new Uri(uriString); - return a; - } - catch + // Prepend "mailto:" if necessary + if (!uriString.StartsWith("mailto:", StringComparison.OrdinalIgnoreCase)) { - // ignored + uriString = "mailto:" + uriString; } - return null; + a.Value = new Uri(uriString); + return a; + } + catch + { + // ignored } - public override object Deserialize(TextReader tr) => Deserialize(tr.ReadToEnd()); + return null; } + + public override object Deserialize(TextReader tr) => Deserialize(tr.ReadToEnd()); } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/DataTypeSerializer.cs b/Ical.Net/Serialization/DataTypes/DataTypeSerializer.cs index adfb2bb50..d82e51830 100644 --- a/Ical.Net/Serialization/DataTypes/DataTypeSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/DataTypeSerializer.cs @@ -1,28 +1,32 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public abstract class DataTypeSerializer : SerializerBase { - public abstract class DataTypeSerializer : SerializerBase - { - protected DataTypeSerializer() { } + protected DataTypeSerializer() { } - protected DataTypeSerializer(SerializationContext ctx) : base(ctx) { } + protected DataTypeSerializer(SerializationContext ctx) : base(ctx) { } - protected virtual ICalendarDataType CreateAndAssociate() + protected virtual ICalendarDataType CreateAndAssociate() + { + // Create an instance of the object + if (!(Activator.CreateInstance(TargetType) is ICalendarDataType dt)) { - // Create an instance of the object - if (!(Activator.CreateInstance(TargetType) is ICalendarDataType dt)) - { - return null; - } - - if (SerializationContext.Peek() is ICalendarObject associatedObject) - { - dt.AssociatedObject = associatedObject; - } + return null; + } - return dt; + if (SerializationContext.Peek() is ICalendarObject associatedObject) + { + dt.AssociatedObject = associatedObject; } + + return dt; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/DateTimeSerializer.cs b/Ical.Net/Serialization/DataTypes/DateTimeSerializer.cs index f96fbbfff..20e67dc24 100644 --- a/Ical.Net/Serialization/DataTypes/DateTimeSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/DateTimeSerializer.cs @@ -1,160 +1,164 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; using System.Text; using System.Text.RegularExpressions; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class DateTimeSerializer : EncodableDataTypeSerializer { - public class DateTimeSerializer : EncodableDataTypeSerializer - { - public DateTimeSerializer() { } + public DateTimeSerializer() { } - public DateTimeSerializer(SerializationContext ctx) : base(ctx) { } + public DateTimeSerializer(SerializationContext ctx) : base(ctx) { } - private DateTime CoerceDateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) + private DateTime CoerceDateTime(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind) + { + var dt = DateTime.MinValue; + + // NOTE: determine if a date/time value exceeds the representable date/time values in .NET. + // If so, let's automatically adjust the date/time to compensate. + // FIXME: should we have a parsing setting that will throw an exception + // instead of automatically adjusting the date/time value to the + // closest representable date/time? + try { - var dt = DateTime.MinValue; - - // NOTE: determine if a date/time value exceeds the representable date/time values in .NET. - // If so, let's automatically adjust the date/time to compensate. - // FIXME: should we have a parsing setting that will throw an exception - // instead of automatically adjusting the date/time value to the - // closest representable date/time? - try + if (year > 9999) { - if (year > 9999) - { - dt = DateTime.MaxValue; - } - else if (year > 0) - { - dt = new DateTime(year, month, day, hour, minute, second, kind); - } + dt = DateTime.MaxValue; + } + else if (year > 0) + { + dt = new DateTime(year, month, day, hour, minute, second, kind); } - catch { } - - return dt; } + catch { } - public override Type TargetType => typeof(CalDateTime); + return dt; + } + + public override Type TargetType => typeof(CalDateTime); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + var dt = obj as IDateTime; + if (dt == null) { - var dt = obj as IDateTime; - if (dt == null) - { - return null; - } + return null; + } - // RFC 5545 3.3.5: - // The date with UTC time, or absolute time, is identified by a LATIN - // CAPITAL LETTER Z suffix character, the UTC designator, appended to - // the time value. The "TZID" property parameter MUST NOT be applied to DATE-TIME - // properties whose time values are specified in UTC. + // RFC 5545 3.3.5: + // The date with UTC time, or absolute time, is identified by a LATIN + // CAPITAL LETTER Z suffix character, the UTC designator, appended to + // the time value. The "TZID" property parameter MUST NOT be applied to DATE-TIME + // properties whose time values are specified in UTC. - var kind = dt.IsUtc - ? DateTimeKind.Utc - : DateTimeKind.Local; + var kind = dt.IsUtc + ? DateTimeKind.Utc + : DateTimeKind.Local; - if (dt.IsUtc) - { - dt.Parameters.Remove("TZID"); - } - else if (!string.IsNullOrWhiteSpace(dt.TzId)) - { - dt.Parameters.Set("TZID", dt.TzId); - } + if (dt.IsUtc) + { + dt.Parameters.Remove("TZID"); + } + else if (!string.IsNullOrWhiteSpace(dt.TzId)) + { + dt.Parameters.Set("TZID", dt.TzId); + } - DateTime.SpecifyKind(dt.Value, kind); + DateTime.SpecifyKind(dt.Value, kind); - // FIXME: what if DATE is the default value type for this? - // Also, what if the DATE-TIME value type is specified on something - // where DATE-TIME is the default value type? It should be removed - // during serialization, as it's redundant... - if (!dt.HasTime) - { - dt.SetValueType("DATE"); - } + // FIXME: what if DATE is the default value type for this? + // Also, what if the DATE-TIME value type is specified on something + // where DATE-TIME is the default value type? It should be removed + // during serialization, as it's redundant... + if (!dt.HasTime) + { + dt.SetValueType("DATE"); + } - var value = new StringBuilder(); - value.Append($"{dt.Year:0000}{dt.Month:00}{dt.Day:00}"); - if (dt.HasTime) + var value = new StringBuilder(); + value.Append($"{dt.Year:0000}{dt.Month:00}{dt.Day:00}"); + if (dt.HasTime) + { + value.Append($"T{dt.Hour:00}{dt.Minute:00}{dt.Second:00}"); + if (dt.IsUtc) { - value.Append($"T{dt.Hour:00}{dt.Minute:00}{dt.Second:00}"); - if (dt.IsUtc) - { - value.Append("Z"); - } + value.Append("Z"); } - - // Encode the value as necessary - return Encode(dt, value.ToString()); } - private const RegexOptions _ciCompiled = RegexOptions.Compiled | RegexOptions.IgnoreCase; - internal static readonly Regex DateOnlyMatch = new Regex(@"^((\d{4})(\d{2})(\d{2}))?$", _ciCompiled, RegexDefaults.Timeout); - internal static readonly Regex FullDateTimePatternMatch = new Regex(@"^((\d{4})(\d{2})(\d{2}))T((\d{2})(\d{2})(\d{2})(Z)?)$", _ciCompiled, RegexDefaults.Timeout); + // Encode the value as necessary + return Encode(dt, value.ToString()); + } - public override object Deserialize(TextReader tr) - { - var value = tr.ReadToEnd(); + private const RegexOptions _ciCompiled = RegexOptions.Compiled | RegexOptions.IgnoreCase; + internal static readonly Regex DateOnlyMatch = new Regex(@"^((\d{4})(\d{2})(\d{2}))?$", _ciCompiled, RegexDefaults.Timeout); + internal static readonly Regex FullDateTimePatternMatch = new Regex(@"^((\d{4})(\d{2})(\d{2}))T((\d{2})(\d{2})(\d{2})(Z)?)$", _ciCompiled, RegexDefaults.Timeout); - var dt = CreateAndAssociate() as IDateTime; - if (dt == null) - { - return null; - } + public override object Deserialize(TextReader tr) + { + var value = tr.ReadToEnd(); - // Decode the value as necessary - value = Decode(dt, value); + var dt = CreateAndAssociate() as IDateTime; + if (dt == null) + { + return null; + } - var match = FullDateTimePatternMatch.Match(value); - if (!match.Success) - { - match = DateOnlyMatch.Match(value); - } + // Decode the value as necessary + value = Decode(dt, value); - if (!match.Success) - { - return null; - } - var now = DateTime.Now; + var match = FullDateTimePatternMatch.Match(value); + if (!match.Success) + { + match = DateOnlyMatch.Match(value); + } - var year = now.Year; - var month = now.Month; - var date = now.Day; - var hour = 0; - var minute = 0; - var second = 0; + if (!match.Success) + { + return null; + } + var now = DateTime.Now; - if (match.Groups[1].Success) - { - dt.HasDate = true; - year = Convert.ToInt32(match.Groups[2].Value); - month = Convert.ToInt32(match.Groups[3].Value); - date = Convert.ToInt32(match.Groups[4].Value); - } - if (match.Groups.Count >= 6 && match.Groups[5].Success) - { - dt.HasTime = true; - hour = Convert.ToInt32(match.Groups[6].Value); - minute = Convert.ToInt32(match.Groups[7].Value); - second = Convert.ToInt32(match.Groups[8].Value); - } + var year = now.Year; + var month = now.Month; + var date = now.Day; + var hour = 0; + var minute = 0; + var second = 0; - var isUtc = match.Groups[9].Success; - var kind = isUtc - ? DateTimeKind.Utc - : DateTimeKind.Local; + if (match.Groups[1].Success) + { + dt.HasDate = true; + year = Convert.ToInt32(match.Groups[2].Value); + month = Convert.ToInt32(match.Groups[3].Value); + date = Convert.ToInt32(match.Groups[4].Value); + } + if (match.Groups.Count >= 6 && match.Groups[5].Success) + { + dt.HasTime = true; + hour = Convert.ToInt32(match.Groups[6].Value); + minute = Convert.ToInt32(match.Groups[7].Value); + second = Convert.ToInt32(match.Groups[8].Value); + } - if (isUtc) - { - dt.TzId = "UTC"; - } + var isUtc = match.Groups[9].Success; + var kind = isUtc + ? DateTimeKind.Utc + : DateTimeKind.Local; - dt.Value = CoerceDateTime(year, month, date, hour, minute, second, kind); - return dt; + if (isUtc) + { + dt.TzId = "UTC"; } + + dt.Value = CoerceDateTime(year, month, date, hour, minute, second, kind); + return dt; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/EncodableDataTypeSerializer.cs b/Ical.Net/Serialization/DataTypes/EncodableDataTypeSerializer.cs index ad0cf9e81..4edae16d6 100644 --- a/Ical.Net/Serialization/DataTypes/EncodableDataTypeSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/EncodableDataTypeSerializer.cs @@ -1,82 +1,86 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// -namespace Ical.Net.Serialization.DataTypes +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; + +public abstract class EncodableDataTypeSerializer : DataTypeSerializer { - public abstract class EncodableDataTypeSerializer : DataTypeSerializer - { - protected EncodableDataTypeSerializer() { } + protected EncodableDataTypeSerializer() { } - protected EncodableDataTypeSerializer(SerializationContext ctx) : base(ctx) { } + protected EncodableDataTypeSerializer(SerializationContext ctx) : base(ctx) { } - protected string Encode(IEncodableDataType dt, string value) + protected string Encode(IEncodableDataType dt, string value) + { + if (value == null) { - if (value == null) - { - return null; - } + return null; + } - if (dt?.Encoding == null) - { - return value; - } + if (dt?.Encoding == null) + { + return value; + } - // Return the value in the current encoding + // Return the value in the current encoding + var encodingStack = GetService(); + return Encode(dt, encodingStack.Current.GetBytes(value)); + } + + protected string Encode(IEncodableDataType dt, byte[] data) + { + if (data == null) + { + return null; + } + + if (dt?.Encoding == null) + { + // Default to the current encoding var encodingStack = GetService(); - return Encode(dt, encodingStack.Current.GetBytes(value)); + return encodingStack.Current.GetString(data); } - protected string Encode(IEncodableDataType dt, byte[] data) + var encodingProvider = GetService(); + return encodingProvider?.Encode(dt.Encoding, data); + } + + protected string Decode(IEncodableDataType dt, string value) + { + if (dt?.Encoding == null) { - if (data == null) - { - return null; - } - - if (dt?.Encoding == null) - { - // Default to the current encoding - var encodingStack = GetService(); - return encodingStack.Current.GetString(data); - } - - var encodingProvider = GetService(); - return encodingProvider?.Encode(dt.Encoding, data); + return value; } - protected string Decode(IEncodableDataType dt, string value) + var data = DecodeData(dt, value); + if (data == null) { - if (dt?.Encoding == null) - { - return value; - } + return null; + } - var data = DecodeData(dt, value); - if (data == null) - { - return null; - } + // Default to the current encoding + var encodingStack = GetService(); + return encodingStack.Current.GetString(data); + } - // Default to the current encoding - var encodingStack = GetService(); - return encodingStack.Current.GetString(data); + protected byte[] DecodeData(IEncodableDataType dt, string value) + { + if (value == null) + { + return null; } - protected byte[] DecodeData(IEncodableDataType dt, string value) + if (dt?.Encoding == null) { - if (value == null) - { - return null; - } - - if (dt?.Encoding == null) - { - // Default to the current encoding - var encodingStack = GetService(); - return encodingStack.Current.GetBytes(value); - } - - var encodingProvider = GetService(); - return encodingProvider?.DecodeData(dt.Encoding, value); + // Default to the current encoding + var encodingStack = GetService(); + return encodingStack.Current.GetBytes(value); } + + var encodingProvider = GetService(); + return encodingProvider?.DecodeData(dt.Encoding, value); } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/EnumSerializer.cs b/Ical.Net/Serialization/DataTypes/EnumSerializer.cs index 9e2c29623..d4ee20809 100644 --- a/Ical.Net/Serialization/DataTypes/EnumSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/EnumSerializer.cs @@ -1,70 +1,74 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class EnumSerializer : EncodableDataTypeSerializer { - public class EnumSerializer : EncodableDataTypeSerializer - { - private readonly Type _mEnumType; + private readonly Type _mEnumType; - public EnumSerializer(Type enumType) - { - _mEnumType = enumType; - } + public EnumSerializer(Type enumType) + { + _mEnumType = enumType; + } - public EnumSerializer(Type enumType, SerializationContext ctx) : base(ctx) - { - _mEnumType = enumType; - } + public EnumSerializer(Type enumType, SerializationContext ctx) : base(ctx) + { + _mEnumType = enumType; + } - public override Type TargetType => _mEnumType; + public override Type TargetType => _mEnumType; - public override string SerializeToString(object enumValue) + public override string SerializeToString(object enumValue) + { + try { - try + var obj = SerializationContext.Peek() as ICalendarObject; + if (obj != null) { - var obj = SerializationContext.Peek() as ICalendarObject; - if (obj != null) + // Encode the value as needed. + var dt = new EncodableDataType { - // Encode the value as needed. - var dt = new EncodableDataType - { - AssociatedObject = obj - }; - return Encode(dt, enumValue.ToString()); - } - return enumValue.ToString(); - } - catch - { - return null; + AssociatedObject = obj + }; + return Encode(dt, enumValue.ToString()); } + return enumValue.ToString(); } - - public override object Deserialize(TextReader tr) + catch { - var value = tr.ReadToEnd(); + return null; + } + } - try + public override object Deserialize(TextReader tr) + { + var value = tr.ReadToEnd(); + + try + { + var obj = SerializationContext.Peek() as ICalendarObject; + if (obj != null) { - var obj = SerializationContext.Peek() as ICalendarObject; - if (obj != null) + // Decode the value, if necessary! + var dt = new EncodableDataType { - // Decode the value, if necessary! - var dt = new EncodableDataType - { - AssociatedObject = obj - }; - value = Decode(dt, value); - } - - // Remove "-" characters while parsing Enum values. - return Enum.Parse(_mEnumType, value.Replace("-", ""), true); + AssociatedObject = obj + }; + value = Decode(dt, value); } - catch { } - return value; + // Remove "-" characters while parsing Enum values. + return Enum.Parse(_mEnumType, value.Replace("-", ""), true); } + catch { } + + return value; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/FreeBusyEntrySerializer.cs b/Ical.Net/Serialization/DataTypes/FreeBusyEntrySerializer.cs index 01522910e..4bf6fea6c 100644 --- a/Ical.Net/Serialization/DataTypes/FreeBusyEntrySerializer.cs +++ b/Ical.Net/Serialization/DataTypes/FreeBusyEntrySerializer.cs @@ -1,80 +1,84 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class FreeBusyEntrySerializer : PeriodSerializer { - public class FreeBusyEntrySerializer : PeriodSerializer - { - public FreeBusyEntrySerializer() { } + public FreeBusyEntrySerializer() { } - public FreeBusyEntrySerializer(SerializationContext ctx) : base(ctx) { } + public FreeBusyEntrySerializer(SerializationContext ctx) : base(ctx) { } - public override Type TargetType => typeof(FreeBusyEntry); + public override Type TargetType => typeof(FreeBusyEntry); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + var entry = obj as FreeBusyEntry; + if (entry == null) { - var entry = obj as FreeBusyEntry; - if (entry == null) - { - return base.SerializeToString(obj); - } - - switch (entry.Status) - { - case FreeBusyStatus.Busy: - entry.Parameters.Remove("FBTYPE"); - break; - case FreeBusyStatus.BusyTentative: - entry.Parameters.Set("FBTYPE", "BUSY-TENTATIVE"); - break; - case FreeBusyStatus.BusyUnavailable: - entry.Parameters.Set("FBTYPE", "BUSY-UNAVAILABLE"); - break; - case FreeBusyStatus.Free: - entry.Parameters.Set("FBTYPE", "FREE"); - break; - } - return base.SerializeToString(obj); } - public override object Deserialize(TextReader tr) + switch (entry.Status) { - var entry = base.Deserialize(tr) as FreeBusyEntry; - if (entry == null) - { - return entry; - } + case FreeBusyStatus.Busy: + entry.Parameters.Remove("FBTYPE"); + break; + case FreeBusyStatus.BusyTentative: + entry.Parameters.Set("FBTYPE", "BUSY-TENTATIVE"); + break; + case FreeBusyStatus.BusyUnavailable: + entry.Parameters.Set("FBTYPE", "BUSY-UNAVAILABLE"); + break; + case FreeBusyStatus.Free: + entry.Parameters.Set("FBTYPE", "FREE"); + break; + } - if (!entry.Parameters.ContainsKey("FBTYPE")) - { - return entry; - } + return base.SerializeToString(obj); + } - var value = entry.Parameters.Get("FBTYPE"); - if (value == null) - { - return entry; - } + public override object Deserialize(TextReader tr) + { + var entry = base.Deserialize(tr) as FreeBusyEntry; + if (entry == null) + { + return entry; + } - switch (value.ToUpperInvariant()) - { - case "FREE": - entry.Status = FreeBusyStatus.Free; - break; - case "BUSY": - entry.Status = FreeBusyStatus.Busy; - break; - case "BUSY-UNAVAILABLE": - entry.Status = FreeBusyStatus.BusyUnavailable; - break; - case "BUSY-TENTATIVE": - entry.Status = FreeBusyStatus.BusyTentative; - break; - } + if (!entry.Parameters.ContainsKey("FBTYPE")) + { + return entry; + } + var value = entry.Parameters.Get("FBTYPE"); + if (value == null) + { return entry; } + + switch (value.ToUpperInvariant()) + { + case "FREE": + entry.Status = FreeBusyStatus.Free; + break; + case "BUSY": + entry.Status = FreeBusyStatus.Busy; + break; + case "BUSY-UNAVAILABLE": + entry.Status = FreeBusyStatus.BusyUnavailable; + break; + case "BUSY-TENTATIVE": + entry.Status = FreeBusyStatus.BusyTentative; + break; + } + + return entry; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/GeographicLocationSerializer.cs b/Ical.Net/Serialization/DataTypes/GeographicLocationSerializer.cs index b61325c75..f2f36b665 100644 --- a/Ical.Net/Serialization/DataTypes/GeographicLocationSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/GeographicLocationSerializer.cs @@ -1,63 +1,67 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Globalization; using System.IO; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class GeographicLocationSerializer : EncodableDataTypeSerializer { - public class GeographicLocationSerializer : EncodableDataTypeSerializer + public GeographicLocationSerializer() { } + + public GeographicLocationSerializer(SerializationContext ctx) : base(ctx) { } + + public override Type TargetType => typeof(GeographicLocation); + + public override string SerializeToString(object obj) { - public GeographicLocationSerializer() { } + var g = obj as GeographicLocation; + if (g == null) + { + return null; + } - public GeographicLocationSerializer(SerializationContext ctx) : base(ctx) { } + var value = g.Latitude.ToString("0.000000", CultureInfo.InvariantCulture.NumberFormat) + ";" + + g.Longitude.ToString("0.000000", CultureInfo.InvariantCulture.NumberFormat); + return Encode(g, value); + } - public override Type TargetType => typeof(GeographicLocation); + public GeographicLocation Deserialize(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + return null; + } - public override string SerializeToString(object obj) + var g = CreateAndAssociate() as GeographicLocation; + if (g == null) { - var g = obj as GeographicLocation; - if (g == null) - { - return null; - } - - var value = g.Latitude.ToString("0.000000", CultureInfo.InvariantCulture.NumberFormat) + ";" - + g.Longitude.ToString("0.000000", CultureInfo.InvariantCulture.NumberFormat); - return Encode(g, value); + return null; } - public GeographicLocation Deserialize(string value) + // Decode the value, if necessary! + value = Decode(g, value); + + var values = value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + if (values.Length != 2) { - if (string.IsNullOrWhiteSpace(value)) - { - return null; - } - - var g = CreateAndAssociate() as GeographicLocation; - if (g == null) - { - return null; - } - - // Decode the value, if necessary! - value = Decode(g, value); - - var values = value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - if (values.Length != 2) - { - return null; - } - - double lat; - double lon; - double.TryParse(values[0], NumberStyles.Any, CultureInfo.InvariantCulture, out lat); - double.TryParse(values[1], NumberStyles.Any, CultureInfo.InvariantCulture, out lon); - g.Latitude = lat; - g.Longitude = lon; - - return g; + return null; } - public override object Deserialize(TextReader tr) => Deserialize(tr.ReadToEnd()); + double lat; + double lon; + double.TryParse(values[0], NumberStyles.Any, CultureInfo.InvariantCulture, out lat); + double.TryParse(values[1], NumberStyles.Any, CultureInfo.InvariantCulture, out lon); + g.Latitude = lat; + g.Longitude = lon; + + return g; } + + public override object Deserialize(TextReader tr) => Deserialize(tr.ReadToEnd()); } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/IntegerSerializer.cs b/Ical.Net/Serialization/DataTypes/IntegerSerializer.cs index 9ff0e37ca..a63aafc0a 100644 --- a/Ical.Net/Serialization/DataTypes/IntegerSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/IntegerSerializer.cs @@ -1,67 +1,71 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class IntegerSerializer : EncodableDataTypeSerializer { - public class IntegerSerializer : EncodableDataTypeSerializer - { - public IntegerSerializer() { } + public IntegerSerializer() { } - public IntegerSerializer(SerializationContext ctx) : base(ctx) { } + public IntegerSerializer(SerializationContext ctx) : base(ctx) { } - public override Type TargetType => typeof(int); + public override Type TargetType => typeof(int); - public override string SerializeToString(object integer) + public override string SerializeToString(object integer) + { + try { - try - { - var i = Convert.ToInt32(integer); + var i = Convert.ToInt32(integer); - var obj = SerializationContext.Peek() as ICalendarObject; - if (obj != null) - { - // Encode the value as needed. - var dt = new EncodableDataType - { - AssociatedObject = obj - }; - return Encode(dt, i.ToString()); - } - return i.ToString(); - } - catch + var obj = SerializationContext.Peek() as ICalendarObject; + if (obj != null) { - return null; + // Encode the value as needed. + var dt = new EncodableDataType + { + AssociatedObject = obj + }; + return Encode(dt, i.ToString()); } + return i.ToString(); } - - public override object Deserialize(TextReader tr) + catch { - var value = tr.ReadToEnd(); + return null; + } + } - try - { - var obj = SerializationContext.Peek() as ICalendarObject; - if (obj != null) - { - // Decode the value, if necessary! - var dt = new EncodableDataType - { - AssociatedObject = obj - }; - value = Decode(dt, value); - } + public override object Deserialize(TextReader tr) + { + var value = tr.ReadToEnd(); - int i; - if (Int32.TryParse(value, out i)) + try + { + var obj = SerializationContext.Peek() as ICalendarObject; + if (obj != null) + { + // Decode the value, if necessary! + var dt = new EncodableDataType { - return i; - } + AssociatedObject = obj + }; + value = Decode(dt, value); } - catch { } - return value; + int i; + if (Int32.TryParse(value, out i)) + { + return i; + } } + catch { } + + return value; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/OrganizerSerializer.cs b/Ical.Net/Serialization/DataTypes/OrganizerSerializer.cs index 80107b560..a41f8bc70 100644 --- a/Ical.Net/Serialization/DataTypes/OrganizerSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/OrganizerSerializer.cs @@ -1,56 +1,60 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class OrganizerSerializer : StringSerializer { - public class OrganizerSerializer : StringSerializer - { - public OrganizerSerializer() { } + public OrganizerSerializer() { } - public OrganizerSerializer(SerializationContext ctx) : base(ctx) { } + public OrganizerSerializer(SerializationContext ctx) : base(ctx) { } - public override Type TargetType => typeof(Organizer); + public override Type TargetType => typeof(Organizer); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + try { - try - { - var o = obj as Organizer; - return o?.Value == null - ? null - : Encode(o, Escape(o.Value.OriginalString)); - } - catch - { - return null; - } + var o = obj as Organizer; + return o?.Value == null + ? null + : Encode(o, Escape(o.Value.OriginalString)); } - - public override object Deserialize(TextReader tr) + catch { - var value = tr.ReadToEnd(); + return null; + } + } - Organizer o = null; - try - { - o = CreateAndAssociate() as Organizer; - if (o != null) - { - var uriString = Unescape(Decode(o, value)); + public override object Deserialize(TextReader tr) + { + var value = tr.ReadToEnd(); - // Prepend "mailto:" if necessary - if (!uriString.StartsWith("mailto:", StringComparison.OrdinalIgnoreCase)) - { - uriString = "mailto:" + uriString; - } + Organizer o = null; + try + { + o = CreateAndAssociate() as Organizer; + if (o != null) + { + var uriString = Unescape(Decode(o, value)); - o.Value = new Uri(uriString); + // Prepend "mailto:" if necessary + if (!uriString.StartsWith("mailto:", StringComparison.OrdinalIgnoreCase)) + { + uriString = "mailto:" + uriString; } - } - catch { } - return o; + o.Value = new Uri(uriString); + } } + catch { } + + return o; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/PeriodListSerializer.cs b/Ical.Net/Serialization/DataTypes/PeriodListSerializer.cs index 1f3c85903..7dd3f6993 100644 --- a/Ical.Net/Serialization/DataTypes/PeriodListSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/PeriodListSerializer.cs @@ -1,91 +1,95 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.IO; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class PeriodListSerializer : EncodableDataTypeSerializer { - public class PeriodListSerializer : EncodableDataTypeSerializer + public PeriodListSerializer() { } + + public PeriodListSerializer(SerializationContext ctx) : base(ctx) { } + + public override Type TargetType => typeof(PeriodList); + + public override string SerializeToString(object obj) { - public PeriodListSerializer() { } + var periodList = obj as PeriodList; + var factory = GetService(); + if (periodList == null || factory == null) + { + return null; + } - public PeriodListSerializer(SerializationContext ctx) : base(ctx) { } + var dtSerializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; + var periodSerializer = factory.Build(typeof(Period), SerializationContext) as IStringSerializer; + if (dtSerializer == null || periodSerializer == null) + { + return null; + } - public override Type TargetType => typeof(PeriodList); + var parts = new List(periodList.Count); - public override string SerializeToString(object obj) + foreach (var p in periodList) { - var periodList = obj as PeriodList; - var factory = GetService(); - if (periodList == null || factory == null) + if (p.EndTime != null) { - return null; + parts.Add(periodSerializer.SerializeToString(p)); } - - var dtSerializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; - var periodSerializer = factory.Build(typeof(Period), SerializationContext) as IStringSerializer; - if (dtSerializer == null || periodSerializer == null) + else if (p.StartTime != null) { - return null; + parts.Add(dtSerializer.SerializeToString(p.StartTime)); } + } - var parts = new List(periodList.Count); + return Encode(periodList, string.Join(",", parts)); + } - foreach (var p in periodList) - { - if (p.EndTime != null) - { - parts.Add(periodSerializer.SerializeToString(p)); - } - else if (p.StartTime != null) - { - parts.Add(dtSerializer.SerializeToString(p.StartTime)); - } - } + public override object Deserialize(TextReader tr) + { + var value = tr.ReadToEnd(); - return Encode(periodList, string.Join(",", parts)); + // Create the day specifier and associate it with a calendar object + var rdt = CreateAndAssociate() as PeriodList; + var factory = GetService(); + if (rdt == null || factory == null) + { + return null; } - public override object Deserialize(TextReader tr) - { - var value = tr.ReadToEnd(); + // Decode the value, if necessary + value = Decode(rdt, value); - // Create the day specifier and associate it with a calendar object - var rdt = CreateAndAssociate() as PeriodList; - var factory = GetService(); - if (rdt == null || factory == null) - { - return null; - } + var dtSerializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; + var periodSerializer = factory.Build(typeof(Period), SerializationContext) as IStringSerializer; + if (dtSerializer == null || periodSerializer == null) + { + return null; + } - // Decode the value, if necessary - value = Decode(rdt, value); + var values = value.Split(','); + foreach (var v in values) + { + var dt = dtSerializer.Deserialize(new StringReader(v)) as IDateTime; + var p = periodSerializer.Deserialize(new StringReader(v)) as Period; - var dtSerializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; - var periodSerializer = factory.Build(typeof(Period), SerializationContext) as IStringSerializer; - if (dtSerializer == null || periodSerializer == null) + if (dt != null) { - return null; + dt.AssociatedObject = rdt.AssociatedObject; + rdt.Add(dt); } - - var values = value.Split(','); - foreach (var v in values) + else if (p != null) { - var dt = dtSerializer.Deserialize(new StringReader(v)) as IDateTime; - var p = periodSerializer.Deserialize(new StringReader(v)) as Period; - - if (dt != null) - { - dt.AssociatedObject = rdt.AssociatedObject; - rdt.Add(dt); - } - else if (p != null) - { - p.AssociatedObject = rdt.AssociatedObject; - rdt.Add(p); - } + p.AssociatedObject = rdt.AssociatedObject; + rdt.Add(p); } - return rdt; } + return rdt; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/PeriodSerializer.cs b/Ical.Net/Serialization/DataTypes/PeriodSerializer.cs index 3340a1a34..a0d828b76 100644 --- a/Ical.Net/Serialization/DataTypes/PeriodSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/PeriodSerializer.cs @@ -1,109 +1,113 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; using System.Text; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class PeriodSerializer : EncodableDataTypeSerializer { - public class PeriodSerializer : EncodableDataTypeSerializer - { - public PeriodSerializer() { } + public PeriodSerializer() { } - public PeriodSerializer(SerializationContext ctx) : base(ctx) { } + public PeriodSerializer(SerializationContext ctx) : base(ctx) { } - public override Type TargetType => typeof(Period); + public override Type TargetType => typeof(Period); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + var p = obj as Period; + var factory = GetService(); + + if (p == null || factory == null) { - var p = obj as Period; - var factory = GetService(); + return null; + } + + // Push the period onto the serialization context stack + SerializationContext.Push(p); - if (p == null || factory == null) + try + { + var dtSerializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; + var timeSpanSerializer = factory.Build(typeof(TimeSpan), SerializationContext) as IStringSerializer; + if (dtSerializer == null || timeSpanSerializer == null) { return null; } + var sb = new StringBuilder(); - // Push the period onto the serialization context stack - SerializationContext.Push(p); + // Serialize the start time + sb.Append(dtSerializer.SerializeToString(p.StartTime)); - try + // Serialize the duration or end time + if (p.EndTime.HasTime) { - var dtSerializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; - var timeSpanSerializer = factory.Build(typeof(TimeSpan), SerializationContext) as IStringSerializer; - if (dtSerializer == null || timeSpanSerializer == null) - { - return null; - } - var sb = new StringBuilder(); - - // Serialize the start time - sb.Append(dtSerializer.SerializeToString(p.StartTime)); - - // Serialize the duration or end time - if (p.EndTime.HasTime) - { - // serialize the end time - sb.Append("/"); - sb.Append(dtSerializer.SerializeToString(p.EndTime)); - } - else - { - // Serialize the duration - sb.Append("/"); - sb.Append(timeSpanSerializer.SerializeToString(p.Duration)); - } - - // Encode the value as necessary - return Encode(p, sb.ToString()); + // serialize the end time + sb.Append("/"); + sb.Append(dtSerializer.SerializeToString(p.EndTime)); } - finally + else { - // Pop the period off the serialization context stack - SerializationContext.Pop(); + // Serialize the duration + sb.Append("/"); + sb.Append(timeSpanSerializer.SerializeToString(p.Duration)); } - } - public override object Deserialize(TextReader tr) + // Encode the value as necessary + return Encode(p, sb.ToString()); + } + finally { - var value = tr.ReadToEnd(); + // Pop the period off the serialization context stack + SerializationContext.Pop(); + } + } - var p = CreateAndAssociate() as Period; - var factory = GetService(); - if (p == null || factory == null) - { - return null; - } + public override object Deserialize(TextReader tr) + { + var value = tr.ReadToEnd(); - var dtSerializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; - var durationSerializer = factory.Build(typeof(TimeSpan), SerializationContext) as IStringSerializer; - if (dtSerializer == null || durationSerializer == null) - { - return null; - } + var p = CreateAndAssociate() as Period; + var factory = GetService(); + if (p == null || factory == null) + { + return null; + } - // Decode the value as necessary - value = Decode(p, value); + var dtSerializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; + var durationSerializer = factory.Build(typeof(TimeSpan), SerializationContext) as IStringSerializer; + if (dtSerializer == null || durationSerializer == null) + { + return null; + } - var values = value.Split('/'); - if (values.Length != 2) - { - return false; - } + // Decode the value as necessary + value = Decode(p, value); - p.StartTime = dtSerializer.Deserialize(new StringReader(values[0])) as IDateTime; - p.EndTime = dtSerializer.Deserialize(new StringReader(values[1])) as IDateTime; - if (p.EndTime == null) - { - p.Duration = (TimeSpan)durationSerializer.Deserialize(new StringReader(values[1])); - } + var values = value.Split('/'); + if (values.Length != 2) + { + return false; + } - // Only return an object if it has been deserialized correctly. - if (p.StartTime != null && p.Duration != null) - { - return p; - } + p.StartTime = dtSerializer.Deserialize(new StringReader(values[0])) as IDateTime; + p.EndTime = dtSerializer.Deserialize(new StringReader(values[1])) as IDateTime; + if (p.EndTime == null) + { + p.Duration = (TimeSpan) durationSerializer.Deserialize(new StringReader(values[1])); + } - return null; + // Only return an object if it has been deserialized correctly. + if (p.StartTime != null && p.Duration != null) + { + return p; } + + return null; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs b/Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs index 3d11fea09..0d7e95971 100644 --- a/Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs @@ -1,492 +1,496 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class RecurrencePatternSerializer : EncodableDataTypeSerializer { - public class RecurrencePatternSerializer : EncodableDataTypeSerializer - { - public RecurrencePatternSerializer() { } + public RecurrencePatternSerializer() { } - public RecurrencePatternSerializer(SerializationContext ctx) : base(ctx) { } + public RecurrencePatternSerializer(SerializationContext ctx) : base(ctx) { } - public static DayOfWeek GetDayOfWeek(string value) + public static DayOfWeek GetDayOfWeek(string value) + { + switch (value.ToUpper()) { - switch (value.ToUpper()) - { - case "SU": - return DayOfWeek.Sunday; - case "MO": - return DayOfWeek.Monday; - case "TU": - return DayOfWeek.Tuesday; - case "WE": - return DayOfWeek.Wednesday; - case "TH": - return DayOfWeek.Thursday; - case "FR": - return DayOfWeek.Friday; - case "SA": - return DayOfWeek.Saturday; - } - throw new ArgumentException(value + " is not a valid iCal day-of-week indicator."); + case "SU": + return DayOfWeek.Sunday; + case "MO": + return DayOfWeek.Monday; + case "TU": + return DayOfWeek.Tuesday; + case "WE": + return DayOfWeek.Wednesday; + case "TH": + return DayOfWeek.Thursday; + case "FR": + return DayOfWeek.Friday; + case "SA": + return DayOfWeek.Saturday; } + throw new ArgumentException(value + " is not a valid iCal day-of-week indicator."); + } - protected static void AddInt32Values(IList list, string value) + protected static void AddInt32Values(IList list, string value) + { + var values = value.Split(','); + foreach (var v in values) { - var values = value.Split(','); - foreach (var v in values) - { - list.Add(Convert.ToInt32(v)); - } + list.Add(Convert.ToInt32(v)); } + } - public virtual void CheckRange(string name, IList values, int min, int max) + public virtual void CheckRange(string name, IList values, int min, int max) + { + var allowZero = (min == 0 || max == 0); + foreach (var value in values) { - var allowZero = (min == 0 || max == 0); - foreach (var value in values) - { - CheckRange(name, value, min, max, allowZero); - } + CheckRange(name, value, min, max, allowZero); } + } - public virtual void CheckRange(string name, int value, int min, int max) + public virtual void CheckRange(string name, int value, int min, int max) + { + var allowZero = min == 0 || max == 0; + CheckRange(name, value, min, max, allowZero); + } + + public virtual void CheckRange(string name, int value, int min, int max, bool allowZero) + { + if (value != int.MinValue && (value < min || value > max || (!allowZero && value == 0))) { - var allowZero = min == 0 || max == 0; - CheckRange(name, value, min, max, allowZero); + throw new ArgumentException(name + " value " + value + " is out of range. Valid values are between " + min + " and " + max + + (allowZero ? "" : ", excluding zero (0)") + "."); } + } - public virtual void CheckRange(string name, int value, int min, int max, bool allowZero) + public virtual void CheckMutuallyExclusive(string name1, string name2, T obj1, TU obj2) + { + if (Equals(obj1, default(T)) || Equals(obj2, default(TU))) { - if (value != int.MinValue && (value < min || value > max || (!allowZero && value == 0))) - { - throw new ArgumentException(name + " value " + value + " is out of range. Valid values are between " + min + " and " + max + - (allowZero ? "" : ", excluding zero (0)") + "."); - } + return; } + // If the object is MinValue instead of its default, consider + // that to be unassigned. - public virtual void CheckMutuallyExclusive(string name1, string name2, T obj1, TU obj2) - { - if (Equals(obj1, default(T)) || Equals(obj2, default(TU))) - { - return; - } - // If the object is MinValue instead of its default, consider - // that to be unassigned. + var t1 = obj1.GetType(); - var t1 = obj1.GetType(); + var fi1 = t1.GetField("MinValue"); + var fi2 = t1.GetField("MinValue"); - var fi1 = t1.GetField("MinValue"); - var fi2 = t1.GetField("MinValue"); + var isMin1 = fi1 != null && obj1.Equals(fi1.GetValue(null)); + var isMin2 = fi2 != null && obj2.Equals(fi2.GetValue(null)); + if (isMin1 || isMin2) + { + return; + } - var isMin1 = fi1 != null && obj1.Equals(fi1.GetValue(null)); - var isMin2 = fi2 != null && obj2.Equals(fi2.GetValue(null)); - if (isMin1 || isMin2) - { - return; - } + throw new ArgumentException("Both " + name1 + " and " + name2 + " cannot be supplied together; they are mutually exclusive."); + } - throw new ArgumentException("Both " + name1 + " and " + name2 + " cannot be supplied together; they are mutually exclusive."); + private void SerializeByValue(List aggregate, IList byValue, string name) + { + if (byValue.Any()) + { + aggregate.Add(name + "=" + string.Join(",", byValue.Select(i => i.ToString()))); } + } + + public override Type TargetType => typeof(RecurrencePattern); - private void SerializeByValue(List aggregate, IList byValue, string name) + public override string SerializeToString(object obj) + { + var recur = obj as RecurrencePattern; + var factory = GetService(); + if (recur == null || factory == null) { - if (byValue.Any()) - { - aggregate.Add(name + "=" + string.Join(",", byValue.Select(i => i.ToString()))); - } + return null; } - public override Type TargetType => typeof(RecurrencePattern); + // Push the recurrence pattern onto the serialization stack + SerializationContext.Push(recur); + var values = new List() + { + "FREQ=" + Enum.GetName(typeof(FrequencyType), recur.Frequency).ToUpper() + }; + + + //-- FROM RFC2445 -- + //The INTERVAL rule part contains a positive integer representing how + //often the recurrence rule repeats. The default value is "1", meaning + //every second for a SECONDLY rule, or every minute for a MINUTELY + //rule, every hour for an HOURLY rule, every day for a DAILY rule, + //every week for a WEEKLY rule, every month for a MONTHLY rule and + //every year for a YEARLY rule. + var interval = recur.Interval; + if (interval == int.MinValue) + { + interval = 1; + } - public override string SerializeToString(object obj) + if (interval != 1) { - var recur = obj as RecurrencePattern; - var factory = GetService(); - if (recur == null || factory == null) - { - return null; - } + values.Add("INTERVAL=" + interval); + } - // Push the recurrence pattern onto the serialization stack - SerializationContext.Push(recur); - var values = new List() - { - "FREQ=" + Enum.GetName(typeof(FrequencyType), recur.Frequency).ToUpper() - }; - - - //-- FROM RFC2445 -- - //The INTERVAL rule part contains a positive integer representing how - //often the recurrence rule repeats. The default value is "1", meaning - //every second for a SECONDLY rule, or every minute for a MINUTELY - //rule, every hour for an HOURLY rule, every day for a DAILY rule, - //every week for a WEEKLY rule, every month for a MONTHLY rule and - //every year for a YEARLY rule. - var interval = recur.Interval; - if (interval == int.MinValue) + if (recur.Until != DateTime.MinValue) + { + var serializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; + if (serializer != null) { - interval = 1; + IDateTime until = new CalDateTime(recur.Until); + until.HasTime = true; + values.Add("UNTIL=" + serializer.SerializeToString(until)); } + } - if (interval != 1) - { - values.Add("INTERVAL=" + interval); - } + if (recur.FirstDayOfWeek != DayOfWeek.Monday) + { + values.Add("WKST=" + Enum.GetName(typeof(DayOfWeek), recur.FirstDayOfWeek).ToUpper().Substring(0, 2)); + } - if (recur.Until != DateTime.MinValue) - { - var serializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; - if (serializer != null) - { - IDateTime until = new CalDateTime(recur.Until); - until.HasTime = true; - values.Add("UNTIL=" + serializer.SerializeToString(until)); - } - } + if (recur.Count != int.MinValue) + { + values.Add("COUNT=" + recur.Count); + } - if (recur.FirstDayOfWeek != DayOfWeek.Monday) - { - values.Add("WKST=" + Enum.GetName(typeof(DayOfWeek), recur.FirstDayOfWeek).ToUpper().Substring(0, 2)); - } + if (recur.ByDay.Count > 0) + { + var bydayValues = new List(recur.ByDay.Count); - if (recur.Count != int.MinValue) + var serializer = factory.Build(typeof(WeekDay), SerializationContext) as IStringSerializer; + if (serializer != null) { - values.Add("COUNT=" + recur.Count); + bydayValues.AddRange(recur.ByDay.Select(byday => serializer.SerializeToString(byday))); } - if (recur.ByDay.Count > 0) - { - var bydayValues = new List(recur.ByDay.Count); - - var serializer = factory.Build(typeof(WeekDay), SerializationContext) as IStringSerializer; - if (serializer != null) - { - bydayValues.AddRange(recur.ByDay.Select(byday => serializer.SerializeToString(byday))); - } + values.Add("BYDAY=" + string.Join(",", bydayValues)); + } - values.Add("BYDAY=" + string.Join(",", bydayValues)); - } + SerializeByValue(values, recur.ByHour, "BYHOUR"); + SerializeByValue(values, recur.ByMinute, "BYMINUTE"); + SerializeByValue(values, recur.ByMonth, "BYMONTH"); + SerializeByValue(values, recur.ByMonthDay, "BYMONTHDAY"); + SerializeByValue(values, recur.BySecond, "BYSECOND"); + SerializeByValue(values, recur.BySetPosition, "BYSETPOS"); + SerializeByValue(values, recur.ByWeekNo, "BYWEEKNO"); + SerializeByValue(values, recur.ByYearDay, "BYYEARDAY"); - SerializeByValue(values, recur.ByHour, "BYHOUR"); - SerializeByValue(values, recur.ByMinute, "BYMINUTE"); - SerializeByValue(values, recur.ByMonth, "BYMONTH"); - SerializeByValue(values, recur.ByMonthDay, "BYMONTHDAY"); - SerializeByValue(values, recur.BySecond, "BYSECOND"); - SerializeByValue(values, recur.BySetPosition, "BYSETPOS"); - SerializeByValue(values, recur.ByWeekNo, "BYWEEKNO"); - SerializeByValue(values, recur.ByYearDay, "BYYEARDAY"); + // Pop the recurrence pattern off the serialization stack + SerializationContext.Pop(); - // Pop the recurrence pattern off the serialization stack - SerializationContext.Pop(); + return Encode(recur, string.Join(";", values)); + } - return Encode(recur, string.Join(";", values)); - } + //Compiling these is a one-time penalty of about 80ms + private const RegexOptions _ciCompiled = RegexOptions.IgnoreCase | RegexOptions.Compiled; - //Compiling these is a one-time penalty of about 80ms - private const RegexOptions _ciCompiled = RegexOptions.IgnoreCase | RegexOptions.Compiled; + internal static readonly Regex OtherInterval = + new Regex(@"every\s+(?other|\d+)?\w{0,2}\s*(?second|minute|hour|day|week|month|year)s?,?\s*(?.+)", _ciCompiled, RegexDefaults.Timeout); - internal static readonly Regex OtherInterval = - new Regex(@"every\s+(?other|\d+)?\w{0,2}\s*(?second|minute|hour|day|week|month|year)s?,?\s*(?.+)", _ciCompiled, RegexDefaults.Timeout); + internal static readonly Regex AdverbFrequencies = new Regex(@"FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY);?(.*)", _ciCompiled, RegexDefaults.Timeout); - internal static readonly Regex AdverbFrequencies = new Regex(@"FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY);?(.*)", _ciCompiled, RegexDefaults.Timeout); + internal static readonly Regex NumericTemporalUnits = new Regex(@"(?\d+)\w\w\s+(?second|minute|hour|day|week|month)", _ciCompiled, RegexDefaults.Timeout); - internal static readonly Regex NumericTemporalUnits = new Regex(@"(?\d+)\w\w\s+(?second|minute|hour|day|week|month)", _ciCompiled, RegexDefaults.Timeout); + internal static readonly Regex TemporalUnitType = new Regex(@"(?second|minute|hour|day|week|month)\s+(?\d+)", _ciCompiled, RegexDefaults.Timeout); - internal static readonly Regex TemporalUnitType = new Regex(@"(?second|minute|hour|day|week|month)\s+(?\d+)", _ciCompiled, RegexDefaults.Timeout); + internal static readonly Regex RelativeDaysOfWeek = + new Regex( + @"(?\d+\w{0,2})?(\w|\s)+?(?first)?(?last)?\s*((?sunday|monday|tuesday|wednesday|thursday|friday|saturday)\s*(and|or)?\s*)+", + _ciCompiled, RegexDefaults.Timeout); - internal static readonly Regex RelativeDaysOfWeek = - new Regex( - @"(?\d+\w{0,2})?(\w|\s)+?(?first)?(?last)?\s*((?sunday|monday|tuesday|wednesday|thursday|friday|saturday)\s*(and|or)?\s*)+", - _ciCompiled, RegexDefaults.Timeout); + internal static readonly Regex Time = new Regex(@"at\s+(?\d{1,2})(:(?\d{2})((:|\.)(?\d{2}))?)?\s*(?(a|p)m?)?", + _ciCompiled, RegexDefaults.Timeout); - internal static readonly Regex Time = new Regex(@"at\s+(?\d{1,2})(:(?\d{2})((:|\.)(?\d{2}))?)?\s*(?(a|p)m?)?", - _ciCompiled, RegexDefaults.Timeout); + internal static readonly Regex RecurUntil = new Regex(@"^\s*until\s+(?.+)$", _ciCompiled, RegexDefaults.Timeout); - internal static readonly Regex RecurUntil = new Regex(@"^\s*until\s+(?.+)$", _ciCompiled, RegexDefaults.Timeout); + internal static readonly Regex SpecificRecurrenceCount = new Regex(@"^\s*for\s+(?\d+)\s+occurrences\s*$", _ciCompiled, RegexDefaults.Timeout); - internal static readonly Regex SpecificRecurrenceCount = new Regex(@"^\s*for\s+(?\d+)\s+occurrences\s*$", _ciCompiled, RegexDefaults.Timeout); + public override object Deserialize(TextReader tr) + { + var value = tr.ReadToEnd(); - public override object Deserialize(TextReader tr) + // Instantiate the data type + var r = CreateAndAssociate() as RecurrencePattern; + var factory = GetService(); + if (r == null || factory == null) { - var value = tr.ReadToEnd(); + return r; + } - // Instantiate the data type - var r = CreateAndAssociate() as RecurrencePattern; - var factory = GetService(); - if (r == null || factory == null) - { - return r; - } + // Decode the value, if necessary + value = Decode(r, value); - // Decode the value, if necessary - value = Decode(r, value); + var match = AdverbFrequencies.Match(value); + if (match.Success) + { + // Parse the frequency type + r.Frequency = (FrequencyType) Enum.Parse(typeof(FrequencyType), match.Groups[1].Value, true); - var match = AdverbFrequencies.Match(value); - if (match.Success) + // NOTE: fixed a bug where the group 2 match + // resulted in an empty string, which caused + // an error. + if (match.Groups[2].Success && match.Groups[2].Length > 0) { - // Parse the frequency type - r.Frequency = (FrequencyType)Enum.Parse(typeof(FrequencyType), match.Groups[1].Value, true); - - // NOTE: fixed a bug where the group 2 match - // resulted in an empty string, which caused - // an error. - if (match.Groups[2].Success && match.Groups[2].Length > 0) + var keywordPairs = match.Groups[2].Value.Split(';'); + foreach (var keywordPair in keywordPairs) { - var keywordPairs = match.Groups[2].Value.Split(';'); - foreach (var keywordPair in keywordPairs) - { - var keyValues = keywordPair.Split('='); - var keyword = keyValues[0]; - var keyValue = keyValues[1]; + var keyValues = keywordPair.Split('='); + var keyword = keyValues[0]; + var keyValue = keyValues[1]; - switch (keyword.ToUpper()) + switch (keyword.ToUpper()) + { + case "UNTIL": + { + var serializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; + var dt = serializer?.Deserialize(new StringReader(keyValue)) as IDateTime; + if (dt != null) + { + r.Until = dt.Value; + } + } + break; + case "COUNT": + r.Count = Convert.ToInt32(keyValue); + break; + case "INTERVAL": + r.Interval = Convert.ToInt32(keyValue); + break; + case "BYSECOND": + AddInt32Values(r.BySecond, keyValue); + break; + case "BYMINUTE": + AddInt32Values(r.ByMinute, keyValue); + break; + case "BYHOUR": + AddInt32Values(r.ByHour, keyValue); + break; + case "BYDAY": { - case "UNTIL": - { - var serializer = factory.Build(typeof(IDateTime), SerializationContext) as IStringSerializer; - var dt = serializer?.Deserialize(new StringReader(keyValue)) as IDateTime; - if (dt != null) - { - r.Until = dt.Value; - } - } - break; - case "COUNT": - r.Count = Convert.ToInt32(keyValue); - break; - case "INTERVAL": - r.Interval = Convert.ToInt32(keyValue); - break; - case "BYSECOND": - AddInt32Values(r.BySecond, keyValue); - break; - case "BYMINUTE": - AddInt32Values(r.ByMinute, keyValue); - break; - case "BYHOUR": - AddInt32Values(r.ByHour, keyValue); - break; - case "BYDAY": - { - var days = keyValue.Split(','); - foreach (var day in days) - { - r.ByDay.Add(new WeekDay(day)); - } - } - break; - case "BYMONTHDAY": - AddInt32Values(r.ByMonthDay, keyValue); - break; - case "BYYEARDAY": - AddInt32Values(r.ByYearDay, keyValue); - break; - case "BYWEEKNO": - AddInt32Values(r.ByWeekNo, keyValue); - break; - case "BYMONTH": - AddInt32Values(r.ByMonth, keyValue); - break; - case "BYSETPOS": - AddInt32Values(r.BySetPosition, keyValue); - break; - case "WKST": - r.FirstDayOfWeek = GetDayOfWeek(keyValue); - break; + var days = keyValue.Split(','); + foreach (var day in days) + { + r.ByDay.Add(new WeekDay(day)); + } } + break; + case "BYMONTHDAY": + AddInt32Values(r.ByMonthDay, keyValue); + break; + case "BYYEARDAY": + AddInt32Values(r.ByYearDay, keyValue); + break; + case "BYWEEKNO": + AddInt32Values(r.ByWeekNo, keyValue); + break; + case "BYMONTH": + AddInt32Values(r.ByMonth, keyValue); + break; + case "BYSETPOS": + AddInt32Values(r.BySetPosition, keyValue); + break; + case "WKST": + r.FirstDayOfWeek = GetDayOfWeek(keyValue); + break; } } } + } - // - // This matches strings such as: - // - // "Every 6 minutes" - // "Every 3 days" - // - else if ((match = OtherInterval.Match(value)).Success) + // + // This matches strings such as: + // + // "Every 6 minutes" + // "Every 3 days" + // + else if ((match = OtherInterval.Match(value)).Success) + { + if (match.Groups["Interval"].Success) { - if (match.Groups["Interval"].Success) - { - int interval; - r.Interval = !int.TryParse(match.Groups["Interval"].Value, out interval) - ? 2 - : interval; - } - else - { - r.Interval = 1; - } + int interval; + r.Interval = !int.TryParse(match.Groups["Interval"].Value, out interval) + ? 2 + : interval; + } + else + { + r.Interval = 1; + } - switch (match.Groups["Freq"].Value.ToLower()) - { - case "second": - r.Frequency = FrequencyType.Secondly; - break; - case "minute": - r.Frequency = FrequencyType.Minutely; - break; - case "hour": - r.Frequency = FrequencyType.Hourly; - break; - case "day": - r.Frequency = FrequencyType.Daily; - break; - case "week": - r.Frequency = FrequencyType.Weekly; - break; - case "month": - r.Frequency = FrequencyType.Monthly; - break; - case "year": - r.Frequency = FrequencyType.Yearly; - break; - } + switch (match.Groups["Freq"].Value.ToLower()) + { + case "second": + r.Frequency = FrequencyType.Secondly; + break; + case "minute": + r.Frequency = FrequencyType.Minutely; + break; + case "hour": + r.Frequency = FrequencyType.Hourly; + break; + case "day": + r.Frequency = FrequencyType.Daily; + break; + case "week": + r.Frequency = FrequencyType.Weekly; + break; + case "month": + r.Frequency = FrequencyType.Monthly; + break; + case "year": + r.Frequency = FrequencyType.Yearly; + break; + } - var values = match.Groups["More"].Value.Split(','); - foreach (var item in values) + var values = match.Groups["More"].Value.Split(','); + foreach (var item in values) + { + if ((match = NumericTemporalUnits.Match(item)).Success || (match = TemporalUnitType.Match(item)).Success) { - if ((match = NumericTemporalUnits.Match(item)).Success || (match = TemporalUnitType.Match(item)).Success) + int num; + if (!int.TryParse(match.Groups["Num"].Value, out num)) { - int num; - if (!int.TryParse(match.Groups["Num"].Value, out num)) - { - continue; - } + continue; + } - switch (match.Groups["Type"].Value.ToLower()) - { - case "second": - r.BySecond.Add(num); - break; - case "minute": - r.ByMinute.Add(num); - break; - case "hour": - r.ByHour.Add(num); - break; - case "day": - switch (r.Frequency) - { - case FrequencyType.Yearly: - r.ByYearDay.Add(num); - break; - case FrequencyType.Monthly: - r.ByMonthDay.Add(num); - break; - } - break; - case "week": - r.ByWeekNo.Add(num); - break; - case "month": - r.ByMonth.Add(num); - break; - } + switch (match.Groups["Type"].Value.ToLower()) + { + case "second": + r.BySecond.Add(num); + break; + case "minute": + r.ByMinute.Add(num); + break; + case "hour": + r.ByHour.Add(num); + break; + case "day": + switch (r.Frequency) + { + case FrequencyType.Yearly: + r.ByYearDay.Add(num); + break; + case FrequencyType.Monthly: + r.ByMonthDay.Add(num); + break; + } + break; + case "week": + r.ByWeekNo.Add(num); + break; + case "month": + r.ByMonth.Add(num); + break; } - else if ((match = RelativeDaysOfWeek.Match(item)).Success) + } + else if ((match = RelativeDaysOfWeek.Match(item)).Success) + { + var num = int.MinValue; + if (match.Groups["Num"].Success) { - var num = int.MinValue; - if (match.Groups["Num"].Success) + if (int.TryParse(match.Groups["Num"].Value, out num)) { - if (int.TryParse(match.Groups["Num"].Value, out num)) + if (match.Groups["Last"].Success) { - if (match.Groups["Last"].Success) - { - // Make number negative - num *= -1; - } + // Make number negative + num *= -1; } } - else if (match.Groups["Last"].Success) - { - num = -1; - } - else if (match.Groups["First"].Success) - { - num = 1; - } - - var dayOfWeekQuery = from Capture capture in match.Groups["Day"].Captures - select (DayOfWeek)Enum.Parse(typeof(DayOfWeek), capture.Value, true) into dayOfWeek - select new WeekDay(dayOfWeek) { Offset = num }; - - r.ByDay.AddRange(dayOfWeekQuery); } - else if ((match = Time.Match(item)).Success) + else if (match.Groups["Last"].Success) { - int hour; - - if (!int.TryParse(match.Groups["Hour"].Value, out hour)) - { - continue; - } + num = -1; + } + else if (match.Groups["First"].Success) + { + num = 1; + } - // Adjust for PM - if (match.Groups["Meridian"].Success && match.Groups["Meridian"].Value.ToUpper().StartsWith("P")) - { - hour += 12; - } + var dayOfWeekQuery = from Capture capture in match.Groups["Day"].Captures + select (DayOfWeek) Enum.Parse(typeof(DayOfWeek), capture.Value, true) into dayOfWeek + select new WeekDay(dayOfWeek) { Offset = num }; - r.ByHour.Add(hour); + r.ByDay.AddRange(dayOfWeekQuery); + } + else if ((match = Time.Match(item)).Success) + { + int hour; - int minute; - if (match.Groups["Minute"].Success && int.TryParse(match.Groups["Minute"].Value, out minute)) - { - r.ByMinute.Add(minute); - int second; - if (match.Groups["Second"].Success && int.TryParse(match.Groups["Second"].Value, out second)) - { - r.BySecond.Add(second); - } - } - } - else if ((match = RecurUntil.Match(item)).Success) + if (!int.TryParse(match.Groups["Hour"].Value, out hour)) { - var dt = DateTime.Parse(match.Groups["DateTime"].Value); - DateTime.SpecifyKind(dt, DateTimeKind.Utc); + continue; + } - r.Until = dt; + // Adjust for PM + if (match.Groups["Meridian"].Success && match.Groups["Meridian"].Value.ToUpper().StartsWith("P")) + { + hour += 12; } - else if ((match = SpecificRecurrenceCount.Match(item)).Success) + + r.ByHour.Add(hour); + + int minute; + if (match.Groups["Minute"].Success && int.TryParse(match.Groups["Minute"].Value, out minute)) { - int count; - if (!int.TryParse(match.Groups["Count"].Value, out count)) + r.ByMinute.Add(minute); + int second; + if (match.Groups["Second"].Success && int.TryParse(match.Groups["Second"].Value, out second)) { - return false; + r.BySecond.Add(second); } - r.Count = count; } } - } - else - { - // Couldn't parse the object, return null! - r = null; - } + else if ((match = RecurUntil.Match(item)).Success) + { + var dt = DateTime.Parse(match.Groups["DateTime"].Value); + DateTime.SpecifyKind(dt, DateTimeKind.Utc); - if (r == null) - { - return r; + r.Until = dt; + } + else if ((match = SpecificRecurrenceCount.Match(item)).Success) + { + int count; + if (!int.TryParse(match.Groups["Count"].Value, out count)) + { + return false; + } + r.Count = count; + } } + } + else + { + // Couldn't parse the object, return null! + r = null; + } - CheckMutuallyExclusive("COUNT", "UNTIL", r.Count, r.Until); - CheckRange("INTERVAL", r.Interval, 1, int.MaxValue); - CheckRange("COUNT", r.Count, 1, int.MaxValue); - CheckRange("BYSECOND", r.BySecond, 0, 59); - CheckRange("BYMINUTE", r.ByMinute, 0, 59); - CheckRange("BYHOUR", r.ByHour, 0, 23); - CheckRange("BYMONTHDAY", r.ByMonthDay, -31, 31); - CheckRange("BYYEARDAY", r.ByYearDay, -366, 366); - CheckRange("BYWEEKNO", r.ByWeekNo, -53, 53); - CheckRange("BYMONTH", r.ByMonth, 1, 12); - CheckRange("BYSETPOS", r.BySetPosition, -366, 366); - + if (r == null) + { return r; } + + CheckMutuallyExclusive("COUNT", "UNTIL", r.Count, r.Until); + CheckRange("INTERVAL", r.Interval, 1, int.MaxValue); + CheckRange("COUNT", r.Count, 1, int.MaxValue); + CheckRange("BYSECOND", r.BySecond, 0, 59); + CheckRange("BYMINUTE", r.ByMinute, 0, 59); + CheckRange("BYHOUR", r.ByHour, 0, 23); + CheckRange("BYMONTHDAY", r.ByMonthDay, -31, 31); + CheckRange("BYYEARDAY", r.ByYearDay, -366, 366); + CheckRange("BYWEEKNO", r.ByWeekNo, -53, 53); + CheckRange("BYMONTH", r.ByMonth, 1, 12); + CheckRange("BYSETPOS", r.BySetPosition, -366, 366); + + return r; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/RequestStatusSerializer.cs b/Ical.Net/Serialization/DataTypes/RequestStatusSerializer.cs index f192884a3..429131bf3 100644 --- a/Ical.Net/Serialization/DataTypes/RequestStatusSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/RequestStatusSerializer.cs @@ -1,121 +1,125 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; using System.Text; using System.Text.RegularExpressions; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class RequestStatusSerializer : StringSerializer { - public class RequestStatusSerializer : StringSerializer - { - public RequestStatusSerializer() { } + public RequestStatusSerializer() { } - public RequestStatusSerializer(SerializationContext ctx) : base(ctx) { } + public RequestStatusSerializer(SerializationContext ctx) : base(ctx) { } - public override Type TargetType => typeof(RequestStatus); + public override Type TargetType => typeof(RequestStatus); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + try { + var rs = obj as RequestStatus; + if (rs == null) + { + return null; + } + + // Push the object onto the serialization stack + SerializationContext.Push(rs); + try { - var rs = obj as RequestStatus; - if (rs == null) + var factory = GetService(); + var serializer = factory?.Build(typeof(StatusCode), SerializationContext) as IStringSerializer; + if (serializer == null) { return null; } - // Push the object onto the serialization stack - SerializationContext.Push(rs); - - try + var builder = new StringBuilder(); + builder.Append(Escape(serializer.SerializeToString(rs.StatusCode))); + builder.Append(";"); + builder.Append(Escape(rs.Description)); + if (!string.IsNullOrWhiteSpace(rs.ExtraData)) { - var factory = GetService(); - var serializer = factory?.Build(typeof(StatusCode), SerializationContext) as IStringSerializer; - if (serializer == null) - { - return null; - } - - var builder = new StringBuilder(); - builder.Append(Escape(serializer.SerializeToString(rs.StatusCode))); builder.Append(";"); - builder.Append(Escape(rs.Description)); - if (!string.IsNullOrWhiteSpace(rs.ExtraData)) - { - builder.Append(";"); - builder.Append(Escape(rs.ExtraData)); - } - return Encode(rs, builder.ToString()); - } - finally - { - // Pop the object off the serialization stack - SerializationContext.Pop(); + builder.Append(Escape(rs.ExtraData)); } + return Encode(rs, builder.ToString()); } - catch + finally { - return null; + // Pop the object off the serialization stack + SerializationContext.Pop(); } } + catch + { + return null; + } + } - internal static readonly Regex NarrowRequestMatch = new Regex(@"(.*?[^\\]);(.*?[^\\]);(.+)", RegexOptions.Compiled, RegexDefaults.Timeout); - internal static readonly Regex BroadRequestMatch = new Regex(@"(.*?[^\\]);(.+)", RegexOptions.Compiled, RegexDefaults.Timeout); + internal static readonly Regex NarrowRequestMatch = new Regex(@"(.*?[^\\]);(.*?[^\\]);(.+)", RegexOptions.Compiled, RegexDefaults.Timeout); + internal static readonly Regex BroadRequestMatch = new Regex(@"(.*?[^\\]);(.+)", RegexOptions.Compiled, RegexDefaults.Timeout); - public override object Deserialize(TextReader tr) + public override object Deserialize(TextReader tr) + { + var value = tr.ReadToEnd(); + + var rs = CreateAndAssociate() as RequestStatus; + if (rs == null) { - var value = tr.ReadToEnd(); + return null; + } - var rs = CreateAndAssociate() as RequestStatus; - if (rs == null) + // Decode the value as needed + value = Decode(rs, value); + + // Push the object onto the serialization stack + SerializationContext.Push(rs); + + try + { + var factory = GetService(); + if (factory == null) { return null; } - // Decode the value as needed - value = Decode(rs, value); - - // Push the object onto the serialization stack - SerializationContext.Push(rs); + var match = NarrowRequestMatch.Match(value); + if (!match.Success) + { + match = BroadRequestMatch.Match(value); + } - try + if (match.Success) { - var factory = GetService(); - if (factory == null) + var serializer = factory.Build(typeof(StatusCode), SerializationContext) as IStringSerializer; + if (serializer == null) { return null; } - var match = NarrowRequestMatch.Match(value); - if (!match.Success) + rs.StatusCode = serializer.Deserialize(new StringReader(Unescape(match.Groups[1].Value))) as StatusCode; + rs.Description = Unescape(match.Groups[2].Value); + if (match.Groups.Count == 4) { - match = BroadRequestMatch.Match(value); + rs.ExtraData = Unescape(match.Groups[3].Value); } - if (match.Success) - { - var serializer = factory.Build(typeof(StatusCode), SerializationContext) as IStringSerializer; - if (serializer == null) - { - return null; - } - - rs.StatusCode = serializer.Deserialize(new StringReader(Unescape(match.Groups[1].Value))) as StatusCode; - rs.Description = Unescape(match.Groups[2].Value); - if (match.Groups.Count == 4) - { - rs.ExtraData = Unescape(match.Groups[3].Value); - } - - return rs; - } - } - finally - { - // Pop the object off the serialization stack - SerializationContext.Pop(); + return rs; } - return null; } + finally + { + // Pop the object off the serialization stack + SerializationContext.Pop(); + } + return null; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/StatusCodeSerializer.cs b/Ical.Net/Serialization/DataTypes/StatusCodeSerializer.cs index 5e93808d5..634e5c9f6 100644 --- a/Ical.Net/Serialization/DataTypes/StatusCodeSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/StatusCodeSerializer.cs @@ -1,68 +1,72 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.IO; using System.Text.RegularExpressions; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class StatusCodeSerializer : StringSerializer { - public class StatusCodeSerializer : StringSerializer - { - public StatusCodeSerializer() { } + public StatusCodeSerializer() { } - public StatusCodeSerializer(SerializationContext ctx) : base(ctx) { } + public StatusCodeSerializer(SerializationContext ctx) : base(ctx) { } - public override Type TargetType => typeof(StatusCode); + public override Type TargetType => typeof(StatusCode); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + var sc = obj as StatusCode; + if (sc == null) { - var sc = obj as StatusCode; - if (sc == null) - { - return null; - } + return null; + } - var vals = new string[sc.Parts.Length]; - for (var i = 0; i < sc.Parts.Length; i++) - { - vals[i] = sc.Parts[i].ToString(); - } - return Encode(sc, Escape(string.Join(".", vals))); + var vals = new string[sc.Parts.Length]; + for (var i = 0; i < sc.Parts.Length; i++) + { + vals[i] = sc.Parts[i].ToString(); } + return Encode(sc, Escape(string.Join(".", vals))); + } - internal static readonly Regex StatusCode = new Regex(@"\d(\.\d+)*", RegexOptions.Compiled | RegexOptions.CultureInvariant, RegexDefaults.Timeout); + internal static readonly Regex StatusCode = new Regex(@"\d(\.\d+)*", RegexOptions.Compiled | RegexOptions.CultureInvariant, RegexDefaults.Timeout); - public override object Deserialize(TextReader tr) - { - var value = tr.ReadToEnd(); + public override object Deserialize(TextReader tr) + { + var value = tr.ReadToEnd(); - var sc = CreateAndAssociate() as StatusCode; - if (sc == null) - { - return null; - } + var sc = CreateAndAssociate() as StatusCode; + if (sc == null) + { + return null; + } - // Decode the value as needed - value = Decode(sc, value); + // Decode the value as needed + value = Decode(sc, value); - var match = StatusCode.Match(value); - if (!match.Success) - { - return null; - } + var match = StatusCode.Match(value); + if (!match.Success) + { + return null; + } - var parts = match.Value.Split('.'); - var iparts = new int[parts.Length]; - for (var i = 0; i < parts.Length; i++) + var parts = match.Value.Split('.'); + var iparts = new int[parts.Length]; + for (var i = 0; i < parts.Length; i++) + { + int num; + if (!int.TryParse(parts[i], out num)) { - int num; - if (!int.TryParse(parts[i], out num)) - { - return false; - } - iparts[i] = num; + return false; } - - return new StatusCode(iparts); + iparts[i] = num; } + + return new StatusCode(iparts); } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/StringSerializer.cs b/Ical.Net/Serialization/DataTypes/StringSerializer.cs index e54946cb1..132426c49 100644 --- a/Ical.Net/Serialization/DataTypes/StringSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/StringSerializer.cs @@ -1,153 +1,157 @@ -using Ical.Net.DataTypes; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using Ical.Net.DataTypes; + +namespace Ical.Net.Serialization.DataTypes; -namespace Ical.Net.Serialization.DataTypes +public class StringSerializer : EncodableDataTypeSerializer { - public class StringSerializer : EncodableDataTypeSerializer - { - public StringSerializer() { } + public StringSerializer() { } - public StringSerializer(SerializationContext ctx) : base(ctx) { } + public StringSerializer(SerializationContext ctx) : base(ctx) { } - internal static readonly Regex SingleBackslashMatch = new Regex(@"(? typeof(string); + // NOTE: fixed a bug that caused text parsing to fail on + // programmatically entered strings. + // SEE unit test SERIALIZE25(). + value = value.Replace(SerializationConstants.LineBreak, @"\n"); + value = value.Replace("\r", @"\n"); + value = value.Replace("\n", @"\n"); + value = value.Replace(";", @"\;"); + value = value.Replace(",", @"\,"); + return value; + } + + public override Type TargetType => typeof(string); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + if (obj == null) { - if (obj == null) - { - return null; - } + return null; + } - var values = new List(); - if (obj is string) - { - values.Add((string)obj); - } - else if (obj is IEnumerable) - { - values.AddRange(from object child in (IEnumerable)obj select child.ToString()); - } + var values = new List(); + if (obj is string) + { + values.Add((string) obj); + } + else if (obj is IEnumerable) + { + values.AddRange(from object child in (IEnumerable) obj select child.ToString()); + } - var co = SerializationContext.Peek() as ICalendarObject; - if (co != null) + var co = SerializationContext.Peek() as ICalendarObject; + if (co != null) + { + // Encode the string as needed. + var dt = new EncodableDataType { - // Encode the string as needed. - var dt = new EncodableDataType - { - AssociatedObject = co - }; - for (var i = 0; i < values.Count; i++) - { - values[i] = Encode(dt, Escape(values[i])); - } - - return string.Join(",", values); - } - + AssociatedObject = co + }; for (var i = 0; i < values.Count; i++) { - values[i] = Escape(values[i]); + values[i] = Encode(dt, Escape(values[i])); } + return string.Join(",", values); } - internal static readonly Regex UnescapedCommas = new Regex(@"(? or simply a string, - // depending on the input text. Anything that uses this serializer should - // be prepared to receive either a string, or an IList. + var value = tr.ReadToEnd(); - var serializeAsList = false; + // NOTE: this can deserialize into an IList or simply a string, + // depending on the input text. Anything that uses this serializer should + // be prepared to receive either a string, or an IList. - // Determine if we can serialize this property - // with multiple values per line. - var co = SerializationContext.Peek() as ICalendarObject; - if (co is ICalendarProperty) - { - serializeAsList = GetService().GetPropertyAllowsMultipleValues(co); - } + var serializeAsList = false; - // Try to decode the string - EncodableDataType dt = null; - if (co != null) + // Determine if we can serialize this property + // with multiple values per line. + var co = SerializationContext.Peek() as ICalendarObject; + if (co is ICalendarProperty) + { + serializeAsList = GetService().GetPropertyAllowsMultipleValues(co); + } + + // Try to decode the string + EncodableDataType dt = null; + if (co != null) + { + dt = new EncodableDataType { - dt = new EncodableDataType - { - AssociatedObject = co - }; - } + AssociatedObject = co + }; + } - var encodedValues = serializeAsList ? UnescapedCommas.Split(value) : new[] { value }; - var escapedValues = encodedValues.Select(v => Decode(dt, v)).ToList(); - var values = escapedValues.Select(Unescape).ToList(); + var encodedValues = serializeAsList ? UnescapedCommas.Split(value) : new[] { value }; + var escapedValues = encodedValues.Select(v => Decode(dt, v)).ToList(); + var values = escapedValues.Select(Unescape).ToList(); - if (co is ICalendarProperty) - { - // Is this necessary? - co.SetService("EscapedValue", escapedValues.Count == 1 ? escapedValues[0] : (object)escapedValues); - } + if (co is ICalendarProperty) + { + // Is this necessary? + co.SetService("EscapedValue", escapedValues.Count == 1 ? escapedValues[0] : (object) escapedValues); + } - // Return either a single value, or the entire list. - if (values.Count == 1) - { - return values[0]; - } - return values; + // Return either a single value, or the entire list. + if (values.Count == 1) + { + return values[0]; } + return values; } } \ No newline at end of file diff --git a/Ical.Net/Serialization/DataTypes/TimeSpanSerializer.cs b/Ical.Net/Serialization/DataTypes/TimeSpanSerializer.cs index 27b594b2f..29db47a91 100644 --- a/Ical.Net/Serialization/DataTypes/TimeSpanSerializer.cs +++ b/Ical.Net/Serialization/DataTypes/TimeSpanSerializer.cs @@ -1,128 +1,132 @@ -using System; +// +// Copyright ical.net project maintainers and contributors. +// Licensed under the MIT license. +// + +using System; using System.IO; using System.Text; using System.Text.RegularExpressions; -namespace Ical.Net.Serialization.DataTypes +namespace Ical.Net.Serialization.DataTypes; + +public class TimeSpanSerializer : SerializerBase { - public class TimeSpanSerializer : SerializerBase - { - public TimeSpanSerializer() { } + public TimeSpanSerializer() { } - public TimeSpanSerializer(SerializationContext ctx) : base(ctx) { } + public TimeSpanSerializer(SerializationContext ctx) : base(ctx) { } - public override Type TargetType => typeof(TimeSpan); + public override Type TargetType => typeof(TimeSpan); - public override string SerializeToString(object obj) + public override string SerializeToString(object obj) + { + if (!(obj is TimeSpan)) { - if (!(obj is TimeSpan)) - { - return null; - } + return null; + } - var ts = (TimeSpan)obj; + var ts = (TimeSpan) obj; - if (ts == TimeSpan.Zero) - { - return "P0D"; - } + if (ts == TimeSpan.Zero) + { + return "P0D"; + } - var sb = new StringBuilder(); + var sb = new StringBuilder(); - if (ts < TimeSpan.Zero) - { - sb.Append("-"); - } + if (ts < TimeSpan.Zero) + { + sb.Append("-"); + } - sb.Append("P"); - if (ts.Days > 7 && ts.Days % 7 == 0 && ts.Hours == 0 && ts.Minutes == 0 && ts.Seconds == 0) + sb.Append("P"); + if (ts.Days > 7 && ts.Days % 7 == 0 && ts.Hours == 0 && ts.Minutes == 0 && ts.Seconds == 0) + { + sb.Append(Math.Round(Math.Abs((double) ts.Days) / 7) + "W"); + } + else + { + if (ts.Days != 0) { - sb.Append(Math.Round(Math.Abs((double)ts.Days) / 7) + "W"); + sb.Append(Math.Abs(ts.Days) + "D"); } - else + if (ts.Hours != 0 || ts.Minutes != 0 || ts.Seconds != 0) { - if (ts.Days != 0) + sb.Append("T"); + if (ts.Hours != 0) { - sb.Append(Math.Abs(ts.Days) + "D"); + sb.Append(Math.Abs(ts.Hours) + "H"); } - if (ts.Hours != 0 || ts.Minutes != 0 || ts.Seconds != 0) + if (ts.Minutes != 0) { - sb.Append("T"); - if (ts.Hours != 0) - { - sb.Append(Math.Abs(ts.Hours) + "H"); - } - if (ts.Minutes != 0) - { - sb.Append(Math.Abs(ts.Minutes) + "M"); - } - if (ts.Seconds != 0) - { - sb.Append(Math.Abs(ts.Seconds) + "S"); - } + sb.Append(Math.Abs(ts.Minutes) + "M"); + } + if (ts.Seconds != 0) + { + sb.Append(Math.Abs(ts.Seconds) + "S"); } } - - return sb.ToString(); } - internal static readonly Regex TimespanMatch = - new Regex(@"^(?\+|-)?P(((?\d+)W)|(?
((?\d+)D)?(?