I'm stuck with a problem around parsing date and time:
I'm trying to parse a datetime string extracted from a german website. It is given in the format 'day.month.year 24hours:minutes', like:
01.01.2011 17:00
And it is always in the german timezone. But here comes the problem:
I have no clue how to achieve this. If I set my local clock to the german timezone, and I parse with DateTime.ParseExact
and the flag DateTimeStyles.AssumeLocal
and DateTimeStyles.AdjustToUniversal
it is parsed correctly. However, I want any client to parse it independently from their local clock and timezone. Also, I dont want to do the timezone offset myself, because it depends on the date (summer: -2 / winter: -1).
Once I have the datetime in UTC it would be easy to convert it to any local timezone.
It sounds like you know what time zone you should be parsing it with. Assuming .NET 3.5 (and thus TimeZoneInfo
) you should logically:
Unfortunately DateTime
makes that slightly tricky. EDIT: I thought you'd want to convert parse it using DateTimeStyles.AssumeUniversal
- but that ends up returning a local DateTime
, annoyingly. Basically you want to end up with a DateTime
with the right time so that you can use:
parsed = DateTime.SpecifyKind(parsed, DateTimeKind.Unspecified);
You can then get a UTC value with:
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(parsed, germanTimeZone);
Note that you really want an "unspecified" date time first, so that you can convert it to UTC in an arbitrary time zone. You should also remember the possibility that a local time is ambiguous (occurs twice) or impossible (doesn't occur at all) due to DST changes.
And yes, this will be a lot easier in Noda Time when it's finished :)
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With