Permalink
Browse files

Validators for credit cards; Date util methods

  • Loading branch information...
1 parent 72b63be commit 1229348f3b74521fcc1e9c2027f6bc1f48a72edf @gabriel committed May 10, 2011
View
@@ -111,9 +111,9 @@ extern NSString *const kDateFormatShortMonthFullYearTime;
timeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]];
@endcode
- @param day Day to set
- @param month Month to set
- @param year Year to set
+ @param day Day to set (if 0, uses current day)
+ @param month Month to set (if 0, uses current month)
+ @param year Year to set (if 0, uses current year)
@param addDay Days to add
@param addMonth Month to add
@param addYear Year to add
@@ -125,6 +125,79 @@ extern NSString *const kDateFormatShortMonthFullYearTime;
timeZone:(NSTimeZone *)timeZone;
/*!
+ Create date with day, month, year.
+
+ To use the current day, month or year, specify 0 for that value.
+
+ For example, the use Jan, 1 (current year):
+
+ @code
+ [NSDate gh_dateWithDay:1 month:1 year:0 timeZone:nil];
+ @endcode
+
+ @param day Day to set (if 0, uses current day)
+ @param month Month to set (if 0, uses current month)
+ @param year Year to set (if 0, uses current year)
+ @result Date
+ */
++ (NSDate *)gh_dateWithDay:(NSInteger)day month:(NSInteger)month year:(NSInteger)year
+ timeZone:(NSTimeZone *)timeZone;
+
+/*!
+ Date components for date.
+ @param flags Flags, e.g. NSMonthCalendarUnit, or NSMonthCalendarUnit | NSYearCalendarUnit
+ @param timeZone
+ @result Date components
+ */
+- (NSDateComponents *)gh_dateComponentsFromFlags:(NSUInteger)flags timeZone:(NSTimeZone *)timeZone;
+
+/*!
+ Month symbols.
+ @param format MM for numeric, or MMMM for full string.
+ */
++ (NSArray *)gh_monthSymbolsForFormat:(NSString *)format;
+
+/*!
+ Day of date.
+ @result Day
+ */
+- (NSInteger)gh_day;
+
+/*!
+ Day of date.
+ @param timeZone
+ @result Day
+ */
+- (NSInteger)gh_dayForTimeZone:(NSTimeZone *)timeZone;
+
+/*!
+ Month of date.
+ @result Year
+ */
+- (NSInteger)gh_month;
+
+/*!
+ Month of date.
+ @param timeZone
+ @result Month
+ */
+- (NSInteger)gh_monthForTimeZone:(NSTimeZone *)timeZone;
+
+/*!
+ Year of date.
+ @result Year
+ */
+- (NSInteger)gh_year;
+
+/*!
+ Year of date.
+ @param timeZone
+ @result Year
+ */
+- (NSInteger)gh_yearForTimeZone:(NSTimeZone *)timeZone;
+
+
+/*!
Time ago in words.
For more info, especially on localization, see GHNSString+TimeInterval.h.
View
@@ -65,6 +65,11 @@ + (NSDate *)gh_dateFromDate:(NSDate *)date addDay:(NSInteger)addDay normalize:(B
normalize:normalize timeZone:nil];
}
++ (NSDate *)gh_dateWithDay:(NSInteger)day month:(NSInteger)month year:(NSInteger)year
+ timeZone:(NSTimeZone *)timeZone {
+ return [self gh_dateWithDay:day month:month year:year addDay:0 addMonth:0 addYear:0 timeZone:timeZone];
+}
+
+ (NSDate *)gh_dateWithDay:(NSInteger)day month:(NSInteger)month year:(NSInteger)year
addDay:(NSInteger)addDay addMonth:(NSInteger)addMonth addYear:(NSInteger)addYear
timeZone:(NSTimeZone *)timeZone {
@@ -73,6 +78,46 @@ + (NSDate *)gh_dateWithDay:(NSInteger)day month:(NSInteger)month year:(NSInteger
}
+- (NSDateComponents *)gh_dateComponentsFromFlags:(NSUInteger)flags timeZone:(NSTimeZone *)timeZone {
+ NSCalendar *calendar = [NSCalendar currentCalendar];
+ if (timeZone) {
+ [calendar setTimeZone:timeZone];
+ }
+ return [calendar components:flags fromDate:self];
+}
+
+- (NSInteger)gh_day {
+ return [self gh_dayForTimeZone:nil];
+}
+
+- (NSInteger)gh_dayForTimeZone:(NSTimeZone *)timeZone {
+ return [[self gh_dateComponentsFromFlags:NSDayCalendarUnit timeZone:timeZone] day];
+}
+
+- (NSInteger)gh_month {
+ return [self gh_monthForTimeZone:nil];
+}
+
+- (NSInteger)gh_monthForTimeZone:(NSTimeZone *)timeZone {
+ return [[self gh_dateComponentsFromFlags:NSMonthCalendarUnit timeZone:timeZone] month];
+}
+
+- (NSInteger)gh_year {
+ return [self gh_yearForTimeZone:nil];
+}
+
+- (NSInteger)gh_yearForTimeZone:(NSTimeZone *)timeZone {
+ return [[self gh_dateComponentsFromFlags:NSYearCalendarUnit timeZone:timeZone] year];
+}
+
++ (NSArray *)gh_monthSymbolsForFormat:(NSString *)format {
+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
+ [dateFormatter setDateFormat:format];
+ NSArray *monthSymbols = [dateFormatter standaloneMonthSymbols];
+ [dateFormatter release];
+ return monthSymbols;
+}
+
- (NSDate *)gh_addDays:(NSInteger)days {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [calendar components:kUnitFlags fromDate:self];
View
@@ -43,4 +43,20 @@
*/
+ (BOOL)isEmailAddress:(NSString *)str;
+/*!
+ Check if credit card number passes Luhn validation.
+ @param numberString Credit card number as string
+ @result YES if passes luhn validation
+ */
++ (BOOL)isCreditCardNumber:(NSString *)numberString;
+
+/*!
+ Check if string is a valid credit card expiration.
+ Accept 1/12, 01/12, or 01/2012, or 01/10/2012.
+ @param expiration
+ @param date Current date, fails if expiration < this date
+ @result YES if passes validation
+ */
++ (BOOL)isCreditCardExpiration:(NSString *)expiration date:(NSDate *)date;
+
@end
View
@@ -27,6 +27,7 @@
//
#import "GHValidators.h"
+#import "GHNSDate+Utils.h"
//! @cond DEV
@@ -54,4 +55,59 @@ + (BOOL)isEmailAddress:(NSString *)str {
return [regex matchesString:str];
}
++ (BOOL)isCreditCardNumber:(NSString *)numberString {
+ NSInteger digitsLength = [numberString length];
+ if (digitsLength == 0) return NO;
+ NSInteger digitsArray[digitsLength];
+ for (NSInteger i = 0; i < digitsLength; i++) {
+ NSString *digitString = [numberString substringWithRange:NSMakeRange(i, 1)];
+ // Check for 0 manually since integerValue returns 0 on failure
+ if ([digitString isEqual:@"0"]) {
+ digitsArray[i] = 0;
+ } else {
+ NSInteger digit = [digitString integerValue];
+ if (digit == 0) return NO;
+ digitsArray[i] = digit;
+ }
+ }
+
+ for (NSInteger i = digitsLength - 2; i > -1; i-=2) {
+ digitsArray[i] *= 2;
+ if (digitsArray[i] > 9) {
+ digitsArray[i] -= 9;
+ }
+ }
+
+ NSInteger total = 0;
+ for (NSInteger i = 0; i < digitsLength; i++) {
+ total += digitsArray[i];
+ }
+
+ return (total % 10 == 0);
+}
+
++ (BOOL)isCreditCardExpiration:(NSString *)expiration date:(NSDate *)date {
+ if (!expiration) return NO;
+ NSArray *split = [expiration componentsSeparatedByString:@"/"];
+ if ([split count] != 2) return NO;
+ NSInteger month = [[split objectAtIndex:0] integerValue];
+ NSInteger year = [[split objectAtIndex:1] integerValue];
+ if (month <= 0 || year <= 0) return NO;
+ if (month > 12) return NO;
+
+ if (year >= 0 && year < 100) year += 2000; // TODO(gabe): Y3K
+
+ if (date) {
+ // Check >= date.
+ // Comparing date components like this isn't completely precise across time zones
+ // so we ignore the first day of the month.
+ NSInteger currentYear = [date gh_year];
+ if (year < currentYear) return NO;
+ if (year == currentYear && month < [date gh_month] && [date gh_day] != 1) {
+ return NO;
+ }
+ }
+ return YES;
+}
+
@end
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 000A90D113723B58007B9A51 /* GHValidatorsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 000A90D013723B58007B9A51 /* GHValidatorsTest.m */; };
000ACD3C0F69B2770086708A /* GHNSDictionary+Utils.h in Headers */ = {isa = PBXBuildFile; fileRef = 000ACD3A0F69B2770086708A /* GHNSDictionary+Utils.h */; settings = {ATTRIBUTES = (Public, ); }; };
000ACD3D0F69B2770086708A /* GHNSDictionary+Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = 000ACD3B0F69B2770086708A /* GHNSDictionary+Utils.m */; };
000ACD460F69B34D0086708A /* GHNSMutableDictionary+Utils.h in Headers */ = {isa = PBXBuildFile; fileRef = 000ACD440F69B34D0086708A /* GHNSMutableDictionary+Utils.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -156,6 +157,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 000A90D013723B58007B9A51 /* GHValidatorsTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GHValidatorsTest.m; sourceTree = "<group>"; };
000ACD3A0F69B2770086708A /* GHNSDictionary+Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GHNSDictionary+Utils.h"; sourceTree = "<group>"; };
000ACD3B0F69B2770086708A /* GHNSDictionary+Utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GHNSDictionary+Utils.m"; sourceTree = "<group>"; };
000ACD440F69B34D0086708A /* GHNSMutableDictionary+Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GHNSMutableDictionary+Utils.h"; sourceTree = "<group>"; };
@@ -577,6 +579,7 @@
003ED04C110E656100D57720 /* GHReversableDictionaryTest.m */,
00D17046116AA84800D025EF /* GHNSStringXMLTest.m */,
002D0837135150F00002DFFA /* GHNSAttributedStringTest.m */,
+ 000A90D013723B58007B9A51 /* GHValidatorsTest.m */,
);
path = Tests;
sourceTree = "<group>";
@@ -787,6 +790,7 @@
00D17047116AA84800D025EF /* GHNSStringXMLTest.m in Sources */,
002D0831135149400002DFFA /* GHNSAttributedString+Utils.m in Sources */,
002D0838135150F00002DFFA /* GHNSAttributedStringTest.m in Sources */,
+ 000A90D113723B58007B9A51 /* GHValidatorsTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
- <key>CFBundleDevelopmentRegion</key>
- <string>English</string>
- <key>CFBundleExecutable</key>
- <string>${EXECUTABLE_NAME}</string>
- <key>CFBundleIdentifier</key>
- <string>com.yourcompany.${PRODUCT_NAME:identifier}</string>
- <key>CFBundleInfoDictionaryVersion</key>
- <string>6.0</string>
- <key>CFBundlePackageType</key>
- <string>APPL</string>
- <key>CFBundleSignature</key>
- <string>????</string>
- <key>CFBundleVersion</key>
- <string>1.0</string>
-</dict>
-</plist>
View
@@ -7,24 +7,57 @@
//
#import "GHValidators.h"
+#import "GHNSDate+Utils.h"
+
@interface GHValidatorsTest : GHTestCase { }
@end
@implementation GHValidatorsTest
- (void)testValidateEmail {
- NSString *valid = @"test@domain.com";
- GHAssertTrue([GHValidators isEmailAddress:valid], @"Should be valid email");
+ GHAssertTrue([GHValidators isEmailAddress:@"test@domain.com"], @"Should be valid");
+ GHAssertFalse([GHValidators isEmailAddress:@"foo"], @"Should be invalid");
+ GHAssertFalse([GHValidators isEmailAddress:@""], @"Should be invalid");
+ GHAssertFalse([GHValidators isEmailAddress:nil], @"Should be invalid");
+ GHAssertFalse([GHValidators isEmailAddress:@"~gabrielh@gmail.com"], @"Should be invalid");
+ GHAssertTrue([GHValidators isEmailAddress:@"gabrielh@gmail.commmmmmm"], @"Should be valid");
+}
+
+- (void)testValidateCreditCard {
+ GHAssertFalse([GHValidators isCreditCardNumber:@" "], @"Should be invalid");
+ GHAssertFalse([GHValidators isCreditCardNumber:@""], @"Should be invalid");
+ GHAssertFalse([GHValidators isCreditCardNumber:nil], @"Should be invalid");
+ GHAssertTrue([GHValidators isCreditCardNumber:@"49927398716"], @"Should be valid");
+ GHAssertFalse([GHValidators isCreditCardNumber:@"1234"], @"Should be invalid");
+ GHAssertFalse([GHValidators isCreditCardNumber:@"abc"], @"Should be invalid");
+}
+
+- (void)testValidateCreditCardExpiration {
+ NSDate *date = [NSDate gh_dateWithDay:1 month:12 year:2011 timeZone:nil];
+
+ // Valid
+ GHAssertTrue([GHValidators isCreditCardExpiration:@"01/12" date:date], @"Should be valid");
+ GHAssertTrue([GHValidators isCreditCardExpiration:@"1/12" date:date], @"Should be valid");
+ GHAssertTrue([GHValidators isCreditCardExpiration:@"1/2012" date:date], @"Should be valid");
+ GHAssertTrue([GHValidators isCreditCardExpiration:@"01/12" date:date], @"Should be valid");
- NSString *invalid = @"foo";
- GHAssertFalse([GHValidators isEmailAddress:invalid], @"Should be invalid email");
+ // Empty
+ GHAssertFalse([GHValidators isCreditCardExpiration:@" " date:date], @"Should be invalid");
+ GHAssertFalse([GHValidators isCreditCardExpiration:@"" date:date], @"Should be invalid");
+ GHAssertFalse([GHValidators isCreditCardExpiration:nil date:date], @"Should be invalid");
+
+ // Expired but valid on the one day
+ GHAssertTrue([GHValidators isCreditCardExpiration:@"11/11" date:date], @"Should be valid");
+ NSDate *nextDay = [NSDate gh_dateWithDay:2 month:12 year:2011 timeZone:nil];
+ GHAssertFalse([GHValidators isCreditCardExpiration:@"11/11" date:nextDay], @"Should be invalid");
- NSString *invalid2 = @"~gabrielh@gmail.com";
- GHAssertFalse([GHValidators isEmailAddress:invalid2], @"Should be invalid email");
+ // Invalid month or year
+ GHAssertFalse([GHValidators isCreditCardExpiration:@"13/12" date:date], @"Should be invalid");
+ GHAssertFalse([GHValidators isCreditCardExpiration:@"0/12" date:date], @"Should be invalid");
- NSString *valid3 = @"gabrielh@gmail.commmmmmm";
- GHAssertTrue([GHValidators isEmailAddress:valid3], @"Should be valid email");
+ // Expired
+ GHAssertFalse([GHValidators isCreditCardExpiration:@"1/2010" date:date], @"Should be invalid");
}
@end
View
@@ -46,6 +46,18 @@ - (void)testDate {
GHAssertEqualStrings(dateString, @"1/1/81", nil);
}
+- (void)testComponents {
+ NSDate *date = [NSDate gh_dateWithDay:1 month:2 year:2012 timeZone:nil];
+ GHAssertTrue([date gh_day] == 1, nil);
+ GHAssertTrue([date gh_month] == 2, nil);
+ GHAssertTrue([date gh_year] == 2012, nil);
+}
+
+- (void)testMonthSymbolsForFormat {
+ NSArray *monthSymbols = [NSDate gh_monthSymbolsForFormat:@"MMMM"];
+ GHTestLog([monthSymbols description]);
+}
+
- (void)testMillisSince1970 {
NSDateComponents *comps = [[[NSDateComponents alloc] init] autorelease];
[comps setDay:13];

0 comments on commit 1229348

Please sign in to comment.