/// <summary> /// This method is used to convert an ISO 8601 formatted date string to a DateTime value that it /// represents. /// </summary> /// <param name="dateTimeText">The ISO 8601 formatted date to parse.</param> /// <param name="toLocalTime">If true and the string is in a universal time format, the value is /// converted to local time before being returned. If false, it is returned as a universal time /// value.</param> /// <returns>The specified string converted to a local date/time</returns> /// <exception cref="ArgumentException">This is thrown if the specified date/time string is not valid</exception> /// <remarks><para>The <see cref="System.DateTime.Parse(string)"/> method is capable of parsing a string /// in a very specific layout of the ISO 8601 format (SortableDateTimePattern). However, if the string /// deviates even slightly from that pattern, it cannot be parsed. This method takes an ISO 8601 /// formatted date string in any of various formats and returns the DateTime value that it represents.</para> /// /// <para>This method does not handle all possible forms of the ISO 8601 format, just those related to /// date and time values (<c>YYYY-MM-DDTHH:MM:SS.MMMM+HH:MM</c>). Date and time separators (except the /// 'T') are optional as is the time zone specifier. The time indicator ('T') and the time value can be /// omitted entirely if not needed. A 'Z' (Zulu) can appear in place of the time zone specifier to /// indicate universal time. Date/times in universal time format or with a time zone offset are /// converted to local time if the <c>bToLocalTime</c> parameter is set to true. All other values are /// assumed to be local time already and will be returned unmodified as are date-only values.</para></remarks> public static DateTime FromISO8601String(string dateTimeText, bool toLocalTime) { DateTime convertedDate; int year, day, month, hour = 0, minutes = 0, wholeSeconds = 0, milliseconds = 0; double fracSeconds; Match m = reISO8601.Match(dateTimeText); if(!m.Success) throw new ArgumentException(LR.GetString("ExDUBadISOFormat"), "dateTimeText"); // Day parts must be there year = Convert.ToInt32(m.Groups["Year"].Value, CultureInfo.InvariantCulture); month = Convert.ToInt32(m.Groups["Month"].Value, CultureInfo.InvariantCulture); day = Convert.ToInt32(m.Groups["Day"].Value, CultureInfo.InvariantCulture); // Time parts are optional if(m.Groups["Hour"].Value.Length != 0) { hour = Convert.ToInt32(m.Groups["Hour"].Value, CultureInfo.InvariantCulture); if(m.Groups["Minutes"].Value.Length != 0) { minutes = Convert.ToInt32(m.Groups["Minutes"].Value, CultureInfo.InvariantCulture); if(m.Groups["Seconds"].Value.Length != 0) { fracSeconds = Convert.ToDouble(m.Groups["Seconds"].Value, CultureInfo.InvariantCulture); wholeSeconds = (int)fracSeconds; milliseconds = (int)((fracSeconds - wholeSeconds) * 1000); } } } convertedDate = new DateTime(year, month, day); // Sometimes we get something like 240000 to represent the time for a whole day. By adding the time // parts as a time span, we bypass any potential problems with the DateTime() constructor that // expects the time parts to be within bounds. convertedDate += new TimeSpan(0, hour, minutes, wholeSeconds, milliseconds); // Convert to local time if necessary if(m.Groups["Zulu"].Value.Length != 0) { if(toLocalTime) convertedDate = convertedDate.ToLocalTime(); } else if(m.Groups["TZHours"].Value.Length != 0) { // If a time zone offset was specified, add it to the time to get UTC hour = Convert.ToInt32(m.Groups["TZHours"].Value, CultureInfo.InvariantCulture); if(m.Groups["TZMinutes"].Value.Length == 0) minutes = 0; else minutes = Convert.ToInt32(m.Groups["TZMinutes"].Value, CultureInfo.InvariantCulture); convertedDate = convertedDate.AddMinutes(0 - (hour * 60 + minutes)); if(toLocalTime) convertedDate = convertedDate.ToLocalTime(); } return convertedDate; }