MB34
MB34

Reputation: 4424

C# ConvertTimeFromUtc funkiness

With the code below, I'm trying to convert this datetime string to a Local DateTime

private DateTime ConvertToLocalTime(string datetimestring)
{
    DateTime timeUtc = DateTime.Parse(datetimestring);
    TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
    DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone);
    return cstTime;
}

This is the Exception I'm getting:

at System.TimeZoneInfo.ConvertTime(DateTime dateTime, TimeZoneInfo sourceTimeZone, TimeZoneInfo destinationTimeZone, TimeZoneInfoOptions flags, CachedData cachedData) at System.TimeZoneInfo.ConvertTimeFromUtc(DateTime dateTime, TimeZoneInfo destinationTimeZone)

The conversion could not be completed because the supplied DateTime did not have the Kind property set correctly. For example, when the Kind property is DateTimeKind.Local, the source time zone must be TimeZoneInfo.Local.

The example for ConvertTimeFromUtc looks exactly like my code except I'm parsing this string into the timeUtc: 2017-01-23T05:00:00+00:00

If I call the Parse like this:

DateTime.Parse(datetimestring, null, System.Globalization.DateTimeStyles.RoundtripKind);

timeUtc.Kind.ToString() returns "Local"

So, how do I remedy this? The times are going to be sent to me UTC.

Upvotes: 6

Views: 7497

Answers (4)

Tore Aurstad
Tore Aurstad

Reputation: 3806

Just to digress more about converting Utc time to local time zone specified :

I checked the available time zones called 'Central' on my system and found these three :

Time zones called 'Central'

The time zone called 'Central America Standard Time' has not support for daylight saving time, while 'Central Standard Time'. Probably you do want to use the one with the daylight saving time.

Here is an extension method I created similar to the other answers and accepts an arbitrary specified TimeZoneInfo id supported - it is checking if the time zone provided is recognized and requiring that the DateTime has Kind 'Utc'.

public static class DatetimeExtensions {
    
    /// <summary>Returns the DateTime in a specified time zone</summary>
    /// <remarks>
    /// <paramref name="timeZoneId" /> must be a recognized time zone id, check with System.TimeZoneInfo.GetSystemTimeZones() available time zones on the target system
    /// <paramref name="dateTime" /> must be a datetime of Kind Utc to be properly calculated into its proper value in the specified time zone id
    /// </remarks>
    public static DateTime ConvertToLocalTime(this DateTime dateTime, string timeZoneId){
    
        TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId) ?? System.TimeZoneInfo.GetSystemTimeZones().FirstOrDefault(tz => string.Equals(tz.Id, timeZoneId, StringComparison.InvariantCultureIgnoreCase));
        if (timeZone == null)
        {
            throw new ArgumentException($"The time zone with id {timeZoneId} is not recognized. (Available time zones can be retrieved using 'System.TimeZoneInfo.GetSystemTimeZones()' method)");
        }
        if (dateTime.Kind != DateTimeKind.Utc)
        {
            throw new ArgumentException($"The date time {dateTime} is not of the right Kind 'Utc'");
        }
        return TimeZoneInfo.ConvertTimeFromUtc(dateTime, timeZone);     
    }
    
}

A test using this method:

void Main()
{
    
    var central = TimeZoneInfo.GetSystemTimeZones().Where(x => x.DisplayName.Contains("Central")).ToList(); 
    central.Dump();
    
    var someUtcDate = new DateTime(2024, 6, 4, 6, 30, 0, DateTimeKind.Utc); 
    var someUtcDateInCST = someUtcDate.ConvertToLocalTime("Central America Standard Time"); 
    Console.WriteLine(someUtcDateInCST);
}

Running this method gives:

04.06.2024 00:30:00

If I switch instead to using "Central Standard Time", I get:

04.06.2024 01:30:00

This is only five hours behind UTC time, but it is daylight saving time adjusted, which agrees with the daylight saving.

Upvotes: 0

CodingYoshi
CodingYoshi

Reputation: 27019

If you just want to convert to local datetime and you do not need the offset, since your string has offset info, DateTime.Parse will use the offset info and convert to local datetime. Then all you need to do is:

private static DateTime ConvertToLocalTime(string datetimestring)
{
    // Parses to local datetime
    // check the Kind property and you will see it has Local
    return DateTime.Parse(datetimestring);
}

If you need the local datetime with the offset info, then have a look at DateTimeOffset because it is for this very purpose:

private static DateTimeOffset ConvertToLocalTime(string datetimestring)
{
    DateTime timeUtc = DateTime.Parse(datetimestring, null, DateTimeStyles.AdjustToUniversal);
    DateTimeOffset dateCst = new DateTimeOffset(timeUtc, TimeZoneInfo.Local.BaseUtcOffset);

    return dateCst;
}

If you need the utc datetime converted to some other timezone, not local, then specify it like below:

private static DateTimeOffset ConvertToLocalTime(string datetimestring)
{
    DateTime timeUtc = DateTime.Parse(datetimestring, null, DateTimeStyles.AdjustToUniversal);
    TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
    DateTimeOffset dateCst = new DateTimeOffset(timeUtc, cstZone.GetUtcOffset(timeUtc));

    return dateCst;
}

This is the documentation:

//
// Summary:
//     Initializes a new instance of the System.DateTimeOffset structure using the specified
//     System.DateTime value and offset.
//
// Parameters:
//   dateTime:
//     A date and time.
//
//   offset:
//     The time's offset from Coordinated Universal Time (UTC).
//
// Exceptions:
//   T:System.ArgumentException:
//     dateTime.Kind equals System.DateTimeKind.Utc and offset does not equal zero.-or-dateTime.Kind
//     equals System.DateTimeKind.Local and offset does not equal the offset of the
//     system's local time zone.-or-offset is not specified in whole minutes.
//
//   T:System.ArgumentOutOfRangeException:
//     offset is less than -14 hours or greater than 14 hours.-or-System.DateTimeOffset.UtcDateTime
//     is less than System.DateTimeOffset.MinValue or greater than System.DateTimeOffset.MaxValue.

Upvotes: 0

Ghasan غسان
Ghasan غسان

Reputation: 5867

DateTime.Parse converts the result into local time regardless of the zone specified in the input string. You have to explicitly specify that you want a UTC result, as TimeZoneInfo.ConvertTimeFromUtc requires the DateTime value to be of kind UTC.

private DateTime ConvertToLocalTime(string datetimestring)
{
    DateTime timeUtc = DateTime.Parse(datetimestring, null, System.Globalization.DateTimeStyles.AdjustToUniversal);
    TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
    DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(timeUtc, cstZone);
    return cstTime;
}

Upvotes: 3

Max
Max

Reputation: 1090

You have to add DateTime.SpecifyKind

private static DateTime ConvertToLocalTime(string datetimestring)
{
    DateTime timeUtc = DateTime.Parse(datetimestring);
    var dt = DateTime.SpecifyKind(timeUtc, DateTimeKind.Utc);
    TimeZoneInfo cstZone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
    DateTime cstTime = TimeZoneInfo.ConvertTimeFromUtc(dt, cstZone);
    return cstTime;
}

.net Fiddle

Upvotes: 5

Related Questions