diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs
index 66313f832d..82e1277e00 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertFromJsonCommand.cs
@@ -49,6 +49,12 @@ namespace Microsoft.PowerShell.Commands
[Parameter]
public SwitchParameter NoEnumerate { get; set; }
+ ///
+ /// Gets or sets the switch to control how DateTime values are to be parsed as a dotnet object.
+ ///
+ [Parameter]
+ public JsonDateKind DateKind { get; set; } = JsonDateKind.Default;
+
#endregion parameters
#region overrides
@@ -113,7 +119,7 @@ namespace Microsoft.PowerShell.Commands
private bool ConvertFromJsonHelper(string input)
{
ErrorRecord error = null;
- object result = JsonObject.ConvertFromJson(input, AsHashtable.IsPresent, Depth, out error);
+ object result = JsonObject.ConvertFromJson(input, AsHashtable.IsPresent, Depth, DateKind, out error);
if (error != null)
{
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonDateKind.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonDateKind.cs
new file mode 100644
index 0000000000..2fed27128a
--- /dev/null
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonDateKind.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+namespace Microsoft.PowerShell.Commands
+{
+ ///
+ /// Enums for ConvertFrom-Json -DateKind parameter.
+ ///
+ public enum JsonDateKind
+ {
+ ///
+ /// DateTime values are returned as a DateTime with the Kind representing the time zone in the raw string.
+ ///
+ Default,
+
+ ///
+ /// DateTime values are returned as the Local kind representation of the value.
+ ///
+ Local,
+
+ ///
+ /// DateTime values are returned as the UTC kind representation of the value.
+ ///
+ Utc,
+
+ ///
+ /// DateTime values are returned as a DateTimeOffset value preserving the timezone information.
+ ///
+ Offset,
+
+ ///
+ /// DateTime values are returned as raw strings.
+ ///
+ String,
+ }
+}
diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs
index f08c3a64ae..644ed5e0f7 100644
--- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs
+++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs
@@ -151,9 +151,58 @@ namespace Microsoft.PowerShell.Commands
/// if the parameter is true.
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Preferring Json over JSON")]
public static object ConvertFromJson(string input, bool returnHashtable, int? maxDepth, out ErrorRecord error)
+ => ConvertFromJson(input, returnHashtable, maxDepth, jsonDateKind: JsonDateKind.Default, out error);
+
+ ///
+ /// Convert a JSON string back to an object of type or
+ /// depending on parameter .
+ ///
+ /// The JSON text to convert.
+ /// True if the result should be returned as a
+ /// instead of a .
+ /// The max depth allowed when deserializing the json input. Set to null for no maximum.
+ /// Controls how DateTime values are to be converted.
+ /// An error record if the conversion failed.
+ /// A or a
+ /// if the parameter is true.
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", Justification = "Preferring Json over JSON")]
+ internal static object ConvertFromJson(string input, bool returnHashtable, int? maxDepth, JsonDateKind jsonDateKind, out ErrorRecord error)
{
ArgumentNullException.ThrowIfNull(input);
+ DateParseHandling dateParseHandling;
+ DateTimeZoneHandling dateTimeZoneHandling;
+ switch (jsonDateKind)
+ {
+ case JsonDateKind.Default:
+ dateParseHandling = DateParseHandling.DateTime;
+ dateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind;
+ break;
+
+ case JsonDateKind.Local:
+ dateParseHandling = DateParseHandling.DateTime;
+ dateTimeZoneHandling = DateTimeZoneHandling.Local;
+ break;
+
+ case JsonDateKind.Utc:
+ dateParseHandling = DateParseHandling.DateTime;
+ dateTimeZoneHandling = DateTimeZoneHandling.Utc;
+ break;
+
+ case JsonDateKind.Offset:
+ dateParseHandling = DateParseHandling.DateTimeOffset;
+ dateTimeZoneHandling = DateTimeZoneHandling.Unspecified;
+ break;
+
+ case JsonDateKind.String:
+ dateParseHandling = DateParseHandling.None;
+ dateTimeZoneHandling = DateTimeZoneHandling.Unspecified;
+ break;
+
+ default:
+ throw new ArgumentException($"Unknown JsonDateKind value requested '{jsonDateKind}'");
+ }
+
error = null;
try
{
@@ -161,6 +210,9 @@ namespace Microsoft.PowerShell.Commands
input,
new JsonSerializerSettings
{
+ DateParseHandling = dateParseHandling,
+ DateTimeZoneHandling = dateTimeZoneHandling,
+
// This TypeNameHandling setting is required to be secure.
TypeNameHandling = TypeNameHandling.None,
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1
index f03b29e4c8..599ff35639 100644
--- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1
+++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1
@@ -58,7 +58,7 @@ Describe 'ConvertFrom-Json Unit Tests' -tags "CI" {
$json | Should -BeOfType Hashtable
}
}
-
+
It 'Throws an ArgumentException with an incomplete array with AsHashtable switch set to ' -TestCase $testCasesWithAndWithoutAsHashtableSwitch {
Param($AsHashtable)
{ ConvertFrom-Json '["1",' -AsHashtable:$AsHashtable } |
@@ -66,7 +66,7 @@ Describe 'ConvertFrom-Json Unit Tests' -tags "CI" {
{ ConvertFrom-Json '[' -AsHashtable:$AsHashtable } |
Should -Throw -ErrorId "System.ArgumentException,Microsoft.PowerShell.Commands.ConvertFromJsonCommand"
}
-
+
It 'Can convert multi-line object with AsHashtable switch set to ' -TestCases $testCasesWithAndWithoutAsHashtableSwitch {
Param($AsHashtable)
$json = @('{"a" :', '"x"}') | ConvertFrom-Json -AsHashtable:$AsHashtable
@@ -161,6 +161,195 @@ b 2
c 3
"@
}
+
+ It 'Parses DateKind Default strings for ' -TestCases @(
+ @{
+ Value = '"2022-11-02T12:01:44.5801388+04:00"'
+ Expected = ([DateTimeOffset]::new(2022, 11, 2, 12, 1, 44, 580, 138, (New-TimeSpan -Hours 4)).AddTicks(8).LocalDateTime)
+ }
+ @{
+ Value = '"2022-11-02T12:01:44.5801388-04:00"'
+ Expected = ([DateTimeOffset]::new(2022, 11, 2, 12, 1, 44, 580, 138, (New-TimeSpan -Hours -4)).AddTicks(8).LocalDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00"'
+ Expected = ([DateTime]::new(1970, 1, 1, 0, 0, 0, 0, 0, 'Unspecified'))
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000Z"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)).UtcDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000+00:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)).LocalDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000-00:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)).LocalDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000+10:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 10)).LocalDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000-10:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours -10)).LocalDateTime)
+ }
+ ) {
+ param ($Value, $Expected)
+
+ $json = $Value | ConvertFrom-Json
+ $json | Should -BeOfType ([DateTime])
+ $json.Kind | Should -Be $Expected.Kind
+ $json | Should -Be $Expected
+
+ $json = $Value | ConvertFrom-Json -DateKind Default
+ $json | Should -BeOfType ([DateTime])
+ $json.Kind | Should -Be $Expected.Kind
+ $json | Should -Be $Expected
+ }
+
+ It 'Parses DateKind Local strings for ' -TestCases @(
+ @{
+ Value = '"2022-11-02T12:01:44.5801388+04:00"'
+ Expected = ([DateTimeOffset]::new(2022, 11, 2, 12, 1, 44, 580, 138, (New-TimeSpan -Hours 4)).AddTicks(8).LocalDateTime)
+ }
+ @{
+ Value = '"2022-11-02T12:01:44.5801388-04:00"'
+ Expected = ([DateTimeOffset]::new(2022, 11, 2, 12, 1, 44, 580, 138, (New-TimeSpan -Hours -4)).AddTicks(8).LocalDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00"'
+ Expected = ([DateTime]::new(1970, 1, 1, 0, 0, 0, 0, 0, 'Local'))
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000Z"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)).LocalDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000+00:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)).LocalDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000-00:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)).LocalDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000+10:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 10)).LocalDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000-10:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours -10)).LocalDateTime)
+ }
+ ) {
+ param ($Value, $Expected)
+
+ $json = $Value | ConvertFrom-Json -DateKind Local
+ $json | Should -BeOfType ([DateTime])
+ $json.Kind | Should -Be Local
+ $json | Should -Be $Expected
+ }
+
+ It 'Parses DateKind Utc strings for ' -TestCases @(
+ @{
+ Value = '"2022-11-02T12:01:44.5801388+04:00"'
+ Expected = ([DateTimeOffset]::new(2022, 11, 2, 12, 1, 44, 580, 138, (New-TimeSpan -Hours 4)).AddTicks(8).UtcDateTime)
+ }
+ @{
+ Value = '"2022-11-02T12:01:44.5801388-04:00"'
+ Expected = ([DateTimeOffset]::new(2022, 11, 2, 12, 1, 44, 580, 138, (New-TimeSpan -Hours -4)).AddTicks(8).UtcDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00"'
+ Expected = ([DateTime]::new(1970, 1, 1, 0, 0, 0, 0, 0, 'Utc'))
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000Z"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)).UtcDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000+00:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)).UtcDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000-00:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)).UtcDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000+10:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 10)).UtcDateTime)
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000-10:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours -10)).UtcDateTime)
+ }
+ ) {
+ param ($Value, $Expected)
+
+ $json = $Value | ConvertFrom-Json -DateKind Utc
+ $json | Should -BeOfType ([DateTime])
+ $json.Kind | Should -Be Utc
+ $json | Should -Be $Expected
+ }
+
+ It 'Parses DateKind Offset strings for ' -TestCases @(
+ @{
+ Value = '"2022-11-02T12:01:44.5801388+04:00"'
+ Expected = ([DateTimeOffset]::new(2022, 11, 2, 12, 1, 44, 580, 138, (New-TimeSpan -Hours 4)).AddTicks(8))
+ }
+ @{
+ Value = '"2022-11-02T12:01:44.5801388-04:00"'
+ Expected = ([DateTimeOffset]::new(2022, 11, 2, 12, 1, 44, 580, 138, (New-TimeSpan -Hours -4)).AddTicks(8))
+ }
+ @{
+ Value = '"1970-01-01T00:00:00"'
+ Expected = ([DateTimeOffset]::new([DateTime]::new(1970, 1, 1, 0, 0, 0, 0, 0, 'Local')))
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000Z"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)))
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000+00:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)))
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000-00:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 0)))
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000+10:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours 10)))
+ }
+ @{
+ Value = '"1970-01-01T00:00:00.0000000-10:00"'
+ Expected = ([DateTimeOffset]::new(1970, 1, 1, 0, 0, 0, 0, 0, (New-TimeSpan -Hours -10)))
+ }
+ ) {
+ param ($Value, $Expected)
+
+ $json = $Value | ConvertFrom-Json -DateKind Offset
+ $json | Should -BeOfType ([DateTimeOffset])
+ $json.EqualsExact($Expected) | Should -BeTrue
+ }
+
+ It 'Parses DateKind String strings for ' -TestCases @(
+ @{ Value = '"2022-11-02T12:01:44.5801388+04:00"' }
+ @{ Value = '"2022-11-02T12:01:44.5801388-04:00"' }
+ @{ Value = '"1970-01-01T00:00:00"' }
+ @{ Value = '"1970-01-01T00:00:00.0000000Z"' }
+ @{ Value = '"1970-01-01T00:00:00.0000000+00:00"' }
+ @{ Value = '"1970-01-01T00:00:00.0000000-00:00"' }
+ @{ Value = '"1970-01-01T00:00:00.0000000+10:00"' }
+ @{ Value = '"1970-01-01T00:00:00.0000000-10:00"' }
+ ) {
+ param ($Value)
+
+ $json = $Value | ConvertFrom-Json -DateKind String
+ $json | Should -BeOfType ([string])
+ $json | Should -Be $Value.Substring(1, $Value.Length - 2)
+ }
}
Describe 'ConvertFrom-Json -Depth Tests' -tags "Feature" {