24

Ok i've been working very hard the last few weeks and i've come across a small problem. I don't think my mind is quite up to the task right now :) so i need some tips/help! it's likely very simple but my head isn't clicking yet.

Users will enter a date and time in AEST. There is also a application set "default" timezone (as it might need to change), currently its set to "AUS Eastern Standard Time"

So we have a user string with no time zone and a defined system time zone on a server in the USA (So local doesn't match and it can't be changed or used)

Now what i need is a way to say "parse this user entered string using the timezone X" i can't just enter +10 or +11 as the offset as the dates could be in or out of daylight savings; which yes does change it between +10 and +11 even for the same timezone!

The current AEST time might also be in or out of DST so i can't just convert a UTC date to the current AEST time and get the "zzz" string and attach it either as dates will be off by an hour for anything entered out of the current DST setting.

For now the code actually does just that:

TimeZoneInfo ConvTo = TimeZoneInfo.FindSystemTimeZoneById(ConfigurationManager.AppSettings["DefaultTimeZone"]);
DateTimeOffset getDate = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, ConvTo);
string TimeZoneId = " " + getDate.ToString("zzz");
DateTimeOffset cvStartDate = DateTimeOffset.MinValue; DateTimeOffset.TryParse(StartDate + TimeZoneId, out cvStartDate);

Then i check if the date isn't valid by checking if it still == DateTimeOffset.MinValue or convert it to UTC and add to the database, it will the be converted back to AEST when displayed. However some dates are off by an hour and others are perfect (as expected) :)

What is the most elegant way to solve this?

EDIT:

To help explain the problem, i wrote some test code as a windows test application:

// User entered date
string EnteredDate = "2011/01/01 10:00:00 AM";

// Get the timezone we want to use
TimeZoneInfo myTimeZone = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");

// Find the timezone string of the selected timezone to parse the user string
// This is the part that is incorrect and what i need help with.
DateTimeOffset getDate = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, myTimeZone);
string TimeZoneId = " " + getDate.ToString("zzz");

// Parse the string into the date object
DateTimeOffset cvEnteredDate = DateTimeOffset.MinValue; DateTimeOffset.TryParse(EnteredDate + TimeZoneId, out cvEnteredDate);

// Display
textBox1.Text += "Parsed: " + cvEnteredDate.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;

// Convert to UTC and display
cvEnteredDate = cvEnteredDate.ToUniversalTime();
textBox1.Text += "UTC: " + cvEnteredDate.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;

// Convert back to AEST and display
cvEnteredDate = TimeZoneInfo.ConvertTime(cvEnteredDate, myTimeZone);
textBox1.Text += "Changed Back: " + cvEnteredDate.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;

Whats the output of this?

Parsed: 2011/01/01 10:00:00 +10:00
UTC: 2011/01/01 00:00:00 +00:00
Changed Back: 2011/01/01 11:00:00 +11:00

Please note the hour is off by one and the offset is different. Additionally, what if we JUST change the date entered to:

string EnteredDate = "2011/04/20 10:00:00 AM";

we get:

Parsed: 2011/04/20 10:00:00 +10:00
UTC: 2011/04/20 00:00:00 +00:00
Changed Back: 2011/04/20 10:00:00 +10:00

Which is perfectly good and fine, using the same code just a different entered date.

This happens because the current DST setting and the DST setting of the entered date are different, this is what i want a solution for :)

Think of it like the chicken and egg problem. I need the correct timezone data for the entered string before i parse it which i can only get after i've parsed the string (so will be an elaborate solution)

Or i need .NET to parse the string using the myTimeZone object so it knows what to set it to itself, but i can't see any functions that do this, they all take a already parsed and set datetime or datetimeoffset object

So i'm looking for elegant solutions others might have done? I certainly can't be the only one who has noticed this?

EDIT2:

Ok i've made a 'working' function that solves the problem i think, here is an example (add a textbox to a c# windows app and use the code below to test yourself):

private void Form1_Load(object sender, EventArgs e)
{
    TimeZoneInfo myTimeZone = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");

    DateTimeOffset get1Date = ReadStringWithTimeZone("2011/01/01 10:00:00 AM", myTimeZone);
    textBox1.Text += "Read1: " + get1Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;
    get1Date = get1Date.ToUniversalTime();
    textBox1.Text += "Read1 - UTC: " + get1Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;
    get1Date = TimeZoneInfo.ConvertTime(get1Date, myTimeZone);
    textBox1.Text += "Changed Back: " + get1Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine + Environment.NewLine;

    DateTimeOffset get2Date = ReadStringWithTimeZone("2011/04/20 10:00:00 AM", myTimeZone);
    textBox1.Text += "Read2: " + get2Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;
    get2Date = get2Date.ToUniversalTime();
    textBox1.Text += "Read2 - UTC: " + get2Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine;
    get2Date = TimeZoneInfo.ConvertTime(get2Date, myTimeZone);
    textBox1.Text += "Changed Back: " + get2Date.ToString("yyyy/MM/dd HH:mm:ss zzz") + Environment.NewLine + Environment.NewLine;
}

public DateTimeOffset ReadStringWithTimeZone(string EnteredDate, TimeZoneInfo tzi)
{
    DateTimeOffset cvUTCToTZI = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tzi);
    DateTimeOffset cvParsedDate = DateTimeOffset.MinValue; DateTimeOffset.TryParse(EnteredDate + " " + cvUTCToTZI.ToString("zzz"), out cvParsedDate);
    if (tzi.SupportsDaylightSavingTime)
    {
        TimeSpan getDiff = tzi.GetUtcOffset(cvParsedDate);
        string MakeFinalOffset = (getDiff.Hours < 0 ? "-" : "+") + (getDiff.Hours > 9 ? "" : "0") + getDiff.Hours + ":" + (getDiff.Minutes > 9 ? "" : "0") + getDiff.Minutes;
        textBox1.Text += "Diff: " + MakeFinalOffset + Environment.NewLine;
        DateTimeOffset.TryParse(EnteredDate + " " + MakeFinalOffset, out cvParsedDate);
        return cvParsedDate;
    }
    else
    {
        return cvParsedDate;
    }
}

And the output:

Diff: +11:00
Read1: 2011/01/01 10:00:00 +11:00
Read1 - UTC: 2010/12/31 23:00:00 +00:00
Changed Back: 2011/01/01 10:00:00 +11:00

Diff: +10:00
Read2: 2011/04/20 10:00:00 +10:00
Read2 - UTC: 2011/04/20 00:00:00 +00:00
Changed Back: 2011/04/20 10:00:00 +10:00

Only thing is there might be a problem if the user entered date is right on the change over hour of DST it still might be an hour off as it's just reading the current offset and using that, then checking if its supposed to be daylight savings or not, and if its out there it would read incorrectly. However it's miles better than what i have now.

Can anyone maybe help me with cleaning up this function? Is this the best route go down for what i need? ideas?

White Dragon
  • 1,259
  • 1
  • 8
  • 20

5 Answers5

17

Heres a simple solution for a predefined format, this can be dynamic as well. I personally use this for talking with javascript:

public DateTimeOffset ParseDateExactForTimeZone(string dateTime, TimeZoneInfo timezone)
{
    var parsedDateLocal = DateTimeOffset.ParseExact(dateTime, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
    var tzOffset = timezone.GetUtcOffset(parsedDateLocal.DateTime);
    var parsedDateTimeZone = new DateTimeOffset(parsedDateLocal.DateTime, tzOffset);
    return parsedDateTimeZone;
}
André Snede
  • 9,899
  • 7
  • 43
  • 67
  • 1
    Thanks! same results but a nice tighter version (my reworked one is still an extra line over yours). There is still the odd change of an hour on the change over hour: Read1: 2012/10/07 02:00:00 +10:00 Read1 - UTC: 2012/10/06 16:00:00 +00:00 Changed Back: 2012/10/07 03:00:00 +11:00 But otherwise works fine for me. – White Dragon Mar 17 '14 at 04:49
  • @WhiteDragon If you need to do that, then I would take the UTC time, go back the amount of time you need, and then transform it to the timezone again. – André Snede Apr 22 '14 at 09:48
  • This seems to just applythe offset to the datetime. Wouldn't there be a problem if the date was during daylight savings? – Fidel Jul 09 '14 at 08:08
  • @Fidel No, the `GetUtcOffset` function, takes the datetime, and looks up what offset would apply in that timezone, at that time and date. And then creates a DateTimeOffset. The `GetUtcOffset` is used to avoid the problem you are talking about. But it is a very valid concern. – André Snede Jul 09 '14 at 10:45
2

This seems to work for me when I need to parse a datetime string from a known timezone into my local one. Haven't tested it in many cases but so far it has worked great for parsing times on a server somewhere in the eu.

TimeZoneInfo.ConvertTime(DateTime.Parse("2012-05-25 23:17:15", CultureInfo.CreateSpecificCulture("en-EU")),TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"),TimeZoneInfo.Local)
  • I had completely forgotten about this little question! Your solution is actually very close to what i use now. I need to use DateTimeOffset objects however and i cannot use the local time of the server ever - But as a note to others you can parse the text directly into a timezone with a DateTimeOffset object using the same method! :) – White Dragon Sep 23 '12 at 07:57
  • 1
    Id like to add to my above comment (to be more clear) that this only seems to work if the server uses the same timezone as you wish to read the user parsed data from. Using the above on my local machine works (and our current server) but moving the code to GoGrid (which is something like -9) it fails unless i use my function as below – White Dragon Feb 13 '14 at 01:22
1

As no-one has provided any better solutions (which is very surprising!) i'm accept my own function as the answer, although i might make the function more concise and re-work it a bit later:

public DateTimeOffset ReadStringWithTimeZone(string EnteredDate, TimeZoneInfo tzi)
{
    DateTimeOffset cvUTCToTZI = TimeZoneInfo.ConvertTime(DateTimeOffset.UtcNow, tzi);
    DateTimeOffset cvParsedDate = DateTimeOffset.MinValue;
    DateTimeOffset.TryParse(EnteredDate + " " + cvUTCToTZI.ToString("zzz"), out cvParsedDate);
    if (tzi.SupportsDaylightSavingTime)
    {
        TimeSpan getDiff = tzi.GetUtcOffset(cvParsedDate);
        string MakeFinalOffset = (getDiff.Hours < 0 ? "-" : "+") + (getDiff.Hours > 9 ? "" : "0") + getDiff.Hours + ":" + (getDiff.Minutes > 9 ? "" : "0") + getDiff.Minutes;
        DateTimeOffset.TryParse(EnteredDate + " " + MakeFinalOffset, out cvParsedDate);
        return cvParsedDate;
    }
    else
    {
        return cvParsedDate;
    }
}
White Dragon
  • 1,259
  • 1
  • 8
  • 20
0

Simplest solution: First parse string to local DateTime, use any parse method then call

new DateTimeOffset(dateTimeLocal.Ticks, timezone.BaseUtcOffset)
Otabek Kholikov
  • 1,188
  • 11
  • 19
0

TimeZoneInfo has a static method ConvertTimeToUtc which enables you to specify the datetime and timezone. This will to do the time adjustment for you, and return the UTC date.

MSDN documentation is http://msdn.microsoft.com/en-us/library/bb495915.aspx, complete with example.

David McEwing
  • 3,320
  • 18
  • 16
  • I think you've missed what i'm saying, that function takes a already set datetime object, i know HOW to convert between timezones but i'm asking how to solve the issue i've described above. string -> datetimeoffset compensating for timezone – White Dragon Apr 11 '11 at 00:44
  • Could I suggest then a different tack. Make sure you save all dates as DateTime values (even in the database, rather than strings), then the datetime conversions will be semi-automated for you. Or alternately convert to UTC and store all the times in UTC, then when you need to display them convert them from UTC into the appropriate timezone. – David McEwing Apr 11 '11 at 02:17
  • I think your still off here, all dates are DateTimeOffsets in the database and the code, stored as UTC already. The parsing is from USER entered data :) and this part thats the problem. Check the 2 edits and the new function and the code to see exactly what i'm talking about (code speaks louder than words) – White Dragon Apr 11 '11 at 02:22
  • user enters a start date and end date in the admin for a item to go live and end, these dates have to be entered as AEST but the server is a USA one so localtime isn't something i can use even if i wanted to. WE have strict embargo's down to the hour so when DST change time rolls around the entered dates and times must be correct and could be entered for the future which might be a completely different offset (but the same timezone!) cause of DST – White Dragon Apr 11 '11 at 02:30