Skip to content

Commit

Permalink
Better date support
Browse files Browse the repository at this point in the history
  • Loading branch information
nicklockwood committed Mar 30, 2015
1 parent 88248bc commit 6885c74
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 88 deletions.
69 changes: 9 additions & 60 deletions React/Base/RCTConvert.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,17 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
}
#endif

/**
* This macro is used for creating simple converter functions that just call
* the specified getter method on the json value.
*/
#define RCT_CONVERTER(type, name, getter) \
RCT_CUSTOM_CONVERTER(type, name, [json getter])

/**
* This macro is used for creating converter functions with arbitrary logic.
*/
#define RCT_CONVERTER_CUSTOM(type, name, code) \
#define RCT_CUSTOM_CONVERTER(type, name, code) \
+ (type)name:(id)json \
{ \
if (json == [NSNull null]) { \
Expand All @@ -143,20 +150,13 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath);
} \
}

/**
* This macro is used for creating simple converter functions that just call
* the specified getter method on the json value.
*/
#define RCT_CONVERTER(type, name, getter) \
RCT_CONVERTER_CUSTOM(type, name, [json getter])

/**
* This macro is similar to RCT_CONVERTER, but specifically geared towards
* numeric types. It will handle string input correctly, and provides more
* detailed error reporting if a wrong value is passed in.
*/
#define RCT_NUMBER_CONVERTER(type, getter) \
RCT_CONVERTER_CUSTOM(type, type, [[self NSNumber:json] getter])
RCT_CUSTOM_CONVERTER(type, type, [[self NSNumber:json] getter])

/**
* This macro is used for creating converters for enum types.
Expand Down Expand Up @@ -189,57 +189,6 @@ RCT_CONVERTER_CUSTOM(type, type, [[self NSNumber:json] getter])
return value ? [value getter] : default; \
}

/**
* This macro is used for creating converter functions for structs that consist
* of a number of CGFloat properties, such as CGPoint, CGRect, etc.
*/
#define RCT_CGSTRUCT_CONVERTER(type, values, _aliases) \
+ (type)type:(id)json \
{ \
@try { \
static NSArray *fields; \
static NSUInteger count; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
fields = values; \
count = [fields count]; \
}); \
type result; \
if ([json isKindOfClass:[NSArray class]]) { \
if ([json count] != count) { \
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
} else { \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [self CGFloat:json[i]]; \
} \
} \
} else if ([json isKindOfClass:[NSDictionary class]]) { \
NSDictionary *aliases = _aliases; \
if (aliases.count) { \
json = [json mutableCopy]; \
for (NSString *alias in aliases) { \
NSString *key = aliases[alias]; \
NSNumber *number = json[key]; \
if (number) { \
((NSMutableDictionary *)json)[key] = number; \
} \
} \
} \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
} \
} else if (json && json != [NSNull null]) { \
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
} \
return result; \
} \
@catch (__unused NSException *e) { \
RCTLogError(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
type result; \
return result; \
} \
}

/**
* This macro is used for creating converter functions for typed arrays.
*/
Expand Down
142 changes: 114 additions & 28 deletions React/Base/RCTConvert.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,21 @@ @implementation RCTConvert
RCT_NUMBER_CONVERTER(NSInteger, integerValue)
RCT_NUMBER_CONVERTER(NSUInteger, unsignedIntegerValue)

RCT_CONVERTER_CUSTOM(NSArray *, NSArray, [NSArray arrayWithArray:json])
RCT_CONVERTER_CUSTOM(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
RCT_CUSTOM_CONVERTER(NSArray *, NSArray, [NSArray arrayWithArray:json])
RCT_CUSTOM_CONVERTER(NSDictionary *, NSDictionary, [NSDictionary dictionaryWithDictionary:json])
RCT_CONVERTER(NSString *, NSString, description)

+ (NSNumber *)NSNumber:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
return json;
} else if ([json isKindOfClass:[NSString class]]) {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
static NSNumberFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSNumberFormatter alloc] init];
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
});
NSNumber *number = [formatter numberFromString:json];
if (!number) {
RCTLogError(@"JSON String '%@' could not be interpreted as a number", json);
Expand Down Expand Up @@ -74,12 +79,35 @@ + (NSURLRequest *)NSURLRequest:(id)json
return [NSURLRequest requestWithURL:[self NSURL:json]];
}

+ (NSDate *)NSDate:(id)json
{
if ([json isKindOfClass:[NSNumber class]]) {
return [NSDate dateWithTimeIntervalSince1970:[self NSTimeInterval:json]];
} else if ([json isKindOfClass:[NSString class]]) {
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
});
NSDate *date = [formatter dateFromString:json];
if (!date) {
RCTLogError(@"JSON String '%@' could not be interpreted as a date. Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
}
return date;
} else if (json && json != [NSNull null]) {
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a date", json, [json class]);
}
return nil;
}

// JS Standard for time is milliseconds
RCT_CONVERTER_CUSTOM(NSDate *, NSDate, [NSDate dateWithTimeIntervalSince1970:[self double:json] / 1000.0])
RCT_CONVERTER_CUSTOM(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0)
RCT_CUSTOM_CONVERTER(NSTimeInterval, NSTimeInterval, [self double:json] / 1000.0)

// JS standard for time zones is minutes.
RCT_CONVERTER_CUSTOM(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])
RCT_CUSTOM_CONVERTER(NSTimeZone *, NSTimeZone, [NSTimeZone timeZoneForSecondsFromGMT:[self double:json] * 60.0])

RCT_ENUM_CONVERTER(NSTextAlignment, (@{
@"auto": @(NSTextAlignmentNatural),
Expand Down Expand Up @@ -142,7 +170,58 @@ + (NSURLRequest *)NSURLRequest:(id)json
}), UIBarStyleDefault, integerValue)

// TODO: normalise the use of w/width so we can do away with the alias values (#6566645)
RCT_CONVERTER_CUSTOM(CGFloat, CGFloat, [self double:json])
/**
* This macro is used for creating converter functions for structs that consist
* of a number of CGFloat properties, such as CGPoint, CGRect, etc.
*/
#define RCT_CGSTRUCT_CONVERTER(type, values, _aliases) \
+ (type)type:(id)json \
{ \
@try { \
static NSArray *fields; \
static NSUInteger count; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
fields = values; \
count = [fields count]; \
}); \
type result; \
if ([json isKindOfClass:[NSArray class]]) { \
if ([json count] != count) { \
RCTLogError(@"Expected array with count %zd, but count is %zd: %@", count, [json count], json); \
} else { \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [self CGFloat:json[i]]; \
} \
} \
} else if ([json isKindOfClass:[NSDictionary class]]) { \
NSDictionary *aliases = _aliases; \
if (aliases.count) { \
json = [json mutableCopy]; \
for (NSString *alias in aliases) { \
NSString *key = aliases[alias]; \
NSNumber *number = json[key]; \
if (number) { \
((NSMutableDictionary *)json)[key] = number; \
} \
} \
} \
for (NSUInteger i = 0; i < count; i++) { \
((CGFloat *)&result)[i] = [self CGFloat:json[fields[i]]]; \
} \
} else if (json && json != [NSNull null]) { \
RCTLogError(@"Expected NSArray or NSDictionary for %s, received %@: %@", #type, [json class], json); \
} \
return result; \
} \
@catch (__unused NSException *e) { \
RCTLogError(@"JSON value '%@' cannot be converted to '%s'", json, #type); \
type result; \
return result; \
} \
}

RCT_CUSTOM_CONVERTER(CGFloat, CGFloat, [self double:json])
RCT_CGSTRUCT_CONVERTER(CGPoint, (@[@"x", @"y"]), nil)
RCT_CGSTRUCT_CONVERTER(CGSize, (@[@"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
RCT_CGSTRUCT_CONVERTER(CGRect, (@[@"x", @"y", @"width", @"height"]), (@{@"w": @"width", @"h": @"height"}))
Expand Down Expand Up @@ -701,25 +780,32 @@ BOOL RCTSetProperty(id target, NSString *keyPath, SEL type, id json)
return NO;
}

// Get converted value
NSMethodSignature *signature = [RCTConvert methodSignatureForSelector:type];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&type atIndex:1];
[invocation setArgument:&json atIndex:2];
[invocation invokeWithTarget:[RCTConvert class]];
NSUInteger length = [signature methodReturnLength];
void *value = malloc(length);
[invocation getReturnValue:value];

// Set converted value
signature = [target methodSignatureForSelector:setter];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&setter atIndex:1];
[invocation setArgument:value atIndex:2];
[invocation invokeWithTarget:target];
free(value);

return YES;
@try {
// Get converted value
NSMethodSignature *signature = [RCTConvert methodSignatureForSelector:type];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&type atIndex:1];
[invocation setArgument:&json atIndex:2];
[invocation invokeWithTarget:[RCTConvert class]];
NSUInteger length = [signature methodReturnLength];
void *value = malloc(length);
[invocation getReturnValue:value];

// Set converted value
signature = [target methodSignatureForSelector:setter];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&setter atIndex:1];
[invocation setArgument:value atIndex:2];
[invocation invokeWithTarget:target];
free(value);

return YES;
}
@catch (NSException *exception) {
RCTLogError(@"Exception thrown while attempting to set property '%@' of \
'%@' with value '%@': %@", key, [target class], json, exception);
return NO;
}
}

BOOL RCTCopyProperty(id target, id source, NSString *keyPath)
Expand Down Expand Up @@ -748,7 +834,7 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath)
return NO;
}

// Get converted value
// Get value
NSMethodSignature *signature = [source methodSignatureForSelector:getter];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&getter atIndex:1];
Expand All @@ -757,7 +843,7 @@ BOOL RCTCopyProperty(id target, id source, NSString *keyPath)
void *value = malloc(length);
[invocation getReturnValue:value];

// Set converted value
// Set value
signature = [target methodSignatureForSelector:setter];
invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setArgument:&setter atIndex:1];
Expand Down

0 comments on commit 6885c74

Please sign in to comment.