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.
Related
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.
I have following date time format
TimeZoneDetails.TimeZoneInstance ="Australia/Perth"
DateTime Today = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow,TimeZoneDetails.TimeZoneInstance);
Does today variable store the date based on timezone?
string date = "2020-03-19";
DateTime startdate = DateTime.Parse(date);
What is the timezone of startdate variable?
DateTime enddate = TimeZoneInfo.ConvertTimeToUtc(startdate, TimeZoneDetails.TimeZoneInstance);
Will enddate variable converted to UTC time?
A few things:
"Australia/Perth" is an IANA time zone identifier. It will work with .NET on Linux or Mac OSX, but on Windows you'd have to use "W. Australia Standard Time" instead. Alternatively, you could use my TimeZoneConverter library to work with either form of identifier on any platform.
In your code:
TimeZoneDetails.TimeZoneInstance ="Australia/Perth"
This isn't generally valid. Given the usage in the rest of your code, your TimeZoneInstance would have to be a TimeZoneInfo object. You can't assign a string in that way. You'd have to use a function like TimeZoneInfo.FindSystemTimeZoneById, or TZConvert.GetTimeZoneInfo from TimeZoneConverter, or a similar function in your own code. (Also you're missing a semicolon.)
In your code:
string date = "2020-03-19";
DateTime startdate = DateTime.Parse(date);
You asked what the time zone is in the startdate variable. That's a DateTime, which does not store time zone or offset information. It only has a .Kind property, which is of type DateTimeKind. In your example, it will be DateTimeKind.Unspecified. Also note the time will be set to 00:00:00.0000000.
You can read more about this in the documentation, here and here.
In your code:
DateTime enddate = TimeZoneInfo.ConvertTimeToUtc(startdate, TimeZoneDetails.TimeZoneInstance);
Yes, that will correctly convert the DateTime from the time zone given to UTC. Because startdate.Kind == DateTimeKind.Unspecified, the value is treated as belonging to the time zone specified. The resulting value will have enddate.Kind == DateTimeKind.Utc.
You can read more in the documentation, here.
In comments you asked:
which one is default for DateTimeKind?
That depends on which method you call to create the DateTime, and what values you pass in. In your case, because you call DateTime.Parse and pass a string that contains no time zone offset information, the resulting value has .Kind == DateTimeKind.Unspecified. You can read more about the behavior of DateTime.Parse in the remarks section here. Other methods and constructors behave similarly, but you should check the documentation for each, or validate the results yourself. You may find conversion errors if you, for example, think a DateTime has Unspecified kind, but it actually has Local kind due to how you obtain it.
If daylight saving time is in effect, and a date object has been saved into the database (UTC format) which you retrieve to show it in the view (for example the view in asp.net-mvc).
And you do that by using this method:
public static DateTime ConvertToLocalTimeFromUtcTime(DateTime utcDate, string timeZoneId)
{
TimeZoneInfo localZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId);
DateTime localTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, localZone);
if (localZone.IsDaylightSavingTime(localTime))
localTime = localTime.AddHours(1); // is this needed !?
return localTime;
}
The question is, does TimeZoneInfo.ConvertTimeFromUtc() handle DST's or do you have to check that yourself and either add or subtract X hour(s) to the date object?
Same question for when persisting a date object to the database by converting it to UTC format with ToUniversalTime().
Yes. ConvertTimeFromUtc will automatically handle daylight saving time adjustments, as long as the time zone that you are targeting uses daylight saving time.
From the MSDN documentation:
When performing the conversion, the ConvertTimeFromUtc method applies any adjustment rules in effect in the destinationTimeZone time zone.
You should not try to add an additional hour in your conversion. That will give you an incorrect translation.
Regarding DateTime.ToUniversalTime, it does take DST into account, but be careful with this method. It assumes that the input value is in the computer's local time zone. If you just need to mark it with DateTimeKind.Utc, then use DateTime.SpecifyKind instead.
Can anyone explain the difference between System.DateTime.Now and System.DateTime.Today in C#.NET? Pros and cons of each if possible.
DateTime.Now returns a DateTime value that consists of the local date and time of the computer where the code is running. It has DateTimeKind.Local assigned to its Kind property. It is equivalent to calling any of the following:
DateTime.UtcNow.ToLocalTime()
DateTimeOffset.UtcNow.LocalDateTime
DateTimeOffset.Now.LocalDateTime
TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Local)
TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.Local)
DateTime.Today returns a DateTime value that has the same year, month, and day components as any of the above expressions, but with the time components set to zero. It also has DateTimeKind.Local in its Kind property. It is equivalent to any of the following:
DateTime.Now.Date
DateTime.UtcNow.ToLocalTime().Date
DateTimeOffset.UtcNow.LocalDateTime.Date
DateTimeOffset.Now.LocalDateTime.Date
TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.Local).Date
TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, TimeZoneInfo.Local).Date
Note that internally, the system clock is in terms of UTC, so when you call DateTime.Now it first gets the UTC time (via the GetSystemTimeAsFileTime function in the Win32 API) and then it converts the value to the local time zone. (Therefore DateTime.Now.ToUniversalTime() is more expensive than DateTime.UtcNow.)
Also note that DateTimeOffset.Now.DateTime will have similar values to DateTime.Now, but it will have DateTimeKind.Unspecified rather than DateTimeKind.Local - which could lead to other errors depending on what you do with it.
So, the simple answer is that DateTime.Today is equivalent to DateTime.Now.Date.
But IMHO - You shouldn't use either one of these, or any of the above equivalents.
When you ask for DateTime.Now, you are asking for the value of the local calendar clock of the computer that the code is running on. But what you get back does not have any information about that clock! The best that you get is that DateTime.Now.Kind == DateTimeKind.Local. But whose local is it? That information gets lost as soon as you do anything with the value, such as store it in a database, display it on screen, or transmit it using a web service.
If your local time zone follows any daylight savings rules, you do not get that information back from DateTime.Now. In ambiguous times, such as during a "fall-back" transition, you won't know which of the two possible moments correspond to the value you retrieved with DateTime.Now. For example, say your system time zone is set to Mountain Time (US & Canada) and you ask for DateTime.Now in the early hours of November 3rd, 2013. What does the result 2013-11-03 01:00:00 mean? There are two moments of instantaneous time represented by this same calendar datetime. If I were to send this value to someone else, they would have no idea which one I meant. Especially if they are in a time zone where the rules are different.
The best thing you could do would be to use DateTimeOffset instead:
// This will always be unambiguous.
DateTimeOffset now = DateTimeOffset.Now;
Now for the same scenario I described above, I get the value 2013-11-03 01:00:00 -0600 before the transition, or 2013-11-03 01:00:00 -0700 after the transition. Anyone looking at these values can tell what I meant.
I wrote a blog post on this very subject. Please read - The Case Against DateTime.Now.
Also, there are some places in this world (such as Brazil) where the "spring-forward" transition happens exactly at Midnight. The clocks go from 23:59 to 01:00. This means that the value you get for DateTime.Today on that date, does not exist! Even if you use DateTimeOffset.Now.Date, you are getting the same result, and you still have this problem. It is because traditionally, there has been no such thing as a Date object in .Net. So regardless of how you obtain the value, once you strip off the time - you have to remember that it doesn't really represent "midnight", even though that's the value you're working with.
If you really want a fully correct solution to this problem, the best approach is to use NodaTime. The LocalDate class properly represents a date without a time. You can get the current date for any time zone, including the local system time zone:
using NodaTime;
...
Instant now = SystemClock.Instance.Now;
DateTimeZone zone1 = DateTimeZoneProviders.Tzdb.GetSystemDefault();
LocalDate todayInTheSystemZone = now.InZone(zone1).Date;
DateTimeZone zone2 = DateTimeZoneProviders.Tzdb["America/New_York"];
LocalDate todayInTheOtherZone = now.InZone(zone2).Date;
If you don't want to use Noda Time, there is now another option. I've contributed an implementation of a date-only object to the .Net CoreFX Lab project. You can find the System.Time package object in their MyGet feed. Once added to your project, you will find you can do any of the following:
using System;
...
Date localDate = Date.Today;
Date utcDate = Date.UtcToday;
Date tzSpecificDate = Date.TodayInTimeZone(anyTimeZoneInfoObject);
Time. .Now includes the 09:23:12 or whatever; .Today is the date-part only (at 00:00:00 on that day).
So use .Now if you want to include the time, and .Today if you just want the date!
.Today is essentially the same as .Now.Date
The DateTime.Now property returns the current date and time, for example 2011-07-01 10:09.45310.
The DateTime.Today property returns the current date with the time compnents set to zero, for example 2011-07-01 00:00.00000.
The DateTime.Today property actually is implemented to return DateTime.Now.Date:
public static DateTime Today {
get {
DateTime now = DateTime.Now;
return now.Date;
}
}
DateTime.Today represents the current system date with the time part set to 00:00:00
and
DateTime.Now represents the current system date and time
I thought of Adding these links -
A brief History of DateTime - By Anthony Moore by BCL team
Choosing between Datetime and DateTime Offset - by MSDN
Do not forget SQL server 2008 onwards has a new Datatype as DateTimeOffset
The .NET Framework includes the DateTime, DateTimeOffset, and
TimeZoneInfo types, all of which can be used to build applications
that work with dates and times.
Performing Arithmetic Operations with Dates and Times-MSDN
Coming back to original question , Using Reflector i have explained the difference in code
public static DateTime Today
{
get
{
return DateTime.Now.Date; // It returns the date part of Now
//Date Property
// returns same date as this instance, and the time value set to 12:00:00 midnight (00:00:00)
}
}
private const long TicksPerMillisecond = 10000L;
private const long TicksPerDay = 864000000000L;
private const int MillisPerDay = 86400000;
public DateTime Date
{
get
{
long internalTicks = this.InternalTicks; // Date this instance is converted to Ticks
return new DateTime((ulong) (internalTicks - internalTicks % 864000000000L) | this.InternalKind);
// Modulo of TicksPerDay is subtracted - which brings the time to Midnight time
}
}
public static DateTime Now
{
get
{
/* this is why I guess Jon Skeet is recommending to use UtcNow as you can see in one of the above comment*/
DateTime utcNow = DateTime.UtcNow;
/* After this i guess it is Timezone conversion */
bool isAmbiguousLocalDst = false;
long ticks1 = TimeZoneInfo.GetDateTimeNowUtcOffsetFromUtc(utcNow, out isAmbiguousLocalDst).Ticks;
long ticks2 = utcNow.Ticks + ticks1;
if (ticks2 > 3155378975999999999L)
return new DateTime(3155378975999999999L, DateTimeKind.Local);
if (ticks2 < 0L)
return new DateTime(0L, DateTimeKind.Local);
else
return new DateTime(ticks2, DateTimeKind.Local, isAmbiguousLocalDst);
}
}
DateTime dt = new DateTime();// gives 01/01/0001 12:00:00 AM
DateTime dt = DateTime.Now;// gives today date with current time
DateTime dt = DateTime.Today;// gives today date and 12:00:00 AM time
DateTime.Today is DateTime.Now with time set to zero.
It is important to note that there is a difference between a DateTime value, which represents the number of ticks that have elapsed since midnight of January 1, 0000, and the string representation of that DateTime value, which expresses a date and time value in a culture-specific-specific format:
https://msdn.microsoft.com/en-us/library/system.datetime.now%28v=vs.110%29.aspx
DateTime.Now.Ticks is the actual time stored by .net (essentially UTC time), the rest are just representations (which are important for display purposes).
If the Kind property is DateTimeKind.Local it implicitly includes the time zone information of the local computer. When sending over a .net web service, DateTime values are by default serialized with time zone information included, e.g. 2008-10-31T15:07:38.6875000-05:00, and a computer in another time zone can still exactly know what time is being referred to.
So, using DateTime.Now and DateTime.Today is perfectly OK.
You usually start running into trouble when you begin confusing the string representation with the actual value and try to "fix" the DateTime, when it isn't broken.
DateTime.Now.ToShortDateString() will display only the date part
I'm using the DateTimeWithZone struct that Jon Skeet posted at Creating a DateTime in a specific Time Zone in c# fx 3.5
This didn't work exactly for my situation since it assumes that the DateTime passed in the constructor is the local time, and therefore converts it to Utc using the specified TimeZone.
In my case we will mostly be passing in DateTime objects already in Utc (since this is what we are storing) so we need to only perform the conversion if the source DateTime.Kind is not Utc.
Therefore I changed the constructor to:
public DateTimeWithZone(DateTime dateTime, TimeZoneInfo timeZone, DateTimeKind kind = DateTimeKind.Utc) {
dateTime = DateTime.SpecifyKind(dateTime, kind);
utcDateTime = TimeZoneInfo.ConvertTimeToUtc(dateTime, timeZone);
this.timeZone = timeZone;
}
Here we have an optional Kind parameter that defaults to Utc.
However, running this code and passing a Utc DateTime generates the following exception:
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.
According to the docs (http://msdn.microsoft.com/en-us/library/bb495915.aspx):
If the Kind property of the dateTime parameter equals DateTimeKind.Utc and the sourceTimeZone parameter equals TimeZoneInfo.Utc, this method returns dateTime without performing any conversion.
Since both the input time and the timezone both have a Kind property of Utc then I would not expect to get this exception.
Have I misunderstood?
Like the MSDN docs say if you pass in a DateTime with the kind set to anything besides DateTimeKind.Utc and specify a TimeZone other than Utc the conversion function will throw an exception. That Must be what is happening here. In your code you should check if the DateTime is already in Utc and skip the conversion if it is.
Also since the dateTime you are passing in will have a DateTime attached to it already you probably don't need to pass in a separate Kind parameter.
from the docs
Converts the time in a specified time
zone to Coordinated Universal Time
(UTC).
meaning that it converts from the time zone supplied to Utc
the function throws an argument exception if:
dateTime .Kind is DateTimeKind.Utc and
sourceTimeZone does not equal
TimeZoneInfo.Utc.
-or-
dateTime .Kind is DateTimeKind.Local
and sourceTimeZone does not equal
TimeZoneInfo.Local.
-or-
sourceTimeZone .IsInvalidDateTime(
dateTime ) returns true.