Conversion Iterators
Api specification links
The ConversionIterators
class provides methods to create iterators of various types:
IToTimeStampConversionIterator
IFromTimeStampConversionIterator
ConvertsTimeStamp
objects to times in a known timezone.ITimeZoneConversionIterator
Converts time values from one timezone to another.
Conversion iterators provide the fastest-possible way to perform timezone conversions on a stream of chronological-order times. They are also the best way to transform streams of TimeStamp
objects to DateTime
or DateTimeOffset
objects or vice versa.
Benchmark testing shows that the Conversion iterators are 32 times faster at converting timezones than the built-in .net framework types.
Basic usage
// Copyright (c) True Goodwill. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace FFT.TimeStamps.Examples
{
using System;
using static FFT.TimeStamps.Examples.TimeZones;
internal class ConversionIteratorExamples : IExample
{
public void Run()
{
TimeZone_TimeStamp();
TimeStamp_TimeZone();
TimeZone_TimeZone();
DemonstrateLowLevelFeatures();
}
// Demonstrates conversion of various time objects in the New York timezone to TimeStamps
private void TimeZone_TimeStamp()
{
IToTimeStampConversionIterator converter = ConversionIterators.ToTimeStamp(fromTimeZone: NewYork);
foreach (DateTime estTime in ExampleFeed.ChronologicalUnspecifiedDateTimes())
{
TimeStamp timeStamp = converter.GetTimeStamp(estTime.Ticks);
}
}
// Demonstrates conversion of TimeStamp to various time objects in the New York timezone.
private void TimeStamp_TimeZone()
{
IFromTimeStampConversionIterator converter = ConversionIterators.FromTimeStamp(toTimeZone: NewYork);
foreach (TimeStamp timestamp in ExampleFeed.ChronologicalTimeStamps())
{
DateTime estTime = converter.GetDateTime(timestamp);
DateTimeOffset estTime2 = converter.GetDateTimeOffset(timestamp);
long estTicks = converter.GetTicks(timestamp);
}
}
// Demonstrates conversion of time objects from one timezone to another.
private void TimeZone_TimeZone()
{
ITimeZoneConversionIterator converter = ConversionIterators.Create(NewYork, Sydney);
foreach (DateTime newYorkTime in ExampleFeed.ChronologicalUnspecifiedDateTimes())
{
DateTime sydneyTime = converter.GetDateTime(newYorkTime.Ticks);
DateTimeOffset sydneyTime2 = converter.GetDateTimeOffset(newYorkTime.Ticks);
long sydneyTicks = converter.GetTicks(newYorkTime.Ticks);
}
}
// Demonstrates use of the "MoveTo" method and the "DifferenceTicks" property
// of the conversion iterators for low-level (and faster) operation.
private void DemonstrateLowLevelFeatures()
{
ITimeZoneConversionIterator converter = ConversionIterators.Create(NewYork, Sydney);
foreach (DateTime newYorkTime in ExampleFeed.ChronologicalUnspecifiedDateTimes())
{
var differenceChanged = converter.MoveTo(newYorkTime.Ticks);
if (differenceChanged)
{
var newDifferenceInHours = (int)converter.DifferenceTicks.ToHours();
Console.WriteLine($"Difference between New York and Sydney time changed to {newDifferenceInHours} hours at {newYorkTime:yyyy-MM-dd HH:mm:ss}, New York time.");
}
}
}
}
}
Warning
Conversion iterators require that the input times be supplied in ascending chronological order in order to provide correct conversion results. This requirement allows code optimization to make the conversion algorithm extremely efficient, but it also means you can get incorrect results if you accidentally supply inputs in descending chronological order. The iterators are coded to be fast, so they do not check YOUR work to ensure you have provided only ascending chronological order inputs.
Tip
The exception to the warning rule above is for moments when a timezone clock flies back in time, typically from its advanced Daylight Savings offset to its normal Standard time offset. When the clock flies back in time, you will have to pass an out-of-chronological order timestamp to the Conversion iterator. The iterator has been coded to handle this correctly. See the Ambiguous times section below for more information.
Tip
Did you notice in the examples above that the conversion iterators can provide outputs in multiple formats (long ticksTimezone
, DateTime
, DateTimeOffset
, and TimeStamp
), but they accept inputs only in long ticksTimezone
?
The conversion iterators should not accept DateTime
objects as inputs because they can be ambiguous due to their DateTimeKind
property, and checking this property and doing all the extra work would slow them down, not to mention introducing errors that you would only know to avoid if you read the documentation very carefully. They are meant to be fast and error-free. By accepting only long ticks
inputs, Conversion iterators require you to think carefully when you are writing the code that uses them, and helps you avoid making mistakes from simply not knowing the internal implementation details.
Ambiguous times
Imagine that at 3am in your timezone, the clock flies one hour back to 2am as Daylight Savings comes to an end and the Standard offset for your timezone is resumed.
In this scenario, just on this day, the clock must complete the period of time from 2am to 3am TWICE on the same day! This causes ambiguity: On this particular day in this particular timezone, every time between 2am and 3am occurs twice, and has two equivalent UTC times.
Default behaviour in the .net framework classes and, therefore in FFT.TimeStamps
, is to treat ambiguous times as though they are in the timezone's standard offset, which is the same as assuming that the clock is passing through the ambiguous period for the second time.
However, a conversion iterator must be able to handle the situation where times are streamed through it corresponding to the clock's first passage through the ambiguous period (with the timezone's Daylight Savings offset), and then to the clock's second passage through it (with the timezone's Standard Offset). When the conversion iterator detects the input flying back in time, it assumes that the clock is now passing through the ambiguous period for the second time.
Checkout the code for the ToUtcIterator
class for an example of how to work with the TimeZoneCalculator
within ambiguous time periods.
Tip
Ambiguous times happen when converting from timezones that adjustments such as daylight savings, but they never happen when converting from UTC or any timezone that does not change.
Benchmarking
Converting 1 million sequential (in chronological order) times from UTC timezone to New York timezone is roughly 32 times faster with conversion iterators provided by FFT.TimeStamps
than it is using in-built .net framework methods.
Method | Mean | Error | StdDev |
---|---|---|---|
With_DotNet | 211.477 ms | 3.2234 ms | 2.5166 ms |
With_ConversionIterators | 6.607 ms | 0.1123 ms | 0.1248 ms |
// Copyright (c) True Goodwill. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace FFT.TimeStamps.Benchmarks
{
using System;
using System.Linq;
using BenchmarkDotNet.Attributes;
using static FFT.TimeStamps.Benchmarks.TimeZones;
/// <summary>
/// Measures the speed of converting 1,000,000 UTC DateTimes in chronological order to New York timezone DateTimes.
/// </summary>
public class ConversionIteratorSpeed
{
private static readonly DateTime[] _dateTimes = ExampleFeed.ChronologicalUtcDateTimes().ToArray();
/// <summary>
/// Performs test using built-in .net framework feature.
/// </summary>
[Benchmark]
public void With_DotNet()
{
foreach (var datetime in _dateTimes)
TimeZoneInfo.ConvertTime(datetime, NewYork);
}
/// <summary>
/// Performs test using FFT.TimeStamps conversion feature.
/// </summary>
[Benchmark]
public void With_ConversionIterators()
{
var iterator = ConversionIterators.Create(TimeZoneInfo.Utc, NewYork);
foreach (var datetime in _dateTimes)
iterator.GetDateTime(datetime.Ticks);
}
}
}