├── 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 | [](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 |
--------------------------------------------------------------------------------