From 094bad6a7076126c097f41d8954812c74f7b2f2d Mon Sep 17 00:00:00 2001 From: Edward Cooke Date: Mon, 21 Aug 2023 22:38:14 -0600 Subject: [PATCH] Added a DateTimeOffsetConverter with a sample on usage --- YamlDotNet.Benchmark/Program.cs | 11 +- YamlDotNet.Samples/UseTypeConverters.cs | 56 +++++++ .../DateTimeOffsetConverterTests.cs | 144 ++++++++++++++++++ .../Converters/DateTimeOffsetConverter.cs | 102 +++++++++++++ 4 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 YamlDotNet.Samples/UseTypeConverters.cs create mode 100644 YamlDotNet.Test/Serialization/DateTimeOffsetConverterTests.cs create mode 100644 YamlDotNet/Serialization/Converters/DateTimeOffsetConverter.cs diff --git a/YamlDotNet.Benchmark/Program.cs b/YamlDotNet.Benchmark/Program.cs index 9fc2ebd9..8b88132d 100644 --- a/YamlDotNet.Benchmark/Program.cs +++ b/YamlDotNet.Benchmark/Program.cs @@ -1,4 +1,11 @@ -using BenchmarkDotNet.Running; +using System.Globalization; +using BenchmarkDotNet.Running; using YamlDotNet.Benchmark; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; -BenchmarkSwitcher.FromAssembly(typeof(YamlStreamBenchmark).Assembly).Run(args); +var dateTimeOffset = new DateTimeOffset(new DateTime(2017, 1, 2, 3, 4, 5), new TimeSpan(-6, 0, 0)); +Console.WriteLine(dateTimeOffset.ToString("MM/dd/yyyy HH:mm:ss zzz", CultureInfo.InvariantCulture)); +Console.WriteLine(dateTimeOffset.ToString("O", CultureInfo.InvariantCulture)); diff --git a/YamlDotNet.Samples/UseTypeConverters.cs b/YamlDotNet.Samples/UseTypeConverters.cs new file mode 100644 index 00000000..8459428b --- /dev/null +++ b/YamlDotNet.Samples/UseTypeConverters.cs @@ -0,0 +1,56 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit.Abstractions; +using YamlDotNet.Samples.Helpers; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.Converters; + +namespace YamlDotNet.Samples +{ + public class UseTypeConverters + { + ITestOutputHelper output; + + public UseTypeConverters(ITestOutputHelper output) + { + this.output = output; + } + + [Sample( + Description = "Shows how to deserialize objects with a type converter", + DisplayName = "Type Converters")] + public void Main() + { + var serializer = new SerializerBuilder() + .WithTypeConverter(new DateTimeOffsetConverter()) + .Build(); + var o = new { Hello = new DateTimeOffset(DateTime.Now, new TimeSpan(-6, 0, 0)) }; + var yaml = serializer.Serialize(o); + output.WriteLine(yaml); + } + } +} diff --git a/YamlDotNet.Test/Serialization/DateTimeOffsetConverterTests.cs b/YamlDotNet.Test/Serialization/DateTimeOffsetConverterTests.cs new file mode 100644 index 00000000..8bfbb222 --- /dev/null +++ b/YamlDotNet.Test/Serialization/DateTimeOffsetConverterTests.cs @@ -0,0 +1,144 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Globalization; +using FakeItEasy; +using FluentAssertions; +using Xunit; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.Converters; + +namespace YamlDotNet.Test.Serialization +{ + /// + /// This represents the test entity for the class. + /// + public class DateTimeOffsetConverterTests + { + private readonly DateTimeOffset _expected = new DateTimeOffset(new DateTime(2017, 1, 2, 3, 4, 5), new TimeSpan(-6, 0, 0)); + /// + /// Tests whether the Accepts() method should return expected result or not. + /// + /// to check. + /// Expected result. + [Theory] + [InlineData(typeof(DateTimeOffset), true)] + [InlineData(typeof(string), false)] + public void AcceptsTypeReturns(Type type, bool expected) + { + var converter = new DateTimeOffsetConverter(CultureInfo.InvariantCulture); + + var result = converter.Accepts(type); + + result.Should().Be(expected); + } + + [Fact] + public void InvalidFormatThrowsException() + { + var yaml = "2019-01-01"; + + var parser = A.Fake(); + A.CallTo(() => parser.Current).ReturnsLazily(() => new Scalar(yaml)); + + var converter = new DateTimeOffsetConverter(CultureInfo.InvariantCulture); + + Action action = () => { converter.ReadYaml(parser, typeof(DateTimeOffset)); }; + + action.ShouldThrow(); + } + + [Fact] + public void ValidYamlReturnsDateTimeOffsetDefaultFormat() + { + var yaml = "2017-01-02T03:04:05.0000000-06:00"; + + var parser = A.Fake(); + A.CallTo(() => parser.Current).ReturnsLazily(() => new Scalar(yaml)); + var converter = new DateTimeOffsetConverter(CultureInfo.InvariantCulture); + var actual = converter.ReadYaml(parser, typeof(DateTimeOffset)); + Assert.Equal(_expected, actual); + } + + [Fact] + public void ValidYamlReturnsDateTimeOffsetAdditionalFormats() + { + var yaml = "01/02/2017 03:04:05 -06:00"; + + var parser = A.Fake(); + A.CallTo(() => parser.Current).ReturnsLazily(() => new Scalar(yaml)); + + var converter = new DateTimeOffsetConverter( + CultureInfo.InvariantCulture, + ScalarStyle.Any, + DateTimeStyles.None, + "O", + "MM/dd/yyyy HH:mm:ss zzz"); + + converter.ReadYaml(parser, typeof(DateTimeOffset)); + var actual = converter.ReadYaml(parser, typeof(DateTimeOffset)); + Assert.Equal(_expected, actual); + } + + [Fact] + public void ShouldSerializeRoundTripWithDefaults() + { + var converter = new DateTimeOffsetConverter(CultureInfo.InvariantCulture); + var serializer = new SerializerBuilder().WithTypeConverter(converter).Build(); + var actual = serializer.Serialize(new Test { X = _expected }).NormalizeNewLines(); + var expected = "X: 2017-01-02T03:04:05.0000000-06:00\r\n".NormalizeNewLines(); + Assert.Equal(actual, expected); + } + + [Fact] + public void ShouldSerializeWithCustomFormat() + { + var converter = new DateTimeOffsetConverter( + CultureInfo.InvariantCulture, + ScalarStyle.Any, + DateTimeStyles.None, + "MM/dd/yyyy HH:mm:ss zzz"); + + var serializer = new SerializerBuilder().WithTypeConverter(converter).Build(); + var actual = serializer.Serialize(new Test { X = _expected }).NormalizeNewLines(); + var expected = "X: 01/02/2017 03:04:05 -06:00\r\n".NormalizeNewLines(); + Assert.Equal(actual, expected); + } + + [Fact] + public void ShouldSerializeAndRespectQuotingStyle() + { + var converter = new DateTimeOffsetConverter(CultureInfo.InvariantCulture, ScalarStyle.DoubleQuoted); + var serializer = new SerializerBuilder().WithTypeConverter(converter).Build(); + var actual = serializer.Serialize(new Test { X = _expected }).NormalizeNewLines(); + var expected = "X: \"2017-01-02T03:04:05.0000000-06:00\"\r\n".NormalizeNewLines(); + Assert.Equal(actual, expected); + } + + private class Test + { + public DateTimeOffset X { get; set; } + } + } +} diff --git a/YamlDotNet/Serialization/Converters/DateTimeOffsetConverter.cs b/YamlDotNet/Serialization/Converters/DateTimeOffsetConverter.cs new file mode 100644 index 00000000..81f7c51a --- /dev/null +++ b/YamlDotNet/Serialization/Converters/DateTimeOffsetConverter.cs @@ -0,0 +1,102 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Globalization; +using System.Linq; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; + +namespace YamlDotNet.Serialization.Converters +{ + /// + /// Converts the object to a string representation + /// To use this converter, call WithTypeConverter(new DateTimeOffsetConverter()) on the + /// or . + /// + public class DateTimeOffsetConverter : IYamlTypeConverter + { + private readonly IFormatProvider provider; + private readonly ScalarStyle style; + private readonly DateTimeStyles dateStyle; + private readonly string[] formats; + + /// + /// Initializes a new instance of the class. + /// + /// instance. Default value is . + /// If true, will use double quotes when writing the value to the stream. + /// + /// List of date/time formats for parsing. Default value is "O". + /// On deserializing, all formats in the list are used for conversion, while on serializing, the first format in the list is used. + public DateTimeOffsetConverter( + IFormatProvider? provider = null, + ScalarStyle style = ScalarStyle.Any, + DateTimeStyles dateStyle = DateTimeStyles.None, + params string[] formats) + { + this.provider = provider ?? CultureInfo.InvariantCulture; + this.style = style; + this.dateStyle = dateStyle; + this.formats = formats.DefaultIfEmpty("O").ToArray(); + } + + /// + /// Gets a value indicating whether the current converter supports converting the specified type. + /// + /// to check. + /// Returns True, if the current converter supports; otherwise returns False. + public bool Accepts(Type type) + { + return type == typeof(DateTimeOffset); + } + + /// + /// Reads an object's state from a YAML parser. + /// + /// instance. + /// to convert. + /// Returns the instance converted. + /// On deserializing, all formats in the list are used for conversion. + public object ReadYaml(IParser parser, Type type) + { + var value = parser.Consume().Value; + var result = DateTimeOffset.ParseExact(value, formats, provider, dateStyle); + + return result; + } + + /// + /// Writes the specified object's state to a YAML emitter. + /// + /// instance. + /// Value to write. + /// to convert. + /// On serializing, the first format in the list is used. + public void WriteYaml(IEmitter emitter, object? value, Type type) + { + var dt = (DateTimeOffset)value!; + var formatted = dt.ToString(formats.First(), this.provider); // Always take the first format of the list. + + emitter.Emit(new Scalar(AnchorName.Empty, TagName.Empty, formatted, style, true, false)); + } + } +}