2008-11-12 19 views
33

¿Cómo convierto una estructura DateTime a su representación de cadena formateada RFC 822 date-time equivalente y analizo esta representación de cadena de nuevo a una estructura DateTime en .NET? El formato de fecha y hora RFC-822 se utiliza en una serie de especificaciones, como el RSS Syndication Format.¿Cómo analizo y convierto DateTime al formato de fecha y hora RFC 822?

+0

Estaba buscando una implementación .NET. – Oppositional

+0

Me encantaría una actualización para usar DateTimeOffset. – Broam

+0

.NET tiene un formateador RSS 2.0 listo para usar, que serializa correctamente las fechas de publicación de elementos a RFC 822. Consulte ['Rss20FeedFormatter'] (http://msdn.microsoft.com/en-us/library/system.servicemodel Clase .syndication.rss20feedformatter (v = vs.110) .aspx) en el ensamblado 'System.ServiceModel' para más detalles. – whyleee

Respuesta

29

Esta es una implementación en C# de cómo analizar y convertir un DateTime desde y hacia su representación RFC-822. La única restricción que tiene es que DateTime está en hora universal coordinada (UTC). Estoy de acuerdo en que este código no es muy elegante, pero cumple su función.

/// <summary> 
/// Provides methods for converting <see cref="DateTime"/> structures 
/// to and from the equivalent <a href="http://www.w3.org/Protocols/rfc822/#z28">RFC 822</a> 
/// string representation. 
/// </summary> 
public class Rfc822DateTime 
{ 
    //============================================================ 
    // Private members 
    //============================================================ 
    #region Private Members 
    /// <summary> 
    /// Private member to hold array of formats that RFC 822 date-time representations conform to. 
    /// </summary> 
    private static string[] formats = new string[0]; 
    /// <summary> 
    /// Private member to hold the DateTime format string for representing a DateTime in the RFC 822 format. 
    /// </summary> 
    private const string format  = "ddd, dd MMM yyyy HH:mm:ss K"; 
    #endregion 

    //============================================================ 
    // Public Properties 
    //============================================================ 
    #region Rfc822DateTimeFormat 
    /// <summary> 
    /// Gets the custom format specifier that may be used to represent a <see cref="DateTime"/> in the RFC 822 format. 
    /// </summary> 
    /// <value>A <i>DateTime format string</i> that may be used to represent a <see cref="DateTime"/> in the RFC 822 format.</value> 
    /// <remarks> 
    /// <para> 
    /// This method returns a string representation of a <see cref="DateTime"/> that utilizes the time zone 
    /// offset (local differential) to represent the offset from Greenwich mean time in hours and minutes. 
    /// The <see cref="Rfc822DateTimeFormat"/> is a valid date-time format string for use 
    /// in the <see cref="DateTime.ToString(String, IFormatProvider)"/> method. 
    /// </para> 
    /// <para> 
    /// The <a href="http://www.w3.org/Protocols/rfc822/#z28">RFC 822</a> Date and Time specification 
    /// specifies that the year will be represented as a two-digit value, but the 
    /// <a href="http://www.rssboard.org/rss-profile#data-types-datetime">RSS Profile</a> recommends that 
    /// all date-time values should use a four-digit year. The <see cref="Rfc822DateTime"/> class 
    /// follows the RSS Profile recommendation when converting a <see cref="DateTime"/> to the equivalent 
    /// RFC 822 string representation. 
    /// </para> 
    /// </remarks> 
    public static string Rfc822DateTimeFormat 
    { 
     get 
     { 
      return format; 
     } 
    } 
    #endregion 

    #region Rfc822DateTimePatterns 
    /// <summary> 
    /// Gets an array of the expected formats for RFC 822 date-time string representations. 
    /// </summary> 
    /// <value> 
    /// An array of the expected formats for RFC 822 date-time string representations 
    /// that may used in the <see cref="DateTime.TryParseExact(String, string[], IFormatProvider, DateTimeStyles, out DateTime)"/> method. 
    /// </value> 
    /// <remarks> 
    /// The array of the expected formats that is returned assumes that the RFC 822 time zone 
    /// is represented as or converted to a local differential representation. 
    /// </remarks> 
    /// <seealso cref="ConvertZoneToLocalDifferential(String)"/> 
    public static string[] Rfc822DateTimePatterns 
    { 
     get 
     { 
      if (formats.Length > 0) 
      { 
       return formats; 
      } 
      else 
      { 
       formats = new string[35]; 

       // two-digit day, four-digit year patterns 
       formats[0] = "ddd',' dd MMM yyyy HH':'mm':'ss'.'fffffff zzzz"; 
       formats[1] = "ddd',' dd MMM yyyy HH':'mm':'ss'.'ffffff zzzz"; 
       formats[2] = "ddd',' dd MMM yyyy HH':'mm':'ss'.'fffff zzzz"; 
       formats[3] = "ddd',' dd MMM yyyy HH':'mm':'ss'.'ffff zzzz"; 
       formats[4] = "ddd',' dd MMM yyyy HH':'mm':'ss'.'fff zzzz"; 
       formats[5] = "ddd',' dd MMM yyyy HH':'mm':'ss'.'ff zzzz"; 
       formats[6] = "ddd',' dd MMM yyyy HH':'mm':'ss'.'f zzzz"; 
       formats[7] = "ddd',' dd MMM yyyy HH':'mm':'ss zzzz"; 

       // two-digit day, two-digit year patterns 
       formats[8] = "ddd',' dd MMM yy HH':'mm':'ss'.'fffffff zzzz"; 
       formats[9] = "ddd',' dd MMM yy HH':'mm':'ss'.'ffffff zzzz"; 
       formats[10] = "ddd',' dd MMM yy HH':'mm':'ss'.'fffff zzzz"; 
       formats[11] = "ddd',' dd MMM yy HH':'mm':'ss'.'ffff zzzz"; 
       formats[12] = "ddd',' dd MMM yy HH':'mm':'ss'.'fff zzzz"; 
       formats[13] = "ddd',' dd MMM yy HH':'mm':'ss'.'ff zzzz"; 
       formats[14] = "ddd',' dd MMM yy HH':'mm':'ss'.'f zzzz"; 
       formats[15] = "ddd',' dd MMM yy HH':'mm':'ss zzzz"; 

       // one-digit day, four-digit year patterns 
       formats[16] = "ddd',' d MMM yyyy HH':'mm':'ss'.'fffffff zzzz"; 
       formats[17] = "ddd',' d MMM yyyy HH':'mm':'ss'.'ffffff zzzz"; 
       formats[18] = "ddd',' d MMM yyyy HH':'mm':'ss'.'fffff zzzz"; 
       formats[19] = "ddd',' d MMM yyyy HH':'mm':'ss'.'ffff zzzz"; 
       formats[20] = "ddd',' d MMM yyyy HH':'mm':'ss'.'fff zzzz"; 
       formats[21] = "ddd',' d MMM yyyy HH':'mm':'ss'.'ff zzzz"; 
       formats[22] = "ddd',' d MMM yyyy HH':'mm':'ss'.'f zzzz"; 
       formats[23] = "ddd',' d MMM yyyy HH':'mm':'ss zzzz"; 

       // two-digit day, two-digit year patterns 
       formats[24] = "ddd',' d MMM yy HH':'mm':'ss'.'fffffff zzzz"; 
       formats[25] = "ddd',' d MMM yy HH':'mm':'ss'.'ffffff zzzz"; 
       formats[26] = "ddd',' d MMM yy HH':'mm':'ss'.'fffff zzzz"; 
       formats[27] = "ddd',' d MMM yy HH':'mm':'ss'.'ffff zzzz"; 
       formats[28] = "ddd',' d MMM yy HH':'mm':'ss'.'fff zzzz"; 
       formats[29] = "ddd',' d MMM yy HH':'mm':'ss'.'ff zzzz"; 
       formats[30] = "ddd',' d MMM yy HH':'mm':'ss'.'f zzzz"; 
       formats[31] = "ddd',' d MMM yy HH':'mm':'ss zzzz"; 

       // Fall back patterns 
       formats[32] = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffffffK"; // RoundtripDateTimePattern 
       formats[33] = DateTimeFormatInfo.InvariantInfo.UniversalSortableDateTimePattern; 
       formats[34] = DateTimeFormatInfo.InvariantInfo.SortableDateTimePattern; 

       return formats; 
      } 
     } 
    } 
    #endregion 

    //============================================================ 
    // Public Methods 
    //============================================================ 
    #region Parse(string s) 
    /// <summary> 
    /// Converts the specified string representation of a date and time to its <see cref="DateTime"/> equivalent. 
    /// </summary> 
    /// <param name="s">A string containing a date and time to convert.</param> 
    /// <returns> 
    /// A <see cref="DateTime"/> equivalent to the date and time contained in <paramref name="s"/>, 
    /// expressed as <i>Coordinated Universal Time (UTC)</i>. 
    /// </returns> 
    /// <remarks> 
    /// The string <paramref name="s"/> is parsed using formatting information in the <see cref="DateTimeFormatInfo.InvariantInfo"/> object. 
    /// </remarks> 
    /// <exception cref="ArgumentNullException"><paramref name="s"/> is a <b>null</b> reference (Nothing in Visual Basic).</exception> 
    /// <exception cref="ArgumentNullException"><paramref name="s"/> is an empty string.</exception> 
    /// <exception cref="FormatException"><paramref name="s"/> does not contain a valid RFC 822 string representation of a date and time.</exception> 
    public static DateTime Parse(string s) 
    { 
     //------------------------------------------------------------ 
     // Validate parameter 
     //------------------------------------------------------------ 
     if (String.IsNullOrEmpty(s)) 
     { 
      throw new ArgumentNullException("s"); 
     } 

     DateTime result; 
     if (Rfc822DateTime.TryParse(s, out result)) 
     { 
      return result; 
     } 
     else 
     { 
      throw new FormatException(String.Format(null, "{0} is not a valid RFC 822 string representation of a date and time.", s)); 
     } 
    } 
    #endregion 

    #region ConvertZoneToLocalDifferential(string s) 
    /// <summary> 
    /// Converts the time zone component of an RFC 822 date and time string representation to its local differential (time zone offset). 
    /// </summary> 
    /// <param name="s">A string containing an RFC 822 date and time to convert.</param> 
    /// <returns>A date and time string that uses local differential to describe the time zone equivalent to the date and time contained in <paramref name="s"/>.</returns> 
    /// <exception cref="ArgumentNullException"><paramref name="s"/> is a <b>null</b> reference (Nothing in Visual Basic).</exception> 
    /// <exception cref="ArgumentNullException"><paramref name="s"/> is an empty string.</exception> 
    public static string ConvertZoneToLocalDifferential(string s) 
    { 
     string zoneRepresentedAsLocalDifferential = String.Empty; 

     //------------------------------------------------------------ 
     // Validate parameter 
     //------------------------------------------------------------ 
     if (String.IsNullOrEmpty(s)) 
     { 
      throw new ArgumentNullException("s"); 
     } 

     if(s.EndsWith(" UT", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" UT") + 1)), "+00:00"); 
     } 
     else if (s.EndsWith(" GMT", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" GMT") + 1)), "+00:00"); 
     } 
     else if (s.EndsWith(" EST", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" EST") + 1)), "-05:00"); 
     } 
     else if (s.EndsWith(" EDT", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" EDT") + 1)), "-04:00"); 
     } 
     else if (s.EndsWith(" CST", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" CST") + 1)), "-06:00"); 
     } 
     else if (s.EndsWith(" CDT", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" CDT") + 1)), "-05:00"); 
     } 
     else if (s.EndsWith(" MST", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" MST") + 1)), "-07:00"); 
     } 
     else if (s.EndsWith(" MDT", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" MDT") + 1)), "-06:00"); 
     } 
     else if (s.EndsWith(" PST", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" PST") + 1)), "-08:00"); 
     } 
     else if (s.EndsWith(" PDT", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" PDT") + 1)), "-07:00"); 
     } 
     else if (s.EndsWith(" Z", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" Z") + 1)), "+00:00"); 
     } 
     else if (s.EndsWith(" A", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" A") + 1)), "-01:00"); 
     } 
     else if (s.EndsWith(" M", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" M") + 1)), "-12:00"); 
     } 
     else if (s.EndsWith(" N", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" N") + 1)), "+01:00"); 
     } 
     else if (s.EndsWith(" Y", StringComparison.OrdinalIgnoreCase)) 
     { 
      zoneRepresentedAsLocalDifferential = String.Concat(s.Substring(0, (s.LastIndexOf(" Y") + 1)), "+12:00"); 
     } 
     else 
     { 
      zoneRepresentedAsLocalDifferential = s; 
     } 

     return zoneRepresentedAsLocalDifferential; 
    } 
    #endregion 

    #region ToString(DateTime utcDateTime) 
    /// <summary> 
    /// Converts the value of the specified <see cref="DateTime"/> object to its equivalent string representation. 
    /// </summary> 
    /// <param name="utcDateTime">The Coordinated Universal Time (UTC) <see cref="DateTime"/> to convert.</param> 
    /// <returns>A RFC 822 string representation of the value of the <paramref name="utcDateTime"/>.</returns> 
    /// <exception cref="ArgumentException">The specified <paramref name="utcDateTime"/> object does not represent a <see cref="DateTimeKind.Utc">Coordinated Universal Time (UTC)</see> value.</exception> 
    public static string ToString(DateTime utcDateTime) 
    { 
     if (utcDateTime.Kind != DateTimeKind.Utc) 
     { 
      throw new ArgumentException("utcDateTime"); 
     } 

     return utcDateTime.ToString(Rfc822DateTime.Rfc822DateTimeFormat, DateTimeFormatInfo.InvariantInfo); 
    } 
    #endregion 

    #region TryParse(string s, out DateTime result) 
    /// <summary> 
    /// Converts the specified string representation of a date and time to its <see cref="DateTime"/> equivalent. 
    /// </summary> 
    /// <param name="s">A string containing a date and time to convert.</param> 
    /// <param name="result"> 
    /// When this method returns, contains the <see cref="DateTime"/> value equivalent to the date and time 
    /// contained in <paramref name="s"/>, expressed as <i>Coordinated Universal Time (UTC)</i>, 
    /// if the conversion succeeded, or <see cref="DateTime.MinValue">MinValue</see> if the conversion failed. 
    /// The conversion fails if the s parameter is a <b>null</b> reference (Nothing in Visual Basic), 
    /// or does not contain a valid string representation of a date and time. 
    /// This parameter is passed uninitialized. 
    /// </param> 
    /// <returns><b>true</b> if the <paramref name="s"/> parameter was converted successfully; otherwise, <b>false</b>.</returns> 
    /// <remarks> 
    /// The string <paramref name="s"/> is parsed using formatting information in the <see cref="DateTimeFormatInfo.InvariantInfo"/> object. 
    /// </remarks> 
    public static bool TryParse(string s, out DateTime result) 
    { 
     //------------------------------------------------------------ 
     // Attempt to convert string representation 
     //------------------------------------------------------------ 
     bool wasConverted = false; 
     result    = DateTime.MinValue; 

     if (!String.IsNullOrEmpty(s)) 
     { 
      DateTime parseResult; 
      if (DateTime.TryParseExact(Rfc822DateTime.ConvertZoneToLocalDifferential(s), Rfc822DateTime.Rfc822DateTimePatterns, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal, out parseResult)) 
      { 
       result   = DateTime.SpecifyKind(parseResult, DateTimeKind.Utc); 
       wasConverted = true; 
      } 
     } 

     return wasConverted; 
    } 
    #endregion 
} 
+24

Este código se ve horrible. – Esko

+0

El uso del tipo 'Guard' en este código me recuerda la solución en Argotic Syndication Framework. Es este código beta de Argotic? Su solución a este problema en la última versión de Argotic es la mejor que he visto. – rasx

+0

Este no es un código beta de Argotic, y creo que lo último de Argotic probablemente tenga una mejor implicación que la que se describe aquí. – Oppositional

38

Prueba esto:

DateTime today = DateTime.Now; 
    String rfc822 = today.ToString("r"); 
    Console.WriteLine("RFC-822 date: {0}", rfc822); 

    DateTime parsedRFC822 = DateTime.Parse(rfc822); 
    Console.WriteLine("Date: {0}", parsedRFC822); 

La "r" especificador de formato pasado a ToString de DateTime() método en realidad produce una cadena de fecha y hora con formato RFC-1123, pero pasa como una fecha de RFC-822 como bien, basado en la lectura de la especificación encontrada en http://www.w3.org/Protocols/rfc822/#z28. Utilicé este método para crear feeds RSS y aprobaron la validación según el validador disponible en http://validator.w3.org/feed/check.cgi.

El inconveniente es que, en la conversión, convierte la fecha y hora a GMT. Para convertir de nuevo a la hora local, necesitaría aplicar su desplazamiento de zona horaria local. Por eso, es posible utilizar la clase de zona horaria para obtener su zona horaria actual que se encuentra, y reemplazar "GMT" con un desplazamiento de cadena zona horaria:

TimeZone tz = TimeZone.CurrentTimeZone; 

String offset = tz.GetUtcOffset().ToString(); 
// My locale is Mountain time; offset is set to "-07:00:00" 
// if local time is behind utc time, offset should start with "-". 
// otherwise, add a plus sign to the beginning of the string. 
if (!offset.StartsWith("-")) 
    offset = "+" + offset; // Add a (+) if it's a UTC+ timezone 
offset = offset.Substring(0,6); // only want the first 6 chars. 
offset = offset.Replace(":", ""); // remove colons. 
// offset now looks something like "-0700". 
rfc822 = rfc822.Replace("GMT", offset); 
// The rfc822 string can now be parsed back to a DateTime object, 
// with the local time accounted for. 
DateTime new = DateTime.Parse(rfc822); 
+0

Simple ... ¡me gusta! –

2

Así es como Microsoft lo hace en el Rss20FeedFormatter. El código de oposición no elimina el ":" en la porción de compensación GMT. Jeff Woodman parece hacer esto. El código siguiente también lo hace (si no usa Atom10FeedFormatter.zeroOffset).

private string AsString(DateTimeOffset dateTime) 
{ 
    if (dateTime.Offset == Atom10FeedFormatter.zeroOffset) 
    { 
     return dateTime.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss Z", CultureInfo.InvariantCulture); 
    } 
    StringBuilder builder = new StringBuilder(dateTime.T)oString("ddd, dd MMM yyyy HH:mm:ss zzz", CultureInfo.InvariantCulture)); 
    builder.Remove(builder.Length - 3, 1); 
    return builder.ToString(); 
} 
+0

No se puede resolver 'Atom10FeedFormatter'. ¿Cómo lo arreglo bajo VS2015? – Adrian

+0

Han pasado 7 años, así que quizás esto no funcione, pero prueba lo siguiente si aún no lo has hecho. Incluya System.ServiceModel.dll como referencia de ensamblado y agregue una instrucción using para System.ServiceModel.Syndication. Consulte también: https://msdn.microsoft.com/en-us/library/system.servicemodel.syndication.atom10feedformatter(v=vs.110).aspx –

1

Sobre la base de la respuesta de Kirk Liemohn, que utilizan este método con éxito:

private DateTimeOffset? ParseDate(string date) 
    { 
     const string FORMAT = "ddd, d MMM yyyy HH:mm:ss zzz"; 
     const string FORMAT2 = "ddd, dd MMM yyyy HH:mm:ss zzz"; 
     const string FORMAT3 = "dd MMM yyyy HH:mm:ss zzz"; 
     const string FORMAT4 = "d MMM yyyy HH:mm:ss zzz"; 
     DateTimeOffset d; 
     if (DateTimeOffset.TryParseExact(date, new string[] { FORMAT, FORMAT2, FORMAT3, FORMAT4 }, CultureInfo.InvariantCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite, out d)) 
      return d; 
     return null; 
    } 

Ejemplo:

DateTimeOffset? date = ParseDate("Thu, 5 Apr 2012 23:47:37 +0200"); 
Console.WriteLine(date.ToString()); 
// => 05/04/2012 23:47:37 +02:00 

No respeta completa de especificaciones de RFC pero funciona para mis casos de uso.

Específicamente, no funciona con timezone express como: "GMT", "CST", etc. (ver "zone" in RFC822 Section 5.1). Vea el better answer of Oleksandr Pshenychnyy.

1

Aquí está mi aplicación utilizando un método de extensión:

namespace MyNamespace 
{ 
    public static partial class ExtensionMethods 
    { 
     public static string ToRFC822String(this DateTime timestamp) 
     { 
      return timestamp.ToString("ddd',' d MMM yyyy HH':'mm':'ss") 
       + " " 
       + timestamp.ToString("zzzz").Replace(":", ""); 
     } 
    } 
} 

de usar:

using MyNamespace; 

.... 

string MyRFC822String = DateTime.Now.ToRFC822String(); 
+1

por favor, no publique solo código en su respuesta – Cybermaxs

+0

Documentación añadida. –

1

Siguiendo la idea de Kirk, que decompilados fuentes de System.ServiceModel.Syndication.Rss20FeedFormatter clase (System.ServiceModel.dll) y aquí está analizador interno de Microsoft para el formato de fechas RFC 822 (simplifiqué un poco su lógica de manejo de excepciones para reducir las dependencias):

public static class DateTimeParser 
{ 
    public static DateTimeOffset ParseDateTimeRFC822(string dateTimeString) 
    { 
     StringBuilder dateTimeStringBuilder = new StringBuilder(dateTimeString.Trim()); 
     if (dateTimeStringBuilder.Length < 18) 
     { 
      throw new FormatException("Invalid date format. Expected date in RFC 822 format"); 
     } 
     if (dateTimeStringBuilder[3] == ',') 
     { 
      // There is a leading (e.g.) "Tue, ", strip it off 
      dateTimeStringBuilder.Remove(0, 4); 
      // There's supposed to be a space here but some implementations dont have one 
      RemoveExtraWhiteSpaceAtStart(dateTimeStringBuilder); 
     } 
     ReplaceMultipleWhiteSpaceWithSingleWhiteSpace(dateTimeStringBuilder); 
     if (char.IsDigit(dateTimeStringBuilder[1])) 
     { 
      // two-digit day, we are good 
     } 
     else 
     { 
      dateTimeStringBuilder.Insert(0, '0'); 
     } 
     if (dateTimeStringBuilder.Length < 19) 
     { 
      throw new FormatException("Invalid date format. Expected date in RFC 822 format"); 
     } 
     bool thereAreSeconds = (dateTimeStringBuilder[17] == ':'); 
     int timeZoneStartIndex; 
     if (thereAreSeconds) 
     { 
      timeZoneStartIndex = 21; 
     } 
     else 
     { 
      timeZoneStartIndex = 18; 
     } 
     string timeZoneSuffix = dateTimeStringBuilder.ToString().Substring(timeZoneStartIndex); 
     dateTimeStringBuilder.Remove(timeZoneStartIndex, dateTimeStringBuilder.Length - timeZoneStartIndex); 
     bool isUtc; 
     dateTimeStringBuilder.Append(NormalizeTimeZone(timeZoneSuffix, out isUtc)); 
     string wellFormattedString = dateTimeStringBuilder.ToString(); 

     DateTimeOffset theTime; 
     string parseFormat; 
     if (thereAreSeconds) 
     { 
      parseFormat = "dd MMM yyyy HH:mm:ss zzz"; 
     } 
     else 
     { 
      parseFormat = "dd MMM yyyy HH:mm zzz"; 
     } 
     if (DateTimeOffset.TryParseExact(wellFormattedString, parseFormat, 
      CultureInfo.InvariantCulture.DateTimeFormat, 
      (isUtc ? DateTimeStyles.AdjustToUniversal : DateTimeStyles.None), out theTime)) 
     { 
      return theTime; 
     } 
     throw new FormatException("Invalid date format. Expected date in RFC 822 format"); 
    } 

    static string NormalizeTimeZone(string rfc822TimeZone, out bool isUtc) 
    { 
     isUtc = false; 
     // return a string in "-08:00" format 
     if (rfc822TimeZone[0] == '+' || rfc822TimeZone[0] == '-') 
     { 
      // the time zone is supposed to be 4 digits but some feeds omit the initial 0 
      StringBuilder result = new StringBuilder(rfc822TimeZone); 
      if (result.Length == 4) 
      { 
       // the timezone is +/-HMM. Convert to +/-HHMM 
       result.Insert(1, '0'); 
      } 
      result.Insert(3, ':'); 
      return result.ToString(); 
     } 
     switch (rfc822TimeZone) 
     { 
      case "UT": 
      case "Z": 
       isUtc = true; 
       return "-00:00"; 
      case "GMT": 
       return "-00:00"; 
      case "A": 
       return "-01:00"; 
      case "B": 
       return "-02:00"; 
      case "C": 
       return "-03:00"; 
      case "D": 
      case "EDT": 
       return "-04:00"; 
      case "E": 
      case "EST": 
      case "CDT": 
       return "-05:00"; 
      case "F": 
      case "CST": 
      case "MDT": 
       return "-06:00"; 
      case "G": 
      case "MST": 
      case "PDT": 
       return "-07:00"; 
      case "H": 
      case "PST": 
       return "-08:00"; 
      case "I": 
       return "-09:00"; 
      case "K": 
       return "-10:00"; 
      case "L": 
       return "-11:00"; 
      case "M": 
       return "-12:00"; 
      case "N": 
       return "+01:00"; 
      case "O": 
       return "+02:00"; 
      case "P": 
       return "+03:00"; 
      case "Q": 
       return "+04:00"; 
      case "R": 
       return "+05:00"; 
      case "S": 
       return "+06:00"; 
      case "T": 
       return "+07:00"; 
      case "U": 
       return "+08:00"; 
      case "V": 
       return "+09:00"; 
      case "W": 
       return "+10:00"; 
      case "X": 
       return "+11:00"; 
      case "Y": 
       return "+12:00"; 
      default: 
       return ""; 
     } 
    } 

    static void RemoveExtraWhiteSpaceAtStart(StringBuilder stringBuilder) 
    { 
     int i = 0; 
     while (i < stringBuilder.Length) 
     { 
      if (!char.IsWhiteSpace(stringBuilder[i])) 
      { 
       break; 
      } 
      ++i; 
     } 
     if (i > 0) 
     { 
      stringBuilder.Remove(0, i); 
     } 
    } 

    static void ReplaceMultipleWhiteSpaceWithSingleWhiteSpace(StringBuilder builder) 
    { 
     int index = 0; 
     int whiteSpaceStart = -1; 
     while (index < builder.Length) 
     { 
      if (char.IsWhiteSpace(builder[index])) 
      { 
       if (whiteSpaceStart < 0) 
       { 
        whiteSpaceStart = index; 
        // normalize all white spaces to be ' ' so that the date time parsing works 
        builder[index] = ' '; 
       } 
      } 
      else if (whiteSpaceStart >= 0) 
      { 
       if (index > whiteSpaceStart + 1) 
       { 
        // there are at least 2 spaces... replace by 1 
        builder.Remove(whiteSpaceStart, index - whiteSpaceStart - 1); 
        index = whiteSpaceStart + 1; 
       } 
       whiteSpaceStart = -1; 
      } 
      ++index; 
     } 
     // we have already trimmed the start and end so there cannot be a trail of white spaces in the end 
     Debug.Assert(builder.Length == 0 || builder[builder.Length - 1] != ' ', "The string builder doesnt end in a white space"); 
    } 
} 

La primera cosa que puede parecer inusual es que devuelven [DateTimeOffset][1] clase en lugar de DateTime.Pero cuando leemos más sobre esto, parece ser completamente lógico: DateTimeOffset almacena fecha, hora y zona horaria información (exactamente como la cadena en formato RFC 822). Si devolviera solo el objeto DateTime, qué zona horaria sería en: UTC, local o la especificada en la cadena analizada, cualquier respuesta sería incorrecta en algunos casos. Entonces DateTimeOffset resuelve un importante problema de incertidumbre. Y puede convertirlo a la zona horaria que necesite más tarde utilizando los métodos DateTimeOffset.ToUniversalTime(), DateTimeOffset.ToLocalTime().

Lo probé en algunos casos y parece que hace el trabajo a la perfección.

No estoy seguro, por qué Microsoft decidió hacer esta implementación privada, no parece requerir mucho apoyo.

Cuestiones relacionadas