After having seen that the task can not be archieved with the help of the WP7/Silverlight framework, I wrote a small helper that does the job:
public static class DateTimeHelper
{
/// <summary>
/// Tries to parse the given datetime string that is not annotated with a timezone
/// information but known to be in the CET/CEST zone and returns a DateTime struct
/// in UTC (so it can be converted to the devices local time). If it could not be
/// parsed, result contains the current date/time in UTC.
/// </summary>
public static bool TryParseCetCest(string s, string format, IFormatProvider provider, DateTimeStyles style, out DateTime result)
{
// Parse datetime, knowing it is in CET/CEST timezone. Parse as universal as we fix it afterwards
if (!DateTime.TryParseExact(s, format, provider, style, out result))
{
result = DateTime.UtcNow;
return false;
}
result = DateTime.SpecifyKind(result, DateTimeKind.Utc);
// The boundaries of the daylight saving time period in CET and CEST (_not_ in UTC!)
// Both DateTime structs are of kind 'Utc', to be able to compare them with the parsing result
DateTime DstStart = LastSundayOf(result.Year, 3).AddHours(2);
DateTime DstEnd = LastSundayOf(result.Year, 10).AddHours(3);
// Are we inside the daylight saving time period?
if (DstStart.CompareTo(result) <= 0 && result.CompareTo(DstEnd) < 0)
result = result.AddHours(-2); // CEST = UTC+2h
else
result = result.AddHours(-1); // CET = UTC+1h
return true;
}
/// <summary>
/// Returns the last sunday of the given month and year in UTC
/// </summary>
private static DateTime LastSundayOf(int year, int month)
{
DateTime firstOfNextMonth = new DateTime(year, month + 1, 1, 0, 0, 0, DateTimeKind.Utc);
return firstOfNextMonth.AddDays(firstOfNextMonth.DayOfWeek == DayOfWeek.Sunday ? -7 :
(-1 * (int)firstOfNextMonth.DayOfWeek));
}
}
The trick was to parse it without the DateTimeStyles.AssumeUniversal flag (this makes TryParseExact
assume the date is UTC and returning the date converted/adjusted to local), respecifying it as UTC and then manually adjusting it to the actual UTC equivalent.
It follows the DST rules that can be found here. I tested it with all 4 boundary cases just before/after the start/end of the daylight saving time. That showed again the importance of testing: I had to change the <
operator in DstStart.CompareTo(result) < 0
to <=
to make it produce the correct result.
I had the feeling that I am reinventing the wheel here (which I hate to do), but did not want to use a dedicated library for this simple job. I had a look at Noda Time which is a great project, but I think its not necessary for this.
I hope I can save someone a little time with this small helper. It is intentionally not generic for all time zones (if you need this use a lib like Noda Time instead), but for these cases in which you just have one fixed single time zone, like in my case.