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
37 changes: 19 additions & 18 deletions Fluid.Tests/MiscFiltersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class MiscFiltersTests
{
private static readonly TimeZoneInfo Pacific = TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/Los_Angeles");
private static readonly TimeZoneInfo Eastern = TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/New_York");
private static readonly string RoundTripDateTimePattern = "%Y-%m-%dT%H:%M:%S.%L%Z"; // Equivalent to "o" format

[Fact]
public async Task DefaultReturnsValueIfDefined()
Expand Down Expand Up @@ -393,33 +394,33 @@ public async Task DateIsParsed()
}

[Theory]
[InlineData(0, "0")]
[InlineData(10, "10")]
[InlineData(-10, "-10")]
[InlineData(0, "1969-12-31T19:00:00.000-05:00")]
[InlineData(10, "1969-12-31T19:00:10.000-05:00")]
[InlineData(-10, "1969-12-31T18:59:50.000-05:00")]
public async Task DateNumberIsParsedAsSeconds(long number, string expected)
{
// Converting to Unix time should not vary by TimeSone
// The resulting DateTimeValue should use the default TimeZone

var options = new TemplateOptions { TimeZone = Eastern };
var input = NumberValue.Create(number);
var format = new FilterArguments(new StringValue("%s"));
var context = new TemplateContext { TimeZone = Eastern };
var format = new FilterArguments(new StringValue(RoundTripDateTimePattern));
var context = new TemplateContext(options);

var result = await MiscFilters.Date(input, format, context);

Assert.Equal(expected, result.ToStringValue());
}

[Theory]
[InlineData("0", "00:00:00.000")]
[InlineData("1:2", "01:02:00.000")]
[InlineData("1:2:3.1", "01:02:03.100")]
public async Task DateTimeSpan(string timespan, string expected)
{
// Converting to Unix time should not vary by TimeSone

var input = FluidValue.Create(TimeSpan.Parse(timespan), new TemplateOptions());
var format = new FilterArguments(new StringValue("%H:%M:%S.%L"));
var context = new TemplateContext { TimeZone = Eastern };
[InlineData("0", "1969-12-31T19:00:00.000-05:00")]
[InlineData("1:2", "1969-12-31T20:02:00.000-05:00")]
[InlineData("1:2:3.1", "1969-12-31T20:02:03.100-05:00")]
public async Task DateTimeSpanIsParsedWithLocalTimeZone(string timespan, string expected)
{
var options = new TemplateOptions { TimeZone = Eastern };
var input = FluidValue.Create(TimeSpan.Parse(timespan), options);
var format = new FilterArguments(new StringValue(RoundTripDateTimePattern));
var context = new TemplateContext(options);

var result = await MiscFilters.Date(input, format, context);

Expand All @@ -430,12 +431,12 @@ public async Task DateTimeSpan(string timespan, string expected)
public async Task NoTimeZoneIsParsedAsLocal()
{
var input = StringValue.Create("1970-01-01 00:00:00");
var format = new FilterArguments(new StringValue("%a %b %e %H:%M:%S %Y %z"));
var format = new FilterArguments(new StringValue(RoundTripDateTimePattern));
var context = new TemplateContext { TimeZone = Pacific };

var result = await MiscFilters.Date(input, format, context);

Assert.Equal("Thu Jan 1 00:00:00 1970 -0800", result.ToStringValue());
Assert.Equal("1970-01-01T00:00:00.000-08:00", result.ToStringValue());
}

[Fact]
Expand Down
47 changes: 38 additions & 9 deletions Fluid.Tests/TemplateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ public class TemplateTests
private static FluidParser _parser = new FluidParser();
#endif

private object _products = new []
private static readonly TimeZoneInfo Eastern = TimeZoneConverter.TZConvert.GetTimeZoneInfo("America/New_York");

private object _products = new[]
{
new { name = "product 1", price = 1 },
new { name = "product 2", price = 2 },
Expand Down Expand Up @@ -73,12 +75,12 @@ public Task ShouldUseEncoder(string source, string expected, string encoderType)
{
var context = new TemplateContext();
TextEncoder encoder = null;

switch (encoderType)
{
case "html" : encoder = HtmlEncoder.Default; break;
case "url" : encoder = UrlEncoder.Default; break;
case "null" : encoder = NullEncoder.Default; break;
case "html": encoder = HtmlEncoder.Default; break;
case "url": encoder = UrlEncoder.Default; break;
case "null": encoder = NullEncoder.Default; break;
}

return CheckAsync(source, expected, context, encoder);
Expand Down Expand Up @@ -153,7 +155,7 @@ public async Task ShouldEvaluateFilters(string source, string expected)

var context = new TemplateContext(options);

options.Filters.AddFilter("inc", (i, args, ctx) =>
options.Filters.AddFilter("inc", (i, args, ctx) =>
{
var increment = 1;
if (args.Count > 0)
Expand Down Expand Up @@ -214,6 +216,33 @@ public async Task ShouldEvaluateNumberValue()
Assert.Equal("1", result);
}

[Fact]
public async Task ShouldEvaluateDateTimeValue()
{
// DateTimeValue is rendered as Universal Sortable Date/Time (u)
_parser.TryParse("{{ x }}", out var template, out var error);
var context = new TemplateContext { TimeZone = Eastern };
context.SetValue("x", new DateTime(2022, 10, 20, 17, 00, 00, 000, DateTimeKind.Utc));

var result = await template.RenderAsync(context);
Assert.Equal("2022-10-20 17:00:00Z", result);
}

[Fact]
public async Task ShouldEvaluateTimeSpanValue()
{
// TimeSpan should be converted to DateTimeValue
// Then a DateTimeValue is rendered as Universal Sortable Date/Time (u)

_parser.TryParse("{{ x }}", out var template, out var _);
var context = new TemplateContext { TimeZone = Eastern };
var oneHour = new TimeSpan(0, 1, 00, 00, 000);
context.SetValue("x", oneHour);

var result = await template.RenderAsync(context);
Assert.Equal("1970-01-01 01:00:00Z", result);
}

[Fact]
public async Task ShouldEvaluateObjectProperty()
{
Expand All @@ -224,7 +253,7 @@ public async Task ShouldEvaluateObjectProperty()

var context = new TemplateContext(options);
context.SetValue("p", new Person { Firstname = "John" });


var result = await template.RenderAsync(context);
Assert.Equal("John", result);
Expand Down Expand Up @@ -352,7 +381,7 @@ public async Task ShouldEvaluateCustomObjectIndex()
options.ValueConverters.Add(o => o is Person p ? new PersonValue(p) : null);

var context = new TemplateContext(options);
context.SetValue("p", new Person { Firstname = "Bill" } );
context.SetValue("p", new Person { Firstname = "Bill" });

_parser.TryParse("{{ p[1] }} {{ p['blah'] }}", out var template, out var error);
var result = await template.RenderAsync(context);
Expand Down Expand Up @@ -809,7 +838,7 @@ public async Task ShouldLimitSteps()

await Assert.ThrowsAsync<InvalidOperationException>(() => template.RenderAsync(context).AsTask());
}

[Fact]
public Task ForLoopLimitAndOffset()
{
Expand Down
3 changes: 2 additions & 1 deletion Fluid/Values/FluidValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ public static FluidValue Create(object value, TemplateOptions options)
return new DateTimeValue(dateTimeOffset);

case TimeSpan timeSpan:
return new DateTimeValue(new DateTime(timeSpan.Ticks));
var baseDateTime = DateTimeOffset.FromUnixTimeMilliseconds((long)timeSpan.TotalMilliseconds).ToOffset(options.TimeZone.BaseUtcOffset);
return new DateTimeValue(baseDateTime);

case IFormattable formattable:
return new StringValue(formattable.ToString(null, options.CultureInfo));
Expand Down
3 changes: 2 additions & 1 deletion Fluid/Values/FluidValueExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ public static bool TryGetDateTimeInput(this FluidValue input, TemplateContext co
}
else if (input.Type == FluidValues.Number)
{
var dateTime = DateTimeOffset.FromUnixTimeSeconds((long)input.ToNumberValue());
var milliseconds = input.ToNumberValue() * 1000;
var dateTime = DateTimeOffset.FromUnixTimeMilliseconds((long)milliseconds);
result = dateTime.ToOffset(context.TimeZone.GetUtcOffset(dateTime));
}
else if (input.Type == FluidValues.DateTime)
Expand Down