I have a datetime in database which I read using SqlDataReader and then cast it to (DateTime). After the cast its Kind property is DateTimeKind.Unspecified.
Then I have another string which I read from some other source. Its format is like this 2016-01-20T22:20:29.055Z. I do DateTime.Parse("2016-01-20T22:20:29.055Z") and its Kind property is DateTimeKind.Local.
How do I properly parse the both date times for comparison? Do I need to use DateTimeOffsets? How should I parse them?
Thanks
Because SQLReader cannot reasonably infer a DateTimeKind, it leaves it as unspecified. You'll want to use DateTime.SpecifyKind to change the DateTimeKind on your output from the SQLReader to the appropriate value. This works ok if you are only dealing with UTC and one consistent local time zone; otherwise, you really should be using DateTimeOffset in both your code and the SQL Database.
The string "2016-01-20T22:20:29.055Z" is ISO 8601 compliant and is a UTC date; however, DateTime.Parse with only 1 argument can end up performing a conversion to local time. Per the documentation:
Generally, the Parse method returns a DateTime object whose Kind
property is DateTimeKind.Unspecified. However, the Parse method may
also perform time zone conversion and set the value of the Kind
property differently, depending on the values of the s and styles
parameters:
If s contains time zone information, the date and time is converted
to the time in the local time zone and the Kind is DateTimeKind.Local.
If s contains time zone information, and styles includes the
AdjustToUniversalflag, the date and time is converted to Coordinated
Universal Time (UTC) and the Kind is DateTimeKind.Utc.
If s contains the Z or GMT time zone designator, and styles includes
the RoundtripKind flag, the date and time are interpreted as UTC and
the Kind is DateTimeKind.Utc.
Also see UTC gotchas in .NET and SQL Server in Derek Fowler's blog for additional coverage on the topic.
In your second example, 2016-01-20T22:20:29.055Z has timezone information provided with it; the 'Z' at the end indicates that the timestamp is intended for Coordinated Universal Time (UTC). However, DateTime.Parse() will default its conversion using DateTimeKind.Local unless a specific timezone is specified. You can use DateTime.ParseExact to be more specific.
As to why the datetime values in your database are coming out as Unspecified, that's likely because they contain no timezone indication at all. Check to see if your database values specify timezone information, either by using 'Z' at the end or specifying an exact timezone, such as 2016-01-20T22:20:29.055-07:00 (UTC-7).
You can use something like this:
string format = "ddd dd MMM h:mm tt yyyy";
DateTime dateTime = DateTime.ParseExact(dateString, format,
CultureInfo.InvariantCulture);
In format variable, you can put the format you want, and pass it to ParseExact function.
Hope it helps.
You are missing the datetime context (offset) in your database. You should persist it either in a datetimeoffset column or in a datetime column but persisting utc datetimes.
And always better compare two utc datetimes.
I coded a quick C# console app that I pasted in below. This converts a UTC date and time to a string (format similar to the ISO 8601 format described in another post with some extra digits of precision), writes it to a file, reads it from the file (as a string) and then converts it back to a UTC date and time.
It then compares the two UTC Date Time objects, which are both of UTC kind, and they match.
class Program
{
// "2016-01-20T22:20:29.055Z" is ISO 8601 compliant and is a UTC date
const string dtf = "yyyy-MM-ddTHH:mm:ss.fffffffZ";
static void Main(string[] args)
{
string file = #"c:\temp\file.txt";
DateTime dt = DateTime.UtcNow;
using (var sw = new System.IO.StreamWriter(file))
{
sw.WriteLine(dt.ToString(dtf, System.Globalization.CultureInfo.InvariantCulture));
}
DateTime dtin;
using (var sr = new System.IO.StreamReader(file))
{
dtin = DateTime.ParseExact(sr.ReadLine(), dtf, System.Globalization.CultureInfo.InvariantCulture);
}
Console.WriteLine(dt.ToString(dtf) + "\r\n" + dtin.ToString(dtf) + "\r\nEquality:" + (dt == dtin));
Console.ReadLine();
}
}
Related
I'm simply trying to print a DateTime in its UTC equivalent time format. What am I doing wrong?
var utcEpoch = DateTime.Parse("1970-01-01", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); //This specifies the time I provided is in UTC
Console.WriteLine(utcEpoch.ToString("yyyy-MM-dd HH:mm:ss zzz")); //This properly shows my UTC offset of -6, so it's not wrong
Console.WriteLine(utcEpoch.ToString("u")); //This just flat out seems wrong because it doesn't specify a timezone or offset in its output
> 1969-12-31 18:00:00 -06:00
> 1969-12-31 18:00:00Z
I expected to see 1970-01-01 00:00:00Z for the last one.
From The Universal Sortable ("u") Format Specifier :
Although the result string should express a time as Coordinated
Universal Time (UTC), no conversion of the original DateTime value is
performed during the formatting operation. Therefore, you must convert
a DateTime value to UTC by calling the DateTime.ToUniversalTime method
before formatting it. In contrast, DateTimeOffset values perform this
conversion automatically; there is no need to call the
DateTimeOffset.ToUniversalTime method before the formatting operation.
Your utcEpoch.Kind is not UTC, it is Local. DateTime's are triciker than you might think. You are expecting that it will return UTC as Kind property but it is not. It returns Local.
This situation has been discussed on Phil Haack blog post as well and Matt Johnson has a quite nice comment about this;
AssumeLocal and AssumeUniversal are both related to how the input
string is interpreted. By themselves, neither will change the output
kind.
The default output kind is Local. To get it to be Utc, you can use the
AdjustToUniversal style.
The DateTimeStyles enum is flags-based, so you can combine these in
some ways that make sense. To achieve what you originally set out to
do (parse the input as UTC and output it as UTC), then you would use:
DateTime utcDate = DateTime.Parse("10/01/2006 19:30", culture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal);
As others pointed pointed out, a separate call to ToUniversalTime()
would also work, but this is technically more correct.
You can see it on referance source as well;
case 'u': // Universal time in sortable format.
if (offset != NullOffset)
{
// Convert to UTC invariants mean this will be in range
dateTime = dateTime - offset;
}
else if (dateTime.Kind == DateTimeKind.Local)
{
InvalidFormatForLocal(format, dateTime);
}
I think you are missunderstanding what the API is doing.
First thing to note is that both DateTimeStyles.AssumeUniversal and DateTimeStyles.AssumeLocalwill still return a DateTime where Kind = Local
> DateTime.Parse("1970-01-01 00:00:00", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).Kind
=> Local
> DateTime.Parse("1970-01-01 00:00:00", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal).Kind
=> Local
So no matter what we will get a local date. That means that the API most likely is there to make it possible to get a local time from a UTC date. Let's try if that's correct.
I'm in Sweden so we are UTC + 1 during standard time. So if I use DateTimeStyles.AssumeUniversal and put in todays date I should get a local date being 01:00 today.
Running this in C# Interactive:
> DateTime.Parse("2018-03-03", .CultureInfo.InvariantCulture, .DateTimeStyles.AssumeUniversal)
=> [2018-03-03 01:00:00]
Meaning C# assumed that the string I inputed was in UTC and I wanted it in local so it "fixed" it for me.
Doing the same with AssumeLocal.
DateTime.Parse("2018-03-03", .CultureInfo.InvariantCulture, .DateTimeStyles.AssumeLocal)
=> [2018-03-03 00:00:00]
As expected we now treated the input as a local string and got the same value.
To get the date as UTC you can specify the kind
DateTime.SpecifyKind(DateTime.Parse("2018-03-03", CultureInfo.InvariantCulture), DateTimeKind.Utc).ToString("o")
=> "2018-03-03T00:00:00.0000000Z"
I am getting this error while in Debug though the ToString() is executed:
A UTC DateTime is being converted to text in a format that is only
correct for local times. This can happen when calling
DateTime.ToString using the 'z' format specifier, which will include a
local time zone offset in the output. In that case, either use the 'Z'
format specifier, which designates a UTC time, or use the 'o' format
string, which is the recommended way to persist a DateTime in text.
This can also occur when passing a DateTime to be serialized by
XmlConvert or DataSet. If using XmlConvert.ToString, pass in
XmlDateTimeSerializationMode.RoundtripKind to serialize correctly. If
using DataSet, set the DateTimeMode on the DataColumn object to
DataSetDateTime.Utc.
public static string ToInterfaceString(this DateTime value)
{
return value != DateTime.MinValue ? value.ToString("yyyy-MM-ddTHH:mm:sszzz") : string.Empty;
}
In the app that I've just starting to work on it is used this format in many places. So what should I do in fact? Replace zzz with Z?
Update 1:
the DateTime that is passed to my extension is initiated to:
DateTimeCreated = DateTime.UtcNow;
Weird thing is that if I pass to this extension some other DateTime objects I don't receive any error/warning.
It's a green warning only.
So - as you seem to know what you are doing - you can just comprehend the message (which is correct) and mark the checkbox to Ignore this warning in the future.
As the DateTimeInvalidLocalFormat error explains, the conversion of the date value to string with your defined date format is not usable with DateTime which have the property Kind set to 'Utc'. Even though this error is just a warning and your code should still work, the resulting date string you will get after such operation will be incorrect. This means that if you convert your date to string and will try to parse it again to DateTime, the resulting DateTime value will be different from your original DateTime value. The difference will be as large as your time zone offset from the UTC time is. This is a serious error and should not be ignored especially if your local time does not match the UTC time.
Example:
In this example I assume that the local time zone is 2 hours ahead of UTC time.
DateTime yourLocalTime = DateTime.Now; // => 2020-05-15 08:00:00 => yourLocalTime.Kind = Local
DateTime yourTimeInUTC = DateTime.UtcNow; // => 2020-05-15 06:00:00 => yourTimeInUTC.Kind = Utc, note hours, e.g. 6 vs 8
string dateFormat = "yyyy-MM-ddTHH:mm:sszzz";
string testDateLocal = yourLocalTime.ToString( dateFormat ); // 2020-05-15T08:00:00+02:00 - This is correct date and time
string testDateUtc = yourTimeInUTC.ToString( dateFormat ); // 2020-05-15T06:00:00+02:00 - This date and time is 2 hours behind your actual date and time
To print the date and time string correctly using your date format you have to first convert your UTC date and time to the local date and time:
string testDateUtc2 = yourTimeInUTC.ToLocalTime().ToString( dateFormat ); // 2020-05-15T08:00:00+02:00 - This is correct date and time
To fix your code you should add conversion to the local time in your method:
public static string ToInterfaceString(this DateTime value)
{
return value != DateTime.MinValue ? value.ToLocalTime().ToString("yyyy-MM-ddTHH:mm:sszzz") : string.Empty;
}
You could also print UTC date and time instead of local date and time by replacing date format specifiers 'zzz' to 'Z'. However, in this case you will have a similar issue with conversion in case the DateTime passed to your method is not in UTC Kind, but e.g. in Local Kind and therefore, you have to always convert it to UTC before generating the string:
public static string ToInterfaceString(this DateTime value)
{
return value != DateTime.MinValue ? value.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ") : string.Empty;
}
Printing of date and time without doing conversion can also be done by using date format specifier 'o', but then the generated date string format will differ based on supplied DateTime Kind and this date format also includes the milliseconds.
public static string ToInterfaceString(this DateTime value)
{
return value != DateTime.MinValue ? value.ToString("o") : string.Empty;
}
This exception is thrown and caught internally inside of ToString() method so the application will continue to run further. It's the only debugger who shows this.
It might initially look like a warning which is visible when debugging only but in fact, this code will generate valid results only in case when your computer time zone is UTC (seems there will be no internal exception in this case too). If your local time zone offset is, for example, UTC+02 than the result of:
new DateTime(2020,7,30,11,22,33, DateTimeKind.Utc).ToString(#"yyyy-MM-dd\THH:mm:sszzz");
on your computer will be "2020-07-30T11:22:33+02:00" which doesn't make sense because the result obviously should have UTC offset as 00:00.
Also, of course, such internal exceptions is a bad thing for control flow and might affect your app performance if this occurs too often.
To avoid invalid .ToString() results and also internal exceptions you can just create DateTimeOffset from given DateTime and invoke .ToString() for it. So the code:
new DateTimeOffset(new DateTime(2020,7,30,11,22,33, DateTimeKind.Utc)).ToString(#"yyyy-MM-dd\THH:mm:sszzz");
will give expected result "2020-07-30T11:22:33+00:00" for any local time zone.
Another suggestion is to use the "K" custom format specifier which gives the correct results for both DateTime and DateTimerOffset.
Simple question, I have this string:
string dateString = "7/12/2014 4:42:00 PM";
This is a date string and it's in the UTC timezone.
I need to convert it to a date, so I'm doing the following:
DateTimeOffset dateOffset;
DateTimeOffset.TryParse(dateString, out dateOffset);
DateTime date = dateOffset.UtcDateTime;
The problem:
When I'm parsing the string to date, the code is considering that the dateString is in the Local Timezone of the PC (+3 GMT), and not in the UTC timezone.
So I am getting the following the dateOffset = {7/12/2014 4:42:00 PM +03:00} and thus date = {7/12/2014 1:42:00 PM}
how can I tell him that the date string provided is in the UTC format and not in the local timezone format?
Thanks
how can I tell him that the date string provided is in the UTC format and not in the local timezone format?
Specify a DateTimeStyles value of AssumeUniversal in the call. That tells the parsing code what to do. For example:
// null here means the thread's current culture - adjust it accordingly.
if (DateTimeOffset.TryParse(dateString, null, DateTimeStyles.AssumeUniversal,
out dateOffset))
{
// Valid
}
You should always use the result of TryParse to tell whether or not it's successfully parsed.
If you know the format and the specific culture, I'd personally use DateTimeOffset.TryParseExact. (Well, to be honest I'd use my Noda Time project to start with, but that's a different matter.)
There is another overload of DateTimeOffset.TryParse
DateTimeOffset.TryParse Method (String, IFormatProvider, DateTimeStyles, DateTimeOffset)
which allows you specify DateTimeStyles. One of the DateTimeStyles is AssumeUniversal, which is what you're looking for:
If no time zone is specified in the parsed string, the string is
assumed to denote a UTC. This value cannot be used with AssumeLocal or
RoundtripKind.
Don't know how .Net API provides, but I guess you could probably use ISO8601 format to indicate a UTC timezone before parsing, i.e, first translate 7/12/2014 4:42:00 PM into something 2014-07-02T16:42:00Z, then use try parse using DateTimeOffset
I had a string in config file, defining date time with time zone.
I am not able to get this value, while reading values from config file.
In config file:
Setting name="abcdefgh" value="2012-08-10T22:00:00-08:00"
In C#, I am reading this as follows:
DateTime StartDate;
StartDate = DateTime.ParseExact(RoleEnvironment.GetConfigurationSettingValue("abcdefgh"), "yyyy-MM-dd HH:mm:ss", null);
Configuration.Instance.abcdefgh= StartDate;
In start date, i am getting 11 Aug, 2012 11:30:00, with no time zone.
I want to read it as it is. also tell, if my format of writing datetime in config file is correct
MSDN link to DateTimeOffset.
Use DateTimeOffset whenever you are referring to an exact point in
time. For example, use it to calculate "now", transaction times, file
change times, logging event times, etc. If the time zone is not
known, use it with UTC. These uses are much more common than the
scenarios where DateTime is preferred, so this should be considered
the default.
var date = DateTimeOffset.Parse("2012-08-10T22:00:00-08:00");
date.Offset // -08:00:00, offset from Coordinated Universal Time (UTC)
date.DateTime // 10/08/2012 22:00:00,
DateTime doesn't keep information about timezone. To parse the string and keep information about timezone - you should use DateTimeOffset structure.
Use the DateTimeOffset structure (and DateTimeOffset.ParseExact) if you want to store timezone information.
Your ParseExact format also doesn't quite match the setting value: it should have a zz at the end for the timezone information. You can also use DateTimeOffset.Parse since your setting string is in a standard format.
It's a standard format, so the ParseExact isn't needed, try:
StartDate = DateTime.Parse(RoleEnvironment.GetConfigurationSettingValue("abcdefgh"));
I substituted the hard-coded value you provided and got the correct result for my timezone (GMT-4) as
8/11/2012 2:00 AM
Note: as others mentioned, the timezone is not retained, so you will get the correct localized time corresponding to whatever timezone information was in the string, but you won't be able to find out what timezone that was. The DateTime.Kind property will reflect that it's a local time.
I have to use some dates and times from a legacy database. They are represented as strings. Dates are dd/MM/yy. Times are HH:mm.
I'd like to convert these to UTC as soon as I pull them from the database. I'm working on US systems, so need a common time.
The problem I'm facing is how to convert them to UTC DateTime values. I can do the parsing, etc. The real problem I have concerns the timezone.
I'm trying to use the following approach:
DateTime ukTime = // Parse the strings in a DateTime value.
TimeZoneInfo timeZoneInformation = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
DateTimeOffset utcTime = new DateTimeOffset(ukTime, timeZoneInformation.BaseUtcOffset);
However, this gives incorrect values if the date is in the British Summer Time period.
I can use "GMT Daylight Time" on those dates, but that requires me to know when the switchover is. I'm sure there must be a less laborious way.
As I'm not using a machine with UK time settings I can't rely on local time.
Basically, I need something like:
// Works for both GMT (UTC+0) and BST (UTC+1) regardless of the regional settings of the system it runs on.
DateTime ParseUkTimeAsUtcTime(string date, string time)
{
...
}
I've scoured the posts, but couldn't find anything that addressed this directly. Surely this is also an issue with EST, EDT, etc?
Try using the GetUtcOffset() method on your TimeZoneInfo instance, which takes "adjustment rules" into consideration.
Using this should work basically the same as your original example, but you'll use that method instead of the BaseUtcOffset property.
DateTime ukTime = // Parse the strings in a DateTime value.
TimeZoneInfo timeZoneInformation = TimeZoneInfo.FindSystemTimeZoneById("GMT Standard Time");
DateTimeOffset utcTime = new DateTimeOffset(ukTime, timeZoneInformation.GetUtcOffset(ukTime));
How about:
DateTime.Parse(dateTimeString).ToUniversalTime();
Assuming that the database server stores its datetimes in the same timezone as your application server.