Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ src/Auth0.ManagementApi/.shiprc
src/Auth0.ManagementApi/.version
src/Auth0.ManagementApi/ManagementApiClient.Internal.cs
src/Auth0.ManagementApi/Core/Public/ClientOptions.Custom.cs
src/Auth0.ManagementApi/Types/UserDateSchemaExtensions.cs

tests/Auth0.AuthenticationApi.IntegrationTests
tests/Auth0.Core.UnitTests
tests/Auth0.IntegrationTests.Shared
tests/Auth0.ManagementApi.IntegrationTests
tests/Auth0.ManagementApi.Test/Auth0.ManagementApi.Test.Custom.props
tests/Auth0.ManagementApi.Test/Unit/Extensions/
tests/Auth0.ManagementApi.Test/Unit/MockServer/Auth0ClientHeaderTest.cs
tests/Auth0.ManagementApi.Test/Wrapper

Expand Down
63 changes: 63 additions & 0 deletions src/Auth0.ManagementApi/Types/UserDateSchemaExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Globalization;

namespace Auth0.ManagementApi;

/// <summary>
/// Extension methods for <see cref="UserDateSchema"/>.
/// </summary>
public static class UserDateSchemaExtensions
{
/// <summary>
/// Parses the underlying string value into a <see cref="DateTime"/> using invariant culture and round-trip kind.
/// Returns <c>null</c> if the schema is null or does not contain a valid date-time string.
/// </summary>
public static DateTime? ToDateTime(this UserDateSchema? schema)
{
if (schema is null || !schema.IsString())
return null;

return DateTime.TryParse(
schema.AsString(),
CultureInfo.InvariantCulture,
DateTimeStyles.RoundtripKind,
out var result
)
? result
: null;
}

/// <summary>
/// Parses the underlying string value into a <see cref="DateTime"/>, then converts it to the provided time zone.
/// Returns <c>null</c> if the schema is null or does not contain a valid date-time string.
/// </summary>
/// <remarks>
/// For timestamps without an explicit offset, parsing uses the local machine time zone.
/// </remarks>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="targetTimeZone"/> is null.</exception>
public static DateTime? ToDateTime(this UserDateSchema? schema, TimeZoneInfo targetTimeZone)
{
if (targetTimeZone is null)
throw new ArgumentNullException(nameof(targetTimeZone));

if (schema is null || !schema.IsString())
return null;

if (
!DateTimeOffset.TryParse(
schema.AsString(),
CultureInfo.InvariantCulture,
DateTimeStyles.None,
out var parsedDateTimeOffset
)
)
{
return null;
}

var convertedDateTimeOffset = TimeZoneInfo.ConvertTime(parsedDateTimeOffset, targetTimeZone);

return targetTimeZone.Equals(TimeZoneInfo.Utc)
? convertedDateTimeOffset.UtcDateTime
: DateTime.SpecifyKind(convertedDateTimeOffset.DateTime, DateTimeKind.Unspecified);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using NUnit.Framework;

namespace Auth0.ManagementApi.Test.Unit.Extensions;

[TestFixture]
public class UserDateSchemaExtensionsTest
{
[Test]
public void ToDateTime_WithValidIso8601String_ReturnsUtcDateTime()
{
var schema = UserDateSchema.FromString("2024-03-15T10:30:00.000Z");

var result = schema.ToDateTime();

Assert.That(result, Is.Not.Null);
Assert.That(result!.Value, Is.EqualTo(new DateTime(2024, 3, 15, 10, 30, 0, DateTimeKind.Utc)));
Assert.That(result.Value.Kind, Is.EqualTo(DateTimeKind.Utc));
}

[Test]
public void ToDateTime_WithNull_ReturnsNull()
{
UserDateSchema? schema = null;

var result = schema.ToDateTime();

Assert.That(result, Is.Null);
}

[Test]
public void ToDateTime_WithMapType_ReturnsNull()
{
var schema = UserDateSchema.FromMapOfStringToUnknown(new Dictionary<string, object?>
{
{ "some_key", "some_value" }
});

var result = schema.ToDateTime();

Assert.That(result, Is.Null);
}

[Test]
public void ToDateTime_WithMalformedString_ReturnsNull()
{
var schema = UserDateSchema.FromString("not-a-date");

var result = schema.ToDateTime();

Assert.That(result, Is.Null);
}

[Test]
public void ToDateTime_WithOffsetString_PreservesLocalKind()
{
var schema = UserDateSchema.FromString("2024-06-20T14:00:00.000+05:30");

var result = schema.ToDateTime();
var expectedLocal = new DateTimeOffset(2024, 6, 20, 14, 0, 0, TimeSpan.FromMinutes(330)).LocalDateTime;

Assert.That(result, Is.Not.Null);
Assert.That(result!.Value, Is.EqualTo(expectedLocal));
Assert.That(result.Value.Kind, Is.EqualTo(DateTimeKind.Local));
}

[Test]
public void ToDateTime_WithDateTimeOffset_ReturnsUtcConverted()
{
var schema = UserDateSchema.FromString("2024-06-20T14:00:00.000+05:30");

var result = schema.ToDateTime(TimeZoneInfo.Utc);

Assert.That(result, Is.Not.Null);
Assert.That(result!.Value.Kind, Is.EqualTo(DateTimeKind.Utc));
Assert.That(result.Value, Is.EqualTo(new DateTime(2024, 6, 20, 8, 30, 0, DateTimeKind.Utc)));
}

[Test]
public void ToDateTime_WithMalformedStringAndTargetTimeZone_ReturnsNull()
{
var schema = UserDateSchema.FromString("not-a-date");

var result = schema.ToDateTime(TimeZoneInfo.Utc);

Assert.That(result, Is.Null);
}

[Test]
public void ToDateTime_WithNullTargetTimeZone_ThrowsArgumentNullException()
{
var schema = UserDateSchema.FromString("2024-03-15T10:30:00.000Z");

Assert.Throws<ArgumentNullException>(() => schema.ToDateTime(null!));
}
}
Loading