NSDateFormatter и строки с форматом часового пояса "+ ЧЧ: мм" - PullRequest
6 голосов
/ 12 июля 2011

Обновление

Начиная с iOS 7, NSDateFormatter действительно действительно создает NSDate, когда представлена ​​строка в следующем формате:

NSDateFormatter *formatter = [NSDateFormatter new];
[formatter setDateFormat:@"@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ""];

NSLog(@"non–nil date, even honoring the 7–minute–offset in the time–zone on iOS 7: %@",
     [formatter dateFromString:@"2011-07-12T18:07:31+02:07"]);

Для iOS 6 ответ не использовать NSDateFormatter…


Хорошо, до этого момента я прочитал

о том, как использовать NSDateFormatter для создания NSDate из строки.

Я также наткнулся на Питера Хоси ISO8601DateFormatter .
Заглядывая в его реализацию, мне интересно:

Разве не существует способа, который является правильным и вменяемым, чтобы получить строку, подобную этой 2011-07-12T18:07:31+02:00, в NSDate?

  • Не было бы проблем, если бы отсутствовала последняя двоеточие.
  • Не было бы проблем, если бы GMT имел префикс знака "+", но ...
  • это не так.

Я могу взломать , чтобы это работало для моего приложения (используя формат @"yyyy'-'MM'-'dd'T'HH':'mm':'ssz':'00"), но это - конечно - неверно , потому что это отбросит минутную информацию часовой пояс.

Я также мог бы заменить последнее двоеточие пустой строкой, но я бы тоже посчитал это взломом.

Итак, есть ли какой-нибудь секретный соус, чтобы заставить NSDateFormatter взять эту строку сверху и дать мне правильный и правильный NSDate?


За исключением:

Я где-то нашел подсказку, что можно использовать +[NSDate dateWithNaturalLanguageString:] для достижения моей цели. Это - однако - только устанавливает дату, но не время! (Хорошо, что устанавливает время, но учитывает только смещение часового пояса, а не ЧЧ: мм: сс часть ...)

Ответы [ 2 ]

2 голосов
/ 03 мая 2012

Этот вопрос немного устарел, но у меня возникла та же проблема.Я придумал некоторый код, который является ответом и может быть полезен для других ...

Я использую регулярное выражение для разбора строки ISO-8601 и захвата вывода в кучу строк, которые затем можно использовать длясоздайте свою собственную строку для передачи в NSDateFormatter (т. е. удалите двоеточия и т. д.) или, если вы всегда хотите использовать одну и ту же строку вывода, просто создайте ее по результатам вызова NSRegularExpression.

//    ISO-8601 regex: 
//        YYYY-MM-DDThh:mm[:ss[.nnnnnnn]][{+|-}hh:mm]
// Unfortunately NSDateFormatter does not parse iso-8601 out of the box,
// so we need to use a regex and build up a date string ourselves.
static const char * REGEX_ISO8601_TIMESTAMP = 
            "\\A(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2})" // Mandatory - YYYY-MM-DDThh:mm
            "(?:"
            ":(\\d{2})"                                       // Optional - :ss
            "(?:"
            "[.](\\d{1,6})"                                   // Optional - .nnnnnn
            ")?"
            ")?"
            "(?:"
            "([+-])(\\d{2}):(\\d{2})|Z"                       // Optional -[+-]hh:mm or Z
            ")?\\z";

// Extract all the parts of the timestamp
NSError *error = NULL;
NSString *regexString = [[NSString alloc] initWithUTF8String:REGEX_ISO8601_TIMESTAMP];

NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexString
                                                                       options:NSRegularExpressionCaseInsensitive
                                                                         error:&error];

NSArray *matches = [regex matchesInString:timestamp
                              options:0
                                range:NSMakeRange(0, [timestamp length])];

// Groups:
//
// elements start at 1 in the array returned from regex, as [0] contains the original string.
//
// MANDATORY - must exist as per ISO standard
//  1  - YYYY
//  2  - MM
//  3  - DD
//  4  - hh
//  5  - mm
// OPTIONAL (each one can be optional)
//  6  - ss
//  7  - nn (microseconds)
//  8  - offset sign (+/-)
//  9  - offset hour
//  10 - offset min
// put the parts into a string which will then be recognised by NSDateFormatter
// (which is acutally RFC822 format)

// mandatory init'd to nil, optional set to defaults.
NSString *YYYY, *MM, *DD, *hh, *mm, *ss, *nn, *sign, *Zhh, *Zmm;
NSRange tempRange;

for (NSTextCheckingResult *match in matches) {
    NSRange matchRange = [match range];
    NSInteger matchCount = [match numberOfRanges] - 1;
    NSUInteger idx = 1;

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        YYYY = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        MM   = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
         tempRange = [match rangeAtIndex:idx++];
         DD   = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        hh   = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        mm   = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        ss   = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        nn = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        sign = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        Zhh  = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }

    if (idx < matchCount) {
        tempRange = [match rangeAtIndex:idx++];
        Zmm  = tempRange.location != NSNotFound ? [timestamp substringWithRange:tempRange] : nil;
    }
}

Надеюсь, что этопомогает кому-то!

1 голос
/ 07 ноября 2014

Старый вопрос, но я нашел правильный ответ на чью-то суть:

https://gist.github.com/soffes/840291

Он анализирует и создает строки ISO-8601, и это быстрее, чем NSDateFormatter

Вот код:

+ (NSDate *)dateFromISO8601String:(NSString *)string {
    if (!string) {
        return nil;
    }

    struct tm tm;
    time_t t;    

    strptime([string cStringUsingEncoding:NSUTF8StringEncoding], "%Y-%m-%dT%H:%M:%S%z", &tm);
    tm.tm_isdst = -1;
    t = mktime(&tm);

    return [NSDate dateWithTimeIntervalSince1970:t + [[NSTimeZone localTimeZone] secondsFromGMT]];
}


- (NSString *)ISO8601String {
    struct tm *timeinfo;
    char buffer[80];

    time_t rawtime = [self timeIntervalSince1970] - [[NSTimeZone localTimeZone] secondsFromGMT];
    timeinfo = localtime(&rawtime);

    strftime(buffer, 80, "%Y-%m-%dT%H:%M:%S%z", timeinfo);

    return [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];
}
...