Skip to content

Instantly share code, notes, and snippets.

@dmorosinotto
Last active March 7, 2024 09:42
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmorosinotto/81e8d809a3833b7caf7b3c813703a8e6 to your computer and use it in GitHub Desktop.
Save dmorosinotto/81e8d809a3833b7caf7b3c813703a8e6 to your computer and use it in GitHub Desktop.
C# Extension Methods to handle ISO-8601 with the same exact ISOFormat used in Javascript
public static class ISOFormatExtensions
{
const string ISOFORMAT = "yyyy-MM-dd\\THH:mm:ss.fffK"; //ISO-8601 used by Javascript (ALWAYS UTC)
public static string toISOString(this DateTime d, bool useLocal = false) {
if (!useLocal && d.Kind == DateTimeKind.Local) {
//If d is LT or you don't want LocalTime -> convert to UTC and always add K format always add 'Z' postfix
return d.ToUniversalTime().ToString(ISOFORMAT);
} else { //If d is already UTC K format add 'Z' postfix, if d is LT K format add +/-TIMEOFFSET
return d.ToString(ISOFORMAT);
}
}
public static DateTime fromISOString(this DateTime d, string s, bool useLocal = false) {
//Return a new DateTime parsed used ISOFORMAT - YOU MUST PASS A STRING ENDING WITH 'Z' OR +/-TIMEOFFSET
var l = DateTime.ParseExact(s, ISOFORMAT, System.Globalization.CultureInfo.InvariantCulture);
return useLocal ? l : l.ToUniversalTime(); //If you don't set useLocal returned date is always Kind=UTC
}
public static DateTime fromISOString(this DateTime d, string date, string time, bool useLocal = false) {
//Return a new DateTime buiding an ISOFROMAT string from date, time params expressed in UTC (by default) or in LT if you set useLocal=true
var sb = new System.Text.StringBuilder(30);
if (!string.IsNullOrEmpty(date)) { sb.Append(date); sb.Replace('-', '/'); }
if (!string.IsNullOrEmpty(time)) { sb.Append(' '); sb.Append(time); }
var s = sb.ToString();
if (!useLocal) { //Always return DateTime Kind=UTC, if you don't pass +/-TIMEOFFSET or 'Z' postfix I'll add it by default (as needed for UTC)
if (!(s.Contains('Z') || s.Contains('+') || s.Contains('-'))) s += "Z";
return d = DateTime.Parse(s, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AdjustToUniversal | System.Globalization.DateTimeStyles.AssumeUniversal);
} else { //Return DateTime Kind=Local and do necessary conversion to LT if you pass time with +/-TIMEOFFSET or referred as UTC with 'Z' postfix
return d = DateTime.Parse(s, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AssumeLocal);
}
}
}
@dmorosinotto
Copy link
Author

dmorosinotto commented Apr 24, 2018

//SAMPLE USING Extension Methods IN C#
var u = DateTime.UtcNow;
var n = DateTime.Now;
Console.WriteLine("NOW {0} n.KIND={1} -> {2} in LT {3}",n,n.Kind,n.toISOString(),n.toISOString(true));
Console.WriteLine("NOWUTC {0} u.KIND={1} -> {2} in LT {3}",u,u.Kind,u.toISOString(),u.toISOString(true));
var ok = new DateTime().fromISOString(n.toISOString(),true);
var okZ = new DateTime().fromISOString(u.toISOString());
Console.WriteLine("ASSERT NOW {0} {1}",(ok-n).TotalMilliseconds, ok.Kind);
Console.WriteLine("ASSERT NOWUTC {0} {1}",(okZ-u).TotalMilliseconds, okZ.Kind);
Console.WriteLine("-------------------------------------------------");
var z = new DateTime().fromISOString("2018-04-26","14:46:23.456Z"); //14.46Z
Console.WriteLine("Z UTC {0} KIND={1} ISO={2}",z,z.Kind,z.toISOString());  
var y = new DateTime().fromISOString("2018/04/26","14:46:23.456");  //14.46Z
Console.WriteLine("Y LT+TOFF {0} KIND={1} ISO={2}",y,y.Kind,y.toISOString());
var x = new DateTime().fromISOString("2018/04/26","14:46:23.456+02:00"); //12.46Z
Console.WriteLine("X LT->UTC {0} KIND={1} ISO={2}",x,x.Kind,x.toISOString());
Console.WriteLine("-------------------------------------------------");
var l = new DateTime().fromISOString("2018/04/26","14:46:23.456",true);  //12.46Z <- LT=14.46
Console.WriteLine("L LT {0} KIND={1} ISO={2}",l,l.Kind,l.toISOString());
var t = new DateTime().fromISOString("2018/04/26","14:46:23.456+02:00",true); //12.46Z
Console.WriteLine("T LT+TOFF {0} KIND={1} ISO={2}",t,t.Kind,t.toISOString());
var c = new DateTime().fromISOString("2018-04-26","14:46:23.456Z",true); //14.46Z -> LT=16.46
Console.WriteLine("C UTC->LT {0} KIND={1} ISO={2}",c,c.Kind,c.toISOString());

When you want to pass DateTime to JSON / Javascript simply serialize the value using toISOString()

//USING Date OBJECT IN JAVASCRIPT 
const strUTC = "2018-04-26T14:46:23.456Z"; //String serialized with extension method DateTime.toISOString(); //UTC
const strLocal = "2018-04-26T16:46:23.456+02:00";  //String serialized with DateTime.toISOString(true); //LT+TIMEOFFSET

Date.parse(strUTC); 
//-> 1524753983456
Date.parse(strLocal)
//-> 1524753983456
new Date(strUTC).toISOString()
//-> "2018-04-26T14:46:23.456Z"
new Date(strLocal).toISOString()
//-> "2018-04-26T14:46:23.456Z"

@andreadottor
Copy link

I suggest to rename the methods in camel-casing for respect the Microsoft naming guideline (seeing that the file is c#)
https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions

@mmalka
Copy link

mmalka commented Nov 3, 2020

You meant Pascal Casing ?

Note: C# support new DateTime(string), and for a ISO string, a ToString("o") is enough, isn't it?

@dmorosinotto
Copy link
Author

Hi @mmalka yes I think that for toISOString can be simplified with the ToString("o") formatting, I don't know when it was added, I'm pretty sure that 2 year ago it wasn't available, so I create this Extension method to simplify my works to correctly format and parse ISO-8601 format in C#

PS: do you know if there is something like the "o" format even to simplify the parse?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment