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.
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.
I assumed that when subtracting 2 datetimes the framework will check their timezone and make the appropriate conversions.
I tested it with this code:
Console.WriteLine(DateTime.Now.ToUniversalTime() - DateTime.UtcNow.ToUniversalTime());
Console.WriteLine(DateTime.Now.ToUniversalTime() - DateTime.UtcNow);
Console.WriteLine(DateTime.Now - DateTime.UtcNow);
Output:
-00:00:00.0020002
-00:00:00.0020001
01:59:59.9989999
To my surprise, DateTime.Now - DateTime.UtcNow does not make the appropriate conversion automatically. At the same time, DateTime.UtcNow is the same as DateTime.UtcNow.ToUniversalTime(), so obviously there is some internal flag that indicates the time zone.
Is that correct, does that framework not perform the appropriate timezone conversion automatically, even if the information is already present? If so, is applying ToUniversalTime() safe for both UTC and non-UTC datetimes, i.e. an already UTC datetime will not be incorrectly corrected by ToUniversalTime()?
The type System.DateTime does not hold time zone information, only a .Kind property that specifies whether it is Local or UTC. But before .NET 2.0, there was not even a .Kind property.
When you subtract (or do other arithmetic, like == or >, on) two DateTime values, their "kinds" are not considered at all. Only the numbers of ticks are considered. This gives compatibility with .NET 1.1 when no kinds existed.
The functionality you ask for (and expect) is in the newer and richer type System.DateTimeOffset. In particular, if you do the subtraction DateTimeOffset.Now - DateTimeOffset.UtcNow you get the result you want. The DateTimeOffset structure does not have a local/UTC flag; instead, it holds the entire time zone, such as +02:00 in your area.
The framework does nothing with the date if it is already UTC:
internal static DateTime ConvertTimeToUtc(DateTime dateTime, TimeZoneInfoOptions flags)
{
if (dateTime.Kind == DateTimeKind.Utc)
{
return dateTime;
}
CachedData cachedData = s_cachedData;
return ConvertTime(dateTime, cachedData.Local, cachedData.Utc, flags, cachedData);
}
I have the following date string: 2015-11-10T23:52:18.5245011Z
And when I parse it using DateTime.Parse method it returns 11/11/2015 10:52:18 AM which is incorrect.
I also tried the follwing conversion:
TimeZoneInfo est = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");
DateTime targetTimeUtcconverted = TimeZoneInfo.ConvertTime(UtcDate, est);
and it still gives out: 11/11/2015 10:52:18 AM
Can't figure out what I am missing here.
Use:
DateTime.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind)
Or:
DateTime.Parse(s, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal)
Or (best option):
DateTimeOffset.Parse(s)
By default, DateTime.Parse will adjust to local time if there is any offset information present in the string. Since Z is the same as +00:00, it assumes the input is +00:00, then adjusts from UTC to the local time zone.
If there is no offset information present, it returns a DateTime with Unspecified kind.
Passing DateTimeStyles.RoundtripKind tells it to treat any value with an offset as local time (as before), but any value containing Z, UTC, GMT, etc. to have DateTimeKind.Utc.
Passing DateTimeStyles.AdjustToUniversal tells it that the output should always have DateTimeKind.Utc, and the value should be adjusted if necessary.
Parsing using DateTimeOffset.Parse bypasses all of that convoluted behavior and just returns a value with an offset matching what was provided. This is the best approach when an offset (or Z) is present in the input string. If you need a DateTime, you can use the UtcDateTime, LocalDateTime, or DateTime properties from the resulting DateTimeOffset.
The time zone conversion code you gave is correct, as long as the Kind is UTC. It would be more explicit to use ConvertTimeFromUtc, but that wouldn't really matter in this case. The best approach is to use the overload of ConvertTime that works with DateTimeOffset values. The resulting value will be a DateTimeOffset whose DateTime property matches the time in that time zone, and whose Offset property is the correct offset for that time in that time zone.
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.