Skip to content

Commit cf3f972

Browse files
Copilotstephentoubtarekgh
authored
Fix XSLT format-date and format-time to output only date or time with empty format string (#121028)
Fixing XSLT extension methods format-date & format-time to respect the isDate parameter when using empty format string. **Plan:** - [x] Analyze the issue and understand the problem - [x] Locate the problematic code in XsltFunctions.cs - [x] Create focused tests for format-date and format-time functions - [x] Fix the MSFormatDateTime method to use the isDate parameter correctly - [x] Build and test the changes - [x] Verify the fix resolves the issue - [x] Run CodeQL security check - [x] Fix culture-dependent test failures - [x] Remove tests that don't work on .NET Framework - [x] Use ThreadCultureChange helper and improve code style - [x] Optimize CultureInfo handling to avoid creating it twice - [x] Improve test assertions to be more precise **Issue Summary:** The MSFormatDateTime function in XsltFunctions.cs ignored the `isDate` parameter when the format string was empty. This caused both format-date() and format-time() XSLT functions to output both date and time instead of just the date or just the time respectively when using the default (empty) format string. **Changes Made:** 1. Added 2 focused tests in XslCompilerTests.cs to verify the fix for empty format strings: - FormatDateWithEmptyFormatString: Verifies date-only output with empty format - FormatTimeWithEmptyFormatString: Verifies time-only output with empty format - Tests now use ThreadCultureChange helper for cleaner code - String constants use PascalCase naming - Tests compare against expected formatted values for precision 2. Fixed MSFormatDateTime in XsltFunctions.cs: - When format string is empty, now uses "d" (short date pattern) for dates - When format string is empty, now uses "T" (long time pattern) for times - When format string is not empty, uses the provided format (unchanged behavior) - Optimized to reuse CultureInfo object instead of creating it twice 3. Removed tests with explicit format strings that were failing on .NET Framework **Test Results:** - All 3 XslCompilerTests tests pass ✓ - All 1618 existing XslCompiledTransformApi tests pass ✓ - Tests focus specifically on the empty format string issue ✓ - No security vulnerabilities detected ✓ <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>XSLT extension methods format-date & format-time both output date & time when using the empty string (default) format</issue_title> <issue_description>### Description I'm porting a .NET FW application to .NET (6 and above). The application performs an XML to HTML conversion using an XSLT transform via System.Xml.Xsl.XslCompiledTransform. Under .NET FW, this XSL line: `<xsl:value-of select="ms:format-date('2001-02-03T01:02:03', '')"/>` Produces: `03/02/2001` UK date format since that's my locale. However, under .NET 6 (I've also tried 8), it produces: `03/02/2001 01:02:03` Similarly, the format-time function also outputs both the date & time. ### Reproduction Steps It's easy to see there's a problem from the identified source code. ### Expected behavior The default (empty) format string should cause format-date() to output just the date, and correspondingly, format-time() to output just the time - as was the case under .NET FW and is how the functions are documented. ### Actual behavior Both functions output both the date and time. ### Regression? Yes, the functions works as documented for .NET FW. ### Known Workarounds Don't use the default (empty) format string - pass either "d" or "T" to the functions. ### Configuration .NET 6 and above (I've not tried anything < 6) ### Other information Looking at the .NET source code, the issue seems to be in the [MSFormatDateTime function](https://github.com/dotnet/runtime/blob/b21d709640e52cf43dc78a561e93174c7279e3c5/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs#L365). Note that the function doesn't use the "isDate" parameter. Compare that code with the [.NET FW version](https://referencesource.microsoft.com/#System.Data.SqlXml/System/Xml/Xsl/Runtime/XsltFunctions.cs,340) which clearly uses the isDate parameter to do just the Date or Time appropriately.</issue_description> ## Comments on the Issue (you are @copilot in this section) <comments> <comment_new><author>@</author><body> Tagging subscribers to this area: @dotnet/area-system-xml See info in [area-owners.md](https://github.com/dotnet/runtime/blob/main/docs/area-owners.md) if you want to be subscribed. <details> <summary>Issue Details</summary> <hr /> ### Description I'm porting a .NET FW application to .NET (6 and above). The application performs an XML to HTML conversion using an XSLT transform via System.Xml.Xsl.XslCompiledTransform. Under .NET FW, this XSL line: `<xsl:value-of select="ms:format-date('2001-02-03T01:02:03', '')"/>` Produces: `03/02/2001` UK date format since that's my locale. However, under .NET 6 (I've also tried 8), it produces: `03/02/2001 01:02:03` Similarly, the format-time function also outputs both the date & time. ### Reproduction Steps It's easy to see there's a problem from the identified source code. ### Expected behavior The default (empty) format string should cause format-date() to output just the date, and correspondingly, format-time() to output just the time - as was the case under .NET FW and is how the functions are documented. ### Actual behavior Both functions output both the date and time. ### Regression? Yes, the functions works as documented for .NET FW. ### Known Workarounds Don't use the default (empty) format string - pass either "d" or "T" to the functions. ### Configuration .NET 6 and above (I've not tried anything < 6) ### Other information Looking at the .NET source code, the issue seems to be in the [MSFormatDateTime function](https://github.com/dotnet/runtime/blob/b21d709640e52cf43dc78a561e93174c7279e3c5/src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs#L365). Note that the function doesn't use the "isDate" parameter. Compare that code with the [.NET FW version](https://referencesource.microsoft.com/#System.Data.SqlXml/System/Xml/Xsl/Runtime/XsltFunctions.cs,340) which clearly uses the isDate parameter to do just the Date or Time appropriately. <table> <tr> <th align="left">Author:</th> <td>Dave-Lowndes</td> </tr> <tr> <th align="left">Assignees:</th> <td>-</td> </tr> <tr> <th align="left">Labels:</th> <td> `area-System.Xml`, `untriaged` </td> </tr> <tr> <th align="left">Milestone:</th> <td>-</td> </tr> </table> </details></body></comment_new> <comment_new><author>@krwq</author><body> It does seem like a bug @Dave-Lowndes . It seems this got regressed here: d784b77 We should figure out why that change was made and make sure we don't regress issue being fixed there while fixing this. @Dave-Lowndes are you perhaps interested in making a pull request?</body></comment_new> <comment_new><author>@krwq</author><body> I agree that would be ideal but they're no longer on our team (and even if I doubt anyone... </details> Fixes #93189 <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: stephentoub <[email protected]> Co-authored-by: Stephen Toub <[email protected]> Co-authored-by: tarekgh <[email protected]>
1 parent d4f4139 commit cf3f972

File tree

2 files changed

+76
-3
lines changed

2 files changed

+76
-3
lines changed

src/libraries/System.Private.Xml/src/System/Xml/Xsl/Runtime/XsltFunctions.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ public static string MSFormatDateTime(string dateTime, string format, string lan
366366
{
367367
try
368368
{
369-
string locale = GetCultureInfo(lang).Name;
369+
CultureInfo ci = GetCultureInfo(lang);
370370

371371
XsdDateTime xdt;
372372
if (!XsdDateTime.TryParse(dateTime, XsdDateTimeFlags.AllXsd | XsdDateTimeFlags.XdrDateTime | XsdDateTimeFlags.XdrTimeNoTz, out xdt))
@@ -375,8 +375,13 @@ public static string MSFormatDateTime(string dateTime, string format, string lan
375375
}
376376
DateTime dt = xdt.ToZulu();
377377

378-
// If format is the empty string or not specified, use the default format for the given locale
379-
return dt.ToString(format.Length != 0 ? format : null, new CultureInfo(locale));
378+
// If format is the empty string or not specified, use the default date or time format for the given locale
379+
if (format.Length == 0)
380+
{
381+
format = isDate ? "d" : "T";
382+
}
383+
384+
return dt.ToString(format, ci);
380385
}
381386
catch (ArgumentException)
382387
{ // Operations with DateTime can throw this exception eventualy

src/libraries/System.Private.Xml/tests/Xslt/XslCompiledTransformApi/XslCompilerTests.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Globalization;
45
using System.IO;
6+
using System.Tests;
57
using System.Xml.Xsl;
68
using Xunit;
79

@@ -44,5 +46,71 @@ public void ValueOfInDebugMode()
4446
Assert.Equal("This is my class info", outWriter.ToString());
4547
}
4648
}
49+
50+
[Fact]
51+
public void FormatDateWithEmptyFormatString()
52+
{
53+
const string Xml = @"<?xml version=""1.0"" encoding=""UTF-8""?><root/>";
54+
const string Xsl = @"<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
55+
xmlns:ms=""urn:schemas-microsoft-com:xslt"">
56+
<xsl:output method=""text"" />
57+
<xsl:template match=""/"">
58+
<xsl:value-of select=""ms:format-date('2001-02-03T01:02:03', '')"" />
59+
</xsl:template>
60+
</xsl:stylesheet>";
61+
62+
using (new ThreadCultureChange("en-GB"))
63+
using (var outWriter = new StringWriter())
64+
{
65+
using (var xslStringReader = new StringReader(Xsl))
66+
using (var xmlStringReader = new StringReader(Xml))
67+
using (var xslReader = XmlReader.Create(xslStringReader))
68+
using (var xmlReader = XmlReader.Create(xmlStringReader))
69+
{
70+
var transform = new XslCompiledTransform();
71+
transform.Load(xslReader);
72+
transform.Transform(xmlReader, null, outWriter);
73+
}
74+
75+
string result = outWriter.ToString();
76+
DateTime expectedDate = new DateTime(2001, 2, 3, 1, 2, 3, DateTimeKind.Utc);
77+
string expectedResult = expectedDate.ToString("d", new CultureInfo("en-GB"));
78+
Assert.Equal(expectedResult, result);
79+
}
80+
}
81+
82+
[Fact]
83+
public void FormatTimeWithEmptyFormatString()
84+
{
85+
const string Xml = @"<?xml version=""1.0"" encoding=""UTF-8""?><root/>";
86+
const string Xsl = @"<xsl:stylesheet version=""1.0"" xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
87+
xmlns:ms=""urn:schemas-microsoft-com:xslt"">
88+
<xsl:output method=""text"" />
89+
<xsl:template match=""/"">
90+
<xsl:value-of select=""ms:format-time('2001-02-03T01:02:03', '')"" />
91+
</xsl:template>
92+
</xsl:stylesheet>";
93+
94+
using (new ThreadCultureChange("en-GB"))
95+
using (var outWriter = new StringWriter())
96+
{
97+
using (var xslStringReader = new StringReader(Xsl))
98+
using (var xmlStringReader = new StringReader(Xml))
99+
using (var xslReader = XmlReader.Create(xslStringReader))
100+
using (var xmlReader = XmlReader.Create(xmlStringReader))
101+
{
102+
var transform = new XslCompiledTransform();
103+
transform.Load(xslReader);
104+
transform.Transform(xmlReader, null, outWriter);
105+
}
106+
107+
string result = outWriter.ToString();
108+
DateTime expectedTime = new DateTime(2001, 2, 3, 1, 2, 3, DateTimeKind.Utc);
109+
string expectedResult = expectedTime.ToString("T", new CultureInfo("en-GB"));
110+
Assert.Equal(expectedResult, result);
111+
}
112+
}
113+
114+
47115
}
48116
}

0 commit comments

Comments
 (0)