This guide explains how time zones work in Fluid templates, covering parsing, rendering, and conversion of date/time values.
- Understanding Time Zone Behavior
- The TimeZone Property
- Converting Time Zones During Rendering
- Automatic Conversion with Value Converters
- Common Patterns and Examples
In Fluid, time zones affect two different operations:
- Parsing: When converting strings to DateTime values
- Rendering: When displaying DateTime values in templates
Important: The TimeZone property in TemplateContext and TemplateOptions is used only for parsing, not for rendering. This is a common source of confusion.
When you parse a date string that doesn't include time zone information:
var context = new TemplateContext
{
TimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/New_York")
};
// This string has no time zone information
var input = new StringValue("2022-12-13T21:02:18.399");
// The date filter will parse it assuming the context's TimeZone
var result = await MiscFilters.Date(input, new FilterArguments(), context);In this case:
- The string "2022-12-13T21:02:18.399" has no time zone
- Fluid assumes it represents a time in the
America/New_Yorktime zone - The resulting
DateTimeOffsetwill have the offset for New York (-05:00 in winter, -04:00 in summer)
If the string already includes time zone information:
// This string includes time zone information (+00:00)
var input = new StringValue("2022-12-13T21:02:18.399+00:00");
// The context's TimeZone is ignored - the string's time zone is used
var result = await MiscFilters.Date(input, new FilterArguments(), context);When rendering a DateTime value, Fluid displays it in its own time zone, not the context's time zone:
var date = new DateTime(2022, 2, 2, 12, 0, 0, DateTimeKind.Utc);
var timezone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Uzhgorod");
var context = new TemplateContext(data, new TemplateOptions
{
TimeZone = timezone // This does NOT affect rendering!
});
var template = parser.Parse("{{ BirthDate }}");
var result = template.Render(context);
// Output: "2022-02-02 12:00:00Z" (UTC, not Europe/Uzhgorod)The date is rendered in UTC because that's what the DateTime object contains. The context's TimeZone property has no effect on rendering.
The TimeZone property is available in both TemplateOptions and TemplateContext:
// Set globally for all templates using these options
var options = new TemplateOptions
{
TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time")
};
// Or set per template context
var context = new TemplateContext
{
TimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/New_York")
};Use Cases for the TimeZone Property:
- Parsing date strings without explicit time zone information
- Ensuring consistent date parsing across different server environments
- Interpreting user input in a specific time zone
What it Does NOT Do:
- Automatically convert DateTime values during rendering
- Change the time zone of DateTime objects in your model
- Apply to dates that already have time zone information
To display dates in a specific time zone, use the time_zone filter:
The time_zone filter converts a DateTime to a specific time zone:
{{ BirthDate | time_zone: 'America/New_York' | date: '%+' }}The special keyword 'local' converts to the context's configured time zone:
var context = new TemplateContext(data, new TemplateOptions
{
TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Uzhgorod")
});{{ BirthDate | time_zone: 'local' | date: '%c' }}This will convert BirthDate to the "Europe/Uzhgorod" time zone before formatting it.
Important: The time_zone filter should be used in combination with the date filter to see the timezone information in the output. When rendering a DateTime without the date filter, Fluid uses the ISO 8601 format which always displays in UTC regardless of the timezone conversion.
Here's a complete example showing the difference:
using Fluid;
const string text = @"
Without conversion: {{ BirthDate }}
With time_zone filter: {{ BirthDate | time_zone: 'local' | date: '%c' }}
Explicit timezone: {{ BirthDate | time_zone: 'Europe/Uzhgorod' | date: '%c' }}
";
var date = new DateTime(2022, 2, 2, 12, 0, 0, DateTimeKind.Utc);
var timezone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Uzhgorod");
var data = new
{
BirthDate = date
};
var parser = new FluidParser();
var template = parser.Parse(text);
var context = new TemplateContext(data, new TemplateOptions
{
TimeZone = timezone
});
var result = template.Render(context);
Console.WriteLine(result);Output:
Without conversion: 2022-02-02 12:00:00Z
With time_zone filter: Wednesday, 02 February 2022 14:00:00
Explicit timezone: Wednesday, 02 February 2022 14:00:00
Notice that the time_zone filter must be combined with the date filter to properly display the converted time. The first line shows the UTC time, while the filtered versions show the time converted to 14:00 in the Uzhgorod timezone (UTC+2).
If you want to automatically convert all DateTime values to a specific time zone without using the time_zone filter everywhere, you can use a value converter:
using Fluid;
using Fluid.Values;
var options = new TemplateOptions
{
TimeZone = TimeZoneInfo.FindSystemTimeZoneById("Europe/Uzhgorod")
};
// Add a value converter that automatically converts all DateTime values
options.ValueConverters.Add(obj =>
{
if (obj is DateTime dt)
{
// Convert to the context's time zone
var converted = TimeZoneInfo.ConvertTime(dt, options.TimeZone);
return new DateTimeValue(converted);
}
if (obj is DateTimeOffset dto)
{
// Convert to the context's time zone
var converted = TimeZoneInfo.ConvertTime(dto, options.TimeZone);
return new DateTimeValue(converted);
}
return null; // No conversion needed
});
var data = new
{
BirthDate = new DateTime(2022, 2, 2, 12, 0, 0, DateTimeKind.Utc)
};
var context = new TemplateContext(data, options);Now all DateTime values will be automatically converted to the configured time zone. To see the converted time in your output, use the date filter:
{{ BirthDate | date: '%c' }}Output: Wednesday, 02 February 2022 14:00:00 (converted to Europe/Uzhgorod time zone, UTC+2)
Note: When rendering a DateTime without a format filter (e.g., {{ BirthDate }}), Fluid uses the ISO 8601 universal sortable format (yyyy-MM-dd HH:mm:ssZ) which displays in UTC regardless of the timezone conversion. Always use the date filter with a format string to see the timezone-aware output.
- Centralized time zone conversion logic
- Automatic conversion of all dates in your models
- No need to remember to use
time_zonefilter on every date (though you still need thedatefilter for formatted output)
- Less flexibility - all dates are converted the same way
- May not be appropriate if you need different time zones for different dates
- Less explicit - the conversion happens behind the scenes
- Still requires using the
datefilter to see timezone-aware formatted output
// In your application setup
var options = new TemplateOptions();
// Add a converter to display all dates in the user's time zone
options.ValueConverters.Add(obj =>
{
if (obj is DateTime dt && dt.Kind == DateTimeKind.Utc)
{
// This would typically come from user preferences
var userTimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles");
var converted = TimeZoneInfo.ConvertTime(dt, userTimeZone);
return new DateTimeValue(converted);
}
return null;
});Template:
Server time: {{ ServerTime }}UTC: {{ EventTime }}
New York: {{ EventTime | time_zone: 'America/New_York' | date: '%c' }}
London: {{ EventTime | time_zone: 'Europe/London' | date: '%c' }}
Tokyo: {{ EventTime | time_zone: 'Asia/Tokyo' | date: '%c' }}var context = new TemplateContext(model, options);
// Set the user's preferred time zone
context.TimeZone = userPreferredTimeZone;Template:
Your local time: {{ EventTime | time_zone: 'local' | date: '%c' }}var context = new TemplateContext
{
// User is entering times in Pacific time
TimeZone = TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles")
};Template (parsing a user-entered date):
{% assign meeting_time = '2024-03-15 14:30' | date: '%+' %}
Meeting scheduled for: {{ meeting_time }}The date string will be interpreted as Pacific time, not UTC.
Fluid uses the TimeZoneConverter library, which supports:
- IANA time zone IDs (e.g., "America/New_York", "Europe/London")
- Windows time zone IDs (e.g., "Pacific Standard Time", "GMT Standard Time")
- Cross-platform conversion between IANA and Windows identifiers
IANA IDs (recommended for cross-platform apps):
"America/New_York" // Eastern Time
"America/Los_Angeles" // Pacific Time
"Europe/London" // British Time
"Asia/Tokyo" // Japan Time
"UTC" // Coordinated Universal TimeWindows IDs:
"Eastern Standard Time"
"Pacific Standard Time"
"GMT Standard Time"
"Tokyo Standard Time"You can get a list of all available time zones:
foreach (var tz in TimeZoneInfo.GetSystemTimeZones())
{
Console.WriteLine($"{tz.Id}: {tz.DisplayName}");
}| Scenario | Solution |
|---|---|
| Parse date strings without time zone info | Set TemplateContext.TimeZone |
| Display dates in a specific time zone | Use {{ date | time_zone: 'timezone-id' }} filter |
| Display dates in the context's time zone | Use {{ date | time_zone: 'local' }} filter |
| Automatically convert all dates | Use a ValueConverter |
| Display dates in multiple time zones | Use multiple time_zone filters with different IDs |
- Ruby date and time format strings - Used by the
datefilter - TimeZoneConverter Library - Cross-platform time zone support
- IANA Time Zone Database - Standard time zone identifiers
- Fluid README - Time zones section - Quick reference
- Fluid README - Value Converters section - More on value converters