diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index ab843eb..f881918 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -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.
diff --git a/src/main/net-placeholders/IsDateTimePlaceholderHandler.cs b/src/main/net-placeholders/IsDateTimePlaceholderHandler.cs
index 3e8405c..bd6d949 100644
--- a/src/main/net-placeholders/IsDateTimePlaceholderHandler.cs
+++ b/src/main/net-placeholders/IsDateTimePlaceholderHandler.cs
@@ -28,32 +28,94 @@ namespace Org.XmlUnit.Placeholder
///
/// since 2.8.0
///
+ ///
+ /// 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
+ /// de or fr-FR). When no culture is given
+ /// is used.
+ ///
///
public class IsDateTimePlaceholderHandler : IPlaceholderHandler
{
private const string _keyword = "isDateTime";
+ private static readonly IEnumerable _isoPatterns = new List
+ {
+ "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"
+ };
+
///
public string Keyword { get { return _keyword; } }
///
+ ///
+ ///
+ /// When contains one element, it is used as the date/time pattern
+ /// and is used for parsing.
+ ///
+ ///
+ /// When contains two elements, the first is used as the date/time
+ /// pattern and the second as the culture name for parsing.
+ ///
+ ///
+ /// When no arguments are provided, the method tries to parse the text using ISO patterns
+ /// with .
+ ///
+ ///
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;
diff --git a/src/tests/net-placeholders/IsDateTimePlaceholderHandlerTests.cs b/src/tests/net-placeholders/IsDateTimePlaceholderHandlerTests.cs
index 7a02145..dcf5df6 100644
--- a/src/tests/net-placeholders/IsDateTimePlaceholderHandlerTests.cs
+++ b/src/tests/net-placeholders/IsDateTimePlaceholderHandlerTests.cs
@@ -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,
@@ -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", ""));
+ }
+
}
}