16

I'm trying to convert a DateTime object to a ISO8601 string but keep getting wrong results. I've looked around on stackoverflow, but couldn't find the right solution.

I start with a date time string of "2017-06-26T20:45:00.070Z" which deserialized by newtonsoft from json and converted to a DateTime object in C# equivalent to :

var theTime = new DateTime(2017, 6, 26, 20, 45, 00, 70, DateTimeKind.Utc);

Now i need the convert that time back to it's original UTC format string to use it in another algorithm, but every conversion i try doesn't return it to that original string. Not sure what i'm doing wrong.

i've tried:

var newTime = theTime.UtcNow.ToString("o");
// returns "2017-06-26T00:00:00.0000000Z"

var newTime2 = theTime.Date.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.sssZ");
// returns "2017-06-26T00:00:00.00Z"

what am i doing wrong? I want the equivalent to what js will do using toISOString() which is what i have listed in the newTime2 date time format, but it's not showing times either.

thanks!

user1161137
  • 897
  • 1
  • 9
  • 28
  • 1
    So what results are you getting? I would strongly advise explicitly specifying the invariant culture, and your `.sss` should be `.fff`, but other than that it looks okay. – Jon Skeet Jun 27 '17 at 19:27
  • 1
    Possible duplicate of [Given a DateTime object, how do I get an ISO 8601 date in string format?](https://stackoverflow.com/questions/114983/given-a-datetime-object-how-do-i-get-an-iso-8601-date-in-string-format) – Joe Phillips Jun 27 '17 at 19:27
  • i've checked the 'other' listings but "yyyy-MM-ddTHH\\:mm\\:ss.fffffffzzz" give me a runtime error stating that it needs to use a Z, not a z. Didn't quite understand the error message. but as stated i'm not getting 'time'. and the format doesn't quite come back the same as the original js conversion string. – user1161137 Jun 27 '17 at 19:34
  • As per Max, looks like reason for missing time is i am adding Date for no reason. But i'm still off on milliseconds in reproducing the exact string once again. – user1161137 Jun 27 '17 at 19:48
  • an alternative to this problem would be how to get Newtonsoft not to convert the datetime string to a date time which it seems to do automatically. so i can use the original string. – user1161137 Jun 27 '17 at 19:53
  • That part is easy - set the DateParseHandling to DateParseHandling.None in the settings. See https://github.com/GoogleCloudPlatform/google-cloud-dotnet/blob/f28adac63c9b3e4cbd7836528030da2cc5b792c8/apis/Google.Cloud.BigQuery.V2/Google.Cloud.BigQuery.V2/BigQueryClient.cs#L206 for example. – Jon Skeet Jun 27 '17 at 20:06

2 Answers2

54

Observe:

// Your input
DateTime dt = new DateTime(2017, 6, 26, 20, 45, 0, 70, DateTimeKind.Utc);

// ISO8601 with 7 decimal places
string s1 = dt.ToString("o", CultureInfo.InvariantCulture);
//=> "2017-06-26T20:45:00.0700000Z"

// ISO8601 with 3 decimal places
string s2 = dt.ToString("yyyy-MM-dd'T'HH:mm:ss.fffK", CultureInfo.InvariantCulture);
//=> "2017-06-26T20:45:00.070Z"

A few things:

  • Don't use Z in the format string. That's not a valid format specifier, so it is treated as just a character to output. It will be in every string, regardless of .Kind setting of the input datetime.

  • With DateTime, use K - which properly conveys the .Kind by appending a Z to the output for DateTimeKind.Utc, or an offset from UTC for DateTimeKind.Local, or nothing at all for DateTimeKind.Unspecified.

  • Though T will output as a character because it's not a valid format specifier, I suggest always being explicit about those things, so prefer 'T'.

  • Using fff will always give you back three decimals (milliseconds). If you want the decimals omitted when zero, use FFF instead. Your use of sss is not valid.

  • Passing CultureInfo.InvariantCulture is a good practice, as it helps you avoid problems where the current culture might use a different calendar system. For example ar-SA uses the UmAlQuraCalendar, rather than the proleptic Gregorian calendar required by ISO 8601.

  • In your code you tried, you had called theTime.UtcNow - that won't compile. UtcNow is a static property, not an instance property.

  • Also in your code you called theTime.Date.ToUniveralTime() - Don't do that either. .Date will set the time components to zero, and .ToUniversalTime() will have no effect since the input value already has DateTimeKind.Utc.

Matt Johnson-Pint
  • 197,368
  • 66
  • 382
  • 508
  • Tx! Date, max pointed out that it not needed. as far the sssZ, i got that from the toISOString js string spec. Tx for full explanation. – user1161137 Jun 27 '17 at 20:17
  • When it comes to formatting specifiers (tokens, whatever you call them), almost every language and library is going to be slightly different. Always check the docs. – Matt Johnson-Pint Jun 27 '17 at 21:25
  • `.ToString("o")` is really the simplest way to go, and you don't need to specify the culture there either. According to [the Microsoft docs](https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings#the-round-trip-o-o-format-specifier): "Because the "O" or "o" standard format specifier conforms to an international standard, the formatting or parsing operation that uses the specifier always uses the invariant culture and the Gregorian calendar." – Phil Jan 10 '19 at 10:48
1

The problem is that you are losing accuracy by using a cultural standard such as UTC or UniversalTime. If you just print your DateTime:

var theTime = new DateTime(2017, 6, 26, 20, 45, 00, 70, 
    DateTimeKind.Utc);
Console.WriteLine(theTime);

6/26/2017 8:45:00 PM

You can read more about this issue here.

The solution is to not use any "culture". (For example, UniversalTime, or UtcNow). These cultural standards never include milliseconds ... because there is no culture where people really care all that often about milliseconds.

Solution:

var newTime = theTime.ToString("o");
Console.WriteLine(newTime);

2017-06-26T20:45:00.0700000Z

Max von Hippel
  • 2,468
  • 2
  • 28
  • 41
  • when i try theTime.Date.ToString("o") i get "2017-06-26T00:00:00.0000000Z"... oh wait.. when i remove DATE part.. i get the time. – user1161137 Jun 27 '17 at 19:40
  • ok, getting somewhere with this. But how can i get the same? meaning i need the same as the original string! i tried strTime = theTime.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.sssZ"); but it's missing the milli second like you stated... i need the original string to use in a hash, so i need exactly the same. – user1161137 Jun 27 '17 at 19:44
  • Also the issue is that the original DATETIME UTC as listed in the beginning of the issue is what included milliseconds. i only replicated what that was in c# datetime var. – user1161137 Jun 27 '17 at 19:51
  • @user1161137 I gave the answer. Your date time has millisecond accuracy, and I print it back to you with millisecond accuracy. The "07" in "0700000Z" in the timestamp under *Solution* is your "70" in the milliseconds column. – Max von Hippel Jun 27 '17 at 21:25
  • Just do `theTime.ToString("o")` as I clearly do in my answer. – Max von Hippel Jun 27 '17 at 21:25
  • 1
    theTime.ToString("o") doesn't return what i asked.. which is exactly what it came in as. the "yyyy-MM-dd'T'HH:mm:ss.fffK" does return it exactly... which Matt identified and explained.. – user1161137 Jul 04 '17 at 18:11