This commit is contained in:
giuseppenuc committed 2016-02-24 16:56:39 +01:00
1 parent d281d765ea
commit 5b074a5176
1261 files changed
+199158 -7303

No files matched your search

@@ -0,0 +1,26 @@
//
// NSDate+XMPPDateTimeProfiles.h
//
// NSDate category to implement XEP-0082.
//
// Created by Eric Chamberlain on 3/9/11.
// Copyright 2011 RF.com. All rights reserved.
// Copyright 2010 Martin Morrison. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface NSDate(XMPPDateTimeProfiles)
+ (NSDate *)dateWithXmppDateString:(NSString *)str;
+ (NSDate *)dateWithXmppTimeString:(NSString *)str;
+ (NSDate *)dateWithXmppDateTimeString:(NSString *)str;
- (NSString *)xmppDateString;
- (NSString *)xmppTimeString;
- (NSString *)xmppDateTimeString;
@end
@@ -0,0 +1,80 @@
//
// NSDate+XMPPDateTimeProfiles.m
//
// NSDate category to implement XEP-0082.
//
// Created by Eric Chamberlain on 3/9/11.
// Copyright 2011 RF.com. All rights reserved.
// Copyright 2010 Martin Morrison. All rights reserved.
//
#import "NSDate+XMPPDateTimeProfiles.h"
#import "XMPPDateTimeProfiles.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
@interface NSDate(XMPPDateTimeProfilesPrivate)
- (NSString *)xmppStringWithDateFormat:(NSString *)dateFormat;
@end
#pragma mark -
@implementation NSDate(XMPPDateTimeProfiles)
#pragma mark Convert from XMPP string to NSDate
+ (NSDate *)dateWithXmppDateString:(NSString *)str {
return [XMPPDateTimeProfiles parseDate:str];
}
+ (NSDate *)dateWithXmppTimeString:(NSString *)str {
return [XMPPDateTimeProfiles parseTime:str];
}
+ (NSDate *)dateWithXmppDateTimeString:(NSString *)str {
return [XMPPDateTimeProfiles parseDateTime:str];
}
#pragma mark Convert from NSDate to XMPP string
- (NSString *)xmppDateString {
return [self xmppStringWithDateFormat:@"yyyy-MM-dd"];
}
- (NSString *)xmppTimeString {
return [self xmppStringWithDateFormat:@"HH:mm:ss'Z'"];
}
- (NSString *)xmppDateTimeString {
return [self xmppStringWithDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'Z'"];
}
#pragma mark XMPPDateTimeProfilesPrivate methods
- (NSString *)xmppStringWithDateFormat:(NSString *)dateFormat
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[dateFormatter setDateFormat:dateFormat];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
NSString *str = [dateFormatter stringFromDate:self];
return str;
}
@end
@@ -0,0 +1,17 @@
#import <Foundation/Foundation.h>
#import "NSDate+XMPPDateTimeProfiles.h"
@interface XMPPDateTimeProfiles : NSObject
/**
* The following methods attempt to parse the given string following XEP-0082.
* They return nil if the given string doesn't follow the spec.
**/
+ (NSDate *)parseDate:(NSString *)dateStr;
+ (NSDate *)parseTime:(NSString *)timeStr;
+ (NSDate *)parseDateTime:(NSString *)dateTimeStr;
+ (NSTimeZone *)parseTimeZoneOffset:(NSString *)tzo;
@end
+295
View File
@@ -0,0 +1,295 @@
#import "XMPPDateTimeProfiles.h"
#import "NSNumber+XMPP.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
static NSString * const kXEP0082SharedDateFormatterKey = @"xep0082_shared_date_formatter_key";
@interface XMPPDateTimeProfiles (PrivateAPI)
+ (NSDate *)parseDateTime:(NSString *)dateTimeStr withMandatoryTimeZone:(BOOL)mandatoryTZ;
@end
@implementation XMPPDateTimeProfiles
/**
* The following acronyms and characters are used from XEP-0082 to represent time-related concepts:
*
* CCYY four-digit year portion of Date
* MM two-digit month portion of Date
* DD two-digit day portion of Date
* - ISO 8601 separator among Date portions
* T ISO 8601 separator between Date and Time
* hh two-digit hour portion of Time (00 through 23)
* mm two-digit minutes portion of Time (00 through 59)
* ss two-digit seconds portion of Time (00 through 59)
* : ISO 8601 separator among Time portions
* . ISO 8601 separator between seconds and milliseconds
* sss fractional second addendum to Time (MAY contain any number of digits)
* TZD Time Zone Definition (either "Z" for UTC or "(+|-)hh:mm" for a specific time zone)
*
**/
+ (NSDate *)parseDate:(NSString *)dateStr
{
if ([dateStr length] < 10) return nil;
// The Date profile defines a date without including the time of day.
// The lexical representation is as follows:
//
// CCYY-MM-DD
//
// Example:
//
// 1776-07-04
NSDateFormatter *df = [self threadDateFormatter];
[df setFormatterBehavior:NSDateFormatterBehavior10_4]; // Use unicode patterns (as opposed to 10_3)
[df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[df setDateFormat:@"yyyy-MM-dd"];
NSDate *result = [df dateFromString:dateStr];
return result;
}
+ (NSDate *)parseTime:(NSString *)timeStr
{
// The Time profile is used to specify an instant of time that recurs (e.g., every day).
// The lexical representation is as follows:
//
// hh:mm:ss[.sss][TZD]
//
// The Time Zone Definition is optional; if included, it MUST be either UTC (denoted by addition
// of the character 'Z' to the end of the string) or some offset from UTC (denoted by addition
// of '[+|-]' and 'hh:mm' to the end of the string).
//
// Examples:
//
// 16:00:00
// 16:00:00Z
// 16:00:00+07:00
// 16:00:00.123
// 16:00:00.123Z
// 16:00:00.123+07:00
// Extract the current day so the result can be on the current day.
// Why do we bother doing this?
//
// First, it is rather intuitive.
// Second, if we don't we risk being on a date with a conflicting DST (daylight saving time).
//
// For example, -0800 instead of the current -0700.
// This can be rather confusing when printing the result.
NSDateFormatter *df = [self threadDateFormatter];
[df setFormatterBehavior:NSDateFormatterBehavior10_4]; // Use unicode patterns (as opposed to 10_3)
[df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[df setDateFormat:@"yyyy-MM-dd"];
NSString *today = [df stringFromDate:[NSDate date]];
NSString *dateTimeStr = [NSString stringWithFormat:@"%@T%@", today, timeStr];
return [self parseDateTime:dateTimeStr withMandatoryTimeZone:NO];
}
+ (NSDate *)parseDateTime:(NSString *)dateTimeStr
{
// The DateTime profile is used to specify a non-recurring moment in time to an accuracy of seconds (or,
// optionally, fractions of a second). The format is as follows:
//
// CCYY-MM-DDThh:mm:ss[.sss]TZD
//
// The Time Zone Definition is mandatory and MUST be either UTC (denoted by addition of the character 'Z'
// to the end of the string) or some offset from UTC (denoted by addition of '[+|-]' and 'hh:mm' to the
// end of the string).
//
// Examples:
//
// 1969-07-21T02:56:15Z
// 1969-07-20T21:56:15-05:00
// 1969-07-21T02:56:15.123Z
// 1969-07-20T21:56:15.123-05:00
return [self parseDateTime:dateTimeStr withMandatoryTimeZone:YES];
}
+ (NSDate *)parseDateTime:(NSString *)dateTimeStr withMandatoryTimeZone:(BOOL)mandatoryTZ
{
if ([dateTimeStr length] < 19) return nil;
// The DateTime profile is used to specify a non-recurring moment in time to an accuracy of seconds (or,
// optionally, fractions of a second). The format is as follows:
//
// CCYY-MM-DDThh:mm:ss[.sss]{TZD}
//
// Examples:
//
// 1969-07-21T02:56:15
// 1969-07-21T02:56:15Z
// 1969-07-20T21:56:15-05:00
// 1969-07-21T02:56:15.123
// 1969-07-21T02:56:15.123Z
// 1969-07-20T21:56:15.123-05:00
BOOL hasMilliseconds = NO;
BOOL hasTimeZoneInfo = NO;
BOOL hasTimeZoneOffset = NO;
NSInteger fractionalDigits = 0;
if ([dateTimeStr length] > 19)
{
unichar c = [dateTimeStr characterAtIndex:19];
// Check for optional milliseconds
if (c == '.')
{
hasMilliseconds = YES;
// At least one fractional digit?
if ([dateTimeStr length] < 21) return nil;
}
// Check for optional time zone info, which is at the last char (Z), or the
// char 6 chars from the end
if ([dateTimeStr characterAtIndex:[dateTimeStr length] - 1] == 'Z')
{
hasTimeZoneInfo = YES;
hasTimeZoneOffset = NO;
if (hasMilliseconds)
{
// 1969-07-21T02:56:15.1234Z -> 25 - 1 - 20 = 4
fractionalDigits = [dateTimeStr length] - 1 - 20;
}
}
else
{
c = [dateTimeStr characterAtIndex:[dateTimeStr length] - 6];
if (c == '+' || c == '-')
{
hasTimeZoneInfo = YES;
hasTimeZoneOffset = YES;
if (hasMilliseconds)
{
// 1969-07-21T02:56:15.1234+00:00 -> 30 - 6 - 20 = 4
fractionalDigits = [dateTimeStr length] - 6 - 20;
}
}
else if (hasMilliseconds)
{
// 1969-07-21T02:56:15.1234 -> 24 - 20 = 4
fractionalDigits = [dateTimeStr length] - 20;
}
}
}
if (mandatoryTZ && !hasTimeZoneInfo) return nil;
NSDateFormatter *df = [self threadDateFormatter];
[df setFormatterBehavior:NSDateFormatterBehavior10_4]; // Use unicode patterns (as opposed to 10_3)
[df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[df setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss"];
NSDate *result = nil;
NSString *dateAndTime = [dateTimeStr substringToIndex:19];
NSString *fraction = fractionalDigits != 0 ? [NSString stringWithFormat:@"0%@", [dateTimeStr substringWithRange:NSMakeRange(19, fractionalDigits + 1)]] : nil;
if (hasTimeZoneInfo && !hasTimeZoneOffset)
{
[df setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
result = [df dateFromString:dateAndTime];
}
else if (hasTimeZoneInfo && hasTimeZoneOffset)
{
NSString *timeZone = [dateTimeStr substringFromIndex:[dateTimeStr length] - 6];
NSTimeZone *tz = [self parseTimeZoneOffset:timeZone];
if (tz == nil)
{
result = nil;
}
else
{
[df setTimeZone:tz];
result = [df dateFromString:dateAndTime];
}
}
else
{
result = [df dateFromString:dateAndTime];
}
if (result && fraction)
{
static NSNumberFormatter *numberFormatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setDecimalSeparator:@"."];
});
NSTimeInterval fractionInterval = [[numberFormatter numberFromString:fraction] doubleValue];
NSTimeInterval current = [result timeIntervalSinceReferenceDate];
result = [NSDate dateWithTimeIntervalSinceReferenceDate:floor(current) + fractionInterval];
}
return result;
}
+ (NSTimeZone *)parseTimeZoneOffset:(NSString *)tzo
{
// The tzo value is supposed to start with '+' or '-'.
// Spec says: (+-)hh:mm
//
// hh : two-digit hour portion (00 through 23)
// mm : two-digit minutes portion (00 through 59)
if ([tzo length] != 6)
{
return nil;
}
NSString *hoursStr = [tzo substringWithRange:NSMakeRange(1, 2)];
NSString *minutesStr = [tzo substringWithRange:NSMakeRange(4, 2)];
NSUInteger hours;
if (![NSNumber xmpp_parseString:hoursStr intoNSUInteger:&hours])
return nil;
NSUInteger minutes;
if (![NSNumber xmpp_parseString:minutesStr intoNSUInteger:&minutes])
return nil;
if (hours > 23) return nil;
if (minutes > 59) return nil;
NSInteger secondsOffset = (NSInteger)((hours * 60 * 60) + (minutes * 60));
if ([tzo hasPrefix:@"-"])
{
secondsOffset = -1 * secondsOffset;
}
else if (![tzo hasPrefix:@"+"])
{
return nil;
}
return [NSTimeZone timeZoneForSecondsFromGMT:secondsOffset];
}
+ (NSDateFormatter *)threadDateFormatter {
NSMutableDictionary *currentThreadStorage = [[NSThread currentThread] threadDictionary];
NSDateFormatter *sharedDateFormatter = currentThreadStorage[kXEP0082SharedDateFormatterKey];
if (!sharedDateFormatter) {
sharedDateFormatter = [NSDateFormatter new];
currentThreadStorage[kXEP0082SharedDateFormatterKey] = sharedDateFormatter;
}
return sharedDateFormatter;
}
@end