I have question pertaining to the DateTimeKind struct in C#.
If I have converted a DateTime to a new DateTime (which is not in my local Timezone) using something like:
TimeZoneInfo.ConvertTimeBySystemTimeZoneId(now, "Tokyo Standard Time");
what should I use for the Kind property of that new DateTime? Unspecified feels a bit weird and does not help much with conversions.
I get the feeling that as soon as you use a Timezone which is not your local and not UTC, then you absolutely have to start using the DateTimeOffset struct.
This is more a question about how to handle non-local TimeZones.
When you go beyond your local timezone, you really do need to use the DateTimeOffset class.
When writing a time service, you may want to add a method for converting a DateTime in one non-local timezone to another non-local timezone. This is pretty straight forward when using the DateTimeOffset class:
public DateTimeOffset ConvertToZonedOffset(DateTimeOffset toConvert, string timeZoneId)
{
var universalTime = toConvert.ToUniversalTime(); // first bring it back to the common baseline (or standard)
var dateTimeOffset = TimeZoneInfo.ConvertTime(universalTime, TimeZoneInfo.FindSystemTimeZoneById(timeZoneId));
return dateTimeOffset;
}
The incoming DateTimeOffset has the source offset and the timeZoneId being passed in gives enough information to realize the target timezone (and offset).
And the returned DateTimeOffset has the target offset.
It gets a bit clunkier when you do it with the DateTime struct, if you wanted to provide an equivalent method:
public DateTime ConvertToZonedOffset(DateTime toConvert, string sourceTimeZoneId, string targetTimeZoneId)
{
return TimeZoneInfo.ConvertTimeBySystemTimeZoneId(toConvert, sourceTimeZoneId, targetTimeZoneId);
}
And this is where the DateTimeKind comes in. If you:
pass the DateTime in with the Kind set to either UTC or Local; AND
the sourceTimeZone is neither of those,
then ConvertTimeBySystemTimeZoneId will throw an exception. So, when you are dealing with a "3rd timezone", Kind must be Unspecified. This tells the method to ignore the system clock, to not assume that it is UTC and to go by whatever is passed in as the sourceTimeZone.
It's not as good as the DateTimeOffset version in another way. The returned DateTime has no information about the timezone and the Kind is set to Unspecified. This basically means that it is the responsibility of the calling code to know and track what timezone that date and time is valid in. Not ideal. So much so that I decided to "be opinionated" and get rid of that method. I'll force the calling code to convert the DateTime they may be working with to a DateTimeOffset and to consume one upon return.
Note 1: if Kind is set to Local and the sourceTimeZone matches your local timezone, it will work fine.
Note 2: if Kind is set to Utc and the sourceTimeZone is set to "Coordinated Universal Time", you may get the following TimeZoneNotFoundException:
The time zone ID 'Coordinated Universal Time' was not found on the local computer
I assume that this is because UTC is a standard and not a timezone, despite being returned by TimeZoneInfo.GetSystemTimeZones as a Timezone.
what should I use for the Kind property of that new DateTime? Unspecified feels a bit weird...
...but it's the correct value in this case. The documentation of the DateTimeKind enum is quite clear on this subject:
Local (2): The time represented is local time.
Unspecified (0): The time represented is not specified as either local time or Coordinated Universal Time (UTC).
Utc (1): The time represented is UTC.
Your time is neither local time nor UTC, so the only correct value is Unspecified.
I get the feeling that as soon as you use a Timezone which is not your local and not UTC, then you absolutely have to start using the DateTimeOffset struct.
You don't have to, but it can definitely make your life easier. As you have noticed, DateTime does not provide an option to store the time zone information along with the date. This is exactly what DateTimeOffset is for.
Related
I've been struggling with this for a bit so I'll punt to SO.
I have an app where I need to parse strings into datetimes. The strings look like this:
"201503131557"
(they will always be this format "yyyyMMddHHmm") and always be in Central Standard Time (or Central Daylight Time depending on the date)even though they don't specify it.
When I parse them I understand that it parses them into local time. I'm running on Azure PaaS and don't want to change it to run in CST just to support this operation.
How do i write code that works both locally and in Azure PaaS that will correctly parse these dates into DateTimes setting their timezone to CST?
Here is a quick unit test I wrote to prove Matt Johnson's answer.
[TestMethod]
public void DateTests()
{
var unSpecDt = DateTime.ParseExact("201503131557", "yyyyMMddHHmm", CultureInfo.InvariantCulture);
var tz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
var utcDt = TimeZoneInfo.ConvertTimeToUtc(unSpecDt, tz);
var offset = tz.GetUtcOffset(unSpecDt);
var dto =new DateTimeOffset(unSpecDt, offset);
var cstDt = dto.DateTime;
Assert.IsTrue(cstDt.Hour - utcDt.Hour == offset.Hours);
}
To parse the string, since you have only the date and time, and you know the specific format the strings will be in, do this:
DateTime dt = DateTime.ParseExact("201503131557", "yyyyMMddHHmm", CultureInfo.InvariantCulture);
The resulting value will have its Kind property set to DateTimeKind.Unspecified (not local time, as you thought). That is to be expected, as you provided no information regarding how this timestamp is related to UTC or local time.
You said the value represents time in Central Standard Time. You'll need a TimeZoneInfo object that understands that time zone.
TimeZoneInfo tz = TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time");
Note that this identifier represents the Central Time observed in the USA, including both CST or CDT depending on which is in effect for the given value (despite the word "Standard" in its name). Also note that it valid on Windows operating systems only. If you wanted to use .NET Core on some other OS, then you'd need to pass the IANA identifier "America/Chicago" instead (or use my TimeZoneConverter library to use either identifier on any platform).
The next step is to figure out what you want to do with this value. You might do a few different things with it:
If you want to convert it to the equivalent UTC value, represented as a DateTime, then you can do this:
DateTime utc = TimeZoneInfo.ConvertTimeToUtc(dt, tz);
If you want a DateTimeOffset representation which holds the input you gave and the offset from UTC as it related to US Central Time, then you can do this:
TimeSpan offset = tz.GetUtcOffset(dt);
DateTimeOffset dto = new DateTimeOffset(dt, offset);
Keep in mind that it's possible for the input time is invalid or ambiguous if it falls near a DST transition. The GetUtcOffset method will return the standard offset in such cases. If you want different behavior, you have more code to write (out of scope for this post).
There are other things you might do, all of which are provided by the TimeZoneInfo class.
Note that "Azure PaaS" might refer to a few different things, and while there is a setting called WEBSITE_TIME_ZONE in Azure App Service - I don't recommend you lean on it. Consider it a last resort to be used only when you can't control the code. In most cases, it is better off writing your code to never depend on the time zone setting of the system it runs on. That means never calling DateTime.Now, or TimeZoneInfo.Local, DateTime.ToLocalTime, or even DateTime.ToUniversalTime (since it converts from the local time zone), etc. Instead, rely upon methods that work explicitly with either UTC or a specific time zone or offset. Then you will never need to care about where your app is hosted.
Lastly, understand that neither the DateTime or DateTimeOffset types have any capability of understanding that a value is tied to a specific time zone. For that, you'd need to either write your own class, or look to the Noda Time library whose ZonedDateTime class provides such functionality.
You can use:
DateTime.ParseExact(...)
public static DateTime ParseExact (string s, string format, IFormatProvider provider);
This datetime do not have any metadata about timezone. You can convert to UTC and then you will be sure that you are able to convert to all timezones.
Use the DateTime.TryParseExact(...) method with DateTimeStyles.RoundtripKind to avoid conversion.
DateTime dt;
DateTime.TryParseExact("201503131557", "yyyyMMddHHmm", null, System.Globalization.DateTimeStyles.RoundtripKind, out dt);
If you need to convert you will need to go to UTC then use the conversion methods of TimeZoneInfo with TimeZoneInfo.FindSystemTimeZoneById("Central Standard Time").
What happens when we convert UTC Date time ToUniversalTime?
DateTime localDate = DateTime.Now.AddMinute(offsetTimeZone);
DateTime todayStart = localDate.Date.ToUniversalTime().AddHours(00).AddMinutes(00);
There is a -lot- going on when converting using ToUniversalTime. DST is addressed, local time, etc. etc.
Just go to the reference source and read through the code:
http://referencesource.microsoft.com/#mscorlib/system/datetime.cs,fddce8be2da82dfc
There is already the same question on stackoverflow.
here is no implicit timezone attached to a DateTime object. If you run ToUniversalTime() on it, it uses the timezone of the context that the code is running in.
For example, if I create a DateTime from the epoch of 1/1/1970, it gives me the same DateTime object no matter where in the world I am.
If I run ToUniversalTime() on it when I'm running the code in Greenwich, then I get the same time. If I do it while I live in Vancouver, then I get an offset DateTime object of -8 hours.
This is why it's important to store time related information in your database as UTC times when you need to do any kind of date conversion or localization. Consider if your codebase got moved to a server facility in another timezone ;)
You can find the question and the complete answer here.
DateTime objects by default are typed as DateTimeKind.Local. On parsing a date and set it as DateTimeKind.Utc, then ToUniversalTime() performs no conversion. If we run ToUniversalTime(), it uses the timezone of the context that the code is running in.
I am using Facebook C# SDK to get some data from my Facebook page.
When I get objects of data, at that time I get one field:
created_time : 2014-05-23T11:55:00+0000
As it is not same as my current time how do I convert it to my current time or to standard UTC time?
For me the code:
DateTime.Parse("2014-05-23T11:55:00+0000").ToString()
returns
2014-05-23 12:55:00
Because 'DateTime.Parse()` understands the timezone in this string, and by default produces a local time. My timezone being Irish Summer Time, one hour ahead of UTC, that means 2014-05-23T12:55:00 for me, for you it'll be a different time depending on your timezone.
For the converse, to get the time parsed in UTC (more useful for behind-the-scenes web stuff or storage, rather than user interface), use DateTime.Parse("2014-05-23T11:55:00+0000").ToUniversalTime()
If you need to deal with timezones other than local to the machine the code is running on, or UTC, then you will need to use DateTimeOffset, with the exception that DateTimeParse will handle other timezones in the string, but from that point on only has the concepts of "local time", "universal time" and "unknown timezone".
I would use DateTimeOffset.ParseExact, specifying the format string an the invariant culture:
DateTimeOffset value = DateTimeOffset.ParseExact(text, "yyyy-MM-dd'T'HH:mm:ssK",
CultureInfo.InvariantCulture);
I strongly recommend this over using DateTime.Parse for the following reasons:
This always uses the invariant culture. It's explicitly stating that you don't want to use the local culture, which might have a different default calendar system etc
This specifies the format exactly. If the data becomes "10/06/2014 11:55:00" you will get an exception, which is better than silently guessing whether this is dd/MM/yyyy or MM/dd/yyyy
It accurately represents the data in the string: a date/time and an offset. You can then do whatever you want with that data, but separating the two steps is clearer
The first two of these can be fixed by using DateTime.ParseExact and specifying the culture as well, of course.
You can then convert that to your local time zone, or to any other time zone you want, including your system local time zone. For example:
DateTimeOffset localTime = value.ToLocalTime(); // Applies the local time zone
or if you want a DateTime:
DateTimeOffset localTime = value.ToLocalTime().DateTime;
It's just one extra line of code, but it makes it clearer (IMO) what you're trying to do. It's also easier to compare DateTimeOffset values without worrying about what "kind" they are, etc.
In fact, I wouldn't personally use DateTimeOffset or DateTime - I'd use OffsetDateTime from my Noda Time project, but that's a different matter.
I've created some code to convert a double precision date & time value to another timezone. It gives behaviour that I just don't understand when DateTimeKind.Local is used in place of DateTimeKind.Unspecified.
My thought is that the double precision value passed in to the method ConvertTime is completely dislocated from it's geographical context. Surely for the time to be converted properly it is necessary to specify that the value is a local time and belongs to a certain timezone?
I'm trying to make certain that the local source time is properly converted to local destination time and observing Daylight Saving Time, irrespective of the time zone settings of the host computer.
Working from this notion I try to specify that the
DateTime sourceDT = DateTime.SpecifyKind(inDT, DateTimeKind.Local);
but this results in the time not being converted. If I specify
DateTime sourceDT = DateTime.SpecifyKind(inDT, DateTimeKind.Unspecified);
then a conversion happens.
Can someone please explain why DateTimeKind.Local is not accepted as a valid specification in the time conversion and how to achieve what I'm attempting.
namespace ConvertTime
{
public interface ConvertTimeClass
{
double ConvertTime(double inTime, [MarshalAs(UnmanagedType.LPStr)] string sourceTZ, [MarshalAs(UnmanagedType.LPStr)] string destTZ);
}
public class ManagedClass : ConvertTimeClass
{
public double ConvertTime(double inTime, [MarshalAs(UnmanagedType.LPStr)] string sourceTZ, [MarshalAs(UnmanagedType.LPStr)] string destTZ)
{
DateTime inDT = DateTime.FromOADate(inTime);//convert decimal date and time value to a DateTime object.
DateTime sourceDT = DateTime.SpecifyKind(inDT, DateTimeKind.Unspecified);//specify that the time represents a local time and save into a new object.
TimeZoneInfo sourceTZI = TimeZoneInfo.FindSystemTimeZoneById(sourceTZ);
TimeZoneInfo destTZI = TimeZoneInfo.FindSystemTimeZoneById(destTZ);
DateTime destDT = TimeZoneInfo.ConvertTime(sourceDT, sourceTZI, destTZI);//convert time. FAILS WHEN DateTimeKind.Local is specified
double outTime = destDT.ToOADate();//extract the decimal date & time value
return outTime;
}
}
}
What you refer to as "double precision date and time value" is also known as an "OLE Automation Date", or an "OADate" for short.
OADates do not convey any time zone information. They are just a point since December 30th 1899 in some unknown calendar. Additionally, there are some strange quirks about how they are encoded (see the remarks in these MSDN docs) that make them slightly undesirable. I would avoid them if at all possible.
Nonetheless, you should always treat them as unspecified. In fact, the FromOADate method you're calling already returns it as Unspecified kind, so there's no reason to call DateTime.SpecifyKind at all. In short, your function should simply be:
public double ConvertTime(double inTime, string sourceTZ, string destTZ)
{
DateTime sourceDT = DateTime.FromOADate(inTime);
TimeZoneInfo sourceTZI = TimeZoneInfo.FindSystemTimeZoneById(sourceTZ);
TimeZoneInfo destTZI = TimeZoneInfo.FindSystemTimeZoneById(destTZ);
DateTime destDT = TimeZoneInfo.ConvertTime(sourceDT, sourceTZI, destTZI);
return destDT.ToOADate();
}
However, if your use case calls for assuming that the input time is the computer's local time zone, then you would create a different method instead:
public double ConvertFromLocalTime(double inTime, string destTZ)
{
DateTime sourceDT = DateTime.FromOADate(inTime);
TimeZoneInfo sourceTZI = TimeZoneInfo.Local;
TimeZoneInfo destTZI = TimeZoneInfo.FindSystemTimeZoneById(destTZ);
DateTime destDT = TimeZoneInfo.ConvertTime(sourceDT, sourceTZI, destTZI);
return destDT.ToOADate();
}
You still do not need to specify the local kind, because when an unspecified DateTime is passed to TimeZoneInfo.ConvertTime it assumes that value is in terms of the source time zone - which is your local time zone in this case. While a local kind would work here, it's not required.
As to why you got the error when trying local kind, I assume this was the error you had:
"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."
As the error explains, you can't pass in a DateTime with local kind to TimeZoneInfo.ConvertTime unless the source timezone is specifically taken from DateTimeKind.Local.
It's not enough that the source time zone ID matches the local time zone ID, because TimeZoneInfo.Local has a special case of taking into account the "Automatically adjust clock for Daylight Saving Time" option. See these MSDN docs for details. In other words:
TimeZoneInfo.Local != TimeZoneInfo.FindSystemTimeZoneById(TimeZoneInfo.Local.Id)
Lastly, I think you misunderstand DateTimeKind. You said:
Surely for the time to be converted properly it is necessary to specify that the value is a local time and belongs to a certain timezone?
The "local" in DateTimeKind.Local and in TimeZoneInfo.Local specifically means local to the computer where the code is running. It's not a local zone, it's the local zone. If the DateTime is tied to some other time zone than UTC or the computer's own local time zone setting, then DateTimeKind.Unspecified is used.
Can anyone explain the difference between System.DateTime and System.DateTimeOffset in C#.NET? Which is best suited for building web apps with users from different time zones?
A DateTime value defines a particular date and time, it includes a Kind property that provides limited information about the time zone to which that date and time belongs.
The DateTimeOffset structure represents a date and time value, together with an offset that indicates how much that value differs from UTC. Thus, the value always unambiguously identifies a single point in time.
DateTimeOffset should be considered the default date and time type for application development as the uses for DateTimeOffset values are much more common than those for DateTime values.
See more info, code examples at:
http://msdn.microsoft.com/en-us/library/bb384267.aspx
There are a couple of point here:
DateTime information should be stored in UTC format in your database:
https://web.archive.org/web/20201202215446/http://www.4guysfromrolla.com/articles/081507-1.aspx
When you use DateTime information in your Web Application you will need to convert it to LocalTime:
DateTime.UtcNow.ToLocalTime();
will convert it to the local time from the Web Server's perspective.
If you have a WebServer in one location, serving clients in multiple countries, then you will need to perform this operation in javascript on the Client itself:
myUTCDate.toLocaleTimeString();
http://www.java2s.com/Code/JavaScript/Date-Time/ConvertDatetoLocaleString.htm
DateTimeOffset represents the datetime as UTC datetime.
So
DateTimeOffset dtoNow = DateTimeOffset.Now;
is same as
DateTimeOffset dtoUTCNow = DateTimeOffset.UTCNow;
Here dtoNow will be equal to dtoUTCNow even though one was initialized to DateTimeOffset.Now and the other was initialize to DateTimeOffset.UTCNow;
So DatetimeOffset is good for storing the difference or Offset w.r.t UTC.
For more details refer to MSDN.