-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
The TimeZoneInfo methods ConvertTimeToUtc, IsAmbiguousTime and IsDayLightSavingTime popped up as eating up about half of the processing time in a program importing, converting and storing lots of data in a database, As this was quite unbelievable to me, I programmed a little converter, which did these same conversions for our time zone "W. Europe Standard Time" and found it to be about 70 times as fast as the dotnet implementation.
I tried to figure out what went wrong in TimeZoneInfo implementation, but it looked pretty complicated (even messed up) to me, and as I only have limited time resources thought, that someone responsible for this code should check this out instead.
I know my comparison is only valid for our W.European timezone, but I think that most time zones are similarily easy to implement, so for these types of zones the dotnet code really should perform much better. Performance should really be an issue here, as time-related functionality is offen used in comination with engineering and measurment data, where converting between local timezones and UTC is very frequent, and needs to be fast.
I have attached my test program and also paste it here for your convenience.
Stefan
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestTimeZone {
class MyWesternEuropeConverter {
const int YFirst = 1900;
DateTime TFirst = new DateTime(YFirst, 1, 1), TLast = new DateTime(YFirst + 200, 1, 1);
DateTime[] FwdTimes = new DateTime[200], BwdTimes = new DateTime[200];
TimeZoneInfo RegularInfo;
int StdHourDiff = 1;
int HourOfChange = 2;
public MyWesternEuropeConverter() {
RegularInfo = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time");
InitDates();
}
public DateTime ConvertTimeToUtcRegular(DateTime dt, out bool isAmbiguous, out bool isDayLightSavingTime, out bool isAllowed) {
try {
isAmbiguous = RegularInfo.IsAmbiguousTime(dt);
dt = TimeZoneInfo.ConvertTimeToUtc(dt, RegularInfo);
isDayLightSavingTime = RegularInfo.IsDaylightSavingTime(dt);
isAllowed = true;
} catch {
isAllowed = false;
isAmbiguous = true;
isDayLightSavingTime = true;
}
return dt;
}
public DateTime ConvertTimeFromUtcRegular(DateTime dt) {
return TimeZoneInfo.ConvertTimeFromUtc(dt, RegularInfo);
}
public DateTime ConvertTimeToUtc(DateTime dt, out bool isAmbiguous, out bool isDayLightSavingTime, out bool isAllowed) {
int yearIdx = dt.Year - YFirst;
if (yearIdx < 0 || yearIdx >= 200) {
return ConvertTimeToUtcRegular(dt, out isAmbiguous, out isDayLightSavingTime, out isAllowed);
} else {
DateTime curFwd = FwdTimes[yearIdx];
DateTime curBwd = BwdTimes[yearIdx];
if (dt < curFwd || dt >= curBwd) {
isAmbiguous = false;
isAllowed = true;
isDayLightSavingTime = false;
return new DateTime(dt.Ticks - TimeSpan.TicksPerHour * StdHourDiff, DateTimeKind.Utc);
}
if (dt.Ticks < curFwd.Ticks + TimeSpan.TicksPerHour) {
isAllowed = false;
isAmbiguous = true;
isDayLightSavingTime = true;
return dt; // new DateTime(dt.Ticks - TimeSpan.TicksPerHour * StdHourDiff, DateTimeKind.Utc);
}
isAllowed = true;
isAmbiguous = dt.Ticks >= curBwd.Ticks - TimeSpan.TicksPerHour;
isDayLightSavingTime = !isAmbiguous;
return new DateTime(dt.Ticks - TimeSpan.TicksPerHour * (StdHourDiff + (isAmbiguous ? 0 : 1)), DateTimeKind.Utc);
}
}
public DateTime ConvertTimeFromUtc(DateTime dt) {
DateTime utc = new DateTime(dt.Ticks + TimeSpan.TicksPerHour * StdHourDiff, DateTimeKind.Local);
int yearIdx = utc.Year - YFirst;
if (yearIdx < 0 || yearIdx >= 200) {
return TimeZoneInfo.ConvertTimeFromUtc(dt, RegularInfo);
}
DateTime curFwd = FwdTimes[yearIdx];
DateTime curBwd = BwdTimes[yearIdx];
if (dt.Ticks >= curFwd.Ticks || dt.Ticks <= curBwd.Ticks - TimeSpan.TicksPerHour) dt = new DateTime(dt.Ticks + TimeSpan.TicksPerHour, DateTimeKind.Local);
return dt;
}
void InitDates() {
int yFirst = TFirst.Year;
for (int i = 0; i < FwdTimes.Length; i++) {
FwdTimes[i] = GetFwdDate(yFirst + i).AddHours(HourOfChange);
BwdTimes[i] = GetBwdDate(yFirst + i).AddHours(HourOfChange + 1);
// System.Diagnostics.Trace.WriteLine(string.Format("{0}-{1}", FwdTimes[i], BwdTimes[i]));
}
}
DateTime GetFwdDate(int year) {
DateTime dLast = new DateTime(year, 3, 31);
return dLast.AddDays(-(int)dLast.DayOfWeek);
}
DateTime GetBwdDate(int year) {
DateTime dLast = new DateTime(year, 10, 31);
return dLast.AddDays(-(int)dLast.DayOfWeek);
}
}
class Program {
static void Echo(string s) {
System.Diagnostics.Trace.WriteLine(s);
System.Console.WriteLine(s);
}
static string Report(DateTime t, bool isAmbiguous, bool isDayLightSaving, bool isAllowed) {
return string.Format("{0:yyyy/MM/dd/HH:mm} ambiguous:{1} dayLightSaving:{2} allowed:{3}", t, isAmbiguous, isDayLightSaving, isAllowed);
}
static void Main(string[] args) {
MyWesternEuropeConverter conv = new MyWesternEuropeConverter();
TimeZoneInfo tzInfo = TimeZoneInfo.Local;
DateTime tStart = new DateTime(1900, 1, 1);
DateTime tEnd = new DateTime(2100, 1, 1);
bool allowed1, allowed2, isAmbiguous1, isAmbiguous2, isDayLightSaving1, isDayLightSaving2;
for (DateTime tCur = tStart; tCur < tEnd; tCur = tCur.AddMinutes(15)) {
DateTime utc1 = conv.ConvertTimeToUtcRegular(tCur, out isAmbiguous1, out isDayLightSaving1, out allowed1);
DateTime utc2 = conv.ConvertTimeToUtc(tCur, out isAmbiguous2, out isDayLightSaving2, out allowed2);
if (utc1 != utc2 || isAmbiguous1 != isAmbiguous2 || isDayLightSaving1 != isDayLightSaving2 || allowed1 != allowed2)
Echo(string.Format("Different: {0} {1}", Report(utc1, isAmbiguous1, isDayLightSaving1, allowed1), Report(utc2, isAmbiguous2, isDayLightSaving2, allowed2)));
}
DateTime utc = DateTime.UtcNow;
int count = 0;
for (DateTime tCur = tStart; tCur < tEnd; tCur = tCur.AddMinutes(15)) {
count++;
DateTime utc1 = conv.ConvertTimeToUtcRegular(tCur, out isAmbiguous1, out isDayLightSaving1, out allowed1);
}
TimeSpan tNeeded = DateTime.UtcNow - utc;
Echo(string.Format("seconds needed for {0} ToUtc regular: {1}, ticks per call: {2}", count, tNeeded.TotalSeconds,((double) tNeeded.Ticks)/count));
utc = DateTime.UtcNow;
count = 0;
for (DateTime tCur = tStart; tCur < tEnd; tCur = tCur.AddMinutes(15)) {
count++;
DateTime utc2 = conv.ConvertTimeToUtc(tCur, out isAmbiguous2, out isDayLightSaving2, out allowed2);
}
tNeeded = DateTime.UtcNow - utc;
Echo(string.Format("seconds needed for {0} ToUtc mine: {1}, ticks per call: {2}", count, tNeeded.TotalSeconds, ((double)tNeeded.Ticks) / count));
System.Console.Write("Ready >");
System.Console.ReadKey();
}
}
}