Skip to content

Commit

Permalink
Apply culture in casing formatters (#743)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored Jan 7, 2025
1 parent 6862612 commit 6c64dc6
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 28 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
global-json-file: global.json

- name: Test
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
global-json-file: global.json

- name: Version
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: |
6.0.x
8.0.x
global-json-file: global.json

- name: Install dependencies
Expand Down
32 changes: 26 additions & 6 deletions Fluid.Tests/MiscFiltersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,13 @@ public async Task EscapeOnce()
[InlineData("%Y-%m-%dT%H:%M:%S.%L", "2017-08-01T17:04:36.123")]
public async Task Date(string format, string expected, string dateTime = "2017-08-01T17:04:36.123456789+08:00")
{
// This test sets the CultureInfo.DateTimeFormat so it's not impacted by changes in ICU
// see https://github.com/dotnet/runtime/issues/95620
var enUsCultureInfo = new CultureInfo("en-US", useUserOverride: false);
enUsCultureInfo.DateTimeFormat.FullDateTimePattern = "dddd, MMMM d, yyyy h:mm:ss tt";

var arguments = new FilterArguments(new StringValue(format));
var options = new TemplateOptions() { CultureInfo = new CultureInfo("en-US", useUserOverride: false), TimeZone = TimeZoneInfo.Utc };
var options = new TemplateOptions() { CultureInfo = enUsCultureInfo, TimeZone = TimeZoneInfo.Utc };
var context = new TemplateContext(options);

new StringValue(dateTime).TryGetDateTimeInput(new TemplateContext(), out var customDateTime);
Expand Down Expand Up @@ -610,19 +615,34 @@ public async Task DateIsParsedWithCulture()
[Fact]
public async Task DateIsRenderedWithCulture()
{
var input = new StringValue("08/01/2017");
var format = "%c";
// This tests 4 things:
// - The date is parsed with the specified culture (July vs February)
// - The date is rendered with the specified culture (French vs English)
// - The date is rendered with the specified timezone (UTC)
// - The uppercase modifier is applied with the culture (Turkish i)

var input = new StringValue("07/02/2017");
var format = "%^c";

var arguments = new FilterArguments(new StringValue(format));

var context = new TemplateContext { CultureInfo = new CultureInfo("fr-FR", useUserOverride: false), TimeZone = TimeZoneInfo.Utc };
var resultFR = await MiscFilters.Date(input, arguments, context);

context = new TemplateContext { CultureInfo = new CultureInfo("en-US", useUserOverride: false), TimeZone = TimeZoneInfo.Utc };
context = new TemplateContext { CultureInfo = new CultureInfo("tr-TR", useUserOverride: false), TimeZone = TimeZoneInfo.Utc };
var resultTR = await MiscFilters.Date(input, arguments, context);

// This test sets the CultureInfo.DateTimeFormat so it's not impacted by changes in ICU
// see https://github.com/dotnet/runtime/issues/95620
var enUsCultureInfo = new CultureInfo("en-US", useUserOverride: false);
enUsCultureInfo.DateTimeFormat.FullDateTimePattern = "dddd, MMMM d, yyyy h:mm:ss tt";

context = new TemplateContext { CultureInfo = enUsCultureInfo, TimeZone = TimeZoneInfo.Utc };
var resultUS = await MiscFilters.Date(input, arguments, context);

Assert.Equal("dimanche 8 janvier 2017 00:00:00", resultFR.ToStringValue());
Assert.Equal("Tuesday, August 1, 2017 12:00:00 AM", resultUS.ToStringValue());
Assert.Equal("7 ŞUBAT 2017 SALI 00:00:00", resultTR.ToStringValue());
Assert.Equal("MARDI 7 FÉVRIER 2017 00:00:00", resultFR.ToStringValue());
Assert.Equal("SUNDAY, JULY 2, 2017 12:00:00 AM", resultUS.ToStringValue());
}

[Theory]
Expand Down
32 changes: 16 additions & 16 deletions Fluid/Filters/MiscFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -420,29 +420,29 @@ string AbbreviatedDayName()
}

var abbreviatedDayName = AbbreviatedDayName();
result.Append(upperCaseFlag ? abbreviatedDayName.ToUpperInvariant() : abbreviatedDayName);
result.Append(upperCaseFlag ? abbreviatedDayName.ToUpper(context.CultureInfo) : abbreviatedDayName);
break;
case 'A':
{
var dayName = context.CultureInfo.DateTimeFormat.DayNames[(int)value.DayOfWeek];
result.Append(upperCaseFlag ? dayName.ToUpperInvariant() : dayName);
result.Append(upperCaseFlag ? dayName.ToUpper(context.CultureInfo) : dayName);
break;
}
case 'b':
var abbreviatedMonthName = context.CultureInfo.DateTimeFormat.AbbreviatedMonthNames[value.Month - 1];
result.Append(upperCaseFlag ? abbreviatedMonthName.ToUpperInvariant() : abbreviatedMonthName);
result.Append(upperCaseFlag ? abbreviatedMonthName.ToUpper(context.CultureInfo) : abbreviatedMonthName);
break;
case 'B':
{
var monthName = context.CultureInfo.DateTimeFormat.MonthNames[value.Month - 1];
result.Append(upperCaseFlag ? monthName.ToUpperInvariant() : monthName);
result.Append(upperCaseFlag ? monthName.ToUpper(context.CultureInfo) : monthName);
break;
}
case 'c':
{
// c is defined as "%a %b %e %T %Y" but it's also supposed to be locale aware, so we are using the
// C# standard format instead
result.Append(upperCaseFlag ? value.ToString("F", context.CultureInfo).ToUpperInvariant() : value.ToString("F", context.CultureInfo));
result.Append(upperCaseFlag ? value.ToString("F", context.CultureInfo).ToUpper(context.CultureInfo) : value.ToString("F", context.CultureInfo));
break;
}
case 'C': result.Append(Format(value.Year / 100, 2)); break;
Expand All @@ -451,7 +451,7 @@ string AbbreviatedDayName()
{
var sb = new StringBuilder();
ForStrf(value, "%m/%d/%y", sb);
result.Append(upperCaseFlag ? sb.ToString().ToUpperInvariant() : sb.ToString());
result.Append(upperCaseFlag ? sb.ToString().ToUpper(context.CultureInfo) : sb.ToString());
break;
}
case 'e':
Expand All @@ -461,7 +461,7 @@ string AbbreviatedDayName()
{
var sb = new StringBuilder();
ForStrf(value, "%Y-%m-%d", sb);
result.Append(upperCaseFlag ? sb.ToString().ToUpperInvariant() : sb.ToString());
result.Append(upperCaseFlag ? sb.ToString().ToUpper(context.CultureInfo) : sb.ToString());
break;
}
case 'g':
Expand Down Expand Up @@ -522,20 +522,20 @@ string AbbreviatedDayName()
var v = (value.Ticks % 10000000).ToString(context.CultureInfo);
result.Append(v.Length >= width ? v.Substring(0, width.Value) : v.PadRight(width.Value, '0'));
break;
case 'p': result.Append(value.ToString("tt", context.CultureInfo).ToUpperInvariant()); break;
case 'P': result.Append(value.ToString("tt", context.CultureInfo).ToLowerInvariant()); break;
case 'p': result.Append(value.ToString("tt", context.CultureInfo).ToUpper(context.CultureInfo)); break;
case 'P': result.Append(value.ToString("tt", context.CultureInfo).ToLower(context.CultureInfo)); break;
case 'r':
{
var sb = new StringBuilder();
ForStrf(value, "%I:%M:%S %p", sb);
result.Append(upperCaseFlag ? sb.ToString().ToUpperInvariant() : sb.ToString());
result.Append(upperCaseFlag ? sb.ToString().ToUpper(context.CultureInfo) : sb.ToString());
break;
}
case 'R':
{
var sb = new StringBuilder();
ForStrf(value, "%H:%M", sb);
result.Append(upperCaseFlag ? sb.ToString().ToUpperInvariant() : sb.ToString());
result.Append(upperCaseFlag ? sb.ToString().ToUpper(context.CultureInfo) : sb.ToString());
break;
}
case 's': result.Append(Format(value.ToUnixTimeSeconds())); break;
Expand All @@ -547,7 +547,7 @@ string AbbreviatedDayName()
{
var sb = new StringBuilder();
ForStrf(value, "%H:%M:%S", sb);
result.Append(upperCaseFlag ? sb.ToString().ToUpperInvariant() : sb.ToString());
result.Append(upperCaseFlag ? sb.ToString().ToUpper(context.CultureInfo) : sb.ToString());
break;
}
case 'u': result.Append(value.DayOfWeek switch { DayOfWeek.Sunday => 7, _ => (int)value.DayOfWeek }); break;
Expand All @@ -565,7 +565,7 @@ string AbbreviatedDayName()
{
var sb = new StringBuilder();
ForStrf(value, "%e-%b-%Y", sb);
result.Append(upperCaseFlag ? sb.ToString().ToUpperInvariant() : sb.ToString());
result.Append(upperCaseFlag ? sb.ToString().ToUpper(context.CultureInfo) : sb.ToString());
break;
}
case 'V': result.Append(Format(value.DayOfYear / 7 + 1, 2)); break;
Expand All @@ -585,15 +585,15 @@ string AbbreviatedDayName()
// x is defined as "%m/%d/%y" but it's also supposed to be locale aware, so we are using the
// C# short date pattern standard format instead

result.Append(upperCaseFlag ? value.ToString("d", context.CultureInfo).ToUpperInvariant() : value.ToString("d", context.CultureInfo));
result.Append(upperCaseFlag ? value.ToString("d", context.CultureInfo).ToUpper(context.CultureInfo) : value.ToString("d", context.CultureInfo));
break;
}
case 'X':
{
// X is defined as "%T" but it's also supposed to be locale aware, so we are using the
// C# short time pattern standard format instead

result.Append(upperCaseFlag ? value.ToString("t", context.CultureInfo).ToUpperInvariant() : value.ToString("t", context.CultureInfo));
result.Append(upperCaseFlag ? value.ToString("t", context.CultureInfo).ToUpper(context.CultureInfo) : value.ToString("t", context.CultureInfo));
break;
}
case 'y':
Expand All @@ -616,7 +616,7 @@ string AbbreviatedDayName()
{
var sb = new StringBuilder();
ForStrf(value, "%a %b %e %H:%M:%S %Z %Y", sb);
result.Append(upperCaseFlag ? sb.ToString().ToUpperInvariant() : sb.ToString());
result.Append(upperCaseFlag ? sb.ToString().ToUpper(context.CultureInfo) : sb.ToString());
break;
}
default: result.Append('%').Append(c); break;
Expand Down
8 changes: 4 additions & 4 deletions Fluid/Filters/StringFilters.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Fluid.Values;
using Fluid.Values;

namespace Fluid.Filters
{
Expand Down Expand Up @@ -48,7 +48,7 @@ public static ValueTask<FluidValue> Capitalize(FluidValue input, FilterArguments
char c;
if (i == 0 || char.IsWhiteSpace(c = source[i - 1]) || c == '-' || c == '.')
{
source[i] = char.ToUpper(source[i]);
source[i] = char.ToUpper(source[i], context.CultureInfo);
}
}

Expand All @@ -57,7 +57,7 @@ public static ValueTask<FluidValue> Capitalize(FluidValue input, FilterArguments

public static ValueTask<FluidValue> Downcase(FluidValue input, FilterArguments arguments, TemplateContext context)
{
return new StringValue(input.ToStringValue().ToLowerInvariant());
return new StringValue(input.ToStringValue().ToLower(context.CultureInfo));
}

public static ValueTask<FluidValue> LStrip(FluidValue input, FilterArguments arguments, TemplateContext context)
Expand Down Expand Up @@ -360,7 +360,7 @@ public static ValueTask<FluidValue> TruncateWords(FluidValue input, FilterArgume

public static ValueTask<FluidValue> Upcase(FluidValue input, FilterArguments arguments, TemplateContext context)
{
return new StringValue(input.ToStringValue().ToUpperInvariant());
return new StringValue(input.ToStringValue().ToUpper(context.CultureInfo));
}
}
}
4 changes: 2 additions & 2 deletions Versions.props
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project>
<!-- This file define constants that can be changed per TFM -->
<PropertyGroup>
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>9.0.0</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
<SystemTextJsonPackageVersion>9.0.0</SystemTextJsonPackageVersion>
<MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>8.0.0</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
<SystemTextJsonPackageVersion>8.0.5</SystemTextJsonPackageVersion>
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'">
Expand Down

0 comments on commit 6c64dc6

Please sign in to comment.