Skip to content
Open
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
11 changes: 11 additions & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

## XMLUnit.NET 2.11.2 - /not released, yet/

* `IsDateTimePlaceholderHandler` now supports an optional second argument specifying a
different CultureInfo than the invariant culture used by default as a BCP 47 language tag.

Also modified the logic when no argument is present to still try
parsing using the current culture and then trying a set of ISO
patterns using the invariant culture in turn - making `isDateTime`
match what the Java version does mor closely.

PR [#54](https://github.com/xmlunit/xmlunit.net/pull/54) based on
PR [xmlunit/#335](https://github.com/xmlunit/xmlunit/pull/335) by [@jmestwa-coder](https://github.com/jmestwa-coder)

## XMLUnit.NET 2.11.1 - /Released 2025-05-19/

* placeholders can now also be used inside of the local part of `xsi:type` attributes.
Expand Down
76 changes: 69 additions & 7 deletions src/main/net-placeholders/IsDateTimePlaceholderHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,32 +28,94 @@ namespace Org.XmlUnit.Placeholder
/// <para>
/// since 2.8.0
/// </para>
/// <para>
/// The first optional argument is the date/time pattern, the second optional
/// argument is the culture used to parse it, given as a culture name (for example
/// <c>de</c> or <c>fr-FR</c>). When no culture is given <see cref="CultureInfo.InvariantCulture"/>
/// is used.
/// </para>
/// </remarks>
public class IsDateTimePlaceholderHandler : IPlaceholderHandler
{
private const string _keyword = "isDateTime";

private static readonly IEnumerable<string> _isoPatterns = new List<string>
{
"yyyy-MM-dd",
"yyyy-MM-ddTHH:mm",
"yyyy-MM-ddTHH:mm:ss",
"yyyy-MM-ddTHH:mm:ss.fff",
"yyyy-MM-ddTHH:mm:ssK",
"yyyy-MM-ddTHH:mm:ss.fffK",
"yyyy-MM-dd HH:mm",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd HH:mm:ss.fff",
"yyyy-MM-dd HH:mm:ssK",
"yyyy-MM-dd HH:mm:ss.fffK"
};

/// <inheritdoc/>
public string Keyword { get { return _keyword; } }

/// <inheritdoc/>
/// <remarks>
/// <para>
/// When <paramref name="args"/> contains one element, it is used as the date/time pattern
/// and <see cref="CultureInfo.InvariantCulture"/> is used for parsing.
/// </para>
/// <para>
/// When <paramref name="args"/> contains two elements, the first is used as the date/time
/// pattern and the second as the culture name for parsing.
/// </para>
/// <para>
/// When no arguments are provided, the method tries to parse the text using ISO patterns
/// with <see cref="CultureInfo.InvariantCulture"/>.
/// </para>
/// </remarks>
public ComparisonResult Evaluate(string testText, params string[] args)
{
if (args != null && args.Length == 1) {
return CanParse(args[0], testText)
if (args != null && args.Length >= 1) {
var culture = args.Length >= 2 && !string.IsNullOrEmpty(args[1])
? new CultureInfo(args[1])
: CultureInfo.InvariantCulture;
return CanParse(args[0], testText, culture)
? ComparisonResult.EQUAL
: ComparisonResult.DIFFERENT;
}
DateTime _;
var result = DateTime.TryParse(testText, out _);
return result
return CanParse(testText)
? ComparisonResult.EQUAL
: ComparisonResult.DIFFERENT;
}

private bool CanParse(string pattern, string testText) {
private bool CanParse(string testText) {
if (string.IsNullOrEmpty(testText)) {
return false;
}
// Try locale-aware short date and datetime formats with current culture
if (CanParseWithCurrentCulture(testText)) {
return true;
}
// Try all ISO patterns with InvariantCulture
foreach (var pattern in _isoPatterns) {
if (CanParse(pattern, testText, CultureInfo.InvariantCulture)) {
return true;
}
}
return false;
}

private bool CanParseWithCurrentCulture(string testText) {
DateTime result;
// Try short date format
if (DateTime.TryParse(testText, out result)) {
return true;
}
return false;
}

private bool CanParse(string pattern, string testText, CultureInfo culture) {
try {
var _ = DateTime.ParseExact(testText, pattern, CultureInfo.InvariantCulture);
var _ = DateTime.ParseExact(testText, pattern, culture);
return true;
} catch (FormatException) {
return false;
Expand Down
51 changes: 51 additions & 0 deletions src/tests/net-placeholders/IsDateTimePlaceholderHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,26 @@ public void ShouldGetKeyword()
Assert.AreEqual(expected, keyword);
}

[Test]
public void ShouldAcceptABunchOfStrings()
{
// Test various date formats that should be accepted by ISO patterns
// Corresponds to Java test shouldAcceptABunchOfStrings
var testStrings = new string[] {
"2020-01-01",
"01/01/2020",
"01/01/2020",
"2020-01-01T15:00",
"2020-01-01 15:00:00Z",
"01/01/2020 15:00"
};

foreach (var testString in testStrings)
{
Assert.AreEqual(ComparisonResult.EQUAL, placeholderHandler.Evaluate(testString));
}
}

[Test]
public void ShouldParseExplicitPattern() {
Assert.AreEqual(ComparisonResult.EQUAL,
Expand All @@ -71,5 +91,36 @@ public void ShouldParseExplicitPattern() {
placeholderHandler.Evaluate("abc", "dd MM yyyy HH:mm"));
}

[Test]
public void ShouldParsePatternIndependentOfDefaultLocale() {
// Test that parsing works regardless of current culture
// This corresponds to the Java test shouldParsePatternIndependentOfDefaultLocale
var originalCulture = Thread.CurrentThread.CurrentCulture;
try {
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE"); // German culture
// Should still parse with InvariantCulture (default for explicit patterns)
Assert.AreEqual(ComparisonResult.EQUAL,
placeholderHandler.Evaluate("24 June 2023", "dd MMMM yyyy"));
} finally {
Thread.CurrentThread.CurrentCulture = originalCulture;
}
}

[Test]
public void ShouldParsePatternWithExplicitLocale() {
// Test explicit locale specification - corresponds to Java test shouldParsePatternWithExplicitLocale
Assert.AreEqual(ComparisonResult.EQUAL,
placeholderHandler.Evaluate("24 Juni 2023", "dd MMMM yyyy", "de"));
Assert.AreEqual(ComparisonResult.DIFFERENT,
placeholderHandler.Evaluate("24 Juni 2023", "dd MMMM yyyy", "en"));
}

[Test]
public void ShouldUseInvariantCultureWithTwoArgsWhenSecondIsEmpty() {
// When second argument is empty string, should fall back to InvariantCulture
Assert.AreEqual(ComparisonResult.EQUAL,
placeholderHandler.Evaluate("24 June 2023", "dd MMMM yyyy", ""));
}

}
}