├── NodaTime Release.snk ├── global.json ├── src ├── NodaTime.Serialization.Test │ ├── GlobalUsings.cs │ ├── Protobuf │ │ ├── NodaExtensionsTest.cs │ │ ├── ProtobufExtensionsTest.cs │ │ ├── NodaExtensionsTest.ToTimeOfDay.cs │ │ ├── NodaExtensionsTest.ToDate.cs │ │ ├── ProtobufExtensionsTest.ToIsoDayOfWeek.cs │ │ ├── NodaExtensionsTest.ToProtobufDayOfWeek.cs │ │ ├── ProtobufExtensionsTest.ToLocalDate.cs │ │ ├── NodaExtensionsTest.ToTimestamp.cs │ │ ├── NodaExtensionsTest.ToProtobufDuration.cs │ │ ├── ProtobufExtensionsTest.ToNodaDuration.cs │ │ ├── ProtobufExtensionsTest.ToInstant.cs │ │ └── ProtobufExtensionsTest.ToLocalTime.cs │ ├── NodaTime.Serialization.Test.csproj │ ├── SystemTextJson │ │ ├── NodaNullableConverterTest.cs │ │ ├── TestHelper.cs │ │ ├── NodaTimeDefaultJsonConverterAttributeTest.cs │ │ ├── DelegatingConverterBaseTest.cs │ │ ├── NodaAnnualDateConverterTest.cs │ │ ├── NodaIsoDateIntervalConverterTest.cs │ │ ├── NodaDateTimeZoneConverterTest.cs │ │ ├── NodaInstantConverterTest.cs │ │ ├── ExtensionsTest.cs │ │ ├── NodaConverterBaseTest.cs │ │ ├── NodaIsoIntervalConverterTest.cs │ │ └── NodaTimeDefaultJsonConverterFactoryTest.cs │ └── JsonNet │ │ ├── TestHelper.cs │ │ ├── NodaDateTimeZoneConverterTest.cs │ │ ├── DelegatingConverterBaseTest.cs │ │ ├── NodaAnnualDateConverterTest.cs │ │ ├── NodaIsoDateIntervalConverterTest.cs │ │ ├── NodaInstantConverterTest.cs │ │ ├── NodaIsoIntervalConverterTest.cs │ │ ├── NodaConverterBaseTest.cs │ │ ├── NodaDateIntervalConverterTest.cs │ │ └── NodaIntervalConverterTest.cs ├── NodaTime.Serialization.JsonNet │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── NodaTime.Serialization.JsonNet.csproj │ ├── Preconditions.cs │ ├── NodaIsoIntervalConverter.cs │ ├── NodaDateTimeZoneConverter.cs │ ├── NodaIsoDateIntervalConverter.cs │ ├── DelegatingConverterBase.cs │ ├── NodaPatternConverter.cs │ ├── NodaIntervalConverter.cs │ ├── NodaDateIntervalConverter.cs │ ├── NodaJsonSettings.cs │ └── NodaConverterBase.cs ├── NodaTime.Serialization.Benchmarks │ ├── NodaTime.Serialization.Benchmarks.csproj │ ├── Program.cs │ └── JsonNet │ │ └── NodaConverterBaseBenchmarks.cs ├── NodaTime.Serialization.Protobuf │ ├── AssemblyInfo.cs │ ├── NodaTime.Serialization.Protobuf.csproj │ ├── Preconditions.cs │ └── NodaExtensions.cs ├── NodaTime.Serialization.SystemTextJson │ ├── NodaTime.Serialization.SystemTextJson.csproj │ ├── Preconditions.cs │ ├── NodaTimeDefaultJsonConverterAttribute.cs │ ├── NodaNullableConverter.cs │ ├── DelegatingConverterBase.cs │ ├── NodaDateTimeZoneConverter.cs │ ├── NodaIsoIntervalConverter.cs │ ├── NodaIsoDateIntervalConverter.cs │ ├── NodaTimeDefaultJsonConverterFactory.cs │ ├── NodaPatternConverter.cs │ ├── NodaIntervalConverter.cs │ ├── NodaDateIntervalConverter.cs │ ├── NodaJsonSettings.cs │ ├── Extensions.cs │ └── NodaConverterBase.cs └── NodaTime.Serialization.sln ├── .gitignore ├── .github └── workflows │ ├── pull-request.yaml │ └── push.yaml ├── Directory.Build.targets ├── Directory.Packages.props ├── README.md ├── NuGet-README.md └── Directory.Build.props /NodaTime Release.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodatime/nodatime.serialization/HEAD/NodaTime Release.snk -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.101", 4 | "allowPrerelease": false, 5 | "rollForward": "latestMinor" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | // See https://docs.nunit.org/articles/nunit/release-notes/Nunit4.0-MigrationGuide.html 2 | global using Assert = NUnit.Framework.Legacy.ClassicAssert; 3 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | 7 | [assembly: CLSCompliant(true)] 8 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/NodaExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | namespace NodaTime.Serialization.Test.Protobuf 6 | { 7 | public partial class NodaExtensionsTest 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Benchmarks/NodaTime.Serialization.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | Exe 6 | False 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Editor backups and temporary files 2 | *~ 3 | *.bak 4 | *.swp 5 | 6 | # IDE-generated files 7 | *.suo 8 | *.user 9 | *.sln.cache 10 | .vs 11 | 12 | # IDE add-ins 13 | *.dotCover 14 | *.ncrunchproject 15 | *.ncrunchsolution 16 | _NCrunch* 17 | .resharper 18 | *.ReSharper 19 | _ReSharper* 20 | StyleCop.Cache 21 | .cr 22 | 23 | # Auto-created thumbnails 24 | Thumbs.db 25 | 26 | # Build output directories (anywhere) 27 | **/bin/ 28 | **/obj/ 29 | 30 | # Release build output 31 | /build/releasebuild 32 | 33 | # NUnit output 34 | TestResult.xml 35 | 36 | # BenchmarkDotNet output 37 | BenchmarkDotNet.Artifacts 38 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using BenchmarkDotNet.Running; 6 | using System.Reflection; 7 | 8 | namespace NodaTime.Serialization.Benchmarks 9 | { 10 | /// 11 | /// Entry point for benchmarking. 12 | /// 13 | public class Program 14 | { 15 | // Run it with args = { "*" } for choosing all of target benchmarks 16 | public static void Main(string[] args) 17 | { 18 | new BenchmarkSwitcher(typeof(Program).Assembly).Run(args); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Protobuf/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Runtime.CompilerServices; 6 | 7 | [assembly: InternalsVisibleTo("NodaTime.Serialization.Test," 8 | + "PublicKey=0024000004800000940000000602000000240000525341310004000001000100d335797ef2bff7" 9 | + "4db7c046f874523c553f88d3f8e0c2ba769820c54f0e64a11b47198b544c74abb487f8d3b64669" 10 | + "08ae2ac6fced4738e46a75e5661d5ac03fb29c7e26b13a220400cb9df95134e85716203f83b96f" 11 | + "ab661135c39b10f33e1c467a6750d8af331c602351b09a7bf5dd3a8943712d676481c5054c8031" 12 | + "84f77ed5")] 13 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yaml: -------------------------------------------------------------------------------- 1 | name: Build pull request 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | 7 | build: 8 | runs-on: ubuntu-latest 9 | env: 10 | DOTNET_NOLOGO: true 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | submodules: true 16 | 17 | - name: Setup .NET SDK 18 | uses: actions/setup-dotnet@v4 19 | with: 20 | dotnet-version: | 21 | 6.0.x 22 | 8.0.x 23 | 24 | - name: Build and test 25 | run: | 26 | dotnet test src/NodaTime.Serialization.Test 27 | dotnet pack src/NodaTime.Serialization.JsonNet 28 | dotnet pack src/NodaTime.Serialization.Protobuf 29 | dotnet pack src/NodaTime.Serialization.SystemTextJson 30 | -------------------------------------------------------------------------------- /.github/workflows/push.yaml: -------------------------------------------------------------------------------- 1 | name: Build push 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | 10 | build: 11 | runs-on: ubuntu-latest 12 | env: 13 | DOTNET_NOLOGO: true 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: true 19 | 20 | - name: Setup .NET SDK 21 | uses: actions/setup-dotnet@v4 22 | with: 23 | dotnet-version: | 24 | 6.0.x 25 | 8.0.x 26 | 27 | - name: Build and test 28 | run: | 29 | dotnet test src/NodaTime.Serialization.Test 30 | dotnet pack src/NodaTime.Serialization.JsonNet 31 | dotnet pack src/NodaTime.Serialization.Protobuf 32 | dotnet pack src/NodaTime.Serialization.SystemTextJson 33 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Github Actions status](https://img.shields.io/github/actions/workflow/status/nodatime/nodatime.serialization/push.yaml?branch=main)](https://github.com/nodatime/nodatime.serialization/actions?query=workflow%3A%22Build+push%22) 2 | 3 | # Noda Time serialization 4 | 5 | This repository contains projects to do with serializing [Noda Time](http://nodatime.org) 6 | data in various forms, such as JSON. 7 | 8 | XML serialization is built into Noda Time itself, but other serialization mechanisms are 9 | expected to simply use the public API. (Noda Time 1.x and 2.x also support .NET binary 10 | serialization natively.) 11 | 12 | Separating the code into a "core" in the [nodatime](https://github.com/nodatime/nodatime) 13 | repository and serialization code here allows for a greater separation between the two, 14 | and independent release cycles. 15 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/ProtobufExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NUnit.Framework; 6 | using static NodaTime.Serialization.Protobuf.ProtobufExtensions; 7 | 8 | namespace NodaTime.Serialization.Test.Protobuf 9 | { 10 | public partial class ProtobufExtensionsTest 11 | { 12 | [Test] 13 | public void MinMaxValidTimestampSeconds() 14 | { 15 | // These are useful to have as compile-time constants, but let's validate them. 16 | Assert.AreEqual(MinValidTimestampSeconds, Instant.FromUtc(1, 1, 1, 0, 0).ToUnixTimeSeconds()); 17 | Assert.AreEqual(MaxValidTimestampSeconds, Instant.FromUtc(9999, 12, 31, 23, 59, 59).ToUnixTimeSeconds()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Protobuf/NodaTime.Serialization.Protobuf.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Provides serialization support between Noda Time and Google.Protobuf 5 | 6 | 2.0.2 7 | 8 | 2.0.0 9 | 10 | netstandard2.0 11 | nodatime;google;protobuf 12 | true 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/NodaTime.Serialization.JsonNet.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Provides serialization support between Noda Time and Json.NET. 5 | 6 | 3.2.0 7 | 8 | 3.2.0 9 | netstandard2.0 10 | nodatime;json;jsonnet 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaTime.Serialization.SystemTextJson.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Provides serialization support between Noda Time and System.Text.Json 5 | 6 | 1.3.0 7 | 8 | 1.3.0 9 | 10 | netstandard2.0;net6.0 11 | nodatime;json 12 | true 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/NodaTime.Serialization.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | NodaExtensionsTest.cs 25 | 26 | 27 | ProtobufExtensionsTest.cs 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /NuGet-README.md: -------------------------------------------------------------------------------- 1 | # NodaTime serialization 2 | 3 | XML serialization is built into Noda Time itself, but other serialization mechanisms are 4 | expected to simply use the public API. (Noda Time 1.x and 2.x also support .NET binary 5 | serialization natively.) 6 | 7 | Separating the code into a "core" in the [nodatime](https://github.com/nodatime/nodatime) 8 | repository and serialization code in a 9 | [separate repository](https://github.com/nodatime/nodatime.serialization) 10 | makes it simpler to implement independent release cycles. 11 | 12 | All Noda Time serialization packages (as published by the Noda Time 13 | authors) start with a prefix "NodaTime.Serialization": 14 | 15 | - [NodaTime.Serialization.JsonNet](https://www.nuget.org/packages/NodaTime.Serialization.JsonNet) 16 | (JSON serialization using Newtonsoft.Json) 17 | - [NodaTime.Serialization.SystemTextJson](https://www.nuget.org/packages/NodaTime.Serialization.SystemTextJson) 18 | (JSON serialization using System.Text.Json) 19 | - [NodaTime.Serialization.Protobuf](https://www.nuget.org/packages/NodaTime.Serialization.Protobuf) 20 | (Protocol Buffer serialization using Google.Protobuf) 21 | 22 | The source code for all these packages is at 23 | https://github.com/nodatime/nodatime.serialization issues should be 24 | filed in that GitHub repository. 25 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/Preconditions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Utility; 6 | using System; 7 | 8 | namespace NodaTime.Serialization.JsonNet 9 | { 10 | /// 11 | /// Helper static methods for argument/state validation. (Just the subset used within this library.) 12 | /// 13 | internal static class Preconditions 14 | { 15 | internal static T CheckNotNull(T argument, string paramName) where T : class 16 | => argument ?? throw new ArgumentNullException(paramName); 17 | 18 | internal static void CheckArgument(bool expression, string parameter, string message) 19 | { 20 | if (!expression) 21 | { 22 | throw new ArgumentException(message, parameter); 23 | } 24 | } 25 | 26 | internal static void CheckData(bool expression, string messageFormat, T messageArg) 27 | { 28 | if (!expression) 29 | { 30 | string message = string.Format(messageFormat, messageArg); 31 | throw new InvalidNodaDataException(message); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/Preconditions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using NodaTime.Utility; 7 | 8 | namespace NodaTime.Serialization.SystemTextJson 9 | { 10 | /// 11 | /// Helper static methods for argument/state validation. (Just the subset used within this library.) 12 | /// 13 | internal static class Preconditions 14 | { 15 | internal static T CheckNotNull(T argument, string paramName) => 16 | argument == null ? throw new ArgumentNullException(paramName) : argument; 17 | 18 | internal static void CheckArgument(bool expression, string parameter, string message) 19 | { 20 | if (!expression) 21 | { 22 | throw new ArgumentException(message, parameter); 23 | } 24 | } 25 | 26 | internal static void CheckData(bool expression, string messageFormat, T messageArg) 27 | { 28 | if (!expression) 29 | { 30 | string message = string.Format(messageFormat, messageArg); 31 | throw new InvalidNodaDataException(message); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaTimeDefaultJsonConverterAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | 9 | namespace NodaTime.Serialization.SystemTextJson; 10 | 11 | /// 12 | /// Provides JSON default converters for Noda Time types, as if using serializer 13 | /// options configured by 14 | /// with a provider of . 15 | /// 16 | /// 17 | /// This attribute allows JSON conversion to be easily specified for properties without 18 | /// having to configure a specific options object. 19 | /// 20 | public sealed class NodaTimeDefaultJsonConverterAttribute : JsonConverterAttribute 21 | { 22 | /// 23 | /// Constructs an instance of the attribute. 24 | /// 25 | public NodaTimeDefaultJsonConverterAttribute() 26 | { 27 | } 28 | 29 | /// 30 | public override JsonConverter CreateConverter(Type typeToConvert) => 31 | NodaTimeDefaultJsonConverterFactory.GetConverter(typeToConvert); 32 | } -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/NodaNullableConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Text.Json; 6 | using NodaTime.Serialization.SystemTextJson; 7 | using NUnit.Framework; 8 | using static NodaTime.Serialization.Test.SystemText.TestHelper; 9 | 10 | namespace NodaTime.Serialization.Test.SystemText 11 | { 12 | /// 13 | /// Tests for the converters exposed in NodaConverters. 14 | /// 15 | public class NodaNullableConverterTest 16 | { 17 | [Test] 18 | public void InstantConverter_NotNull() 19 | { 20 | Instant? value = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 21 | string json = "\"2012-01-02T03:04:05Z\""; 22 | var converter = new NodaTimeDefaultJsonConverterFactory().CreateConverter(typeof(Instant?), new JsonSerializerOptions()); 23 | AssertConversions(value, json, converter); 24 | } 25 | 26 | [Test] 27 | public void InstantConverter_Null() 28 | { 29 | Instant? value = null; 30 | string json = "null"; 31 | var converter = new NodaTimeDefaultJsonConverterFactory().CreateConverter(typeof(Instant?), new JsonSerializerOptions()); 32 | AssertConversions(value, json, converter); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/NodaExtensionsTest.ToTimeOfDay.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Serialization.Protobuf; 6 | using NodaTime.Text; 7 | using NUnit.Framework; 8 | 9 | namespace NodaTime.Serialization.Test.Protobuf 10 | { 11 | public partial class NodaExtensionsTest 12 | { 13 | [Test] 14 | [TestCase("00:00:00.000000000", 0, 0, 0, 0)] 15 | [TestCase("00:00:00.999999999", 0, 0, 0, (int)NodaConstants.NanosecondsPerSecond - 1)] 16 | [TestCase("23:59:59.999999999", 23, 59, 59, (int)NodaConstants.NanosecondsPerSecond - 1)] 17 | // Just a non-extreme value 18 | [TestCase("12:45:23.000500000", 12, 45, 23, 500000)] 19 | public void ToTimeOfDay_Valid(string localTimeText, int expectedHours, int expectedMinutes, int expectedSeconds, int expectedNanos) 20 | { 21 | var pattern = LocalTimePattern.CreateWithInvariantCulture("HH:mm:ss.fffffffff"); 22 | var localTime = pattern.Parse(localTimeText).Value; 23 | var timeOfDay = localTime.ToTimeOfDay(); 24 | Assert.AreEqual(expectedHours, timeOfDay.Hours); 25 | Assert.AreEqual(expectedMinutes, timeOfDay.Minutes); 26 | Assert.AreEqual(expectedSeconds, timeOfDay.Seconds); 27 | Assert.AreEqual(expectedNanos, timeOfDay.Nanos); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/NodaExtensionsTest.ToDate.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Google.Type; 6 | using NodaTime.Serialization.Protobuf; 7 | using NodaTime.Text; 8 | using NUnit.Framework; 9 | using System; 10 | using ProtoDuration = Google.Protobuf.WellKnownTypes.Duration; 11 | 12 | namespace NodaTime.Serialization.Test.Protobuf 13 | { 14 | public partial class NodaExtensionsTest 15 | { 16 | [Test] 17 | [TestCase(1, 1, 1)] 18 | [TestCase(9999, 12, 31)] 19 | [TestCase(2008, 2, 29)] 20 | public void ToDate_Valid(int year, int month, int day) 21 | { 22 | var date = new LocalDate(year, month, day); 23 | var expectedResult = new Date { Year = year, Month = month, Day = day }; 24 | var actualResult = date.ToDate(); 25 | Assert.AreEqual(expectedResult, actualResult); 26 | } 27 | 28 | [Test] 29 | public void ToDate_NonIsoCalendar() 30 | { 31 | var date = new LocalDate(100, 1, 1, CalendarSystem.Julian); 32 | Assert.Throws(() => date.ToDate()); 33 | } 34 | 35 | [Test] 36 | public void ToDate_TooEarly() 37 | { 38 | var date = new LocalDate(1, 1, 1).PlusDays(-1); 39 | Assert.Throws(() => date.ToDate()); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/ProtobufExtensionsTest.ToIsoDayOfWeek.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Serialization.Protobuf; 6 | using NUnit.Framework; 7 | using System; 8 | 9 | using ProtoDayOfWeek = Google.Type.DayOfWeek; 10 | 11 | namespace NodaTime.Serialization.Test.Protobuf 12 | { 13 | public partial class ProtobufExtensionsTest 14 | { 15 | // Might as well just list everything... 16 | [Test] 17 | [TestCase(ProtoDayOfWeek.Unspecified, IsoDayOfWeek.None)] 18 | [TestCase(ProtoDayOfWeek.Sunday, IsoDayOfWeek.Sunday)] 19 | [TestCase(ProtoDayOfWeek.Monday, IsoDayOfWeek.Monday)] 20 | [TestCase(ProtoDayOfWeek.Tuesday, IsoDayOfWeek.Tuesday)] 21 | [TestCase(ProtoDayOfWeek.Wednesday, IsoDayOfWeek.Wednesday)] 22 | [TestCase(ProtoDayOfWeek.Thursday, IsoDayOfWeek.Thursday)] 23 | [TestCase(ProtoDayOfWeek.Friday, IsoDayOfWeek.Friday)] 24 | [TestCase(ProtoDayOfWeek.Saturday, IsoDayOfWeek.Saturday)] 25 | public void Valid(ProtoDayOfWeek input, IsoDayOfWeek expectedOutput) => 26 | Assert.AreEqual(expectedOutput, input.ToIsoDayOfWeek()); 27 | 28 | [Test] 29 | [TestCase((ProtoDayOfWeek) (-1))] 30 | [TestCase((ProtoDayOfWeek) 8)] 31 | public void OutOfRange(ProtoDayOfWeek value) => 32 | Assert.Throws(() => value.ToIsoDayOfWeek()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/NodaExtensionsTest.ToProtobufDayOfWeek.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NUnit.Framework; 6 | using System; 7 | using ProtobufDayOfWeek = Google.Type.DayOfWeek; 8 | using NodaTime.Serialization.Protobuf; 9 | 10 | namespace NodaTime.Serialization.Test.Protobuf 11 | { 12 | public partial class NodaExtensionsTest 13 | { 14 | // Might as well just list everything... 15 | [Test] 16 | [TestCase(IsoDayOfWeek.None, ProtobufDayOfWeek.Unspecified)] 17 | [TestCase(IsoDayOfWeek.Sunday, ProtobufDayOfWeek.Sunday)] 18 | [TestCase(IsoDayOfWeek.Monday, ProtobufDayOfWeek.Monday)] 19 | [TestCase(IsoDayOfWeek.Tuesday, ProtobufDayOfWeek.Tuesday)] 20 | [TestCase(IsoDayOfWeek.Wednesday, ProtobufDayOfWeek.Wednesday)] 21 | [TestCase(IsoDayOfWeek.Thursday, ProtobufDayOfWeek.Thursday)] 22 | [TestCase(IsoDayOfWeek.Friday, ProtobufDayOfWeek.Friday)] 23 | [TestCase(IsoDayOfWeek.Saturday, ProtobufDayOfWeek.Saturday)] 24 | public void Valid(IsoDayOfWeek input, ProtobufDayOfWeek expectedOutput) => 25 | Assert.AreEqual(expectedOutput, input.ToProtobufDayOfWeek()); 26 | 27 | [Test] 28 | [TestCase((IsoDayOfWeek)(-1))] 29 | [TestCase((IsoDayOfWeek)8)] 30 | public void OutOfRange(IsoDayOfWeek value) => 31 | Assert.Throws(() => value.ToProtobufDayOfWeek()); 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | latest 5 | true 6 | ../../NodaTime Release.snk 7 | true 8 | True 9 | true 10 | 11 | True 12 | false 13 | 14 | 15 | https://nodatime.org/ 16 | Apache-2.0 17 | https://github.com/nodatime/nodatime.serialization 18 | The Noda Time authors 19 | README.md 20 | 21 | 22 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 23 | true 24 | true 25 | 26 | 27 | true 28 | 29 | 30 | true 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/JsonNet/TestHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Newtonsoft.Json; 6 | using NodaTime.Utility; 7 | using NUnit.Framework; 8 | 9 | namespace NodaTime.Serialization.Test.JsonNet 10 | { 11 | internal static class TestHelper 12 | { 13 | internal static void AssertConversions(T value, string expectedJson, JsonConverter converter) 14 | { 15 | var settings = new JsonSerializerSettings 16 | { 17 | Converters = { converter }, 18 | DateParseHandling = DateParseHandling.None 19 | }; 20 | AssertConversions(value, expectedJson, settings); 21 | } 22 | 23 | internal static void AssertConversions(T value, string expectedJson, JsonSerializerSettings settings) 24 | { 25 | var actualJson = JsonConvert.SerializeObject(value, Formatting.None, settings); 26 | Assert.AreEqual(expectedJson, actualJson); 27 | 28 | var deserializedValue = JsonConvert.DeserializeObject(expectedJson, settings); 29 | Assert.AreEqual(value, deserializedValue); 30 | } 31 | 32 | internal static void AssertInvalidJson(string json, JsonSerializerSettings settings) 33 | { 34 | var exception = Assert.Throws(() => JsonConvert.DeserializeObject(json, settings)); 35 | Assert.IsInstanceOf(exception.InnerException); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/TestHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Runtime.Serialization; 6 | using System.Text.Json; 7 | using NodaTime.Utility; 8 | using NUnit.Framework; 9 | using JsonConverter = System.Text.Json.Serialization.JsonConverter; 10 | using JsonSerializer = System.Text.Json.JsonSerializer; 11 | 12 | namespace NodaTime.Serialization.Test.SystemText 13 | { 14 | internal static class TestHelper 15 | { 16 | internal static void AssertConversions(T value, string expectedJson, JsonConverter converter) 17 | { 18 | var options = new JsonSerializerOptions 19 | { 20 | Converters = { converter }, 21 | }; 22 | AssertConversions(value, expectedJson, options); 23 | } 24 | 25 | internal static void AssertConversions(T value, string expectedJson, JsonSerializerOptions options) 26 | { 27 | var actualJson = JsonSerializer.Serialize(value, options); 28 | Assert.AreEqual(expectedJson, actualJson); 29 | 30 | var deserializedValue = JsonSerializer.Deserialize(expectedJson, options); 31 | Assert.AreEqual(value, deserializedValue); 32 | } 33 | 34 | internal static void AssertInvalidJson(string json, JsonSerializerOptions options) 35 | { 36 | var exception = Assert.Throws(() => JsonSerializer.Deserialize(json, options)); 37 | Assert.IsInstanceOf(exception.InnerException); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/ProtobufExtensionsTest.ToLocalDate.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Google.Type; 6 | using NodaTime.Serialization.Protobuf; 7 | using NUnit.Framework; 8 | using System; 9 | 10 | using ProtoDayOfWeek = Google.Type.DayOfWeek; 11 | 12 | namespace NodaTime.Serialization.Test.Protobuf 13 | { 14 | public partial class ProtobufExtensionsTest 15 | { 16 | 17 | [Test] 18 | [TestCase(1, 1, 1)] 19 | [TestCase(9999, 12, 31)] 20 | [TestCase(2019, 4, 8)] 21 | [TestCase(2008, 2, 29)] 22 | public void ToLocalDate_Valid(int year, int month, int day) 23 | { 24 | var date = new Date { Year = year, Month = month, Day = day }; 25 | var actualResult = date.ToLocalDate(); 26 | var expectedResult = new LocalDate(year, month, day); 27 | Assert.AreEqual(expectedResult, actualResult); 28 | } 29 | 30 | [Test] 31 | [TestCase(0, 1, 1)] 32 | [TestCase(-1, 1, 1)] 33 | [TestCase(1, 0, 1)] 34 | [TestCase(1, 1, 0)] 35 | [TestCase(1, 0, 0)] 36 | [TestCase(10000, 1, 1)] 37 | [TestCase(2007, 2, 29)] 38 | [TestCase(1, 13, 1)] 39 | [TestCase(1, 1, 32)] 40 | public void ToLocalDate_Invalid(int year, int month, int day) 41 | { 42 | var date = new Date { Year = year, Month = month, Day = day }; 43 | Assert.Throws(() => date.ToLocalDate()); 44 | } 45 | 46 | [Test] 47 | public void ToLocalDate_Null() => 48 | Assert.Throws(() => ((Date) null).ToLocalDate()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/JsonNet/NodaDateTimeZoneConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Newtonsoft.Json; 6 | using NodaTime.Serialization.JsonNet; 7 | using NodaTime.TimeZones; 8 | using NUnit.Framework; 9 | 10 | namespace NodaTime.Serialization.Test.JsonNet 11 | { 12 | public class NodaDateTimeZoneConverterTest 13 | { 14 | private readonly JsonConverter converter = NodaConverters.CreateDateTimeZoneConverter(DateTimeZoneProviders.Tzdb); 15 | 16 | [Test] 17 | public void Serialize() 18 | { 19 | var dateTimeZone = DateTimeZoneProviders.Tzdb["America/Los_Angeles"]; 20 | var json = JsonConvert.SerializeObject(dateTimeZone, Formatting.None, converter); 21 | string expectedJson = "\"America/Los_Angeles\""; 22 | Assert.AreEqual(expectedJson, json); 23 | } 24 | 25 | [Test] 26 | public void Deserialize() 27 | { 28 | string json = "\"America/Los_Angeles\""; 29 | var dateTimeZone = JsonConvert.DeserializeObject(json, converter); 30 | var expectedDateTimeZone = DateTimeZoneProviders.Tzdb["America/Los_Angeles"]; 31 | Assert.AreEqual(expectedDateTimeZone, dateTimeZone); 32 | } 33 | 34 | [Test] 35 | public void Deserialize_TimeZoneNotFound() 36 | { 37 | string json = "\"America/DOES_NOT_EXIST\""; 38 | var exception = Assert.Throws(() => JsonConvert.DeserializeObject(json, converter)); 39 | Assert.IsInstanceOf(exception.InnerException); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/NodaExtensionsTest.ToTimestamp.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Serialization.Protobuf; 6 | using NodaTime.Text; 7 | using NUnit.Framework; 8 | using System; 9 | using static NodaTime.Serialization.Protobuf.ProtobufExtensions; 10 | 11 | namespace NodaTime.Serialization.Test.Protobuf 12 | { 13 | public partial class NodaExtensionsTest 14 | { 15 | [Test] 16 | public void ToTimestamp_OutOfRange() 17 | { 18 | var instant = NodaConstants.BclEpoch.PlusNanoseconds(-1); 19 | Assert.Throws(() => instant.ToTimestamp()); 20 | } 21 | 22 | [Test] 23 | [TestCase("0001-01-01T00:00:00.000000000", MinValidTimestampSeconds, 0)] 24 | [TestCase("9999-12-31T23:59:59.999999999", MaxValidTimestampSeconds, (int)(NodaConstants.NanosecondsPerSecond - 1))] 25 | [TestCase("1970-01-01T00:00:00.000000000", 0, 0)] 26 | [TestCase("1970-01-01T00:00:00.000000001", 0, 1)] 27 | [TestCase("1970-01-01T00:00:01.000000000", 1, 0)] 28 | [TestCase("1969-12-31T23:59:59.000000000", -1, 0)] 29 | [TestCase("2017-07-24T09:37:05.123456789", 1500889025, 123456789)] 30 | public void ToTimestamp_Valid(string instantText, long expectedSeconds, int expectedNanos) 31 | { 32 | var pattern = InstantPattern.CreateWithInvariantCulture("uuuu-MM-dd'T'HH:mm:ss.fffffffff"); 33 | var instant = pattern.Parse(instantText).Value; 34 | var timestamp = instant.ToTimestamp(); 35 | Assert.AreEqual(expectedSeconds, timestamp.Seconds); 36 | Assert.AreEqual(expectedNanos, timestamp.Nanos); 37 | } 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/NodaTimeDefaultJsonConverterAttributeTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Serialization.SystemTextJson; 6 | using NUnit.Framework; 7 | using System.Text.Json; 8 | 9 | namespace NodaTime.Serialization.Test.SystemTextJson; 10 | 11 | public class NodaTimeDefaultJsonConverterAttributeTest 12 | { 13 | [Test] 14 | public void Roundtrip() 15 | { 16 | var obj = new SampleClass 17 | { 18 | NonNullableInstant = Instant.FromUtc(2023, 5, 29, 14, 11, 23), 19 | NullableInstant1 = Instant.FromUtc(2025, 1, 2, 3, 4, 5), 20 | NullableInstant2 = null, 21 | ZonedDateTime = Instant.FromUtc(2023, 5, 29, 14, 11, 23).InZone(DateTimeZoneProviders.Tzdb["Europe/London"]) 22 | }; 23 | 24 | string json = JsonSerializer.Serialize(obj); 25 | var result = JsonSerializer.Deserialize(json); 26 | 27 | Assert.AreEqual(obj.NonNullableInstant, result.NonNullableInstant); 28 | Assert.AreEqual(obj.NullableInstant1, result.NullableInstant1); 29 | Assert.Null(result.NullableInstant2); 30 | Assert.AreEqual(obj.ZonedDateTime, result.ZonedDateTime); 31 | } 32 | 33 | // Just enough properties to check that the custom converters are being used. 34 | public class SampleClass 35 | { 36 | [NodaTimeDefaultJsonConverter] 37 | public Instant NonNullableInstant { get; set; } 38 | [NodaTimeDefaultJsonConverter] 39 | public Instant? NullableInstant1 { get; set; } 40 | [NodaTimeDefaultJsonConverter] 41 | public Instant? NullableInstant2 { get; set; } 42 | [NodaTimeDefaultJsonConverter] 43 | public ZonedDateTime ZonedDateTime { get; set; } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaNullableConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | 9 | namespace NodaTime.Serialization.SystemTextJson; 10 | 11 | /// 12 | /// System.Text.Json converter for value types, wrapping 13 | /// an inner converter. 14 | /// 15 | /// Value type to be converted. 16 | internal sealed class NodaNullableConverter : JsonConverter where T : struct 17 | { 18 | private readonly JsonConverter _innerConverter; 19 | 20 | /// 21 | /// Creates a new NodaNullableConverter. 22 | /// 23 | /// Inner converter for serializing and deserializing when not null. 24 | public NodaNullableConverter(JsonConverter innerConverter) 25 | { 26 | Preconditions.CheckNotNull(innerConverter, nameof(innerConverter)); 27 | 28 | _innerConverter = innerConverter; 29 | } 30 | 31 | /// 32 | public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 33 | { 34 | if (reader.TokenType == JsonTokenType.Null) 35 | { 36 | return null; 37 | } 38 | 39 | return _innerConverter.Read(ref reader, typeToConvert, options); 40 | } 41 | 42 | /// 43 | public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options) 44 | { 45 | if (value is null) 46 | { 47 | writer.WriteNullValue(); 48 | } 49 | else 50 | { 51 | _innerConverter.Write(writer, value.Value, options); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/NodaExtensionsTest.ToProtobufDuration.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Serialization.Protobuf; 6 | using NodaTime.Text; 7 | using NUnit.Framework; 8 | using System; 9 | using ProtoDuration = Google.Protobuf.WellKnownTypes.Duration; 10 | 11 | namespace NodaTime.Serialization.Test.Protobuf 12 | { 13 | public partial class NodaExtensionsTest 14 | { 15 | [Test] 16 | [TestCase(ProtoDuration.MinSeconds - 1)] 17 | [TestCase(ProtoDuration.MaxSeconds + 1)] 18 | public void ToProtobufDuration_OutOfRange(long seconds) 19 | { 20 | var nodaDuration = Duration.FromSeconds(seconds); 21 | Assert.Throws(() => nodaDuration.ToProtobufDuration()); 22 | } 23 | 24 | [Test] 25 | [TestCase("-3652500:00:00:00.999999999", ProtoDuration.MinSeconds, (int)(-NodaConstants.NanosecondsPerSecond) + 1)] 26 | [TestCase("3652500:00:00:00.999999999", ProtoDuration.MaxSeconds, (int)(NodaConstants.NanosecondsPerSecond - 1))] 27 | [TestCase("0:00:00:00", 0, 0)] 28 | [TestCase("-0:00:00:01", -1, 0)] 29 | [TestCase("0:00:00:01", 1, 0)] 30 | [TestCase("0:00:00:00.000000001", 0, 1)] 31 | [TestCase("-0:00:00:00.000000001", 0, -1)] 32 | [TestCase("0:00:00:01.5", 1, 500000000)] 33 | [TestCase("-0:00:00:01.5", -1, -500000000)] 34 | public void ToProtobufDuration_Valid(string nodaDurationText, long expectedSeconds, int expectedNanos) 35 | { 36 | Duration nodaDuration = DurationPattern.Roundtrip.Parse(nodaDurationText).Value; 37 | ProtoDuration protoDuration = nodaDuration.ToProtobufDuration(); 38 | Assert.AreEqual(expectedSeconds, protoDuration.Seconds); 39 | Assert.AreEqual(expectedNanos, protoDuration.Nanos); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Protobuf/Preconditions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | 7 | namespace NodaTime.Serialization.Protobuf 8 | { 9 | /// 10 | /// Helper static methods for argument/state validation. (Just the subset used within this library.) 11 | /// 12 | internal static class Preconditions 13 | { 14 | internal static T CheckNotNull(T argument, string paramName) where T : class 15 | => argument ?? throw new ArgumentNullException(paramName); 16 | 17 | internal static void CheckArgument(bool expression, string parameter, string message) 18 | { 19 | if (!expression) 20 | { 21 | throw new ArgumentException(message, parameter); 22 | } 23 | } 24 | 25 | internal static void CheckArgument(bool expression, string parameter, string messageFormat, T messageArg) 26 | { 27 | if (!expression) 28 | { 29 | string message = string.Format(messageFormat, messageArg); 30 | throw new ArgumentException(message, parameter); 31 | } 32 | } 33 | 34 | internal static void CheckArgument(bool expression, string parameter, string messageFormat, T1 messageArg1, T2 messageArg2) 35 | { 36 | if (!expression) 37 | { 38 | string message = string.Format(messageFormat, messageArg1, messageArg2); 39 | throw new ArgumentException(message, parameter); 40 | } 41 | } 42 | 43 | internal static void CheckArgument(bool expression, string parameter, string messageFormat, T1 messageArg1, T2 messageArg2, T3 messageArg3) 44 | { 45 | if (!expression) 46 | { 47 | string message = string.Format(messageFormat, messageArg1, messageArg2, messageArg3); 48 | throw new ArgumentException(message, parameter); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/ProtobufExtensionsTest.ToNodaDuration.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Serialization.Protobuf; 6 | using NodaTime.Text; 7 | using NUnit.Framework; 8 | using System; 9 | using ProtoDuration = Google.Protobuf.WellKnownTypes.Duration; 10 | 11 | namespace NodaTime.Serialization.Test.Protobuf 12 | { 13 | public partial class ProtobufExtensionsTest 14 | { 15 | // Bounds are from https://github.com/google/protobuf/blob/master/src/google/protobuf/duration.proto 16 | 17 | [Test] 18 | [TestCase(ProtoDuration.MinSeconds - 1, 0)] 19 | [TestCase(ProtoDuration.MaxSeconds + 1, 0)] 20 | [TestCase(0, (int) (-NodaConstants.NanosecondsPerSecond))] 21 | [TestCase(0, (int) NodaConstants.NanosecondsPerSecond)] 22 | [TestCase(-1, 1, Description = "Different sign")] 23 | [TestCase(1, -1, Description = "Different sign")] 24 | public void ToNodaDuration_Invalid(long seconds, int nanos) 25 | { 26 | var input = new ProtoDuration { Seconds = seconds, Nanos = nanos }; 27 | Assert.Throws(() => input.ToNodaDuration()); 28 | } 29 | 30 | [TestCase(ProtoDuration.MinSeconds, (int)(-NodaConstants.NanosecondsPerSecond) + 1, "-3652500:00:00:00.999999999")] 31 | [TestCase(ProtoDuration.MaxSeconds, (int) (NodaConstants.NanosecondsPerSecond - 1), "3652500:00:00:00.999999999")] 32 | [TestCase(0, 0, "0:00:00:00")] 33 | [TestCase(-1, 0, "-0:00:00:01")] 34 | [TestCase(1, 0, "0:00:00:01")] 35 | [TestCase(0, 1, "0:00:00:00.000000001")] 36 | [TestCase(0, -1, "-0:00:00:00.000000001")] 37 | [TestCase(1, 500000000, "0:00:00:01.5")] 38 | [TestCase(-1, -500000000, "-0:00:00:01.5")] 39 | public void ToNodaDuration_Valid(long seconds, int nanos, string expectedResult) 40 | { 41 | var input = new ProtoDuration { Seconds = seconds, Nanos = nanos }; 42 | var nodaDuration = input.ToNodaDuration(); 43 | Assert.AreEqual(expectedResult, DurationPattern.Roundtrip.Format(nodaDuration)); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/ProtobufExtensionsTest.ToInstant.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Google.Protobuf.WellKnownTypes; 6 | using NodaTime.Serialization.Protobuf; 7 | using NodaTime.Text; 8 | using NUnit.Framework; 9 | using System; 10 | using static NodaTime.Serialization.Protobuf.ProtobufExtensions; 11 | 12 | namespace NodaTime.Serialization.Test.Protobuf 13 | { 14 | public partial class ProtobufExtensionsTest 15 | { 16 | // Bounds are from https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto 17 | 18 | [Test] 19 | [TestCase(0, (int) NodaConstants.NanosecondsPerSecond, Description = "Nanos out of range")] 20 | [TestCase(0, -1, Description = "Nanos out of range")] 21 | [TestCase(MinValidTimestampSeconds - 1, 0, Description = "Seconds out of range")] 22 | [TestCase(MaxValidTimestampSeconds + 1, 0, Description = "Seconds out of range")] 23 | public void ToInstant_InvalidTimestamp(long seconds, int nanos) 24 | { 25 | var timestamp = new Timestamp { Seconds = seconds, Nanos = nanos }; 26 | Assert.Throws(() => timestamp.ToInstant()); 27 | } 28 | 29 | [Test] 30 | [TestCase(MinValidTimestampSeconds, 0, "0001-01-01T00:00:00.000000000")] 31 | [TestCase(MaxValidTimestampSeconds, (int) (NodaConstants.NanosecondsPerSecond - 1), "9999-12-31T23:59:59.999999999")] 32 | [TestCase(0, 0, "1970-01-01T00:00:00.000000000")] 33 | [TestCase(0, 1, "1970-01-01T00:00:00.000000001")] 34 | [TestCase(1, 0, "1970-01-01T00:00:01.000000000")] 35 | [TestCase(-1, 0, "1969-12-31T23:59:59.000000000")] 36 | [TestCase(1500889025, 123456789, "2017-07-24T09:37:05.123456789")] 37 | public void ToInstant_Valid(long seconds, int nanos, string expectedResult) 38 | { 39 | var pattern = InstantPattern.CreateWithInvariantCulture("uuuu-MM-dd'T'HH:mm:ss.fffffffff"); 40 | var timestamp = new Timestamp { Seconds = seconds, Nanos = nanos }; 41 | var instant = timestamp.ToInstant(); 42 | Assert.AreEqual(expectedResult, pattern.Format(instant)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/NodaIsoIntervalConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Newtonsoft.Json; 6 | using NodaTime.Text; 7 | using NodaTime.Utility; 8 | 9 | namespace NodaTime.Serialization.JsonNet 10 | { 11 | /// 12 | /// Json.NET converter for . 13 | /// 14 | internal sealed class NodaIsoIntervalConverter : NodaConverterBase 15 | { 16 | protected override Interval ReadJsonImpl(JsonReader reader, JsonSerializer serializer) 17 | { 18 | if (reader.TokenType != JsonToken.String) 19 | { 20 | throw new InvalidNodaDataException( 21 | $"Unexpected token parsing Interval. Expected String, got {reader.TokenType}."); 22 | } 23 | string text = reader.Value.ToString(); 24 | int slash = text.IndexOf('/'); 25 | if (slash == -1) 26 | { 27 | throw new InvalidNodaDataException("Expected ISO-8601-formatted interval; slash was missing."); 28 | } 29 | 30 | string startText = text.Substring(0, slash); 31 | string endText = text.Substring(slash + 1); 32 | var pattern = InstantPattern.ExtendedIso; 33 | var start = startText == "" ? (Instant?) null : pattern.Parse(startText).Value; 34 | var end = endText == "" ? (Instant?) null : pattern.Parse(endText).Value; 35 | 36 | return new Interval(start, end); 37 | } 38 | 39 | /// 40 | /// Serializes the interval as start/end instants. 41 | /// 42 | /// The writer to write JSON to 43 | /// The interval to serialize 44 | /// The serializer for embedded serialization. 45 | protected override void WriteJsonImpl(JsonWriter writer, Interval value, JsonSerializer serializer) 46 | { 47 | var pattern = InstantPattern.ExtendedIso; 48 | string text = (value.HasStart ? pattern.Format(value.Start) : "") + "/" + (value.HasEnd ? pattern.Format(value.End) : ""); 49 | writer.WriteValue(text); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/JsonNet/DelegatingConverterBaseTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Newtonsoft.Json; 6 | using NodaTime.Serialization.JsonNet; 7 | using NodaTime.Text; 8 | using NUnit.Framework; 9 | 10 | namespace NodaTime.Serialization.Test.JsonNet 11 | { 12 | public class DelegatingConverterBaseTest 13 | { 14 | [Test] 15 | public void Serialize() 16 | { 17 | string expected = "{'ShortDate':'2017-02-20','LongDate':'20 February 2017'}" 18 | .Replace("'", "\""); 19 | var date = new LocalDate(2017, 2, 20); 20 | var entity = new Entity { ShortDate = date, LongDate = date }; 21 | var actual = JsonConvert.SerializeObject(entity, Formatting.None); 22 | Assert.AreEqual(expected, actual); 23 | } 24 | 25 | [Test] 26 | public void Deserialize() 27 | { 28 | string json = "{'ShortDate':'2017-02-20','LongDate':'20 February 2017'}" 29 | .Replace("'", "\""); 30 | var expectedDate = new LocalDate(2017, 2, 20); 31 | var entity = JsonConvert.DeserializeObject(json); 32 | Assert.AreEqual(expectedDate, entity.ShortDate); 33 | Assert.AreEqual(expectedDate, entity.LongDate); 34 | } 35 | 36 | public class Entity 37 | { 38 | [JsonConverter(typeof(ShortDateConverter))] 39 | public LocalDate ShortDate { get; set; } 40 | 41 | [JsonConverter(typeof(LongDateConverter))] 42 | public LocalDate LongDate { get; set; } 43 | } 44 | 45 | public class ShortDateConverter : DelegatingConverterBase 46 | { 47 | public ShortDateConverter() : base(NodaConverters.LocalDateConverter) { } 48 | } 49 | 50 | public class LongDateConverter : DelegatingConverterBase 51 | { 52 | // No need to create a new one of these each time... 53 | private static readonly JsonConverter converter = 54 | new NodaPatternConverter(LocalDatePattern.CreateWithInvariantCulture("d MMMM yyyy")); 55 | 56 | public LongDateConverter() : base(converter) 57 | { 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/NodaDateTimeZoneConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Newtonsoft.Json; 6 | using NodaTime.TimeZones; 7 | using NodaTime.Utility; 8 | 9 | namespace NodaTime.Serialization.JsonNet 10 | { 11 | /// 12 | /// Json.NET converter for . 13 | /// 14 | internal sealed class NodaDateTimeZoneConverter : NodaConverterBase 15 | { 16 | private readonly IDateTimeZoneProvider provider; 17 | 18 | /// Provides the that corresponds to each time zone ID in the JSON string. 19 | public NodaDateTimeZoneConverter(IDateTimeZoneProvider provider) 20 | { 21 | this.provider = provider; 22 | } 23 | 24 | /// 25 | /// Reads the time zone ID (which must be a string) from the reader, and converts it to a time zone. 26 | /// 27 | /// The JSON reader to fetch data from. 28 | /// The serializer for embedded serialization. 29 | /// The provider does not support a time zone with the given ID. 30 | /// The identified in the JSON, or null. 31 | protected override DateTimeZone ReadJsonImpl(JsonReader reader, JsonSerializer serializer) 32 | { 33 | Preconditions.CheckData(reader.TokenType == JsonToken.String, 34 | "Unexpected token parsing instant. Expected String, got {0}.", 35 | reader.TokenType); 36 | 37 | var timeZoneId = reader.Value.ToString(); 38 | return provider[timeZoneId]; 39 | } 40 | 41 | /// 42 | /// Writes the time zone ID to the writer. 43 | /// 44 | /// The writer to write JSON data to 45 | /// The value to serialize 46 | /// The serializer to use for nested serialization 47 | protected override void WriteJsonImpl(JsonWriter writer, DateTimeZone value, JsonSerializer serializer) 48 | { 49 | writer.WriteValue(value.Id); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/DelegatingConverterBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | 9 | namespace NodaTime.Serialization.SystemTextJson 10 | { 11 | /// 12 | /// Converter which does nothing but delegate to another one for all operations. 13 | /// 14 | /// 15 | /// Nothing in this class is specific to Noda Time. Its purpose is to make it easy 16 | /// to reuse other converter instances with , 17 | /// which can only identify a converter by type. 18 | /// 19 | /// 20 | /// 21 | /// If you had some properties which needed one converter, 22 | /// but others that needed another, you might want to have different types implementing 23 | /// those converters. Each type would just derive from this, passing the right converter 24 | /// into the base constructor. 25 | /// 26 | /// 27 | /// public sealed class ShortDateConverter : DelegatingConverterBase 28 | /// { 29 | /// public ShortDateConverter() : base(NodaConverters.LocalDateConverter) {} 30 | /// } 31 | /// 32 | /// 33 | /// The type of object or value handled by the converter. 34 | public abstract class DelegatingConverterBase : JsonConverter 35 | { 36 | private readonly JsonConverter original; 37 | 38 | /// 39 | /// Constructs a converter delegating to . 40 | /// 41 | /// The converter to delegate to. Must not be null. 42 | protected DelegatingConverterBase(JsonConverter original) => 43 | this.original = original ?? throw new ArgumentNullException(nameof(original)); 44 | 45 | /// 46 | public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => 47 | original.Write(writer, value, options); 48 | 49 | /// 50 | public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => 51 | original.Read(ref reader, typeToConvert, options); 52 | 53 | /// 54 | public override bool CanConvert(Type objectType) => original.CanConvert(objectType); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/DelegatingConverterBaseTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Text.Json; 6 | using System.Text.Json.Serialization; 7 | using NodaTime.Serialization.SystemTextJson; 8 | using NodaTime.Text; 9 | using NUnit.Framework; 10 | using NodaConverters = NodaTime.Serialization.SystemTextJson.NodaConverters; 11 | 12 | namespace NodaTime.Serialization.Test.SystemText 13 | { 14 | public class DelegatingConverterBaseTest 15 | { 16 | [Test] 17 | public void Serialize() 18 | { 19 | string expected = "{'ShortDate':'2017-02-20','LongDate':'20 February 2017'}" 20 | .Replace("'", "\""); 21 | var date = new LocalDate(2017, 2, 20); 22 | var entity = new Entity { ShortDate = date, LongDate = date }; 23 | var actual = JsonSerializer.Serialize(entity, new JsonSerializerOptions 24 | { 25 | WriteIndented = false 26 | }); 27 | Assert.AreEqual(expected, actual); 28 | } 29 | 30 | [Test] 31 | public void Deserialize() 32 | { 33 | string json = "{'ShortDate':'2017-02-20','LongDate':'20 February 2017'}" 34 | .Replace("'", "\""); 35 | var expectedDate = new LocalDate(2017, 2, 20); 36 | var entity = JsonSerializer.Deserialize(json); 37 | Assert.AreEqual(expectedDate, entity.ShortDate); 38 | Assert.AreEqual(expectedDate, entity.LongDate); 39 | } 40 | 41 | public class Entity 42 | { 43 | [JsonConverter(typeof(ShortDateConverter))] 44 | public LocalDate ShortDate { get; set; } 45 | 46 | [JsonConverter(typeof(LongDateConverter))] 47 | public LocalDate LongDate { get; set; } 48 | } 49 | 50 | public class ShortDateConverter : DelegatingConverterBase 51 | { 52 | public ShortDateConverter() : base(NodaConverters.LocalDateConverter) { } 53 | } 54 | 55 | public class LongDateConverter : DelegatingConverterBase 56 | { 57 | // No need to create a new one of these each time... 58 | private static readonly JsonConverter converter = 59 | new Serialization.SystemTextJson.NodaPatternConverter(LocalDatePattern.CreateWithInvariantCulture("d MMMM yyyy")); 60 | 61 | public LongDateConverter() : base(converter) 62 | { 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/NodaIsoDateIntervalConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Newtonsoft.Json; 6 | using NodaTime.Text; 7 | using NodaTime.Utility; 8 | 9 | namespace NodaTime.Serialization.JsonNet 10 | { 11 | /// 12 | /// Json.NET converter for . 13 | /// 14 | internal sealed class NodaIsoDateIntervalConverter : NodaConverterBase 15 | { 16 | protected override DateInterval ReadJsonImpl(JsonReader reader, JsonSerializer serializer) 17 | { 18 | if (reader.TokenType != JsonToken.String) 19 | { 20 | throw new InvalidNodaDataException( 21 | $"Unexpected token parsing DateInterval. Expected String, got {reader.TokenType}."); 22 | } 23 | string text = reader.Value.ToString(); 24 | int slash = text.IndexOf('/'); 25 | if (slash == -1) 26 | { 27 | throw new InvalidNodaDataException("Expected ISO-8601-formatted date interval; slash was missing."); 28 | } 29 | 30 | string startText = text.Substring(0, slash); 31 | if (startText == "") 32 | { 33 | throw new InvalidNodaDataException("Expected ISO-8601-formatted date interval; start date was missing."); 34 | } 35 | 36 | string endText = text.Substring(slash + 1); 37 | if (endText == "") 38 | { 39 | throw new InvalidNodaDataException("Expected ISO-8601-formatted date interval; end date was missing."); 40 | } 41 | 42 | var pattern = LocalDatePattern.Iso; 43 | var start = pattern.Parse(startText).Value; 44 | var end = pattern.Parse(endText).Value; 45 | 46 | return new DateInterval(start, end); 47 | } 48 | 49 | /// 50 | /// Serializes the date interval as start/end dates. 51 | /// 52 | /// The writer to write JSON to 53 | /// The date interval to serialize 54 | /// The serializer for embedded serialization. 55 | protected override void WriteJsonImpl(JsonWriter writer, DateInterval value, JsonSerializer serializer) 56 | { 57 | var pattern = LocalDatePattern.Iso; 58 | string text = pattern.Format(value.Start) + "/" + pattern.Format(value.End); 59 | writer.WriteValue(text); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/NodaAnnualDateConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Serialization.SystemTextJson; 6 | using NUnit.Framework; 7 | using System.Text.Json; 8 | 9 | namespace NodaTime.Serialization.Test.SystemText 10 | { 11 | public class NodaAnnualDateConverterTest 12 | { 13 | private readonly JsonSerializerOptions options = new JsonSerializerOptions 14 | { 15 | Converters = { NodaConverters.AnnualDateConverter }, 16 | }; 17 | 18 | [Test] 19 | public void Serialize_NonNullableType() 20 | { 21 | var annualDate = new AnnualDate(07, 01); 22 | var json = JsonSerializer.Serialize(annualDate, options); 23 | string expectedJson = "\"07-01\""; 24 | Assert.AreEqual(expectedJson, json); 25 | } 26 | 27 | [Test] 28 | public void Serialize_NullableType_NonNullValue() 29 | { 30 | AnnualDate? annualDate = new AnnualDate(07, 01); 31 | var json = JsonSerializer.Serialize(annualDate, options); 32 | string expectedJson = "\"07-01\""; 33 | Assert.AreEqual(expectedJson, json); 34 | } 35 | 36 | [Test] 37 | public void Serialize_NullableType_NullValue() 38 | { 39 | AnnualDate? annualDate = null; 40 | var json = JsonSerializer.Serialize(annualDate, options); 41 | string expectedJson = "null"; 42 | Assert.AreEqual(expectedJson, json); 43 | } 44 | 45 | [Test] 46 | public void Deserialize_ToNonNullableType() 47 | { 48 | string json = "\"07-01\""; 49 | var annualDate = JsonSerializer.Deserialize(json, options); 50 | var expectedAnnualDate = new AnnualDate(07, 01); 51 | Assert.AreEqual(expectedAnnualDate, annualDate); 52 | } 53 | 54 | [Test] 55 | public void Deserialize_ToNullableType_NonNullValue() 56 | { 57 | string json = "\"07-01\""; 58 | var annualDate = JsonSerializer.Deserialize(json, options); 59 | AnnualDate? expectedAnnualDate = new AnnualDate(07, 01); 60 | Assert.AreEqual(expectedAnnualDate, annualDate); 61 | } 62 | 63 | [Test] 64 | public void Deserialize_ToNullableType_NullValue() 65 | { 66 | string json = "null"; 67 | var annualDate = JsonSerializer.Deserialize(json, options); 68 | Assert.IsNull(annualDate); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/DelegatingConverterBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | using Newtonsoft.Json; 5 | using System; 6 | 7 | namespace NodaTime.Serialization.JsonNet 8 | { 9 | /// 10 | /// Converter which does nothing but delegate to another one for all operations. 11 | /// 12 | /// 13 | /// Nothing in this class is specific to Noda Time. Its purpose is to make it easy 14 | /// to reuse other converter instances with , 15 | /// which can only identify a converter by type. 16 | /// 17 | /// 18 | /// 19 | /// If you had some properties which needed one converter, 20 | /// but others that needed another, you might want to have different types implementing 21 | /// those converters. Each type would just derive from this, passing the right converter 22 | /// into the base constructor. 23 | /// 24 | /// 25 | /// public sealed class ShortDateConverter : DelegatingConverterBase 26 | /// { 27 | /// public ShortDateConverter() : base(NodaConverters.LocalDateConverter) {} 28 | /// } 29 | /// 30 | /// 31 | public abstract class DelegatingConverterBase : JsonConverter 32 | { 33 | private readonly JsonConverter original; 34 | 35 | /// 36 | /// Constructs a converter delegating to . 37 | /// 38 | /// The converter to delegate to. Must not be null. 39 | protected DelegatingConverterBase(JsonConverter original) 40 | { 41 | if (original == null) 42 | { 43 | throw new ArgumentNullException(nameof(original)); 44 | } 45 | this.original = original; 46 | } 47 | 48 | /// 49 | public override void WriteJson( 50 | JsonWriter writer, object value, JsonSerializer serializer) => 51 | original.WriteJson(writer, value, serializer); 52 | 53 | /// 54 | public override object ReadJson( 55 | JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) => 56 | original.ReadJson(reader, objectType, existingValue, serializer); 57 | 58 | /// 59 | public override bool CanRead => original.CanRead; 60 | 61 | /// 62 | public override bool CanWrite => original.CanWrite; 63 | 64 | /// 65 | public override bool CanConvert(Type objectType) => original.CanConvert(objectType); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/NodaIsoDateIntervalConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Text.Json; 6 | using NodaTime.Serialization.SystemTextJson; 7 | using NUnit.Framework; 8 | using static NodaTime.Serialization.Test.SystemText.TestHelper; 9 | 10 | namespace NodaTime.Serialization.Test.SystemText 11 | { 12 | /// 13 | /// The same tests as NodaDateIntervalConverterTest, but using the ISO-based interval converter. 14 | /// 15 | public class NodaIsoDateIntervalConverterTest 16 | { 17 | private readonly JsonSerializerOptions options = new JsonSerializerOptions 18 | { 19 | Converters = { NodaConverters.IsoDateIntervalConverter, NodaConverters.LocalDateConverter }, 20 | }; 21 | 22 | [Test] 23 | public void RoundTrip() 24 | { 25 | var startLocalDate = new LocalDate(2012, 1, 2); 26 | var endLocalDate = new LocalDate(2013, 6, 7); 27 | var dateInterval = new DateInterval(startLocalDate, endLocalDate); 28 | AssertConversions(dateInterval, "\"2012-01-02/2013-06-07\"", options); 29 | } 30 | 31 | [Test] 32 | [TestCase("\"2012-01-022013-06-07\"")] 33 | public void InvalidJson(string json) 34 | { 35 | AssertInvalidJson(json, options); 36 | } 37 | 38 | [Test] 39 | public void Serialize_InObject() 40 | { 41 | var startLocalDate = new LocalDate(2012, 1, 2); 42 | var endLocalDate = new LocalDate(2013, 6, 7); 43 | var dateInterval = new DateInterval(startLocalDate, endLocalDate); 44 | 45 | var testObject = new TestObject { Interval = dateInterval }; 46 | 47 | var json = JsonSerializer.Serialize(testObject, options); 48 | 49 | string expectedJson = "{\"Interval\":\"2012-01-02/2013-06-07\"}"; 50 | Assert.AreEqual(expectedJson, json); 51 | } 52 | 53 | [Test] 54 | public void Deserialize_InObject() 55 | { 56 | string json = "{\"Interval\":\"2012-01-02/2013-06-07\"}"; 57 | 58 | var testObject = JsonSerializer.Deserialize(json, options); 59 | 60 | var interval = testObject.Interval; 61 | 62 | var startLocalDate = new LocalDate(2012, 1, 2); 63 | var endLocalDate = new LocalDate(2013, 6, 7); 64 | var expectedInterval = new DateInterval(startLocalDate, endLocalDate); 65 | Assert.AreEqual(expectedInterval, interval); 66 | } 67 | 68 | public class TestObject 69 | { 70 | public DateInterval Interval { get; set; } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/Protobuf/ProtobufExtensionsTest.ToLocalTime.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Google.Type; 6 | using NodaTime.Serialization.Protobuf; 7 | using NodaTime.Text; 8 | using NUnit.Framework; 9 | using System; 10 | 11 | namespace NodaTime.Serialization.Test.Protobuf 12 | { 13 | public partial class ProtobufExtensionsTest 14 | { 15 | /// 16 | /// These are times of day which are invalid in Protobuf. 17 | /// 18 | [Test] 19 | [TestCase(-1, 0, 0, 0)] 20 | [TestCase(25, 0, 0, 0)] // 24 is handled below 21 | [TestCase(0, -1, 0, 0)] 22 | [TestCase(0, 60, 0, 0)] 23 | [TestCase(0, 0, -1, 0)] 24 | [TestCase(0, 0, 61, 0)] // 60 is handled below 25 | [TestCase(0, 0, 0, -1)] 26 | [TestCase(0, 0, 0, (int) NodaConstants.NanosecondsPerSecond)] 27 | public void ToLocalTime_InvalidTimeOfDay(int hours, int minutes, int seconds, int nanos) 28 | { 29 | var timeOfDay = new TimeOfDay { Hours = hours, Minutes = minutes, Seconds = seconds, Nanos = nanos }; 30 | Assert.Throws(() => timeOfDay.ToLocalTime()); 31 | } 32 | 33 | /// 34 | /// These are times of day which are valid in Protobuf, 35 | /// but not supported by Noda Time. 36 | /// 37 | [Test] 38 | [TestCase(25, 0, 0, 0, Description = "End of day, 24:00")] 39 | [TestCase(0, 0, 60, 0, Description = "Leap second")] 40 | public void ToLocalTime_UnhandledTimeOfDay(int hours, int minutes, int seconds, int nanos) 41 | { 42 | var timeOfDay = new TimeOfDay { Hours = hours, Minutes = minutes, Seconds = seconds, Nanos = nanos }; 43 | Assert.Throws(() => timeOfDay.ToLocalTime()); 44 | } 45 | 46 | [Test] 47 | [TestCase(0, 0, 0, 0, "00:00:00.000000000")] 48 | [TestCase(0, 0, 0, (int) NodaConstants.NanosecondsPerSecond - 1, "00:00:00.999999999")] 49 | [TestCase(23, 59, 59, (int)NodaConstants.NanosecondsPerSecond - 1, "23:59:59.999999999")] 50 | // Just a non-extreme value 51 | [TestCase(12, 45, 23, 500000, "12:45:23.000500000")] 52 | public void ToLocalTime_Valid(int hours, int minutes, int seconds, int nanos, string expectedResult) 53 | { 54 | var pattern = LocalTimePattern.CreateWithInvariantCulture("HH:mm:ss.fffffffff"); 55 | var timeOfDay = new TimeOfDay { Hours = hours, Minutes = minutes, Seconds = seconds, Nanos = nanos }; 56 | var localTime = timeOfDay.ToLocalTime(); 57 | Assert.AreEqual(expectedResult, pattern.Format(localTime)); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/NodaDateTimeZoneConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Runtime.Serialization; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization; 8 | using NodaTime.Serialization.SystemTextJson; 9 | using NodaTime.TimeZones; 10 | using NUnit.Framework; 11 | using JsonSerializer = System.Text.Json.JsonSerializer; 12 | 13 | namespace NodaTime.Serialization.Test.SystemText 14 | { 15 | public class NodaDateTimeZoneConverterTest 16 | { 17 | private readonly JsonConverter converter = 18 | NodaConverters.CreateDateTimeZoneConverter(DateTimeZoneProviders.Tzdb); 19 | 20 | [Test] 21 | public void Serialize() 22 | { 23 | var dateTimeZone = DateTimeZoneProviders.Tzdb["America/Los_Angeles"]; 24 | var json = JsonSerializer.Serialize(dateTimeZone, new JsonSerializerOptions 25 | { 26 | WriteIndented = false, 27 | Converters = {converter} 28 | }); 29 | string expectedJson = "\"America/Los_Angeles\""; 30 | Assert.AreEqual(expectedJson, json); 31 | } 32 | 33 | [Test] 34 | public void SerializeAsObject() 35 | { 36 | object dateTimeZone = DateTimeZoneProviders.Tzdb["America/Los_Angeles"]; 37 | var json = JsonSerializer.Serialize(dateTimeZone, new JsonSerializerOptions 38 | { 39 | WriteIndented = false, 40 | Converters = { converter } 41 | }); 42 | string expectedJson = "\"America/Los_Angeles\""; 43 | Assert.AreEqual(expectedJson, json); 44 | } 45 | 46 | [Test] 47 | public void Deserialize() 48 | { 49 | string json = "\"America/Los_Angeles\""; 50 | var dateTimeZone = JsonSerializer.Deserialize(json, new JsonSerializerOptions 51 | { 52 | Converters = {converter} 53 | }); 54 | var expectedDateTimeZone = DateTimeZoneProviders.Tzdb["America/Los_Angeles"]; 55 | Assert.AreEqual(expectedDateTimeZone, dateTimeZone); 56 | } 57 | 58 | [Test] 59 | public void Deserialize_TimeZoneNotFound() 60 | { 61 | string json = "\"America/DOES_NOT_EXIST\""; 62 | var exception = 63 | Assert.Throws(() => 64 | JsonSerializer.Deserialize(json, new JsonSerializerOptions 65 | { 66 | Converters = {converter} 67 | })); 68 | Assert.IsInstanceOf(exception.InnerException); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/JsonNet/NodaAnnualDateConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Converters; 8 | using NodaTime.Serialization.JsonNet; 9 | using NUnit.Framework; 10 | 11 | namespace NodaTime.Serialization.Test.JsonNet 12 | { 13 | public class NodaAnnualDateConverterTest 14 | { 15 | private readonly JsonSerializerSettings settings = new JsonSerializerSettings 16 | { 17 | Converters = { NodaConverters.AnnualDateConverter }, 18 | DateParseHandling = DateParseHandling.None 19 | }; 20 | 21 | [Test] 22 | public void Serialize_NonNullableType() 23 | { 24 | var annualDate = new AnnualDate(07, 01); 25 | var json = JsonConvert.SerializeObject(annualDate, Formatting.None, settings); 26 | string expectedJson = "\"07-01\""; 27 | Assert.AreEqual(expectedJson, json); 28 | } 29 | 30 | [Test] 31 | public void Serialize_NullableType_NonNullValue() 32 | { 33 | AnnualDate? annualDate = new AnnualDate(07, 01); 34 | var json = JsonConvert.SerializeObject(annualDate, Formatting.None, settings); 35 | string expectedJson = "\"07-01\""; 36 | Assert.AreEqual(expectedJson, json); 37 | } 38 | 39 | [Test] 40 | public void Serialize_NullableType_NullValue() 41 | { 42 | AnnualDate? instant = null; 43 | var json = JsonConvert.SerializeObject(instant, Formatting.None, settings); 44 | string expectedJson = "null"; 45 | Assert.AreEqual(expectedJson, json); 46 | } 47 | 48 | [Test] 49 | public void Deserialize_ToNonNullableType() 50 | { 51 | string json = "\"07-01\""; 52 | var annualDate = JsonConvert.DeserializeObject(json, settings); 53 | var expectedAnnualDate = new AnnualDate(07, 01); 54 | Assert.AreEqual(expectedAnnualDate, annualDate); 55 | } 56 | 57 | [Test] 58 | public void Deserialize_ToNullableType_NonNullValue() 59 | { 60 | string json = "\"07-01\""; 61 | var annualDate = JsonConvert.DeserializeObject(json, settings); 62 | AnnualDate? expectedAnnualDate = new AnnualDate(07, 01); 63 | Assert.AreEqual(expectedAnnualDate, annualDate); 64 | } 65 | 66 | [Test] 67 | public void Deserialize_ToNullableType_NullValue() 68 | { 69 | string json = "null"; 70 | var annualDate = JsonConvert.DeserializeObject(json, settings); 71 | Assert.IsNull(annualDate); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/JsonNet/NodaIsoDateIntervalConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using static NodaTime.Serialization.Test.JsonNet.TestHelper; 6 | 7 | using Newtonsoft.Json; 8 | using NodaTime.Serialization.JsonNet; 9 | using NUnit.Framework; 10 | 11 | namespace NodaTime.Serialization.Test.JsonNet 12 | { 13 | /// 14 | /// The same tests as NodaDateIntervalConverterTest, but using the ISO-based interval converter. 15 | /// 16 | public class NodaIsoDateIntervalConverterTest 17 | { 18 | private readonly JsonSerializerSettings settings = new JsonSerializerSettings 19 | { 20 | Converters = { NodaConverters.IsoDateIntervalConverter, NodaConverters.LocalDateConverter }, 21 | DateParseHandling = DateParseHandling.None 22 | }; 23 | 24 | [Test] 25 | public void RoundTrip() 26 | { 27 | var startLocalDate = new LocalDate(2012, 1, 2); 28 | var endLocalDate = new LocalDate(2013, 6, 7); 29 | var dateInterval = new DateInterval(startLocalDate, endLocalDate); 30 | AssertConversions(dateInterval, "\"2012-01-02/2013-06-07\"", settings); 31 | } 32 | 33 | [Test] 34 | [TestCase("\"2012-01-022013-06-07\"")] 35 | public void InvalidJson(string json) 36 | { 37 | AssertInvalidJson(json, settings); 38 | } 39 | 40 | [Test] 41 | public void Serialize_InObject() 42 | { 43 | var startLocalDate = new LocalDate(2012, 1, 2); 44 | var endLocalDate = new LocalDate(2013, 6, 7); 45 | var dateInterval = new DateInterval(startLocalDate, endLocalDate); 46 | 47 | var testObject = new TestObject { Interval = dateInterval }; 48 | 49 | var json = JsonConvert.SerializeObject(testObject, Formatting.None, settings); 50 | 51 | string expectedJson = "{\"Interval\":\"2012-01-02/2013-06-07\"}"; 52 | Assert.AreEqual(expectedJson, json); 53 | } 54 | 55 | [Test] 56 | public void Deserialize_InObject() 57 | { 58 | string json = "{\"Interval\":\"2012-01-02/2013-06-07\"}"; 59 | 60 | var testObject = JsonConvert.DeserializeObject(json, settings); 61 | 62 | var interval = testObject.Interval; 63 | 64 | var startLocalDate = new LocalDate(2012, 1, 2); 65 | var endLocalDate = new LocalDate(2013, 6, 7); 66 | var expectedInterval = new DateInterval(startLocalDate, endLocalDate); 67 | Assert.AreEqual(expectedInterval, interval); 68 | } 69 | 70 | public class TestObject 71 | { 72 | public DateInterval Interval { get; set; } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Benchmarks/JsonNet/NodaConverterBaseBenchmarks.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using BenchmarkDotNet.Attributes; 6 | using Newtonsoft.Json; 7 | using NodaTime.Serialization.JsonNet; 8 | using System.IO; 9 | 10 | namespace NodaTime.Serialization.Benchmarks.JsonNet 11 | { 12 | public class NodaConverterBaseBenchmarks 13 | { 14 | private readonly NodaConverterBase int32Converter = new DummyConverter(); 15 | private readonly NodaConverterBase stringConverter = new DummyConverter(); 16 | private readonly NodaConverterBase streamConverter = new DummyConverter(); 17 | 18 | // Value types 19 | [Benchmark] 20 | public bool CanConvert_Int32_Int32() => int32Converter.CanConvert(typeof(int)); 21 | 22 | [Benchmark] 23 | public bool CanConvert_Int32_NullableInt32() => int32Converter.CanConvert(typeof(int?)); 24 | 25 | [Benchmark] 26 | public bool CanConvert_Int32_Object() => int32Converter.CanConvert(typeof(object)); 27 | 28 | [Benchmark] 29 | public bool CanConvert_Int32_String() => int32Converter.CanConvert(typeof(string)); 30 | 31 | [Benchmark] 32 | public bool CanConvert_Int32_UInt32() => int32Converter.CanConvert(typeof(uint)); 33 | 34 | // Sealed classes 35 | [Benchmark] 36 | public bool CanConvert_String_String() => stringConverter.CanConvert(typeof(string)); 37 | 38 | [Benchmark] 39 | public bool CanConvert_String_Object() => stringConverter.CanConvert(typeof(object)); 40 | 41 | [Benchmark] 42 | public bool CanConvert_String_UInt32() => stringConverter.CanConvert(typeof(uint)); 43 | 44 | // Unsealed classes 45 | [Benchmark] 46 | public bool CanConvert_Stream_Stream() => streamConverter.CanConvert(typeof(Stream)); 47 | 48 | [Benchmark] 49 | public bool CanConvert_Stream_MemoryStream() => streamConverter.CanConvert(typeof(MemoryStream)); 50 | 51 | [Benchmark] 52 | public bool CanConvert_Stream_Object() => streamConverter.CanConvert(typeof(object)); 53 | 54 | [Benchmark] 55 | public bool CanConvert_Stream_String() => streamConverter.CanConvert(typeof(string)); 56 | 57 | [Benchmark] 58 | public bool CanConvert_Stream_UInt32() => streamConverter.CanConvert(typeof(uint)); 59 | 60 | private class DummyConverter : NodaConverterBase 61 | { 62 | protected override T ReadJsonImpl(JsonReader reader, JsonSerializer serializer) => default(T); 63 | 64 | protected override void WriteJsonImpl(JsonWriter writer, T value, JsonSerializer serializer) 65 | { 66 | writer.WriteValue(value.ToString()); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaDateTimeZoneConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Text.Json; 6 | using NodaTime.TimeZones; 7 | 8 | namespace NodaTime.Serialization.SystemTextJson 9 | { 10 | /// 11 | /// System.Text.Json converter for . 12 | /// 13 | internal sealed class NodaDateTimeZoneConverter : NodaConverterBase 14 | { 15 | private readonly IDateTimeZoneProvider provider; 16 | 17 | /// Provides the that corresponds to each time zone ID in the JSON string. 18 | public NodaDateTimeZoneConverter(IDateTimeZoneProvider provider) => 19 | this.provider = provider; 20 | 21 | /// 22 | /// Reads the time zone ID (which must be a string) from the reader, and converts it to a time zone. 23 | /// 24 | /// The JSON reader to fetch data from. 25 | /// The serialization options to use for nested serialization. 26 | /// The provider does not support a time zone with the given ID. 27 | /// The identified in the JSON, or null. 28 | protected override DateTimeZone ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptions options) 29 | { 30 | Preconditions.CheckData(reader.TokenType == JsonTokenType.String, 31 | "Unexpected token parsing instant. Expected String, got {0}.", 32 | reader.TokenType); 33 | 34 | var timeZoneId = reader.GetString(); 35 | return provider[timeZoneId]; 36 | } 37 | 38 | /// 39 | /// Writes the time zone ID to the writer. 40 | /// 41 | /// The writer to write JSON data to. 42 | /// The value to serialize. 43 | /// The serialization options to use for nested serialization. 44 | protected override void WriteJsonImpl(Utf8JsonWriter writer, DateTimeZone value, JsonSerializerOptions options) => 45 | writer.WriteStringValue(value.Id); 46 | 47 | /// 48 | /// Writes the time zone ID to the writer as a property name 49 | /// 50 | /// The writer to write JSON data to. 51 | /// The value to serialize. 52 | /// The serialization options to use for nested serialization. 53 | protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, DateTimeZone value, JsonSerializerOptions options) => 54 | writer.WritePropertyName(value.Id); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/NodaInstantConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using System.Text.Json; 7 | using NodaTime.Serialization.SystemTextJson; 8 | using NUnit.Framework; 9 | 10 | namespace NodaTime.Serialization.Test.SystemText 11 | { 12 | public class NodaInstantConverterTest 13 | { 14 | private readonly JsonSerializerOptions options = new JsonSerializerOptions 15 | { 16 | Converters = { NodaConverters.InstantConverter }, 17 | }; 18 | 19 | [Test] 20 | public void Serialize_NonNullableType() 21 | { 22 | var instant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 23 | var json = JsonSerializer.Serialize(instant, options); 24 | string expectedJson = "\"2012-01-02T03:04:05Z\""; 25 | Assert.AreEqual(expectedJson, json); 26 | } 27 | 28 | [Test] 29 | public void Serialize_NullableType_NonNullValue() 30 | { 31 | Instant? instant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 32 | var json = JsonSerializer.Serialize(instant, options); 33 | string expectedJson = "\"2012-01-02T03:04:05Z\""; 34 | Assert.AreEqual(expectedJson, json); 35 | } 36 | 37 | [Test] 38 | public void Serialize_NullableType_NullValue() 39 | { 40 | Instant? instant = null; 41 | var json = JsonSerializer.Serialize(instant, options); 42 | string expectedJson = "null"; 43 | Assert.AreEqual(expectedJson, json); 44 | } 45 | 46 | [Test] 47 | public void Deserialize_ToNonNullableType() 48 | { 49 | string json = "\"2012-01-02T03:04:05Z\""; 50 | var instant = JsonSerializer.Deserialize(json, options); 51 | var expectedInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 52 | Assert.AreEqual(expectedInstant, instant); 53 | } 54 | 55 | [Test] 56 | public void Deserialize_ToNullableType_NonNullValue() 57 | { 58 | string json = "\"2012-01-02T03:04:05Z\""; 59 | var instant = JsonSerializer.Deserialize(json, options); 60 | Instant? expectedInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 61 | Assert.AreEqual(expectedInstant, instant); 62 | } 63 | 64 | [Test] 65 | public void Deserialize_ToNullableType_NullValue() 66 | { 67 | string json = "null"; 68 | var instant = JsonSerializer.Deserialize(json, options); 69 | Assert.IsNull(instant); 70 | } 71 | 72 | [Test] 73 | public void Serialize_EquivalentToIsoDateTimeConverter() 74 | { 75 | var dateTime = new DateTime(2012, 1, 2, 3, 4, 5, DateTimeKind.Utc); 76 | var instant = Instant.FromDateTimeUtc(dateTime); 77 | var jsonDateTime = JsonSerializer.Serialize(dateTime, options); 78 | var jsonInstant = JsonSerializer.Serialize(instant, options); 79 | Assert.AreEqual(jsonDateTime, jsonInstant); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaIsoIntervalConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Text.Json; 6 | using NodaTime.Text; 7 | using NodaTime.Utility; 8 | 9 | namespace NodaTime.Serialization.SystemTextJson 10 | { 11 | /// 12 | /// System.Text.Json converter for . 13 | /// 14 | internal sealed class NodaIsoIntervalConverter : NodaConverterBase 15 | { 16 | /// 17 | /// Reads Start and End properties for the start and end of an interval, converting them to instants 18 | /// using the given serializer. 19 | /// 20 | /// The JSON reader to fetch data from. 21 | /// The serializer options for embedded serialization. 22 | /// The identified in the JSON. 23 | protected override Interval ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptions options) 24 | { 25 | if (reader.TokenType != JsonTokenType.String) 26 | { 27 | throw new InvalidNodaDataException( 28 | $"Unexpected token parsing Interval. Expected String, got {reader.TokenType}."); 29 | } 30 | string text = reader.GetString(); 31 | int slash = text.IndexOf('/'); 32 | if (slash == -1) 33 | { 34 | throw new InvalidNodaDataException("Expected ISO-8601-formatted interval; slash was missing."); 35 | } 36 | 37 | string startText = text.Substring(0, slash); 38 | string endText = text.Substring(slash + 1); 39 | var pattern = InstantPattern.ExtendedIso; 40 | var start = startText == "" ? (Instant?) null : pattern.Parse(startText).Value; 41 | var end = endText == "" ? (Instant?) null : pattern.Parse(endText).Value; 42 | 43 | return new Interval(start, end); 44 | } 45 | 46 | /// 47 | /// Serializes the interval as start/end instants. 48 | /// 49 | /// The writer to write JSON to 50 | /// The interval to serialize 51 | /// The serializer options for embedded serialization. 52 | protected override void WriteJsonImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options) 53 | { 54 | var pattern = InstantPattern.ExtendedIso; 55 | var text = $"{(value.HasStart ? pattern.Format(value.Start) : "")}/{(value.HasEnd ? pattern.Format(value.End) : "")}"; 56 | writer.WriteStringValue(text); 57 | } 58 | 59 | protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options) 60 | { 61 | var pattern = InstantPattern.ExtendedIso; 62 | var text = $"{(value.HasStart ? pattern.Format(value.Start) : "")}/{(value.HasEnd ? pattern.Format(value.End) : "")}"; 63 | writer.WritePropertyName(text); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/JsonNet/NodaInstantConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Converters; 8 | using NodaTime.Serialization.JsonNet; 9 | using NUnit.Framework; 10 | 11 | namespace NodaTime.Serialization.Test.JsonNet 12 | { 13 | public class NodaInstantConverterTest 14 | { 15 | private readonly JsonSerializerSettings settings = new JsonSerializerSettings 16 | { 17 | Converters = { NodaConverters.InstantConverter }, 18 | DateParseHandling = DateParseHandling.None 19 | }; 20 | 21 | [Test] 22 | public void Serialize_NonNullableType() 23 | { 24 | var instant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 25 | var json = JsonConvert.SerializeObject(instant, Formatting.None, settings); 26 | string expectedJson = "\"2012-01-02T03:04:05Z\""; 27 | Assert.AreEqual(expectedJson, json); 28 | } 29 | 30 | [Test] 31 | public void Serialize_NullableType_NonNullValue() 32 | { 33 | Instant? instant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 34 | var json = JsonConvert.SerializeObject(instant, Formatting.None, settings); 35 | string expectedJson = "\"2012-01-02T03:04:05Z\""; 36 | Assert.AreEqual(expectedJson, json); 37 | } 38 | 39 | [Test] 40 | public void Serialize_NullableType_NullValue() 41 | { 42 | Instant? instant = null; 43 | var json = JsonConvert.SerializeObject(instant, Formatting.None, settings); 44 | string expectedJson = "null"; 45 | Assert.AreEqual(expectedJson, json); 46 | } 47 | 48 | [Test] 49 | public void Deserialize_ToNonNullableType() 50 | { 51 | string json = "\"2012-01-02T03:04:05Z\""; 52 | var instant = JsonConvert.DeserializeObject(json, settings); 53 | var expectedInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 54 | Assert.AreEqual(expectedInstant, instant); 55 | } 56 | 57 | [Test] 58 | public void Deserialize_ToNullableType_NonNullValue() 59 | { 60 | string json = "\"2012-01-02T03:04:05Z\""; 61 | var instant = JsonConvert.DeserializeObject(json, settings); 62 | Instant? expectedInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 63 | Assert.AreEqual(expectedInstant, instant); 64 | } 65 | 66 | [Test] 67 | public void Deserialize_ToNullableType_NullValue() 68 | { 69 | string json = "null"; 70 | var instant = JsonConvert.DeserializeObject(json, settings); 71 | Assert.IsNull(instant); 72 | } 73 | 74 | [Test] 75 | public void Serialize_EquivalentToIsoDateTimeConverter() 76 | { 77 | var dateTime = new DateTime(2012, 1, 2, 3, 4, 5, DateTimeKind.Utc); 78 | var instant = Instant.FromDateTimeUtc(dateTime); 79 | var jsonDateTime = JsonConvert.SerializeObject(dateTime, new IsoDateTimeConverter()); 80 | var jsonInstant = JsonConvert.SerializeObject(instant, Formatting.None, settings); 81 | Assert.AreEqual(jsonDateTime, jsonInstant); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaIsoDateIntervalConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Text.Json; 6 | using NodaTime.Text; 7 | using NodaTime.Utility; 8 | 9 | namespace NodaTime.Serialization.SystemTextJson 10 | { 11 | /// 12 | /// System.Text.Json converter for . 13 | /// 14 | internal sealed class NodaIsoDateIntervalConverter : NodaConverterBase 15 | { 16 | /// 17 | /// Reads Start and End properties for the start and end of an interval, converting them to instants 18 | /// using the given serializer. 19 | /// 20 | /// The JSON reader to fetch data from. 21 | /// The serializer options for embedded serialization. 22 | /// The identified in the JSON. 23 | protected override DateInterval ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptions options) 24 | { 25 | if (reader.TokenType != JsonTokenType.String) 26 | { 27 | throw new InvalidNodaDataException( 28 | $"Unexpected token parsing DateInterval. Expected String, got {reader.TokenType}."); 29 | } 30 | string text = reader.GetString(); 31 | int slash = text.IndexOf('/'); 32 | if (slash == -1) 33 | { 34 | throw new InvalidNodaDataException("Expected ISO-8601-formatted date interval; slash was missing."); 35 | } 36 | 37 | string startText = text.Substring(0, slash); 38 | if (startText == "") 39 | { 40 | throw new InvalidNodaDataException("Expected ISO-8601-formatted date interval; start date was missing."); 41 | } 42 | 43 | string endText = text.Substring(slash + 1); 44 | if (endText == "") 45 | { 46 | throw new InvalidNodaDataException("Expected ISO-8601-formatted date interval; end date was missing."); 47 | } 48 | 49 | var pattern = LocalDatePattern.Iso; 50 | var start = pattern.Parse(startText).Value; 51 | var end = pattern.Parse(endText).Value; 52 | 53 | return new DateInterval(start, end); 54 | } 55 | 56 | /// 57 | /// Serializes the date interval as start/end dates. 58 | /// 59 | /// The writer to write JSON to 60 | /// The date interval to serialize 61 | /// The serializer options for embedded serialization. 62 | protected override void WriteJsonImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options) 63 | { 64 | var pattern = LocalDatePattern.Iso; 65 | var text = $"{pattern.Format(value.Start)}/{pattern.Format(value.End)}"; 66 | writer.WriteStringValue(text); 67 | } 68 | 69 | protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options) 70 | { 71 | var pattern = LocalDatePattern.Iso; 72 | var text = $"{pattern.Format(value.Start)}/{pattern.Format(value.End)}"; 73 | writer.WritePropertyName(text); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/NodaPatternConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using Newtonsoft.Json; 7 | using NodaTime.Text; 8 | using NodaTime.Utility; 9 | 10 | namespace NodaTime.Serialization.JsonNet 11 | { 12 | /// 13 | /// A JSON converter for types which can be represented by a single string value, parsed or formatted 14 | /// from an . 15 | /// 16 | /// The type to convert to/from JSON. 17 | public sealed class NodaPatternConverter : NodaConverterBase 18 | { 19 | private readonly IPattern pattern; 20 | private readonly Action validator; 21 | 22 | /// 23 | /// Creates a new instance with a pattern and no validator. 24 | /// 25 | /// The pattern to use for parsing and formatting. 26 | /// is null. 27 | public NodaPatternConverter(IPattern pattern) : this(pattern, null) 28 | { 29 | } 30 | 31 | /// 32 | /// Creates a new instance with a pattern and an optional validator. The validator will be called before each 33 | /// value is written, and may throw an exception to indicate that the value cannot be serialized. 34 | /// 35 | /// The pattern to use for parsing and formatting. 36 | /// The validator to call before writing values. May be null, indicating that no validation is required. 37 | /// is null. 38 | public NodaPatternConverter(IPattern pattern, Action validator) 39 | { 40 | Preconditions.CheckNotNull(pattern, nameof(pattern)); 41 | this.pattern = pattern; 42 | this.validator = validator; 43 | } 44 | 45 | /// 46 | /// Implemented by concrete subclasses, this performs the final conversion from a non-null JSON value to 47 | /// a value of type T. 48 | /// 49 | /// The JSON reader to pull data from 50 | /// The serializer to use for nested serialization 51 | /// The deserialized value of type T. 52 | protected override T ReadJsonImpl(JsonReader reader, JsonSerializer serializer) 53 | { 54 | if (reader.TokenType != JsonToken.String) 55 | { 56 | throw new InvalidNodaDataException( 57 | $"Unexpected token parsing {typeof (T).Name}. Expected String, got {reader.TokenType}."); 58 | } 59 | string text = reader.Value.ToString(); 60 | return pattern.Parse(text).Value; 61 | } 62 | 63 | /// 64 | /// Writes the formatted value to the writer. 65 | /// 66 | /// The writer to write JSON data to 67 | /// The value to serialize 68 | /// The serializer to use for nested serialization 69 | protected override void WriteJsonImpl(JsonWriter writer, T value, JsonSerializer serializer) 70 | { 71 | validator?.Invoke(value); 72 | writer.WriteValue(pattern.Format(value)); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaTimeDefaultJsonConverterFactory.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text.Json; 9 | using System.Text.Json.Serialization; 10 | 11 | namespace NodaTime.Serialization.SystemTextJson; 12 | 13 | /// 14 | /// Provides JSON default converters for Noda Time types, as if using serializer 15 | /// options configured by 16 | /// with a provider of . 17 | /// 18 | /// 19 | /// This is a factory equivalent of . 20 | /// 21 | public sealed class NodaTimeDefaultJsonConverterFactory : JsonConverterFactory 22 | { 23 | /// 24 | /// A dictionary of default converters, keyed by type. This includes nullable types. 25 | /// 26 | private static readonly Dictionary converters = CreateConverterDictionary(); 27 | 28 | /// 29 | /// Constructs an instance of the factory. 30 | /// 31 | public NodaTimeDefaultJsonConverterFactory() 32 | { 33 | } 34 | 35 | private static Dictionary CreateConverterDictionary() 36 | { 37 | var converters = new Dictionary(); 38 | 39 | // Value types first, using the local function below to handle nullability. 40 | Add(NodaConverters.AnnualDateConverter); 41 | Add(NodaConverters.DurationConverterImpl); 42 | Add(NodaConverters.InstantConverter); 43 | Add(NodaConverters.IntervalConverter); 44 | Add(NodaConverters.LocalDateConverter); 45 | Add(NodaConverters.LocalDateTimeConverter); 46 | Add(NodaConverters.LocalTimeConverter); 47 | Add(NodaConverters.OffsetConverter); 48 | Add(NodaConverters.OffsetDateConverter); 49 | Add(NodaConverters.OffsetDateTimeConverter); 50 | Add(NodaConverters.OffsetTimeConverter); 51 | Add(NodaConverters.CreateZonedDateTimeConverter(DateTimeZoneProviders.Tzdb)); 52 | 53 | // Reference types 54 | converters[typeof(DateInterval)] = NodaConverters.DateIntervalConverter; 55 | converters[typeof(DateTimeZone)] = NodaConverters.CreateDateTimeZoneConverter(DateTimeZoneProviders.Tzdb); 56 | converters[typeof(Period)] = NodaConverters.RoundtripPeriodConverter; 57 | return converters; 58 | 59 | // Adds the converter for a value type to the dictionary, 60 | // and a nullable converter for the corresponding nullable value type. 61 | void Add(JsonConverter converter) where T : struct 62 | { 63 | converters[typeof(T)] = converter; 64 | converters[typeof(T?)] = new NodaNullableConverter(converter); 65 | } 66 | } 67 | 68 | /// 69 | /// Returns a converter for the given type, or null if no such converter is available. 70 | /// 71 | /// The type to retrieve a converter for. This may 72 | /// be a nullable value type. 73 | internal static JsonConverter GetConverter(Type typeToConvert) => 74 | converters.TryGetValue(typeToConvert, out var converter) ? converter : null; 75 | 76 | /// 77 | public override bool CanConvert(Type typeToConvert) => GetConverter(typeToConvert) is not null; 78 | 79 | /// 80 | public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) => 81 | GetConverter(typeToConvert); 82 | } -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/NodaIntervalConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using Newtonsoft.Json; 7 | 8 | namespace NodaTime.Serialization.JsonNet 9 | { 10 | /// 11 | /// Json.NET converter for using a compound representation. The start and 12 | /// end aspects of the interval are represented with separate properties, each parsed and formatted 13 | /// by the converter for the serializer provided. 14 | /// 15 | internal sealed class NodaIntervalConverter : NodaConverterBase 16 | { 17 | /// 18 | /// Reads Start and End properties for the start and end of an interval, converting them to instants 19 | /// using the given serializer. 20 | /// 21 | /// The JSON reader to fetch data from. 22 | /// The serializer for embedded serialization. 23 | /// The identified in the JSON. 24 | protected override Interval ReadJsonImpl(JsonReader reader, JsonSerializer serializer) 25 | { 26 | Instant? startInstant = null; 27 | Instant? endInstant = null; 28 | while (reader.Read()) 29 | { 30 | if (reader.TokenType != JsonToken.PropertyName) 31 | { 32 | break; 33 | } 34 | 35 | var propertyName = (string)reader.Value; 36 | // If we haven't got a property value, that's pretty weird. Break out of the loop, 37 | // and let JSON.NET fail appropriately... 38 | if (!reader.Read()) 39 | { 40 | break; 41 | } 42 | 43 | var startPropertyName = serializer.ResolvePropertyName(nameof(Interval.Start)); 44 | if (string.Equals(propertyName, startPropertyName, StringComparison.OrdinalIgnoreCase)) 45 | { 46 | startInstant = serializer.Deserialize(reader); 47 | } 48 | 49 | var endPropertyName = serializer.ResolvePropertyName(nameof(Interval.End)); 50 | if (string.Equals(propertyName, endPropertyName, StringComparison.OrdinalIgnoreCase)) 51 | { 52 | endInstant = serializer.Deserialize(reader); 53 | } 54 | } 55 | 56 | return new Interval(startInstant, endInstant); 57 | } 58 | 59 | /// 60 | /// Serializes the interval as start/end instants. 61 | /// 62 | /// The writer to write JSON to 63 | /// The interval to serialize 64 | /// The serializer for embedded serialization. 65 | protected override void WriteJsonImpl(JsonWriter writer, Interval value, JsonSerializer serializer) 66 | { 67 | writer.WriteStartObject(); 68 | 69 | if (value.HasStart) 70 | { 71 | var startPropertyName = serializer.ResolvePropertyName(nameof(Interval.Start)); 72 | writer.WritePropertyName(startPropertyName); 73 | serializer.Serialize(writer, value.Start); 74 | } 75 | if (value.HasEnd) 76 | { 77 | var endPropertyName = serializer.ResolvePropertyName(nameof(Interval.End)); 78 | writer.WritePropertyName(endPropertyName); 79 | serializer.Serialize(writer, value.End); 80 | } 81 | writer.WriteEndObject(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/NodaDateIntervalConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using Newtonsoft.Json; 7 | using NodaTime.Utility; 8 | 9 | namespace NodaTime.Serialization.JsonNet 10 | { 11 | /// 12 | /// Json.NET converter for using a compound representation. The start and 13 | /// end aspects of the date interval are represented with separate properties, each parsed and formatted 14 | /// by the converter for the serializer provided. 15 | /// 16 | internal sealed class NodaDateIntervalConverter : NodaConverterBase 17 | { 18 | /// 19 | /// Reads Start and End properties for the start and end of a date interval, converting them to local dates 20 | /// using the given serializer. 21 | /// 22 | /// The JSON reader to fetch data from. 23 | /// The serializer for embedded serialization. 24 | /// The identified in the JSON. 25 | protected override DateInterval ReadJsonImpl(JsonReader reader, JsonSerializer serializer) 26 | { 27 | LocalDate? startLocalDate = null; 28 | LocalDate? endLocalDate = null; 29 | while (reader.Read()) 30 | { 31 | if (reader.TokenType != JsonToken.PropertyName) 32 | { 33 | break; 34 | } 35 | 36 | var propertyName = (string)reader.Value; 37 | if (!reader.Read()) 38 | { 39 | break; 40 | } 41 | 42 | var startPropertyName = serializer.ResolvePropertyName(nameof(Interval.Start)); 43 | if (string.Equals(propertyName, startPropertyName, StringComparison.OrdinalIgnoreCase)) 44 | { 45 | startLocalDate = serializer.Deserialize(reader); 46 | } 47 | 48 | var endPropertyName = serializer.ResolvePropertyName(nameof(Interval.End)); 49 | if (string.Equals(propertyName, endPropertyName, StringComparison.OrdinalIgnoreCase)) 50 | { 51 | endLocalDate = serializer.Deserialize(reader); 52 | } 53 | } 54 | 55 | if (!startLocalDate.HasValue) 56 | { 57 | throw new InvalidNodaDataException("Expected date interval; start date was missing."); 58 | } 59 | 60 | if (!endLocalDate.HasValue) 61 | { 62 | throw new InvalidNodaDataException("Expected date interval; end date was missing."); 63 | } 64 | 65 | return new DateInterval(startLocalDate.Value, endLocalDate.Value); 66 | } 67 | 68 | /// 69 | /// Serializes the date interval as start/end local dates. 70 | /// 71 | /// The writer to write JSON to 72 | /// The date interval to serialize 73 | /// The serializer for embedded serialization. 74 | protected override void WriteJsonImpl(JsonWriter writer, DateInterval value, JsonSerializer serializer) 75 | { 76 | writer.WriteStartObject(); 77 | 78 | var startPropertyName = serializer.ResolvePropertyName(nameof(Interval.Start)); 79 | writer.WritePropertyName(startPropertyName); 80 | serializer.Serialize(writer, value.Start); 81 | 82 | var endPropertyName = serializer.ResolvePropertyName(nameof(Interval.End)); 83 | writer.WritePropertyName(endPropertyName); 84 | serializer.Serialize(writer, value.End); 85 | 86 | writer.WriteEndObject(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaPatternConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Text; 6 | using System; 7 | using System.Text.Json; 8 | 9 | namespace NodaTime.Serialization.SystemTextJson 10 | { 11 | /// 12 | /// A JSON converter for types which can be represented by a single string value, parsed or formatted 13 | /// from an . 14 | /// 15 | /// The type to convert to/from JSON. 16 | public sealed class NodaPatternConverter : NodaConverterBase 17 | { 18 | private readonly IPattern pattern; 19 | private readonly Action validator; 20 | 21 | /// 22 | /// Creates a new instance with a pattern and no validator. 23 | /// 24 | /// The pattern to use for parsing and formatting. 25 | /// is null. 26 | public NodaPatternConverter(IPattern pattern) : this(pattern, null) 27 | { 28 | } 29 | 30 | /// 31 | /// Creates a new instance with a pattern and an optional validator. The validator will be called before each 32 | /// value is written, and may throw an exception to indicate that the value cannot be serialized. 33 | /// 34 | /// The pattern to use for parsing and formatting. 35 | /// The validator to call before writing values. May be null, indicating that no validation is required. 36 | /// is null. 37 | public NodaPatternConverter(IPattern pattern, Action validator) 38 | { 39 | Preconditions.CheckNotNull(pattern, nameof(pattern)); 40 | this.pattern = pattern; 41 | this.validator = validator; 42 | } 43 | 44 | /// 45 | /// Implemented by concrete subclasses, this performs the final conversion from a non-null JSON value to 46 | /// a value of type T. 47 | /// 48 | /// The JSON reader to pull data from 49 | /// The serializer options to use for nested serialization 50 | /// The deserialized value of type T. 51 | 52 | protected override T ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptions options) 53 | { 54 | string text = reader.GetString(); 55 | return pattern.Parse(text).Value; 56 | } 57 | 58 | /// 59 | /// Writes the formatted value to the writer. 60 | /// 61 | /// The writer to write JSON data to 62 | /// The value to serialize 63 | /// The serializer options to use for nested serialization 64 | protected override void WriteJsonImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options) 65 | { 66 | validator?.Invoke(value); 67 | var text = pattern.Format(value); 68 | writer.WriteStringValue(text); 69 | } 70 | 71 | /// 72 | /// Writes the formatted value to the writer. 73 | /// 74 | /// The writer to write JSON data to. 75 | /// The value to serialize. 76 | /// The serialization options to use for nested serialization. 77 | protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options) 78 | { 79 | validator?.Invoke(value); 80 | var text = pattern.Format(value); 81 | writer.WritePropertyName(text); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/ExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Serialization.SystemTextJson; 6 | using NodaTime.Text; 7 | using NUnit.Framework; 8 | using System.Linq; 9 | using System.Text.Json; 10 | 11 | namespace NodaTime.Serialization.Test.SystemText 12 | { 13 | public class ExtensionsTest 14 | { 15 | [Test] 16 | public void Options_ConfigureForNodaTime_DefaultInterval() 17 | { 18 | var configuredOptions = new JsonSerializerOptions().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); 19 | var explicitOptions = new JsonSerializerOptions 20 | { 21 | Converters = { NodaConverters.IntervalConverter, NodaConverters.InstantConverter } 22 | }; 23 | var interval = new Interval(Instant.FromUnixTimeTicks(1000L), Instant.FromUnixTimeTicks(20000L)); 24 | Assert.AreEqual(JsonSerializer.Serialize(interval, explicitOptions), 25 | JsonSerializer.Serialize(interval, configuredOptions)); 26 | } 27 | 28 | [Test] 29 | public void Options_ConfigureForNodaTime_WithIsoIntervalConverter() 30 | { 31 | var configuredOptions = new JsonSerializerOptions().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb).WithIsoIntervalConverter(); 32 | var explicitOptions = new JsonSerializerOptions { Converters = { NodaConverters.IsoIntervalConverter } }; 33 | var interval = new Interval(Instant.FromUnixTimeTicks(1000L), Instant.FromUnixTimeTicks(20000L)); 34 | Assert.AreEqual(JsonSerializer.Serialize(interval, explicitOptions), 35 | JsonSerializer.Serialize(interval, configuredOptions)); 36 | } 37 | 38 | [Test] 39 | public void Options_ConfigureForNodaTime_DefaultDateInterval() 40 | { 41 | var configuredOptions = new JsonSerializerOptions().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); 42 | var explicitOptions = new JsonSerializerOptions 43 | { 44 | Converters = { NodaConverters.DateIntervalConverter, NodaConverters.LocalDateConverter } 45 | }; 46 | var interval = new DateInterval(new LocalDate(2001, 2, 3), new LocalDate(2004, 5, 6)); 47 | Assert.AreEqual(JsonSerializer.Serialize(interval, explicitOptions), 48 | JsonSerializer.Serialize(interval, configuredOptions)); 49 | } 50 | 51 | [Test] 52 | public void Options_ConfigureForNodaTime_WithIsoDateIntervalConverter() 53 | { 54 | var configuredOptions = new JsonSerializerOptions().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb).WithIsoDateIntervalConverter(); 55 | var explicitOptions = new JsonSerializerOptions { Converters = { NodaConverters.IsoDateIntervalConverter } }; 56 | var interval = new DateInterval(new LocalDate(2001, 2, 3), new LocalDate(2004, 5, 6)); 57 | Assert.AreEqual(JsonSerializer.Serialize(interval, explicitOptions), 58 | JsonSerializer.Serialize(interval, configuredOptions)); 59 | } 60 | 61 | [Test] 62 | public void Options_ConfigureForNodaTime_NodaJsonSettings() 63 | { 64 | var jsonSettings = new NodaJsonSettings 65 | { 66 | LocalDateConverter = new NodaPatternConverter(LocalDatePattern.CreateWithInvariantCulture("dd/MM/yyyy")), 67 | LocalTimeConverter = null 68 | }; 69 | var configuredOptions = new JsonSerializerOptions().ConfigureForNodaTime(jsonSettings); 70 | Assert.AreEqual("\"28/05/2023\"", JsonSerializer.Serialize(new LocalDate(2023, 5, 28), configuredOptions)); 71 | Assert.AreEqual("\"2023-05-28T18:07:12Z UTC\"", JsonSerializer.Serialize(new LocalDateTime(2023, 5, 28, 18, 07, 12).InUtc(), configuredOptions)); 72 | Assert.False(configuredOptions.Converters.Any(c => c.CanConvert(typeof(LocalTime)))); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/NodaConverterBaseTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Serialization.SystemTextJson; 6 | using NUnit.Framework; 7 | using System.Text.Json; 8 | using System.Text.Json.Serialization; 9 | 10 | namespace NodaTime.Serialization.Test.SystemText 11 | { 12 | public class NodaConverterBaseTest 13 | { 14 | [Test] 15 | public void Serialize_NonNullValue() 16 | { 17 | var options = CreateOptions(); 18 | JsonSerializer.Serialize(5, options); 19 | } 20 | 21 | [Test] 22 | public void Serialize_NullValue() 23 | { 24 | var options = CreateOptions(); 25 | JsonSerializer.Serialize((object)null, options); 26 | } 27 | 28 | [Test] 29 | public void Deserialize_NullableType_NullValue() 30 | { 31 | var options = CreateOptions(); 32 | Assert.IsNull(JsonSerializer.Deserialize("null", options)); 33 | } 34 | 35 | [Test] 36 | public void Deserialize_ReferenceType_NullValue() 37 | { 38 | var options = CreateOptions(); 39 | Assert.IsNull(JsonSerializer.Deserialize("null", options)); 40 | } 41 | 42 | [Test] 43 | public void Deserialize_NullableType_NonNullValue() 44 | { 45 | var options = CreateOptions(); 46 | Assert.AreEqual(5, JsonSerializer.Deserialize("\"5\"", options)); 47 | } 48 | 49 | [Test] 50 | public void Deserialize_NonNullableType_NullValue() 51 | { 52 | var options = CreateOptions(); 53 | Assert.Throws(() => JsonSerializer.Deserialize("null", options)); 54 | Assert.Throws(() => JsonSerializer.Deserialize("\"\"", options)); 55 | } 56 | 57 | [Test] 58 | public void Deserialize_NonNullableType_NonNullValue() 59 | { 60 | var options = CreateOptions(); 61 | Assert.AreEqual(5, JsonSerializer.Deserialize("\"5\"", options)); 62 | } 63 | 64 | private static JsonSerializerOptions CreateOptions() where TConverter : JsonConverter, new() => 65 | new JsonSerializerOptions 66 | { 67 | WriteIndented = false, 68 | Converters = { new TConverter() } 69 | }; 70 | 71 | private class TestConverter : NodaConverterBase 72 | { 73 | protected override int ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptions options) 74 | { 75 | return int.Parse(reader.GetString()); 76 | } 77 | 78 | protected override void WriteJsonImpl(Utf8JsonWriter writer, int value, JsonSerializerOptions options) => 79 | writer.WriteStringValue(value.ToString()); 80 | 81 | protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, int value, JsonSerializerOptions options) => 82 | writer.WritePropertyName(value.ToString()); 83 | } 84 | 85 | private class TestStringConverter : NodaConverterBase 86 | { 87 | protected override string ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptions options) 88 | { 89 | return reader.GetString(); 90 | } 91 | 92 | protected override void WriteJsonImpl(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => 93 | writer.WriteStringValue(value); 94 | 95 | protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => 96 | writer.WritePropertyName(value); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/NodaIsoIntervalConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Text.Json; 6 | using NodaTime.Serialization.SystemTextJson; 7 | using NUnit.Framework; 8 | using static NodaTime.Serialization.Test.SystemText.TestHelper; 9 | 10 | namespace NodaTime.Serialization.Test.SystemText 11 | { 12 | /// 13 | /// The same tests as NodaIntervalConverterTest, but using the ISO-based interval converter. 14 | /// 15 | public class NodaIsoIntervalConverterTest 16 | { 17 | private readonly JsonSerializerOptions settings = new JsonSerializerOptions 18 | { 19 | Converters = { NodaConverters.IsoIntervalConverter } 20 | }; 21 | 22 | [Test] 23 | public void RoundTrip() 24 | { 25 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5) + Duration.FromMilliseconds(670); 26 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10) + Duration.FromNanoseconds(123456789); 27 | var interval = new Interval(startInstant, endInstant); 28 | AssertConversions(interval, "\"2012-01-02T03:04:05.67Z/2013-06-07T08:09:10.123456789Z\"", settings); 29 | } 30 | 31 | [Test] 32 | public void RoundTrip_Infinite() 33 | { 34 | var instant = Instant.FromUtc(2013, 6, 7, 8, 9, 10) + Duration.FromNanoseconds(123456789); 35 | AssertConversions(new Interval(null, instant), "\"/2013-06-07T08:09:10.123456789Z\"", settings); 36 | AssertConversions(new Interval(instant, null), "\"2013-06-07T08:09:10.123456789Z/\"", settings); 37 | AssertConversions(new Interval(null, null), "\"/\"", settings); 38 | } 39 | 40 | [Test] 41 | public void DeserializeComma() 42 | { 43 | // Comma is deliberate, to show that we can parse a comma decimal separator too. 44 | string json = "\"2012-01-02T03:04:05.670Z/2013-06-07T08:09:10,1234567Z\""; 45 | 46 | var interval = JsonSerializer.Deserialize(json, settings); 47 | 48 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5) + Duration.FromMilliseconds(670); 49 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10) + Duration.FromTicks(1234567); 50 | var expectedInterval = new Interval(startInstant, endInstant); 51 | Assert.AreEqual(expectedInterval, interval); 52 | } 53 | 54 | [Test] 55 | [TestCase("\"2012-01-02T03:04:05Z2013-06-07T08:09:10Z\"")] 56 | public void InvalidJson(string json) 57 | { 58 | AssertInvalidJson(json, settings); 59 | } 60 | 61 | [Test] 62 | public void Serialize_InObject() 63 | { 64 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 65 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10); 66 | var interval = new Interval(startInstant, endInstant); 67 | 68 | var testObject = new TestObject { Interval = interval }; 69 | 70 | var json = JsonSerializer.Serialize(testObject, settings); 71 | 72 | string expectedJson = "{\"Interval\":\"2012-01-02T03:04:05Z/2013-06-07T08:09:10Z\"}"; 73 | Assert.AreEqual(expectedJson, json); 74 | } 75 | 76 | [Test] 77 | public void Deserialize_InObject() 78 | { 79 | string json = "{\"Interval\":\"2012-01-02T03:04:05Z/2013-06-07T08:09:10Z\"}"; 80 | 81 | var testObject = JsonSerializer.Deserialize(json, settings); 82 | 83 | var interval = testObject.Interval; 84 | 85 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 86 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10); 87 | var expectedInterval = new Interval(startInstant, endInstant); 88 | Assert.AreEqual(expectedInterval, interval); 89 | } 90 | 91 | public class TestObject 92 | { 93 | public Interval Interval { get; set; } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/JsonNet/NodaIsoIntervalConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using static NodaTime.Serialization.Test.JsonNet.TestHelper; 6 | 7 | using Newtonsoft.Json; 8 | using NodaTime.Serialization.JsonNet; 9 | using NUnit.Framework; 10 | 11 | namespace NodaTime.Serialization.Test.JsonNet 12 | { 13 | /// 14 | /// The same tests as NodaIntervalConverterTest, but using the ISO-based interval converter. 15 | /// 16 | public class NodaIsoIntervalConverterTest 17 | { 18 | private readonly JsonSerializerSettings settings = new JsonSerializerSettings 19 | { 20 | Converters = { NodaConverters.IsoIntervalConverter }, 21 | DateParseHandling = DateParseHandling.None 22 | }; 23 | 24 | [Test] 25 | public void RoundTrip() 26 | { 27 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5) + Duration.FromMilliseconds(670); 28 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10) + Duration.FromNanoseconds(123456789); 29 | var interval = new Interval(startInstant, endInstant); 30 | AssertConversions(interval, "\"2012-01-02T03:04:05.67Z/2013-06-07T08:09:10.123456789Z\"", settings); 31 | } 32 | 33 | [Test] 34 | public void RoundTrip_Infinite() 35 | { 36 | var instant = Instant.FromUtc(2013, 6, 7, 8, 9, 10) + Duration.FromNanoseconds(123456789); 37 | AssertConversions(new Interval(null, instant), "\"/2013-06-07T08:09:10.123456789Z\"", settings); 38 | AssertConversions(new Interval(instant, null), "\"2013-06-07T08:09:10.123456789Z/\"", settings); 39 | AssertConversions(new Interval(null, null), "\"/\"", settings); 40 | } 41 | 42 | [Test] 43 | public void DeserializeComma() 44 | { 45 | // Comma is deliberate, to show that we can parse a comma decimal separator too. 46 | string json = "\"2012-01-02T03:04:05.670Z/2013-06-07T08:09:10,1234567Z\""; 47 | 48 | var interval = JsonConvert.DeserializeObject(json, settings); 49 | 50 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5) + Duration.FromMilliseconds(670); 51 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10) + Duration.FromTicks(1234567); 52 | var expectedInterval = new Interval(startInstant, endInstant); 53 | Assert.AreEqual(expectedInterval, interval); 54 | } 55 | 56 | [Test] 57 | [TestCase("\"2012-01-02T03:04:05Z2013-06-07T08:09:10Z\"")] 58 | public void InvalidJson(string json) 59 | { 60 | AssertInvalidJson(json, settings); 61 | } 62 | 63 | [Test] 64 | public void Serialize_InObject() 65 | { 66 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 67 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10); 68 | var interval = new Interval(startInstant, endInstant); 69 | 70 | var testObject = new TestObject { Interval = interval }; 71 | 72 | var json = JsonConvert.SerializeObject(testObject, Formatting.None, settings); 73 | 74 | string expectedJson = "{\"Interval\":\"2012-01-02T03:04:05Z/2013-06-07T08:09:10Z\"}"; 75 | Assert.AreEqual(expectedJson, json); 76 | } 77 | 78 | [Test] 79 | public void Deserialize_InObject() 80 | { 81 | string json = "{\"Interval\":\"2012-01-02T03:04:05Z/2013-06-07T08:09:10Z\"}"; 82 | 83 | var testObject = JsonConvert.DeserializeObject(json, settings); 84 | 85 | var interval = testObject.Interval; 86 | 87 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 88 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10); 89 | var expectedInterval = new Interval(startInstant, endInstant); 90 | Assert.AreEqual(expectedInterval, interval); 91 | } 92 | 93 | public class TestObject 94 | { 95 | public Interval Interval { get; set; } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaIntervalConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using System.Text.Json; 7 | 8 | namespace NodaTime.Serialization.SystemTextJson 9 | { 10 | /// 11 | /// System.Text.Json converter for using a compound representation. The start and 12 | /// end aspects of the interval are represented with separate properties, each parsed and formatted 13 | /// by the converter for the serializer provided. 14 | /// 15 | internal sealed class NodaIntervalConverter : NodaConverterBase 16 | { 17 | /// 18 | /// Reads Start and End properties for the start and end of an interval, converting them to instants 19 | /// using the given serializer. 20 | /// 21 | /// The JSON reader to fetch data from. 22 | /// The serializer options for embedded serialization. 23 | /// The identified in the JSON. 24 | protected override Interval ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptions options) 25 | { 26 | Instant? startInstant = null; 27 | Instant? endInstant = null; 28 | while (reader.Read()) 29 | { 30 | if (reader.TokenType != JsonTokenType.PropertyName) 31 | { 32 | break; 33 | } 34 | 35 | var propertyName = reader.GetString(); 36 | // If we haven't got a property value, that's pretty weird. Break out of the loop, 37 | // and let the underlying framework fail appropriately... 38 | if (!reader.Read()) 39 | { 40 | break; 41 | } 42 | 43 | var caseSensitivity = options.PropertyNameCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; 44 | 45 | var startPropertyName = options.ResolvePropertyName(nameof(Interval.Start)); 46 | if (string.Equals(propertyName, startPropertyName, caseSensitivity)) 47 | { 48 | startInstant = options.ReadType(ref reader); 49 | } 50 | 51 | var endPropertyName = options.ResolvePropertyName(nameof(Interval.End)); 52 | if (string.Equals(propertyName, endPropertyName, caseSensitivity)) 53 | { 54 | endInstant = options.ReadType(ref reader); 55 | } 56 | } 57 | 58 | return new Interval(startInstant, endInstant); 59 | } 60 | 61 | /// 62 | /// Serializes the interval as start/end instants. 63 | /// 64 | /// The writer to write JSON to 65 | /// The interval to serialize 66 | /// The serializer options for embedded serialization. 67 | protected override void WriteJsonImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options) 68 | { 69 | writer.WriteStartObject(); 70 | 71 | if (value.HasStart) 72 | { 73 | var startPropertyName = options.ResolvePropertyName(nameof(Interval.Start)); 74 | writer.WritePropertyName(startPropertyName); 75 | options.WriteType(writer, value.Start); 76 | } 77 | if (value.HasEnd) 78 | { 79 | var endPropertyName = options.ResolvePropertyName(nameof(Interval.End)); 80 | writer.WritePropertyName(endPropertyName); 81 | options.WriteType(writer, value.End); 82 | } 83 | writer.WriteEndObject(); 84 | } 85 | 86 | /// 87 | /// Unconditionally throws an exception, as an Interval cannot be serialized as a JSON property name. 88 | /// 89 | /// The writer to write JSON to 90 | /// The date interval to serialize 91 | /// The serializer options for embedded serialization. 92 | /// Always thrown to indicate this is not an appropriate method to call on this type. 93 | protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, Interval value, JsonSerializerOptions options) => 94 | throw new JsonException("Cannot serialize an Interval as a JSON property name using this converter"); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/SystemTextJson/NodaTimeDefaultJsonConverterFactoryTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Serialization.SystemTextJson; 6 | using NodaTime.TimeZones; 7 | using NUnit.Framework; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Text; 11 | using System.Text.Json; 12 | using System.Text.Json.Serialization; 13 | 14 | namespace NodaTime.Serialization.Test.SystemTextJson; 15 | 16 | public partial class NodaTimeDefaultJsonConverterFactoryTest 17 | { 18 | private static readonly List convertibleTypes = new() 19 | { 20 | typeof(AnnualDate), 21 | typeof(AnnualDate?), 22 | typeof(DateInterval), 23 | typeof(DateTimeZone), 24 | typeof(Duration), 25 | typeof(Duration?), 26 | typeof(Instant), 27 | typeof(Instant?), 28 | typeof(Interval), 29 | typeof(Interval?), 30 | typeof(LocalDate), 31 | typeof(LocalDate?), 32 | typeof(LocalTime), 33 | typeof(LocalTime?), 34 | typeof(Offset), 35 | typeof(Offset?), 36 | typeof(OffsetDate), 37 | typeof(OffsetDate?), 38 | typeof(OffsetDateTime), 39 | typeof(OffsetDateTime?), 40 | typeof(OffsetTime), 41 | typeof(OffsetTime?), 42 | typeof(Period), 43 | // Note: YearMonth isn't supported yet 44 | typeof(ZonedDateTime), 45 | typeof(ZonedDateTime?), 46 | }; 47 | 48 | private static readonly List nonConvertibleTypes = new() 49 | { 50 | typeof(int), 51 | typeof(int?), 52 | typeof(PeriodBuilder), 53 | typeof(ZoneInterval) 54 | }; 55 | 56 | [Test] 57 | [TestCaseSource(nameof(convertibleTypes))] 58 | public void ConvertibleTypes(Type type) 59 | { 60 | var factory = new NodaTimeDefaultJsonConverterFactory(); 61 | Assert.IsTrue(factory.CanConvert(type)); 62 | var converter = factory.CreateConverter(type, default); 63 | Assert.NotNull(converter); 64 | Assert.IsTrue(converter.CanConvert(type)); 65 | } 66 | 67 | [Test] 68 | [TestCaseSource(nameof(nonConvertibleTypes))] 69 | public void NonConvertibleTypes(Type type) 70 | { 71 | var factory = new NodaTimeDefaultJsonConverterFactory(); 72 | Assert.IsFalse(factory.CanConvert(type)); 73 | var converter = factory.CreateConverter(type, default); 74 | Assert.IsNull(converter); 75 | } 76 | 77 | // See https://github.com/nodatime/nodatime.serialization/issues/97 and 78 | // https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation 79 | [Test] 80 | public void SourceGenerationCompatibility() 81 | { 82 | var sample = new SampleData { Foo = Instant.FromUtc(2023, 8, 6, 12, 40, 12) }; 83 | byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(sample, SampleJsonContext.Default.SampleData); 84 | string actual = Encoding.UTF8.GetString(utf8Json); 85 | string expected = "{\"Foo\":\"2023-08-06T12:40:12Z\"}"; 86 | Assert.AreEqual(expected, actual); 87 | } 88 | 89 | // See https://github.com/nodatime/nodatime.serialization/issues/127 and 90 | // https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json/source-generation 91 | [Test] 92 | public void SourceGenerationCompatibility_Nullable_NotNull() 93 | { 94 | var sample = new SampleDataNullable { Foo = Instant.FromUtc(2023, 8, 6, 12, 40, 12) }; 95 | byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(sample, SampleJsonContext.Default.SampleDataNullable); 96 | string actual = Encoding.UTF8.GetString(utf8Json); 97 | string expected = "{\"Foo\":\"2023-08-06T12:40:12Z\"}"; 98 | Assert.AreEqual(expected, actual); 99 | } 100 | 101 | [Test] 102 | public void SourceGenerationCompatibility_Nullable_IsNull() 103 | { 104 | var sample = new SampleDataNullable { Foo = null }; 105 | byte[] utf8Json = JsonSerializer.SerializeToUtf8Bytes(sample, SampleJsonContext.Default.SampleDataNullable); 106 | string actual = Encoding.UTF8.GetString(utf8Json); 107 | string expected = "{\"Foo\":null}"; 108 | Assert.AreEqual(expected, actual); 109 | } 110 | 111 | public class SampleData 112 | { 113 | [JsonConverter(typeof(NodaTimeDefaultJsonConverterFactory))] 114 | public Instant Foo { get; set; } 115 | } 116 | 117 | public class SampleDataNullable 118 | { 119 | [JsonConverter(typeof(NodaTimeDefaultJsonConverterFactory))] 120 | public Instant? Foo { get; set; } 121 | } 122 | 123 | [JsonSerializable(typeof(SampleData))] 124 | [JsonSerializable(typeof(SampleDataNullable))] 125 | public partial class SampleJsonContext : JsonSerializerContext 126 | { 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaDateIntervalConverter.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using System.Text.Json; 7 | using NodaTime.Utility; 8 | 9 | namespace NodaTime.Serialization.SystemTextJson 10 | { 11 | /// 12 | /// System.Text.Json converter for using a compound representation. The start and 13 | /// end aspects of the date interval are represented with separate properties, each parsed and formatted 14 | /// by the converter for the serializer provided. 15 | /// 16 | internal sealed class NodaDateIntervalConverter : NodaConverterBase 17 | { 18 | /// 19 | /// Reads Start and End properties for the start and end of a date interval, converting them to local dates 20 | /// using the given serializer. 21 | /// 22 | /// The JSON reader to fetch data from. 23 | /// The serializer options for embedded serialization. 24 | /// The identified in the JSON. 25 | protected override DateInterval ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptions options) 26 | { 27 | LocalDate? startLocalDate = null; 28 | LocalDate? endLocalDate = null; 29 | while (reader.Read()) 30 | { 31 | if (reader.TokenType != JsonTokenType.PropertyName) 32 | { 33 | break; 34 | } 35 | 36 | var propertyName = reader.GetString(); 37 | if (!reader.Read()) 38 | { 39 | break; 40 | } 41 | 42 | var caseSensitivity = options.PropertyNameCaseInsensitive ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; 43 | 44 | var startPropertyName = options.ResolvePropertyName(nameof(Interval.Start)); 45 | if (string.Equals(propertyName, startPropertyName, caseSensitivity)) 46 | { 47 | startLocalDate = options.ReadType(ref reader); 48 | } 49 | 50 | var endPropertyName = options.ResolvePropertyName(nameof(Interval.End)); 51 | if (string.Equals(propertyName, endPropertyName, caseSensitivity)) 52 | { 53 | endLocalDate = options.ReadType(ref reader); 54 | } 55 | } 56 | 57 | if (!startLocalDate.HasValue) 58 | { 59 | throw new InvalidNodaDataException("Expected date interval; start date was missing."); 60 | } 61 | 62 | if (!endLocalDate.HasValue) 63 | { 64 | throw new InvalidNodaDataException("Expected date interval; end date was missing."); 65 | } 66 | 67 | return new DateInterval(startLocalDate.Value, endLocalDate.Value); 68 | } 69 | 70 | /// 71 | /// Serializes the date interval as start/end local dates. 72 | /// 73 | /// The writer to write JSON to 74 | /// The date interval to serialize 75 | /// The serializer options for embedded serialization. 76 | protected override void WriteJsonImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options) 77 | { 78 | writer.WriteStartObject(); 79 | 80 | var startPropertyName = options.ResolvePropertyName(nameof(Interval.Start)); 81 | writer.WritePropertyName(startPropertyName); 82 | options.WriteType(writer, value.Start); 83 | 84 | var endPropertyName = options.ResolvePropertyName(nameof(Interval.End)); 85 | writer.WritePropertyName(endPropertyName); 86 | options.WriteType(writer, value.End); 87 | 88 | writer.WriteEndObject(); 89 | } 90 | 91 | /// 92 | /// Unconditionally throws an exception, as a DateInterval cannot be serialized as a JSON property name. 93 | /// 94 | /// The writer to write JSON to 95 | /// The date interval to serialize 96 | /// The serializer options for embedded serialization. 97 | /// Always thrown to indicate this is not an appropriate method to call on this type. 98 | protected override void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, DateInterval value, JsonSerializerOptions options) => 99 | throw new JsonException("Cannot serialize a DateInterval as a JSON property name using this converter"); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/JsonNet/NodaConverterBaseTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using System.IO; 7 | using Newtonsoft.Json; 8 | using NodaTime.Serialization.JsonNet; 9 | using NodaTime.Utility; 10 | using NUnit.Framework; 11 | 12 | namespace NodaTime.Serialization.Test.JsonNet 13 | { 14 | public class NodaConverterBaseTest 15 | { 16 | [Test] 17 | public void Serialize_NonNullValue() 18 | { 19 | var converter = new TestConverter(); 20 | 21 | JsonConvert.SerializeObject(5, Formatting.None, converter); 22 | } 23 | 24 | [Test] 25 | public void Serialize_NullValue() 26 | { 27 | var converter = new TestConverter(); 28 | 29 | JsonConvert.SerializeObject(null, Formatting.None, converter); 30 | } 31 | 32 | [Test] 33 | public void Deserialize_NullableType_NullValue() 34 | { 35 | var converter = new TestConverter(); 36 | 37 | Assert.IsNull(JsonConvert.DeserializeObject("null", converter)); 38 | } 39 | 40 | [Test] 41 | public void Deserialize_ReferenceType_NullValue() 42 | { 43 | var converter = new TestStringConverter(); 44 | 45 | Assert.IsNull(JsonConvert.DeserializeObject("null", converter)); 46 | } 47 | 48 | [Test] 49 | public void Deserialize_NullableType_NonNullValue() 50 | { 51 | var converter = new TestConverter(); 52 | 53 | Assert.AreEqual(5, JsonConvert.DeserializeObject("\"5\"", converter)); 54 | } 55 | 56 | [Test] 57 | public void Deserialize_NonNullableType_NullValue() 58 | { 59 | var converter = new TestConverter(); 60 | 61 | Assert.Throws(() => JsonConvert.DeserializeObject("null", converter)); 62 | } 63 | 64 | [Test] 65 | public void Deserialize_NonNullableType_NonNullValue() 66 | { 67 | var converter = new TestConverter(); 68 | 69 | Assert.AreEqual(5, JsonConvert.DeserializeObject("\"5\"", converter)); 70 | } 71 | 72 | [Test] 73 | public void Deserialize_NullableType_EmptyString() 74 | { 75 | var converter = new TestConverter(); 76 | 77 | Assert.IsNull(JsonConvert.DeserializeObject("\"\"", converter)); 78 | } 79 | 80 | [Test] 81 | public void Deserialize_ReferenceType_EmptyString() 82 | { 83 | var converter = new TestStringConverter(); 84 | 85 | Assert.IsNull(JsonConvert.DeserializeObject("\"\"", converter)); 86 | } 87 | 88 | [Test] 89 | public void Deserialize_NonNullableType_EmptyString() 90 | { 91 | var converter = new TestConverter(); 92 | 93 | Assert.Throws(() => JsonConvert.DeserializeObject("\"\"", converter)); 94 | } 95 | 96 | [Test] 97 | public void CanConvert_ValidValues() 98 | { 99 | var converter = new TestConverter(); 100 | 101 | Assert.IsTrue(converter.CanConvert(typeof(int))); 102 | Assert.IsTrue(converter.CanConvert(typeof(int?))); 103 | } 104 | 105 | [Test] 106 | public void CanConvert_InvalidValues() 107 | { 108 | var converter = new TestConverter(); 109 | 110 | Assert.IsFalse(converter.CanConvert(typeof(uint))); 111 | } 112 | 113 | [Test] 114 | public void CanConvert_Inheritance() 115 | { 116 | var converter = new TestInheritanceConverter(); 117 | 118 | Assert.IsTrue(converter.CanConvert(typeof(MemoryStream))); 119 | } 120 | 121 | private class TestConverter : NodaConverterBase 122 | { 123 | protected override int ReadJsonImpl(JsonReader reader, JsonSerializer serializer) 124 | { 125 | return int.Parse(reader.Value.ToString()); 126 | } 127 | 128 | protected override void WriteJsonImpl(JsonWriter writer, int value, JsonSerializer serializer) 129 | { 130 | writer.WriteValue(value.ToString()); 131 | } 132 | } 133 | 134 | private class TestStringConverter : NodaConverterBase 135 | { 136 | protected override string ReadJsonImpl(JsonReader reader, JsonSerializer serializer) 137 | { 138 | return reader.Value.ToString(); 139 | } 140 | 141 | protected override void WriteJsonImpl(JsonWriter writer, string value, JsonSerializer serializer) 142 | { 143 | writer.WriteValue(value); 144 | } 145 | } 146 | 147 | /// 148 | /// Just use for CanConvert testing... 149 | /// 150 | private class TestInheritanceConverter : NodaConverterBase 151 | { 152 | protected override Stream ReadJsonImpl(JsonReader reader, JsonSerializer serializer) 153 | { 154 | throw new NotImplementedException(); 155 | } 156 | 157 | protected override void WriteJsonImpl(JsonWriter writer, Stream value, JsonSerializer serializer) 158 | { 159 | throw new NotImplementedException(); 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaJsonSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System.Text.Json; 6 | using System.Collections.Generic; 7 | using System.Text.Json.Serialization; 8 | 9 | namespace NodaTime.Serialization.SystemTextJson; 10 | 11 | /// 12 | /// A collection of converters and related settings for 13 | /// Noda Time JSON parsing. This can be used to configure System.Text.Json 14 | /// serializers using the 15 | /// extension method. 16 | /// 17 | /// 18 | /// This type does not attempt to ensure any sort of thread safety. 19 | /// The expect use is to create an instance, potentially modify some properties, 20 | /// use it to configure a , and then discard it. 21 | /// 22 | public sealed class NodaJsonSettings 23 | { 24 | /// 25 | /// The converter used for values. 26 | /// 27 | public JsonConverter InstantConverter { get; set; } 28 | 29 | /// 30 | /// The converter used for values. 31 | /// 32 | public JsonConverter IntervalConverter { get; set; } 33 | 34 | /// 35 | /// The converter used for values. 36 | /// 37 | public JsonConverter LocalDateConverter { get; set; } 38 | 39 | /// 40 | /// The converter used for values. 41 | /// 42 | public JsonConverter LocalTimeConverter { get; set; } 43 | 44 | /// 45 | /// The converter used for values. 46 | /// 47 | public JsonConverter LocalDateTimeConverter { get; set; } 48 | 49 | /// 50 | /// The converter used for values. 51 | /// 52 | public JsonConverter AnnualDateConverter { get; set; } 53 | 54 | /// 55 | /// The converter used for values. 56 | /// 57 | public JsonConverter DateIntervalConverter { get; set; } 58 | 59 | /// 60 | /// The converter used for values. 61 | /// 62 | public JsonConverter OffsetConverter { get; set; } 63 | 64 | /// 65 | /// The converter used for values. 66 | /// 67 | public JsonConverter DateTimeZoneConverter { get; set; } 68 | 69 | /// 70 | /// The converter used for values. 71 | /// 72 | public JsonConverter DurationConverter { get; set; } 73 | 74 | /// 75 | /// The converter used for values. 76 | /// 77 | public JsonConverter PeriodConverter { get; set; } 78 | 79 | /// 80 | /// The converter used for values. 81 | /// 82 | public JsonConverter OffsetDateConverter { get; set; } 83 | 84 | /// 85 | /// The converter used for values. 86 | /// 87 | public JsonConverter OffsetTimeConverter { get; set; } 88 | 89 | /// 90 | /// The converter used for values. 91 | /// 92 | public JsonConverter OffsetDateTimeConverter { get; set; } 93 | 94 | /// 95 | /// The converter used for values. 96 | /// 97 | public JsonConverter ZonedDateTimeConverter { get; set; } 98 | 99 | /// 100 | /// Creates an instance with the default converters, using for 101 | /// time zone conversions. 102 | /// 103 | public NodaJsonSettings() : this(DateTimeZoneProviders.Tzdb) 104 | { 105 | } 106 | 107 | /// 108 | /// Creates an instance with the default converters, using the specified 109 | /// for time zone conversions. 110 | /// 111 | /// The time zone provider to use. Must not be null. 112 | /// 113 | public NodaJsonSettings(IDateTimeZoneProvider provider) 114 | { 115 | Preconditions.CheckNotNull(provider, nameof(provider)); 116 | InstantConverter = NodaConverters.InstantConverter; 117 | IntervalConverter = NodaConverters.IntervalConverter; 118 | LocalDateConverter = NodaConverters.LocalDateConverter; 119 | LocalTimeConverter = NodaConverters.LocalTimeConverter; 120 | LocalDateTimeConverter = NodaConverters.LocalDateTimeConverter; 121 | AnnualDateConverter = NodaConverters.AnnualDateConverter; 122 | DateIntervalConverter = NodaConverters.DateIntervalConverter; 123 | OffsetConverter = NodaConverters.OffsetConverter; 124 | DateTimeZoneConverter = NodaConverters.CreateDateTimeZoneConverter(provider); 125 | DurationConverter = NodaConverters.DurationConverter; 126 | PeriodConverter = NodaConverters.RoundtripPeriodConverter; 127 | OffsetDateConverter = NodaConverters.OffsetDateConverter; 128 | OffsetTimeConverter = NodaConverters.OffsetTimeConverter; 129 | OffsetDateTimeConverter = NodaConverters.OffsetDateTimeConverter; 130 | ZonedDateTimeConverter = NodaConverters.CreateZonedDateTimeConverter(provider); 131 | } 132 | 133 | internal void AddConverters(IList converters) 134 | { 135 | MaybeAdd(InstantConverter); 136 | MaybeAdd(IntervalConverter); 137 | MaybeAdd(LocalDateConverter); 138 | MaybeAdd(LocalDateTimeConverter); 139 | MaybeAdd(LocalTimeConverter); 140 | MaybeAdd(AnnualDateConverter); 141 | MaybeAdd(DateIntervalConverter); 142 | MaybeAdd(OffsetConverter); 143 | MaybeAdd(DateTimeZoneConverter); 144 | MaybeAdd(DurationConverter); 145 | MaybeAdd(PeriodConverter); 146 | MaybeAdd(OffsetDateTimeConverter); 147 | MaybeAdd(OffsetDateConverter); 148 | MaybeAdd(OffsetTimeConverter); 149 | MaybeAdd(ZonedDateTimeConverter); 150 | 151 | void MaybeAdd(JsonConverter converter) 152 | { 153 | if (converter is not null) 154 | { 155 | converters.Add(converter); 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/NodaJsonSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Newtonsoft.Json; 6 | using System.Collections.Generic; 7 | 8 | namespace NodaTime.Serialization.JsonNet; 9 | 10 | /// 11 | /// A collection of converters and related settings for 12 | /// Noda Time JSON parsing. This can be used to configure Newtonsoft.Json 13 | /// serializers using the 14 | /// and extension 15 | /// methods. 16 | /// 17 | /// 18 | /// This type does not attempt to ensure any sort of thread safety. 19 | /// The expect use is to create an instance, potentially modify some properties, 20 | /// use it to configure a or , 21 | /// and then discard it. 22 | /// 23 | public sealed class NodaJsonSettings 24 | { 25 | /// 26 | /// The converter used for values. 27 | /// 28 | public JsonConverter InstantConverter { get; set; } 29 | 30 | /// 31 | /// The converter used for values. 32 | /// 33 | public JsonConverter IntervalConverter { get; set; } 34 | 35 | /// 36 | /// The converter used for values. 37 | /// 38 | public JsonConverter LocalDateConverter { get; set; } 39 | 40 | /// 41 | /// The converter used for values. 42 | /// 43 | public JsonConverter LocalTimeConverter { get; set; } 44 | 45 | /// 46 | /// The converter used for values. 47 | /// 48 | public JsonConverter LocalDateTimeConverter { get; set; } 49 | 50 | /// 51 | /// The converter used for values. 52 | /// 53 | public JsonConverter AnnualDateConverter { get; set; } 54 | 55 | /// 56 | /// The converter used for values. 57 | /// 58 | public JsonConverter DateIntervalConverter { get; set; } 59 | 60 | /// 61 | /// The converter used for values. 62 | /// 63 | public JsonConverter OffsetConverter { get; set; } 64 | 65 | /// 66 | /// The converter used for values. 67 | /// 68 | public JsonConverter DateTimeZoneConverter { get; set; } 69 | 70 | /// 71 | /// The converter used for values. 72 | /// 73 | public JsonConverter DurationConverter { get; set; } 74 | 75 | /// 76 | /// The converter used for values. 77 | /// 78 | public JsonConverter PeriodConverter { get; set; } 79 | 80 | /// 81 | /// The converter used for values. 82 | /// 83 | public JsonConverter OffsetDateConverter { get; set; } 84 | 85 | /// 86 | /// The converter used for values. 87 | /// 88 | public JsonConverter OffsetTimeConverter { get; set; } 89 | 90 | /// 91 | /// The converter used for values. 92 | /// 93 | public JsonConverter OffsetDateTimeConverter { get; set; } 94 | 95 | /// 96 | /// The converter used for values. 97 | /// 98 | public JsonConverter ZonedDateTimeConverter { get; set; } 99 | 100 | /// 101 | /// Creates an instance with the default converters, using for 102 | /// time zone conversions. 103 | /// 104 | public NodaJsonSettings() : this(DateTimeZoneProviders.Tzdb) 105 | { 106 | } 107 | 108 | /// 109 | /// Creates an instance with the default converters, using the specified 110 | /// for time zone conversions. 111 | /// 112 | /// The time zone provider to use. Must not be null. 113 | /// 114 | public NodaJsonSettings(IDateTimeZoneProvider provider) 115 | { 116 | Preconditions.CheckNotNull(provider, nameof(provider)); 117 | InstantConverter = NodaConverters.InstantConverter; 118 | IntervalConverter = NodaConverters.IntervalConverter; 119 | LocalDateConverter = NodaConverters.LocalDateConverter; 120 | LocalTimeConverter = NodaConverters.LocalTimeConverter; 121 | LocalDateTimeConverter = NodaConverters.LocalDateTimeConverter; 122 | AnnualDateConverter = NodaConverters.AnnualDateConverter; 123 | DateIntervalConverter = NodaConverters.DateIntervalConverter; 124 | OffsetConverter = NodaConverters.OffsetConverter; 125 | DateTimeZoneConverter = NodaConverters.CreateDateTimeZoneConverter(provider); 126 | DurationConverter = NodaConverters.DurationConverter; 127 | PeriodConverter = NodaConverters.RoundtripPeriodConverter; 128 | OffsetDateConverter = NodaConverters.OffsetDateConverter; 129 | OffsetTimeConverter = NodaConverters.OffsetTimeConverter; 130 | OffsetDateTimeConverter = NodaConverters.OffsetDateTimeConverter; 131 | ZonedDateTimeConverter = NodaConverters.CreateZonedDateTimeConverter(provider); 132 | } 133 | 134 | internal void AddConverters(IList converters) 135 | { 136 | MaybeAdd(InstantConverter); 137 | MaybeAdd(IntervalConverter); 138 | MaybeAdd(LocalDateConverter); 139 | MaybeAdd(LocalDateTimeConverter); 140 | MaybeAdd(LocalTimeConverter); 141 | MaybeAdd(AnnualDateConverter); 142 | MaybeAdd(DateIntervalConverter); 143 | MaybeAdd(OffsetConverter); 144 | MaybeAdd(DateTimeZoneConverter); 145 | MaybeAdd(DurationConverter); 146 | MaybeAdd(PeriodConverter); 147 | MaybeAdd(OffsetDateTimeConverter); 148 | MaybeAdd(OffsetDateConverter); 149 | MaybeAdd(OffsetTimeConverter); 150 | MaybeAdd(ZonedDateTimeConverter); 151 | 152 | void MaybeAdd(JsonConverter converter) 153 | { 154 | if (converter is not null) 155 | { 156 | converters.Add(converter); 157 | } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/JsonNet/NodaDateIntervalConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using static NodaTime.Serialization.Test.JsonNet.TestHelper; 6 | 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Serialization; 9 | using NodaTime.Serialization.JsonNet; 10 | using NUnit.Framework; 11 | 12 | namespace NodaTime.Serialization.Test.JsonNet 13 | { 14 | public class NodaDateIntervalConverterTest 15 | { 16 | private readonly JsonSerializerSettings settings = new JsonSerializerSettings 17 | { 18 | Converters = { NodaConverters.DateIntervalConverter, NodaConverters.LocalDateConverter }, 19 | DateParseHandling = DateParseHandling.None 20 | }; 21 | 22 | private readonly JsonSerializerSettings settingsCamelCase = new JsonSerializerSettings 23 | { 24 | ContractResolver = new CamelCasePropertyNamesContractResolver(), 25 | Converters = { NodaConverters.DateIntervalConverter, NodaConverters.LocalDateConverter }, 26 | DateParseHandling = DateParseHandling.None 27 | }; 28 | 29 | [Test] 30 | public void RoundTrip() 31 | { 32 | var startLocalDate = new LocalDate(2012, 1, 2); 33 | var endLocalDate = new LocalDate(2013, 6, 7); 34 | var dateInterval = new DateInterval(startLocalDate, endLocalDate); 35 | AssertConversions(dateInterval, "{\"Start\":\"2012-01-02\",\"End\":\"2013-06-07\"}", settings); 36 | } 37 | 38 | [Test] 39 | public void RoundTrip_CamelCase() 40 | { 41 | var startLocalDate = new LocalDate(2012, 1, 2); 42 | var endLocalDate = new LocalDate(2013, 6, 7); 43 | var dateInterval = new DateInterval(startLocalDate, endLocalDate); 44 | AssertConversions(dateInterval, "{\"start\":\"2012-01-02\",\"end\":\"2013-06-07\"}", settingsCamelCase); 45 | } 46 | 47 | [Test] 48 | public void Serialize_InObject() 49 | { 50 | var startLocalDate = new LocalDate(2012, 1, 2); 51 | var endLocalDate = new LocalDate(2013, 6, 7); 52 | var dateInterval = new DateInterval(startLocalDate, endLocalDate); 53 | 54 | var testObject = new TestObject { Interval = dateInterval }; 55 | 56 | var json = JsonConvert.SerializeObject(testObject, Formatting.None, settings); 57 | 58 | string expectedJson = "{\"Interval\":{\"Start\":\"2012-01-02\",\"End\":\"2013-06-07\"}}"; 59 | Assert.AreEqual(expectedJson, json); 60 | } 61 | 62 | [Test] 63 | public void Serialize_InObject_CamelCase() 64 | { 65 | var startLocalDate = new LocalDate(2012, 1, 2); 66 | var endLocalDate = new LocalDate(2013, 6, 7); 67 | var dateInterval = new DateInterval(startLocalDate, endLocalDate); 68 | 69 | var testObject = new TestObject { Interval = dateInterval }; 70 | 71 | var json = JsonConvert.SerializeObject(testObject, Formatting.None, settingsCamelCase); 72 | 73 | string expectedJson = "{\"interval\":{\"start\":\"2012-01-02\",\"end\":\"2013-06-07\"}}"; 74 | Assert.AreEqual(expectedJson, json); 75 | } 76 | 77 | [Test] 78 | public void Deserialize_InObject() 79 | { 80 | string json = "{\"Interval\":{\"Start\":\"2012-01-02\",\"End\":\"2013-06-07\"}}"; 81 | 82 | var testObject = JsonConvert.DeserializeObject(json, settings); 83 | 84 | var interval = testObject.Interval; 85 | 86 | var startLocalDate = new LocalDate(2012, 1, 2); 87 | var endLocalDate = new LocalDate(2013, 6, 7); 88 | var expectedInterval = new DateInterval(startLocalDate, endLocalDate); 89 | Assert.AreEqual(expectedInterval, interval); 90 | } 91 | 92 | [Test] 93 | public void Deserialize_InObject_CamelCase() 94 | { 95 | string json = "{\"interval\":{\"start\":\"2012-01-02\",\"end\":\"2013-06-07\"}}"; 96 | 97 | var testObject = JsonConvert.DeserializeObject(json, settingsCamelCase); 98 | 99 | var interval = testObject.Interval; 100 | 101 | var startLocalDate = new LocalDate(2012, 1, 2); 102 | var endLocalDate = new LocalDate(2013, 6, 7); 103 | var expectedInterval = new DateInterval(startLocalDate, endLocalDate); 104 | Assert.AreEqual(expectedInterval, interval); 105 | } 106 | 107 | [Test] 108 | public void Deserialize_CaseInsensitive() 109 | { 110 | string json = "{\"Interval\":{\"Start\":\"2012-01-02\",\"End\":\"2013-06-07\"}}"; 111 | 112 | var testObjectPascalCase = JsonConvert.DeserializeObject(json, settings); 113 | var testObjectCamelCase = JsonConvert.DeserializeObject(json, settingsCamelCase); 114 | 115 | var intervalPascalCase = testObjectPascalCase.Interval; 116 | var intervalCamelCase = testObjectCamelCase.Interval; 117 | 118 | var startLocalDate = new LocalDate(2012, 1, 2); 119 | var endLocalDate = new LocalDate(2013, 6, 7); 120 | var expectedInterval = new DateInterval(startLocalDate, endLocalDate); 121 | Assert.AreEqual(expectedInterval, intervalPascalCase); 122 | Assert.AreEqual(expectedInterval, intervalCamelCase); 123 | } 124 | 125 | [Test] 126 | public void Deserialize_CaseInsensitive_CamelCase() 127 | { 128 | string json = "{\"interval\":{\"start\":\"2012-01-02\",\"end\":\"2013-06-07\"}}"; 129 | 130 | var testObjectPascalCase = JsonConvert.DeserializeObject(json, settings); 131 | var testObjectCamelCase = JsonConvert.DeserializeObject(json, settingsCamelCase); 132 | 133 | var intervalPascalCase = testObjectPascalCase.Interval; 134 | var intervalCamelCase = testObjectCamelCase.Interval; 135 | 136 | var startLocalDate = new LocalDate(2012, 1, 2); 137 | var endLocalDate = new LocalDate(2013, 6, 7); 138 | var expectedInterval = new DateInterval(startLocalDate, endLocalDate); 139 | Assert.AreEqual(expectedInterval, intervalPascalCase); 140 | Assert.AreEqual(expectedInterval, intervalCamelCase); 141 | } 142 | 143 | public class TestObject 144 | { 145 | public DateInterval Interval { get; set; } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.JsonNet/NodaConverterBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using System.Reflection; 7 | using Newtonsoft.Json; 8 | using NodaTime.Utility; 9 | 10 | namespace NodaTime.Serialization.JsonNet 11 | { 12 | /// 13 | /// Base class for all the Json.NET converters which handle value types (which is most of them). 14 | /// This handles all the boilerplate code dealing with nullity. 15 | /// 16 | /// The type to convert to/from JSON. 17 | public abstract class NodaConverterBase : JsonConverter 18 | { 19 | /// 20 | /// Default constructor. 21 | /// 22 | protected NodaConverterBase() 23 | { 24 | } 25 | 26 | // For value types and sealed classes, we can optimize and not call IsAssignableFrom. 27 | private static readonly bool CheckAssignableFrom = 28 | !(typeof(T).IsValueType || (typeof(T).IsClass && typeof(T).IsSealed)); 29 | 30 | private static readonly Type NullableT = typeof(T).IsValueType 31 | ? typeof(Nullable<>).MakeGenericType(typeof(T)) : typeof(T); 32 | 33 | // TODO: It's not clear whether we *should* support inheritance here. The Json.NET docs 34 | // aren't clear on when this is used - is it for reading or writing? If it's for both, that's 35 | // a problem: our "writer" may be okay for subclasses, but that doesn't mean the "reader" is. 36 | // This may well only be an issue for DateTimeZone, as everything else uses a sealed type (e.g. Period) 37 | // or a value type. 38 | 39 | /// 40 | /// Returns whether or not this converter supports the given type. 41 | /// 42 | /// The type to check for compatibility. 43 | /// True if the given type is supported by this converter (including the nullable form for 44 | /// value types); false otherwise. 45 | public override bool CanConvert(Type objectType) => 46 | objectType == typeof(T) || objectType == NullableT || 47 | (CheckAssignableFrom && typeof(T).IsAssignableFrom(objectType.GetTypeInfo())); 48 | 49 | /// 50 | /// Converts the JSON stored in a reader into the relevant Noda Time type. 51 | /// 52 | /// The Json.NET reader to read data from. 53 | /// The type to convert the JSON to. 54 | /// An existing value; ignored by this converter. 55 | /// A serializer to use for any embedded deserialization. 56 | /// The JSON was invalid for this converter. 57 | /// The deserialized value. 58 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 59 | { 60 | if (reader.TokenType == JsonToken.Null) 61 | { 62 | Preconditions.CheckData(objectType == NullableT, 63 | "Cannot convert null value to {0}", 64 | objectType); 65 | return null; 66 | } 67 | 68 | // Handle empty strings automatically 69 | if (reader.TokenType == JsonToken.String) 70 | { 71 | string value = (string) reader.Value; 72 | if (value == "") 73 | { 74 | Preconditions.CheckData(objectType == NullableT, 75 | "Cannot convert null value to {0}", 76 | objectType); 77 | return null; 78 | } 79 | } 80 | 81 | try 82 | { 83 | // Delegate to the concrete subclass. At this point we know that we don't want to return null, so we 84 | // can ask the subclass to return a T, which we will box. That will be valid even if objectType is 85 | // T? because the boxed form of a non-null T? value is just the boxed value itself. 86 | 87 | // Note that we don't currently pass existingValue down; we could change this if we ever found a use for it. 88 | return ReadJsonImpl(reader, serializer); 89 | } 90 | catch (Exception ex) 91 | { 92 | throw new JsonSerializationException($"Cannot convert value to {objectType}", ex); 93 | } 94 | } 95 | 96 | /// 97 | /// Implemented by concrete subclasses, this performs the final conversion from a non-null JSON value to 98 | /// a value of type T. 99 | /// 100 | /// The JSON reader to pull data from 101 | /// The serializer to use for nested serialization 102 | /// The deserialized value of type T. 103 | protected abstract T ReadJsonImpl(JsonReader reader, JsonSerializer serializer); 104 | 105 | /// 106 | /// Writes the given value to a Json.NET writer. 107 | /// 108 | /// The writer to write the JSON to. 109 | /// The value to write. 110 | /// The serializer to use for any embedded serialization. 111 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 112 | { 113 | // Json.NET should prevent this happening, but let's validate... 114 | Preconditions.CheckNotNull(value, nameof(value)); 115 | 116 | // Note: don't need to worry about value is T? due to the way boxing works. 117 | // Again, Json.NET probably prevents us from needing to check this, really. 118 | if (value is T castValue) 119 | { 120 | WriteJsonImpl(writer, castValue, serializer); 121 | return; 122 | } 123 | throw new ArgumentException($"Unexpected value when converting. Expected {typeof(T).FullName}, got {value.GetType().FullName}."); 124 | } 125 | 126 | /// 127 | /// Implemented by concrete subclasses, this performs the final write operation for a non-null value of type T 128 | /// to JSON. 129 | /// 130 | /// The writer to write JSON data to 131 | /// The value to serialize 132 | /// The serializer to use for nested serialization 133 | protected abstract void WriteJsonImpl(JsonWriter writer, T value, JsonSerializer serializer); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Test/JsonNet/NodaIntervalConverterTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using static NodaTime.Serialization.Test.JsonNet.TestHelper; 6 | 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Serialization; 9 | using NodaTime.Serialization.JsonNet; 10 | using NodaTime.Utility; 11 | using NUnit.Framework; 12 | 13 | namespace NodaTime.Serialization.Test.JsonNet 14 | { 15 | public class NodaIntervalConverterTest 16 | { 17 | private readonly JsonSerializerSettings settings = new JsonSerializerSettings 18 | { 19 | ContractResolver = new DefaultContractResolver(), 20 | Converters = { NodaConverters.IntervalConverter, NodaConverters.InstantConverter }, 21 | DateParseHandling = DateParseHandling.None 22 | }; 23 | 24 | private readonly JsonSerializerSettings settingsCamelCase = new JsonSerializerSettings 25 | { 26 | ContractResolver = new CamelCasePropertyNamesContractResolver(), 27 | Converters = { NodaConverters.IntervalConverter, NodaConverters.InstantConverter }, 28 | DateParseHandling = DateParseHandling.None 29 | }; 30 | 31 | [Test] 32 | public void RoundTrip() 33 | { 34 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5) + Duration.FromMilliseconds(670); 35 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10) + Duration.FromNanoseconds(123456789); 36 | var interval = new Interval(startInstant, endInstant); 37 | AssertConversions(interval, "{\"Start\":\"2012-01-02T03:04:05.67Z\",\"End\":\"2013-06-07T08:09:10.123456789Z\"}", settings); 38 | } 39 | 40 | [Test] 41 | public void RoundTrip_Infinite() 42 | { 43 | var instant = Instant.FromUtc(2013, 6, 7, 8, 9, 10) + Duration.FromNanoseconds(123456789); 44 | AssertConversions(new Interval(null, instant), "{\"End\":\"2013-06-07T08:09:10.123456789Z\"}", settings); 45 | AssertConversions(new Interval(instant, null), "{\"Start\":\"2013-06-07T08:09:10.123456789Z\"}", settings); 46 | AssertConversions(new Interval(null, null), "{}", settings); 47 | } 48 | 49 | [Test] 50 | public void Serialize_InObject() 51 | { 52 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 53 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10); 54 | var interval = new Interval(startInstant, endInstant); 55 | 56 | var testObject = new TestObject { Interval = interval }; 57 | 58 | var json = JsonConvert.SerializeObject(testObject, Formatting.None, settings); 59 | 60 | string expectedJson = "{\"Interval\":{\"Start\":\"2012-01-02T03:04:05Z\",\"End\":\"2013-06-07T08:09:10Z\"}}"; 61 | Assert.AreEqual(expectedJson, json); 62 | } 63 | 64 | [Test] 65 | public void Serialize_InObject_CamelCase() 66 | { 67 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 68 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10); 69 | var interval = new Interval(startInstant, endInstant); 70 | 71 | var testObject = new TestObject { Interval = interval }; 72 | 73 | var json = JsonConvert.SerializeObject(testObject, Formatting.None, settingsCamelCase); 74 | 75 | string expectedJson = "{\"interval\":{\"start\":\"2012-01-02T03:04:05Z\",\"end\":\"2013-06-07T08:09:10Z\"}}"; 76 | Assert.AreEqual(expectedJson, json); 77 | } 78 | 79 | [Test] 80 | public void Deserialize_InObject() 81 | { 82 | string json = "{\"Interval\":{\"Start\":\"2012-01-02T03:04:05Z\",\"End\":\"2013-06-07T08:09:10Z\"}}"; 83 | 84 | var testObject = JsonConvert.DeserializeObject(json, settings); 85 | 86 | var interval = testObject.Interval; 87 | 88 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 89 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10); 90 | var expectedInterval = new Interval(startInstant, endInstant); 91 | Assert.AreEqual(expectedInterval, interval); 92 | } 93 | 94 | [Test] 95 | public void Deserialize_InObject_CamelCase() 96 | { 97 | string json = "{\"interval\":{\"start\":\"2012-01-02T03:04:05Z\",\"end\":\"2013-06-07T08:09:10Z\"}}"; 98 | 99 | var testObject = JsonConvert.DeserializeObject(json, settingsCamelCase); 100 | 101 | var interval = testObject.Interval; 102 | 103 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 104 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10); 105 | var expectedInterval = new Interval(startInstant, endInstant); 106 | Assert.AreEqual(expectedInterval, interval); 107 | } 108 | 109 | [Test] 110 | public void Deserialize_CaseInsensitive() 111 | { 112 | string json = "{\"Interval\":{\"Start\":\"2012-01-02T03:04:05Z\",\"End\":\"2013-06-07T08:09:10Z\"}}"; 113 | 114 | var testObjectPascalCase = JsonConvert.DeserializeObject(json, settings); 115 | var testObjectCamelCase = JsonConvert.DeserializeObject(json, settingsCamelCase); 116 | 117 | var intervalPascalCase = testObjectPascalCase.Interval; 118 | var intervalCamelCase = testObjectCamelCase.Interval; 119 | 120 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 121 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10); 122 | var expectedInterval = new Interval(startInstant, endInstant); 123 | Assert.AreEqual(expectedInterval, intervalPascalCase); 124 | Assert.AreEqual(expectedInterval, intervalCamelCase); 125 | } 126 | 127 | [Test] 128 | public void Deserialize_CaseInsensitive_CamelCase() 129 | { 130 | string json = "{\"interval\":{\"start\":\"2012-01-02T03:04:05Z\",\"end\":\"2013-06-07T08:09:10Z\"}}"; 131 | 132 | var testObjectPascalCase = JsonConvert.DeserializeObject(json, settings); 133 | var testObjectCamelCase = JsonConvert.DeserializeObject(json, settingsCamelCase); 134 | 135 | var intervalPascalCase = testObjectPascalCase.Interval; 136 | var intervalCamelCase = testObjectCamelCase.Interval; 137 | 138 | var startInstant = Instant.FromUtc(2012, 1, 2, 3, 4, 5); 139 | var endInstant = Instant.FromUtc(2013, 6, 7, 8, 9, 10); 140 | var expectedInterval = new Interval(startInstant, endInstant); 141 | Assert.AreEqual(expectedInterval, intervalPascalCase); 142 | Assert.AreEqual(expectedInterval, intervalCamelCase); 143 | } 144 | 145 | public class TestObject 146 | { 147 | public Interval Interval { get; set; } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29411.108 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NodaTime.Serialization.Test", "NodaTime.Serialization.Test\NodaTime.Serialization.Test.csproj", "{07063FD8-A2C2-43FB-A52C-093F4FB3F483}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NodaTime.Serialization.JsonNet", "NodaTime.Serialization.JsonNet\NodaTime.Serialization.JsonNet.csproj", "{40445ECC-0F53-4ED2-A764-C6F81410DF73}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NodaTime.Serialization.Benchmarks", "NodaTime.Serialization.Benchmarks\NodaTime.Serialization.Benchmarks.csproj", "{7E96596E-800E-4F71-9304-F1D0F6B9937B}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NodaTime.Serialization.Protobuf", "NodaTime.Serialization.Protobuf\NodaTime.Serialization.Protobuf.csproj", "{7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NodaTime.Serialization.SystemTextJson", "NodaTime.Serialization.SystemTextJson\NodaTime.Serialization.SystemTextJson.csproj", "{3B4A09D5-F07E-435C-8546-F54509C1DC7A}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MSBuild", "MSBuild", "{097D409B-EA16-4053-A8D4-9357ABB62419}" 17 | ProjectSection(SolutionItems) = preProject 18 | ..\Directory.Build.props = ..\Directory.Build.props 19 | ..\Directory.Build.targets = ..\Directory.Build.targets 20 | EndProjectSection 21 | EndProject 22 | Global 23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 24 | Debug|Any CPU = Debug|Any CPU 25 | Debug|x64 = Debug|x64 26 | Debug|x86 = Debug|x86 27 | Release|Any CPU = Release|Any CPU 28 | Release|x64 = Release|x64 29 | Release|x86 = Release|x86 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Debug|x64.ActiveCfg = Debug|Any CPU 35 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Debug|x64.Build.0 = Debug|Any CPU 36 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Debug|x86.ActiveCfg = Debug|Any CPU 37 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Debug|x86.Build.0 = Debug|Any CPU 38 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Release|x64.ActiveCfg = Release|Any CPU 41 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Release|x64.Build.0 = Release|Any CPU 42 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Release|x86.ActiveCfg = Release|Any CPU 43 | {07063FD8-A2C2-43FB-A52C-093F4FB3F483}.Release|x86.Build.0 = Release|Any CPU 44 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Debug|x64.ActiveCfg = Debug|Any CPU 47 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Debug|x64.Build.0 = Debug|Any CPU 48 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Debug|x86.ActiveCfg = Debug|Any CPU 49 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Debug|x86.Build.0 = Debug|Any CPU 50 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Release|x64.ActiveCfg = Release|Any CPU 53 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Release|x64.Build.0 = Release|Any CPU 54 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Release|x86.ActiveCfg = Release|Any CPU 55 | {40445ECC-0F53-4ED2-A764-C6F81410DF73}.Release|x86.Build.0 = Release|Any CPU 56 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Debug|x64.ActiveCfg = Debug|Any CPU 59 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Debug|x64.Build.0 = Debug|Any CPU 60 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Debug|x86.ActiveCfg = Debug|Any CPU 61 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Debug|x86.Build.0 = Debug|Any CPU 62 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Release|x64.ActiveCfg = Release|Any CPU 65 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Release|x64.Build.0 = Release|Any CPU 66 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Release|x86.ActiveCfg = Release|Any CPU 67 | {7E96596E-800E-4F71-9304-F1D0F6B9937B}.Release|x86.Build.0 = Release|Any CPU 68 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 69 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Debug|Any CPU.Build.0 = Debug|Any CPU 70 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Debug|x64.ActiveCfg = Debug|Any CPU 71 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Debug|x64.Build.0 = Debug|Any CPU 72 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Debug|x86.ActiveCfg = Debug|Any CPU 73 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Debug|x86.Build.0 = Debug|Any CPU 74 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Release|Any CPU.ActiveCfg = Release|Any CPU 75 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Release|Any CPU.Build.0 = Release|Any CPU 76 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Release|x64.ActiveCfg = Release|Any CPU 77 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Release|x64.Build.0 = Release|Any CPU 78 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Release|x86.ActiveCfg = Release|Any CPU 79 | {7F18FFC3-BF5A-46E5-B471-94D3F53C47AC}.Release|x86.Build.0 = Release|Any CPU 80 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 81 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Debug|Any CPU.Build.0 = Debug|Any CPU 82 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Debug|x64.ActiveCfg = Debug|Any CPU 83 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Debug|x64.Build.0 = Debug|Any CPU 84 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Debug|x86.ActiveCfg = Debug|Any CPU 85 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Debug|x86.Build.0 = Debug|Any CPU 86 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Release|Any CPU.ActiveCfg = Release|Any CPU 87 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Release|Any CPU.Build.0 = Release|Any CPU 88 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Release|x64.ActiveCfg = Release|Any CPU 89 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Release|x64.Build.0 = Release|Any CPU 90 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Release|x86.ActiveCfg = Release|Any CPU 91 | {3B4A09D5-F07E-435C-8546-F54509C1DC7A}.Release|x86.Build.0 = Release|Any CPU 92 | EndGlobalSection 93 | GlobalSection(SolutionProperties) = preSolution 94 | HideSolutionNode = FALSE 95 | EndGlobalSection 96 | GlobalSection(ExtensibilityGlobals) = postSolution 97 | SolutionGuid = {104569C5-D180-4FB1-885F-DE4D2CBC12D6} 98 | EndGlobalSection 99 | EndGlobal 100 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.Protobuf/NodaExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using Google.Protobuf.WellKnownTypes; 6 | using Google.Type; 7 | using System; 8 | using NodaDuration = NodaTime.Duration; 9 | using ProtobufDuration = Google.Protobuf.WellKnownTypes.Duration; 10 | using ProtobufDayOfWeek = Google.Type.DayOfWeek; 11 | 12 | namespace NodaTime.Serialization.Protobuf 13 | { 14 | /// 15 | /// Extension methods on the Google.Protobuf time-related types to convert them to Noda Time types. 16 | /// 17 | public static class NodaExtensions 18 | { 19 | private static readonly NodaDuration minProtobufDuration = 20 | NodaDuration.FromSeconds(ProtobufDuration.MinSeconds - 1) + NodaDuration.FromNanoseconds(1); 21 | 22 | private static readonly NodaDuration maxProtobufDuration = 23 | NodaDuration.FromSeconds(ProtobufDuration.MaxSeconds + 1) - NodaDuration.FromNanoseconds(1); 24 | 25 | /// 26 | /// Converts a Noda Time to a Protobuf . 27 | /// 28 | /// 29 | /// Noda Time has a wider range of valid durations than Protobuf; durations of more than around 10,000 30 | /// years (positive or negative) cannot be represented. 31 | /// 32 | /// The duration to convert. Must not be null. 33 | /// represents a duration 34 | /// which is invalid in . 35 | /// The Protobuf representation. 36 | public static ProtobufDuration ToProtobufDuration(this NodaDuration duration) 37 | { 38 | if (duration < minProtobufDuration || duration > maxProtobufDuration) 39 | { 40 | throw new ArgumentOutOfRangeException(nameof(duration), "Duration is outside the range of valid Protobuf durations."); 41 | } 42 | // Deliberately long to keep the later arithmetic in 64-bit. 43 | long days = duration.Days; 44 | long nanoOfDay = duration.NanosecondOfDay; 45 | long secondOfDay = nanoOfDay / NodaConstants.NanosecondsPerSecond; 46 | int nanos = duration.SubsecondNanoseconds; 47 | return new ProtobufDuration { Seconds = days * NodaConstants.SecondsPerDay + secondOfDay, Nanos = nanos }; 48 | } 49 | 50 | /// 51 | /// Converts a Noda Time to a Protobuf . 52 | /// 53 | /// 54 | /// Noda Time has a wider range of valid instants than Protobuf timestamps; instants before 0001-01-01 CE 55 | /// are out of range. 56 | /// 57 | /// The instant to convert. 58 | /// represents an instant 59 | /// which is invalid in . 60 | /// The Protobuf representation. 61 | public static Timestamp ToTimestamp(this Instant instant) 62 | { 63 | if (instant < NodaConstants.BclEpoch) 64 | { 65 | throw new ArgumentOutOfRangeException(nameof(instant), "Instant is outside the range of Valid Protobuf timestamps"); 66 | } 67 | // Truncated towards the start of time, which is what we want... 68 | var seconds = instant.ToUnixTimeSeconds(); 69 | var remainder = instant - Instant.FromUnixTimeSeconds(seconds); 70 | // NanosecondOfDay is probably the most efficient way of turning a small, subsecond, non-negative duration 71 | // into a number of nanoseconds... 72 | return new Timestamp { Seconds = seconds, Nanos = (int) remainder.NanosecondOfDay }; 73 | } 74 | 75 | /// 76 | /// Converts a Noda Time to a Protobuf . 77 | /// 78 | /// 79 | /// Every valid Noda Time local time can be represented in Protobuf without loss of information. 80 | /// 81 | /// The local time. 82 | /// The Protobuf representation. 83 | public static TimeOfDay ToTimeOfDay(this LocalTime localTime) => 84 | new TimeOfDay 85 | { 86 | Hours = localTime.Hour, 87 | Minutes = localTime.Minute, 88 | Seconds = localTime.Second, 89 | Nanos = localTime.NanosecondOfSecond 90 | }; 91 | 92 | /// 93 | /// Converts a Noda Time to a Protobuf . 94 | /// 95 | /// 96 | /// The value maps to . 97 | /// 98 | /// The ISO day-of-week value to convert. 99 | /// is neither None nor 100 | /// a valid ISO day-of-week value. 101 | /// The Protobuf representation. 102 | public static ProtobufDayOfWeek ToProtobufDayOfWeek(this IsoDayOfWeek isoDayOfWeek) 103 | { 104 | // Preconditions doesn't have an enum version. 105 | if (isoDayOfWeek < 0 || isoDayOfWeek > IsoDayOfWeek.Sunday) 106 | { 107 | throw new ArgumentOutOfRangeException(nameof(isoDayOfWeek), isoDayOfWeek, 108 | "Only valid day-of-week values (or None) may be converted"); 109 | } 110 | // Handily, Noda Time and Protobuf use the same numbers. 111 | return (ProtobufDayOfWeek) isoDayOfWeek; 112 | } 113 | 114 | /// 115 | /// Converts a Noda Time to a Protobuf . 116 | /// 117 | /// 118 | /// Only dates in the ISO calendar can be converted, and only those in the year range of 1-9999. 119 | /// 120 | /// The date to convert. 121 | /// The Protobuf representation. 122 | public static Date ToDate(this LocalDate date) 123 | { 124 | Preconditions.CheckArgument(date.Calendar == CalendarSystem.Iso, nameof(date), 125 | "Non-ISO dates cannot be converted to Protobuf Date messages. Actual calendar ID: {0}", date.Calendar.Id); 126 | if (date.Year < 1) 127 | { 128 | throw new ArgumentOutOfRangeException(nameof(date), 129 | $"Dates earlier than 1AD cannot be converted to Protobuf Date messages. Year: {date.Year}"); 130 | } 131 | return new Date { Year = date.Year, Month = date.Month, Day = date.Day }; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/Extensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Text.Json; 8 | using System.Text.Json.Serialization; 9 | 10 | namespace NodaTime.Serialization.SystemTextJson 11 | { 12 | /// 13 | /// Static class containing extension methods to configure System.Text.Json for Noda Time types. 14 | /// 15 | public static class Extensions 16 | { 17 | /// 18 | /// Configures System.Text.Json with everything required to properly serialize and deserialize NodaTime data types. 19 | /// 20 | /// The existing options to add Noda Time converters to. 21 | /// The time zone provider to use when parsing time zones and zoned date/times. 22 | /// The original value, for further chaining. 23 | public static JsonSerializerOptions ConfigureForNodaTime(this JsonSerializerOptions options, IDateTimeZoneProvider provider) => 24 | ConfigureForNodaTime(options, new NodaJsonSettings(provider)); 25 | 26 | /// 27 | /// Configures System.Text.Json with everything required to properly serialize and deserialize NodaTime data types. 28 | /// 29 | /// 30 | /// Any converter property in which is null will not be added to the list of 31 | /// converters in . 32 | /// 33 | /// The existing options to add Noda Time converters to. 34 | /// The to add to the System.Text.Json options. 35 | /// The original value, for further chaining. 36 | public static JsonSerializerOptions ConfigureForNodaTime(this JsonSerializerOptions options, NodaJsonSettings nodaJsonSettings) 37 | { 38 | Preconditions.CheckNotNull(options, nameof(options)); 39 | Preconditions.CheckNotNull(nodaJsonSettings, nameof(nodaJsonSettings)); 40 | 41 | // Add our converters 42 | nodaJsonSettings.AddConverters(options.Converters); 43 | 44 | // return to allow fluent chaining if desired 45 | return options; 46 | } 47 | 48 | /// 49 | /// Configures the given serializer settings to use . 50 | /// Any other converters which can convert are removed from the serializer. 51 | /// 52 | /// The existing serializer settings to add Noda Time converters to. 53 | /// The original value, for further chaining. 54 | public static JsonSerializerOptions WithIsoIntervalConverter(this JsonSerializerOptions options) 55 | { 56 | if (options == null) 57 | { 58 | throw new ArgumentNullException(nameof(options)); 59 | } 60 | ReplaceExistingConverters(options.Converters, NodaConverters.IsoIntervalConverter); 61 | return options; 62 | } 63 | 64 | /// 65 | /// Configures the given serializer settings to use . 66 | /// Any other converters which can convert are removed from the serializer. 67 | /// 68 | /// The existing serializer settings to add Noda Time converters to. 69 | /// The original value, for further chaining. 70 | public static JsonSerializerOptions WithIsoDateIntervalConverter(this JsonSerializerOptions options) 71 | { 72 | if (options == null) 73 | { 74 | throw new ArgumentNullException(nameof(options)); 75 | } 76 | ReplaceExistingConverters(options.Converters, NodaConverters.IsoDateIntervalConverter); 77 | return options; 78 | } 79 | 80 | private static void ReplaceExistingConverters(IList converters, JsonConverter newConverter) 81 | { 82 | for (int i = converters.Count - 1; i >= 0; i--) 83 | { 84 | if (converters[i].CanConvert(typeof(T))) 85 | { 86 | converters.RemoveAt(i); 87 | } 88 | } 89 | converters.Add(newConverter); 90 | } 91 | 92 | /// 93 | /// Resolves property name according . 94 | /// If is not specified then original returns. 95 | /// 96 | /// The serializer options to use name resolve. 97 | /// Property name. 98 | /// Resolved or original property name. 99 | internal static string ResolvePropertyName(this JsonSerializerOptions serializerOptions, string propertyName) => 100 | (serializerOptions.PropertyNamingPolicy)?.ConvertName(propertyName) ?? propertyName; 101 | 102 | /// 103 | /// Retrieves the from and deserializes the object as . 104 | /// 105 | /// The serializer options to use. 106 | /// Json reader. 107 | /// The type of object to read. 108 | /// The deserialized object of type . 109 | internal static T ReadType(this JsonSerializerOptions serializerOptions, ref Utf8JsonReader reader) 110 | { 111 | var converter = (JsonConverter)serializerOptions.GetConverter(typeof(T)); 112 | return converter.Read(ref reader, typeof(T), serializerOptions); 113 | } 114 | 115 | /// 116 | /// Retrieves the from and serializes the object as . 117 | /// 118 | /// The serializer options to use. 119 | /// Json writer. 120 | /// The value to serialize 121 | /// The type of object to write. 122 | internal static void WriteType(this JsonSerializerOptions serializerOptions, Utf8JsonWriter writer, T value) 123 | { 124 | var converter = (JsonConverter)serializerOptions.GetConverter(typeof(T)); 125 | converter.Write(writer, value, serializerOptions); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /src/NodaTime.Serialization.SystemTextJson/NodaConverterBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 The Noda Time Authors. All rights reserved. 2 | // Use of this source code is governed by the Apache License 2.0, 3 | // as found in the LICENSE.txt file. 4 | 5 | using NodaTime.Utility; 6 | using System; 7 | using System.Text.Json; 8 | using System.Text.Json.Serialization; 9 | 10 | namespace NodaTime.Serialization.SystemTextJson 11 | { 12 | /// 13 | /// Base class for all the System.Text.Json converters which handle value types (which is most of them). 14 | /// This deals handles all the boilerplate code dealing with nullity. 15 | /// 16 | /// The type to convert to/from JSON. 17 | public abstract class NodaConverterBase : JsonConverter 18 | { 19 | /// 20 | /// Default constructor. 21 | /// 22 | protected NodaConverterBase() 23 | { 24 | } 25 | 26 | #region CanConvert 27 | 28 | // This code section partially copied from NodaTime.Serialization.JsonNet.NodaConverterBase to support inheritance of types. 29 | // For now only DateTimeZone uses inheritance 30 | 31 | // For value types and sealed classes, we can optimize and not call IsAssignableFrom. 32 | private static readonly bool CheckAssignableFrom = 33 | !(typeof(T).IsValueType || (typeof(T).IsClass && typeof(T).IsSealed)); 34 | 35 | /// 36 | /// Returns whether or not this converter supports the given type. 37 | /// 38 | /// The type to check for compatibility. 39 | /// True if the given type is supported by this converter (not including the nullable form for 40 | /// value types); false otherwise. 41 | public override bool CanConvert(Type objectType) => 42 | objectType == typeof(T) || (CheckAssignableFrom && typeof(T).IsAssignableFrom(objectType)); 43 | 44 | #endregion 45 | 46 | /// 47 | /// Converts the JSON stored in a reader into the relevant Noda Time type. 48 | /// 49 | /// The json reader to read data from. 50 | /// The type to convert the JSON to. 51 | /// A serializer options to use for any embedded deserialization. 52 | /// The JSON was invalid for this converter. 53 | /// The deserialized value. 54 | public override T Read(ref Utf8JsonReader reader, Type objectType, JsonSerializerOptions options) 55 | { 56 | try 57 | { 58 | // Delegate to the concrete subclass. 59 | return ReadJsonImpl(ref reader, options); 60 | } 61 | catch (Exception ex) 62 | { 63 | throw new JsonException($"Cannot convert value to {objectType}", ex); 64 | } 65 | } 66 | 67 | /// 68 | /// Converts the JSON stored in a reader into the relevant Noda Time type. 69 | /// 70 | /// The json reader to read data from. 71 | /// The type to convert the JSON to. 72 | /// A serializer options to use for any embedded deserialization. 73 | /// The JSON was invalid for this converter. 74 | /// The deserialized value. 75 | public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, 76 | JsonSerializerOptions options) 77 | { 78 | try 79 | { 80 | // Delegate to the concrete subclass. 81 | return ReadJsonImpl(ref reader, options); 82 | } 83 | catch (Exception ex) 84 | { 85 | throw new JsonException($"Cannot convert value to {typeToConvert}", ex); 86 | } 87 | } 88 | 89 | /// 90 | /// Implemented by concrete subclasses, this performs the final conversion from a non-null JSON value to 91 | /// a value of type T. 92 | /// 93 | /// The JSON reader to pull data from 94 | /// The serializer options to use for nested serialization 95 | /// The deserialized value of type T. 96 | protected abstract T ReadJsonImpl(ref Utf8JsonReader reader, JsonSerializerOptions options); 97 | 98 | // Note: currently there's no concrete benefit in delegating to WriteJsonImpl rather than just 99 | // overriding Write directly, but we *could* put any common code (like the exception handling above) 100 | // in here in the future. 101 | 102 | /// 103 | /// Writes the given value to a Utf8JsonWriter. 104 | /// 105 | /// The writer to write the JSON to. 106 | /// The value to write. 107 | /// The serializer options to use for any embedded serialization. 108 | public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => 109 | WriteJsonImpl(writer, value, options); 110 | 111 | /// 112 | /// Writes the value as a string to a Utf8JsonWriter. 113 | /// 114 | /// The writer to write the JSON to. 115 | /// The value to write. 116 | /// The serializer options to use for any embedded serialization. 117 | public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => 118 | WriteJsonPropertyNameImpl(writer, value, options); 119 | 120 | /// 121 | /// Implemented by concrete subclasses, this performs the final write operation for a non-null value of type T 122 | /// to JSON. 123 | /// 124 | /// The writer to write JSON data to 125 | /// The value to serialize 126 | /// The serializer options to use for nested serialization 127 | protected abstract void WriteJsonImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options); 128 | 129 | /// 130 | /// Implemented by concrete subclasses, this performs the final write operation for a non-null value of type T 131 | /// to JSON, writing the value as a property name. The default implementation throws 132 | /// for compatibility purposes, but all concrete classes within this package override and implement the method fully. 133 | /// 134 | /// The writer to write JSON data to 135 | /// The value to serialize 136 | /// The serializer options to use for nested serialization 137 | protected virtual void WriteJsonPropertyNameImpl(Utf8JsonWriter writer, T value, JsonSerializerOptions options) => 138 | throw new NotImplementedException(); 139 | } 140 | } 141 | --------------------------------------------------------------------------------