2009-12-05 23 views
15

Estoy usando un NSDateFormatter para analizar una fecha RFC 822 en el iPhone. Sin embargo, no hay forma de especificar elementos opcionales en el formato de fecha. Hay un par de partes opcionales en la especificación RFC 822 que está rompiendo el analizador de fecha. Si nada funciona, probablemente tenga que escribir un analizador personalizado para obedecer las especificaciones.Analizando una fecha RFC 822 con NSDateFormatter

Por ejemplo, el nombre del día es opcional en la especificación. Así pues, estas dos fechas son válidas:

Tue, 01 Dec 2009 08:48:25 +0000 se analice con el formato EEE, dd MMM yyyy HH:mm:ss z 01 Dec 2009 08:48:25 +0000 se analice con el formato dd MMM yyyy HH:mm:ss z

Esto es lo que actualmente estoy usando:

+ (NSDateFormatter *)rfc822Formatter { 
    static NSDateFormatter *formatter = nil; 
    if (formatter == nil) { 
     formatter = [[NSDateFormatter alloc] init]; 
     NSLocale *enUS = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; 
     [formatter setLocale:enUS]; 
     [enUS release]; 
     [formatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss z"]; 
    } 
    return formatter; 
} 

+ (NSDate *)dateFromRFC822:(NSString *)date { 
    NSDateFormatter *formatter = [NSDate rfc822Formatter]; 
    return [formatter dateFromString:date]; 
} 

y el análisis de la fecha como sigue:

self.entry.published = [NSDate dateFromRFC822:self.currentString]; 

Una forma es intentar ambas formas ats, y toma lo que devuelva un valor no nulo. Sin embargo, hay dos partes opcionales en la especificación (día nombre y segundos) y habría 4 combinaciones posibles. Todavía no está mal, pero es un poco hacky.

Respuesta

4

Cuente el número de caracteres sobresalientes antes de decidir qué formateador usar. Por ejemplo, los dos que das tienen diferentes números de comas y espacios. Si no hay un formato conocido que coincida con los recuentos, entonces ni siquiera sabe intentar analizarlo como una fecha.

+0

Esto parece ser la solución más práctica para mí, dado que los nombres de mes y día son de longitud fija y todos los demás valores son de longitud fija numérica. ¡Mucho más barato que probar formatos hasta que uno funcione! –

+0

Implementado una solución básica. No muy contento con él, pero es el mejor hasta el momento :) Una coma identifica la presencia del día de la semana, y dos puntos ayudan a identificar los segundos. Hubiera sido genial si la fecha incluyera una referencia a la especificación que estaba siguiendo, ya que el análisis de fecha es realmente engorroso en muchos idiomas con la multitud de formatos. – Anurag

1

Creo que RFC 822 especifica dos componentes opcionales en el tiempo de la fecha: el día de la semana y los segundos después de la hora.

Como un truco, es posible que los símbolos de los cortos días de la semana:

NSArray *shortWeekSymbols = [NSArray arrayWithObjects:@"Sun,", @"Mon,", @"Tue,", @"Wed,", @"Thu,", @"Fri,", @"Sat,", nil]; 
     [formatter setShortWeekdaySymbols:shortWeekSymbols]; 

Si cambia el formato de fecha a esto: EEEdd MMM yyyy HH:mm:ss z. Podrá analizar el tiempo sin el día de la semana. Esto parece permitir un espacio después de la coma también.

Para estar seguro, no debe configurar ciegamente los símbolos de esta manera. Debería usar setShortWeekdaySymbols e iterar sobre ellos agregando la coma al final. La razón es que son potencialmente diferentes para cada localidad y el primer día podría no ser el domingo.

Curiosamente, el formato EEE, dd MMM yyyy HH:mm:ss z analizará los tiempos sin el día de la semana, pero la coma debe estar allí, por ejemplo , 01 Dec 2009 08:48:25 +0000. Por lo tanto, podría hacer algo como dijo Steve, pero luego quitarse el día y pasar al formateador. No tener la coma en el formato no parece permitir que la semana sea opcional. Extraño.

Lamentablemente, esto todavía no ayuda con el opcional: ss en el formato. Pero podría permitirle tener dos formatos en lugar de cuatro.

+0

Gracias por el consejo. Creo que RFC 822 no menciona la localización, y solo usa el formato en inglés. Sin embargo, es una buena idea añadir una coma en lugar de codificar los valores. Pero dado que aún necesitaría verificar dos combinaciones, probablemente sea una buena idea verificar los personajes de antemano en lugar de intentar dos veces. – Anurag

5

He utilizado el following method para analizar las fechas RFC822. Creo que originalmente era de MWFeedParser:

+ (NSDate *)dateFromRFC822String:(NSString *)dateString { 

    // Create date formatter 
    static NSDateFormatter *dateFormatter = nil; 
    if (!dateFormatter) { 
     NSLocale *en_US_POSIX = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; 
     dateFormatter = [[NSDateFormatter alloc] init]; 
     [dateFormatter setLocale:en_US_POSIX]; 
     [dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]]; 
     [en_US_POSIX release]; 
    } 

    // Process 
    NSDate *date = nil; 
    NSString *RFC822String = [[NSString stringWithString:dateString] uppercaseString]; 
    if ([RFC822String rangeOfString:@","].location != NSNotFound) { 
     if (!date) { // Sun, 19 May 2002 15:21:36 GMT 
      [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss zzz"]; 
      date = [dateFormatter dateFromString:RFC822String]; 
     } 
     if (!date) { // Sun, 19 May 2002 15:21 GMT 
      [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm zzz"]; 
      date = [dateFormatter dateFromString:RFC822String]; 
     } 
     if (!date) { // Sun, 19 May 2002 15:21:36 
      [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss"]; 
      date = [dateFormatter dateFromString:RFC822String]; 
     } 
     if (!date) { // Sun, 19 May 2002 15:21 
      [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm"]; 
      date = [dateFormatter dateFromString:RFC822String]; 
     } 
    } else { 
     if (!date) { // 19 May 2002 15:21:36 GMT 
      [dateFormatter setDateFormat:@"d MMM yyyy HH:mm:ss zzz"]; 
      date = [dateFormatter dateFromString:RFC822String]; 
     } 
     if (!date) { // 19 May 2002 15:21 GMT 
      [dateFormatter setDateFormat:@"d MMM yyyy HH:mm zzz"]; 
      date = [dateFormatter dateFromString:RFC822String]; 
     } 
     if (!date) { // 19 May 2002 15:21:36 
      [dateFormatter setDateFormat:@"d MMM yyyy HH:mm:ss"]; 
      date = [dateFormatter dateFromString:RFC822String]; 
     } 
     if (!date) { // 19 May 2002 15:21 
      [dateFormatter setDateFormat:@"d MMM yyyy HH:mm"]; 
      date = [dateFormatter dateFromString:RFC822String]; 
     } 
    } 
    if (!date) NSLog(@"Could not parse RFC822 date: \"%@\" Possibly invalid format.", dateString); 
    return date; 

} 
+0

Cambiar la cadena de formato de fecha es costoso, es posible que desee crear un formateador de fecha para cada uno y aferrarse a él durante toda la vida de la aplicación. – SomeGuy

0

En caso de que esto sea útil a alguien más .. aquí es una extensión NSDate + RFC822String.swift basado en Simucal's answer.

También almacena en caché el último formato de fecha de uso que fue exitoso, ya que establecer la fechaFormatter.dateFormat es costoso.

import Foundation 

private let dateFormatter: NSDateFormatter = { 
    let dateFormatter = NSDateFormatter() 
    dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") 
    dateFormatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) 

    return dateFormatter 
}() 

private let dateFormatsWithComma = ["EEE, d MMM yyyy HH:mm:ss zzz", "EEE, d MMM yyyy HH:mm zzz", "EEE, d MMM yyyy HH:mm:ss", "EEE, d MMM yyyy HH:mm"] 
private let dateFormatsWithoutComma = ["d MMM yyyy HH:mm:ss zzz", "d MMM yyyy HH:mm zzz", "d MMM yyyy HH:mm:ss", "d MMM yyyy HH:mm"] 

private var lastUsedDateFormatString: String? 

extension NSDate { 
    class func dateFromRFC822String(RFC822String: String) -> NSDate? { 
     let RFC822String = RFC822String.uppercaseString 

     if lastUsedDateFormatString != nil { 
      if let date = dateFormatter.dateFromString(RFC822String) { 
       return date 
      } 
     } 

     if RFC822String.containsString(",") { 
      for dateFormat in dateFormatsWithComma { 
       dateFormatter.dateFormat = dateFormat 
       if let date = dateFormatter.dateFromString(RFC822String) { 
        lastUsedDateFormatString = dateFormat 
        return date 
       } 
      } 
     } else { 
      for dateFormat in dateFormatsWithoutComma { 
       dateFormatter.dateFormat = dateFormat 
       if let date = dateFormatter.dateFromString(RFC822String) { 
        lastUsedDateFormatString = dateFormat 
        return date 
       } 
      } 
     } 

     return nil 
    } 
} 
Cuestiones relacionadas