Implement negative support, tests.

This commit is contained in:
Jan Weiß 2021-05-05 17:13:21 +02:00
parent 82098b044c
commit a280f3f69c
3 changed files with 202 additions and 30 deletions

View file

@ -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>

View file

@ -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) {

View file

@ -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
{