Implement negative support, tests.
This commit is contained in:
parent
82098b044c
commit
a280f3f69c
3 changed files with 202 additions and 30 deletions
|
@ -0,0 +1,52 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1240"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "3D42D2732642BEA0002A170C"
|
||||
BuildableName = "SecondsFormatterTests.xctest"
|
||||
BlueprintName = "SecondsFormatterTests"
|
||||
ReferencedContainer = "container:Cog.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -34,64 +34,129 @@
|
|||
if (isnan(floatValue)) { return @"NaN"; }
|
||||
if (isinf(floatValue)) { return @"Inf"; }
|
||||
|
||||
int totalSeconds = (int)floatValue;
|
||||
BOOL isNegative = floatValue < 0;
|
||||
|
||||
int totalSeconds = (int)(isNegative ? -floatValue : floatValue);
|
||||
|
||||
int seconds = totalSeconds % 60;
|
||||
int minutes = totalSeconds / 60;
|
||||
int hours = 0;
|
||||
int days = 0;
|
||||
|
||||
while(60 <= minutes) {
|
||||
while (60 <= minutes) {
|
||||
minutes -= 60;
|
||||
++hours;
|
||||
}
|
||||
|
||||
while(24 <= hours) {
|
||||
while (24 <= hours) {
|
||||
hours -= 24;
|
||||
++days;
|
||||
}
|
||||
|
||||
NSString *result = nil;
|
||||
|
||||
if(0 < days) {
|
||||
result = [NSString stringWithFormat:@"%i:%.2i:%.2i:%.2i", days, hours, minutes, seconds];
|
||||
const char *signPrefix = isNegative ? "-" : "";
|
||||
|
||||
if (0 < days) {
|
||||
result = [NSString stringWithFormat:@"%s%i:%.2i:%.2i:%.2i", signPrefix, days, hours, minutes, seconds];
|
||||
}
|
||||
else if(0 < hours) {
|
||||
result = [NSString stringWithFormat:@"%i:%.2i:%.2i", hours, minutes, seconds];
|
||||
else if (0 < hours) {
|
||||
result = [NSString stringWithFormat:@"%s%i:%.2i:%.2i", signPrefix, hours, minutes, seconds];
|
||||
}
|
||||
else if(0 < minutes) {
|
||||
result = [NSString stringWithFormat:@"%i:%.2i", minutes, seconds];
|
||||
else if (0 < minutes) {
|
||||
result = [NSString stringWithFormat:@"%s%i:%.2i", signPrefix, minutes, seconds];
|
||||
}
|
||||
else {
|
||||
result = [NSString stringWithFormat:@"0:%.2i", seconds];
|
||||
result = [NSString stringWithFormat:@"%s0:%.2i", signPrefix, seconds];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)getObjectValue:(out id _Nullable __autoreleasing *)object forString:(NSString *)string errorDescription:(out NSString * _Nullable __autoreleasing *)error
|
||||
- (BOOL)getObjectValue:(out id _Nullable __autoreleasing *)object
|
||||
forString:(NSString *)string
|
||||
errorDescription:(out NSString * _Nullable __autoreleasing *)error
|
||||
{
|
||||
NSScanner *scanner = nil;
|
||||
BOOL result = NO;
|
||||
int value = 0;
|
||||
int seconds = 0;
|
||||
NSScanner *scanner = [NSScanner scannerWithString:string];
|
||||
|
||||
scanner = [NSScanner scannerWithString:string];
|
||||
BOOL result = NO;
|
||||
|
||||
while(NO == [scanner isAtEnd]) {
|
||||
const int segmentCount = 4;
|
||||
const int lastSegment = segmentCount - 1;
|
||||
int segments[segmentCount] = {-1, -1, -1, -1};
|
||||
int lastScannedSegment = -1;
|
||||
|
||||
BOOL isNegative = NO;
|
||||
|
||||
if ([scanner isAtEnd] == NO) {
|
||||
isNegative = [scanner scanString:@"-" intoString:NULL];
|
||||
|
||||
// Grab a value
|
||||
if([scanner scanInt:&value]) {
|
||||
seconds *= 60;
|
||||
seconds += value;
|
||||
result = YES;
|
||||
int segmentIndex = 0;
|
||||
|
||||
while (NO == [scanner isAtEnd]) {
|
||||
// Grab a value
|
||||
if ([scanner scanInt:&(segments[segmentIndex])] == NO) {
|
||||
segments[segmentIndex] = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (segmentIndex == lastSegment) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Grab the separator, if present
|
||||
if ([scanner scanString:@":" intoString:NULL] == NO) {
|
||||
break;
|
||||
}
|
||||
|
||||
segmentIndex += 1;
|
||||
}
|
||||
|
||||
// Grab the separator, if present
|
||||
[scanner scanString:@":" intoString:NULL];
|
||||
lastScannedSegment = segmentIndex;
|
||||
}
|
||||
|
||||
if(result && NULL != object) {
|
||||
int seconds = 0;
|
||||
|
||||
const BOOL hasDaysSegment = (lastScannedSegment == 3);
|
||||
const BOOL hasHoursSegment = (lastScannedSegment >= 2);
|
||||
|
||||
for (int i = 0; i <= lastScannedSegment; i += 1) {
|
||||
if (segments[i] < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasDaysSegment &&
|
||||
(i == 1)) {
|
||||
// Special case for days.
|
||||
seconds *= 24;
|
||||
}
|
||||
else {
|
||||
seconds *= 60;
|
||||
}
|
||||
|
||||
const BOOL isDaysSegment = (hasDaysSegment && (i == 0));
|
||||
const BOOL isHoursSegment = (hasHoursSegment && (((lastScannedSegment == 3) && (i == 1)) || ((lastScannedSegment == 2) && (i == 0))));
|
||||
|
||||
if (isDaysSegment ||
|
||||
((isDaysSegment == NO) &&
|
||||
((isHoursSegment && (segments[i] < 24)) ||
|
||||
((isHoursSegment == NO) &&
|
||||
(segments[i] < 60))))) {
|
||||
seconds += segments[i];
|
||||
}
|
||||
else {
|
||||
result = NO;
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == 0) {
|
||||
result = YES;
|
||||
}
|
||||
}
|
||||
|
||||
if (isNegative) { seconds *= -1; }
|
||||
|
||||
if (result && NULL != object) {
|
||||
*object = [NSNumber numberWithInt:seconds];
|
||||
}
|
||||
else if(NULL != error) {
|
||||
|
|
|
@ -27,11 +27,6 @@
|
|||
[super tearDown];
|
||||
}
|
||||
|
||||
- (void)testExample {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
- (void)testPositive
|
||||
{
|
||||
NSDictionary *testsDict =
|
||||
|
@ -62,6 +57,66 @@
|
|||
}];
|
||||
}
|
||||
|
||||
- (void)testNegative
|
||||
{
|
||||
NSDictionary *testsDict =
|
||||
@{
|
||||
// key: test name, value: test string
|
||||
@"Negative One Second": @"-0:01",
|
||||
@"Negative One Minute": @"-1:00",
|
||||
@"Negative One Hour": @"-1:00:00",
|
||||
@"Negative One Day": @"-1:00:00:00",
|
||||
@"Negative One of Each": @"-1:01:01:01",
|
||||
};
|
||||
|
||||
#define TEST_INFO @"Test name: %@, Source string: %@", testName, string
|
||||
|
||||
NSFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
|
||||
|
||||
[testsDict enumerateKeysAndObjectsUsingBlock:
|
||||
^(NSString *testName, NSString *string, BOOL * _Nonnull stop) {
|
||||
NSNumber *value;
|
||||
BOOL result =
|
||||
[secondsFormatter getObjectValue:&value
|
||||
forString:string
|
||||
errorDescription:NULL];
|
||||
XCTAssertTrue(result, TEST_INFO);
|
||||
NSString *timeString = [secondsFormatter stringForObjectValue:value];
|
||||
XCTAssertEqualObjects(string, timeString, TEST_INFO);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)testMalformed
|
||||
{
|
||||
NSDictionary *testsDict =
|
||||
@{
|
||||
// key: test name, value: test string
|
||||
@"Empty String": @"",
|
||||
@"Random String": @"abc",
|
||||
@"Solitary Minus": @"-",
|
||||
@"Malformed Seconds": @"0:60",
|
||||
@"Malformed Minutes": @"60:00",
|
||||
@"Malformed Hours": @"24:00:00",
|
||||
@"Illegal #1": @":00",
|
||||
@"Illegal #2": @"-:00",
|
||||
};
|
||||
|
||||
#define TEST_INFO @"Test name: %@, Source string: %@", testName, string
|
||||
|
||||
NSFormatter *secondsFormatter = [[SecondsFormatter alloc] init];
|
||||
|
||||
[testsDict enumerateKeysAndObjectsUsingBlock:
|
||||
^(NSString *testName, NSString *string, BOOL * _Nonnull stop) {
|
||||
NSNumber *value;
|
||||
BOOL result =
|
||||
[secondsFormatter getObjectValue:&value
|
||||
forString:string
|
||||
errorDescription:NULL];
|
||||
XCTAssertFalse(result, TEST_INFO);
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
#if 0
|
||||
- (void)testPerformanceExample
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue