Skip to content

Latest commit

 

History

History
355 lines (266 loc) · 11.4 KB

File metadata and controls

355 lines (266 loc) · 11.4 KB

Working with Time Zones in Fluid

This guide explains how time zones work in Fluid templates, covering parsing, rendering, and conversion of date/time values.

Table of Contents

Understanding Time Zone Behavior

In Fluid, time zones affect two different operations:

  1. Parsing: When converting strings to DateTime values
  2. 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.

What Happens During Parsing

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_York time zone
  • The resulting DateTimeOffset will 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);

What Happens During Rendering

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

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

Converting Time Zones During Rendering

To display dates in a specific time zone, use the time_zone filter:

Using the time_zone Filter

The time_zone filter converts a DateTime to a specific time zone:

{{ BirthDate | time_zone: 'America/New_York' | date: '%+' }}

The 'local' Keyword

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.

Complete Example

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).

Automatic Conversion with Value Converters

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:

Creating a Time Zone 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.

Advantages of Value Converters

  • Centralized time zone conversion logic
  • Automatic conversion of all dates in your models
  • No need to remember to use time_zone filter on every date (though you still need the date filter for formatted output)

Disadvantages of Value Converters

  • 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 date filter to see timezone-aware formatted output

Common Patterns and Examples

Pattern 1: Display Server Time in User's Time Zone

// 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 }}

Pattern 2: Display Multiple Time Zones

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' }}

Pattern 3: User-Specific Time Zone

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' }}

Pattern 4: Parsing User Input

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.

Time Zone Identifiers

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

Finding Time Zone IDs

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 Time

Windows 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}");
}

Summary

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

Additional Resources