When I check optionDate's DateTime property's DateTimeKind value, I see Unspecified, even though I set dt's DateTimeKind as UTC in below code. I expect optionDate has a DateTime which has a DateTimeKind property set to UTC. Where am I wrong here?
var dt = new DateTime(Convert.ToInt32(optionDateInfo.dateTime.year),
Convert.ToInt32(optionDateInfo.dateTime.month), Convert.ToInt32(optionDateInfo.dateTime.day),
Convert.ToInt32(optionDateInfo.dateTime.hour), Convert.ToInt32(optionDateInfo.dateTime.minutes),
0, DateTimeKind.Utc);
var optionDate = new DateTimeOffset(dt);
This is documented:
DateTimeOffset.DateTime
The value of the DateTime.Kind property of the returned DateTime object is DateTimeKind.Unspecified.
Note that a DateTimeOffset does not have a "kind". It has a date, time, and offset. When you pass your DateTime with kind Utc, to it, it sets its offset to 0, and its date & time to the DateTime given. At this point, your DateTimeKind is "lost".
An offset of 0 does not necessarily mean that its kind is DateTimeKind.Utc. It could be the local time in London, or somewhere in Africa too. So it can't give you a DateTime with kind Utc just because its offset is 0 either.
In addition, DateTime being able to represent 3 kinds of things is already a questionable design, and if the DateTime property can now return 3 different kinds of DateTime depending on whether offset matches the local time, is UTC, or something else, that's just even worse.
Instead, it is designed to have 3 properties that give you DateTimes with different kinds.
DateTime gives you the date & time part of the DateTimeOffset, with kind Unspecified
LocalDateTime converts the date & time part of the DateTimeOffset to the current timezone, and gives you a DateTime with kind Local.
UtcDateTime converts the date & time part of the DateTimeOffset to UTC, and gives you a DateTime with kind Utc.
If you want a DateTime with kind Utc, you should use that last one.
Use the SpecifyKind
var myUtcZeroOffset = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc)
//If constructing a datetime offset to be not utc you can supply the offset instead
var myOffSetExplicitLocal = new DateTimeOffset(DateTime.Now, new TimeSpan(1, 0, 0));
var localDateTime = myOffSetExplicitLocal.DateTime;
var utcZeroOffSetDateTime = myOffSetExplicitLocal.UtcDateTime;
To make matters worse of cause it is a criticisable implementation from Microsoft, because Universally Coordinated Time is not a timezone but a notation, as per ISO 8601, so in fact toUTC as a concept is flawed because '2021-11-02T10:16:25.12345+01:00' is completely valid in the UTC format and UTC Zero offset, popularily called Zulu being the '2021-11-02T09:16:25.12345Z' equivalent which then gets datetimekind UTC is actually just in coordinated time the zero line around GMT latitude, but what makes it coordinated is the + part which in +00:00 can be abbreviated to Z, so lots of stuff is done to mitigate the inherent conflict and with build servers and cloud providers the .Local is especially dubious, so I would recommend always to persist in ISO 8601 strings instead, unless you actually need to use them in with date operations in Your DB, in said case to name fields appropriate like DateTimeCreatedUtcZero column e.g.
just my five cents of reason on the topic in general, hope it helps.
Related
I'm converting the UTC time, taken from my local Server time to Central standard time. I have this running on a server in Germany.
Converting the time and date works, but when a library i have converts it to a string it has a wrong Timezone offset.
It comes out as 2019-05-11T14:44:09+02:00
when i need it to be 2019-05-11T14:44:09-06:00
TimeZoneInfo CRtimezone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, CRtimezone);
The +02:00 is the UTCoffset for Germany, which i don't want, even the the time and date are correctly in Central Time.
Is there a way to pass or include the offset in the DateTime object?
Is there a way to pass or include the offset in the DateTime object?
No, DateTime structure does not have UTC Offset but DateTimeOffset has. If you really wanna keep your UTC Offset value in your code, I suggest you to work with DateTimeOffset instead of DateTime.
Since it doesn't keep UTC Offset value, when you get it's textual (aka string) representation, you still get the offset value of your server in Germany (includes K, z, zz and zzz specifiers by the way). TimeZoneInfo.ConvertTimeFromUtc method returns a DateTime instance, the offset value you might wanna represent depends on how you want to show it.
One option might be that you might wanna concatenate The Sortable ("s") Format Specifier representation of your DateTime and your TimeZoneInfo.BaseUtcOffset value.
TimeZoneInfo CRtimezone = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
$"{TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, CRtimezone).ToString("s")}{CRtimezone.BaseUtcOffset}".Dump();
I want to test some time-zone related code by comparing local time to UTC. However this test relies on local time being different to UTC and I'm in the UK so 6 months of the year, local time is UTC according to DateTime comparison tests (due to summer time).
I could hard-code my test to convert UTCNow to a certain timezone like EST but on the off-chance my code was used in an American system, now I have the same issue.
So is there a way I can easily convert DateTime.UtcNow to a timezone that's definitely different to my local timezone, without hard-coding it or making assumptions what timezone I'm in?
Ok, as I mentoined in comments, if you want to get timezones which differs from yours, you can do it in that way:
var zone = TimeZoneInfo.GetSystemTimeZones()
.Where(x=>x.BaseUtcOffset != TimeZoneInfo.Local.BaseUtcOffset)
.First();
To convert UTC DateTime to another timezone, you have to use TimeZoneInfo.ConvertTimeFromUtc, sample:
var datetime = // datetime in UTC, for example, DateTime.UtcNow
var zone = // target zone
return TimeZoneInfo.ConvertTimeFromUtc(datetime, zone);
You can check samples in my pet project here
Time zones in .net are a very confusing matter. DateTime only supports local and UTC timezones really, it has no concept of different zones as it only gets the current offset from the machine and applies it to a tick count value which is in UTC.
So at a first there's no way to change to another timezone, but you can simulate something. If you want per example simulate a timezone GMT+2, you must first retrieve the current zone offset and add the difference of this offset and the desired offset to the local date, something like this:
TimeSpan targetOffset = new TimeSpan(2, 0, 0) - TimeZoneInfo.Local.BaseUtcOffset; //target to GMT+2
DateTime targetNow = DateTime.Now + targetOffset;
In this way the Date will have the values like if it were on that timezone, but for calculations using the datetime object and not just the year/month/day/hour/minute/second all of them will be wrong as the object will be marked as local and thus will have the wrong values.
To solve this you have two options, reverse your logic (convert non-local time to local time) or just work with UTC dates.
The first approach is very easy:
DateTime nonLocal = new DateTime(2016, 10, 21, 13, 33, 0); //We suppose we want 2016-10-21 13:33:00 at GMT+2
DateTime local = nonLocal + (TimeZoneInfo.Local.BaseUtcOffset - new TimeSpan(2, 0, 0));
This will yield to correct DateTime operations, but, here we go again with problems, if you use the year/month/day/hour/minute they will be in your local zone, not in the supposed zone and code using these properties will fail.
Finally, the best approach is the second one, you just forget about timezones and whenever you get a DateTime you convert it to UTC, all will work flawlesly and when you need to represent the data just convert it to local and all is done, no need to worry about DateTime arithmetic or properties differing just because they're on another timezone.
public static class DateTimeExtensions
{
public static DateTime As(this DateTime source, string timeZoneName)
{
DateTime utcTime = DateTime.SpecifyKind(source, DateTimeKind.Unspecified);
TimeZoneInfo newTimeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneName);
return TimeZoneInfo.ConvertTimeFromUtc(utcTime, newTimeZone);
}
}
Usage:
DateTime date1 = DateTime.UtcNow;
DateTime date2 = date1.As("Eastern Standard Time");
DateTime d1=new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc);
DateTime d2=new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Local);
Console.WriteLine(d1==d2); // prints true
Console.WriteLine(d1<d2); // prints false
Console.WriteLine(d1.CompareTo(d2)); // prints 0
Console.WriteLine(d1.ToUniversalTime()==d2.ToUniversalTime()); // prints false
This looks like a bug to me, if not color me surprised.
Do I have to call ToUniversalTime() for every comparison or is there a better alternative?
How do you avoid pitfalls like forgetting to call ToUniversalTime() or getting a wrong result because of DateTimeKind.Unspecified?
The MSDN documentation is quite clear that DateTimeKind is not taken into account using the Equality operator.
The Equality operator determines whether two DateTime values are equal by comparing their number of ticks. Before comparing DateTime objects, make sure that the objects represent times in the same time zone. You can do this by comparing the values of their Kind property.
MSDN - DateTime.Equality Operator
You could write your own extension method to include the DateTimeKind comparison:
public static bool EqualsWithKind(this DateTime time, DateTime other)
{
return time.Kind == other.Kind &&
time == other;
}
Taking into account the comments from Panagiotis Kanavos and James Thorpe about DateTimeOffset:
Use if the offsets are guaranteed to be the same as the local offset.
public static bool EqualsWithTimezone(this DateTime time, DateTime other)
{
return new DateTimeOffset(time) == new DateTimeOffset(other);
}
Use if the offsets are NOT guaranteed to be the same:
public static bool EqualsInclTimezone(this DateTime time, TimeSpan timeOffset, DateTime other, TimeSpan otherOffset)
{
return new DateTimeOffset(time, timeOffset) == new DateTimeOffset(other, otherOffset);
}
It's not exactly a bug but a shortcoming of DateTime. The DateTime type doesn't support timezone information apart from a local/UTC indicator. It says so in the docs - you have to ensure the dates are in the same timezone - not just having the same Kind. DateTimeKind.Local doesn't say anything about what timezone is really used.
If you care about timezones you should always use the DateTimeOffset type. It was introduced in .NET 3.5 partly to address timezones. DateTimeOffset is equivalent to SQL Server's datetimeoffset type and contains the timezone offset along with the time, allowing comparisons and conversions between timezone offsets. This also allows you to store and use the complete time information in code and the database, avoiding conversion errors.
This is similar to using nvarchar instead of varchar to avoid codepages conversion errors.
A timezone may have different offsets though due to daylight savings. Daylight savings rules change from time to time also - Russian rules have changed 4 times at least in the last 10 years. Windows and .NET don't have a fix for this.
This can be an issue eg in the travel industry. In such cases you can use a library like Noda Time, which contains the IANA timezone database with all known timezone rules.
Looks correct to me.
1/1/2015 15:00:00 (utc) (also GMT - Equal to GMT Greenwich Mean Time)
1/1/2015 15:00:00 (local - lets say local time is in NYC)
these two times and dates ARE equal by comparison.
But convert the second one to UTC and it skips ahead 5 hours to become
UTC/GMT
1/1/2015 20:00:00 - and they are no longer equal!
My web service call to a third party applications returns the date,time zone and timezone_offset values. I need to add this to a calendar in Asp.net application. What is the best way to combine this together so that my date object understands that its from Eastern time zone?
<start_date>2014-11-17 19:00:00</start_date>
<timezone>America/New_York</timezone>
<timezone_offset>GMT-0500</timezone_offset>
Since you have the offset too, you can use DateTimeOffset.Parse() to get the DateTimeOffset. From there, you can read the DateTime property. The output dt variable will have 2014-11-17 7:00:00 PM with DateTimeKind property set to "Unspecified"
var dtOffset = DateTimeOffset.Parse("2014-11-17 19:00:00-0500", CultureInfo.InvariantCulture);
var dt = dtOffset.DateTime;
A DateTimeOffset represents a point in time. Usually, its relative to UTC. So, it is a natural structure to initially parse the fields that you have.
If you want a reference to the same datetime in UTC, you can use this. Here the output dt variable will have 2014-11-18 12:00:00 AM with DateTimeKind property set to "Utc"
var dt = DateTime.Parse("2014-11-17 19:00:00-0500", CultureInfo.InvariantCulture).ToUniversalTime();
If you don't have the offset but just have the timeZoneId, you can still do it but you need NodaTime for that.
I'll focus on this part of the question:
What is the best way to combine this together so that my date object understands that its from Eastern time zone?
There is no data type built in to .NET that can sufficiently do that. The DateTimeOffset type associates with a particular offset, such as the -05:00 or -04:00 that might be in use by the Eastern time zone (depending on the date). But those same offsets might also be sourced from some other time zone entirely. A time zone has one or more offsets, but an offset isn't a time zone itself.
Fortunately there are solutions. There are two options to consider:
You could pair a DateTimeOffset with a TimeZoneInfo object. When storing or transmitting these, you would only send the full DateTimeOffset along with the Id of the time zone.
The Noda Time library is a much more effective way to work with date and time, especially when it comes to time zone concerns. It contains a type called ZonedDateTime that already combines a date, time, offset, and time zone. It can also be used to work with IANA time zone identifiers, such as the "America/New_York" time zone you specified in the question.
I get date data from a user. That data is a date (e.g. 4/23/2011) and an hour (0 - 23), representing the time. This date/time that the user selects is a local time.
I need to convert this to a UTC DateTime. I have their GMTOffset for their location. How can I do this?
You should work with the DateTimeOffset structure, specifically, the constructor that takes the DateTime and the TimeSpan that represents the offset.
From there, conversions to/from UTC are a breeze, as the offset is embedded in the structure and not dependent on local system settings.
Note, even though not commonly adhered to, it is recommended to work with DateTimeOffset most of the time, as opposed to DateTime (see the note under the section titled "The DateTimeOffset Structure").
var utcDateTime =
new DateTimeOffset(userDateTime, TimeSpan.FromHours(userUtcOffset)).UtcDateTime;
Of course you can use TimeSpan differently if the GMT offset has minutes / fractions of an hour.
Just use the DateTime.ToUniversalTime in C#, will that do what you want?