I'm programming a data logging application. I need to be able to store a time interval typed in by the user using Core Data. For instance, if the user completes a task in seven minutes and twenty-three seconds, he/she can type 7:28 into the NSTextField and that will be part of the data.
What class should I use to store the time? NSDate seems to be the right way of doing it, but it does not seem to store time intervals. I see that there is an NSTimeInterval class. However, with no particular reference for it, I do not know how to use it.
Also, when this time interval is stored in objects within Core Data, I need to be able to retrieve those items and sort them (using NSSortDescriptor); in order to retrieve the entry that logged the lowest time interval. This is just additional information to help figure out what I need to do here.
From the docs: NSDate objects represent a single point in time.
From your use case it sounds like you want the user to log a relative time, and then to be able to sort by which is the smallest. In that case, NSDate is not a good option. The best solution is to just store the time interval as an NSUInteger, where the integer represents your value in seconds, and then do the appropriate conversions on either end.
If the user types in 7:28, could you convert this into seconds (448 seconds) and store it in a NSUInteger? That would make sorting it easily because you would not have to deal with separate minute and second values.
Here's what I think you should do:
Have two fields for user input: one for minutes and one for seconds.
Have some code like this:
NSInteger totalTime1 = 0;
totalTime += [minuteField.text integerValue]*60;
totalTime += [secondField.text integerValue];
Now store totalTime1 using Core Data. To retrieve the times and sort them, do something like this:
//Retrive times
NSArray *retrievedTimes = [NSArray arrayWithObjects: time1FromCoreDataAsNSNumber, time2FromCoreDataAsNSNumber, etc, nil];
NSArray *sortedRetrievedTimes = [retrievedTimes sortedArrayUsingSelector:#selector(compare:)];
//Now the array is sorted from lowest to highest
NSInteger lowestValue = [[sortedRetrievedTimes objectAtIndex:0] integerValue];
Hope this helps!
Related
Here's what I mean.
Let's say we have numerous pieces of data, and they each have a date.
a: 2017/04/20
b: 2017/04/23
c: 2017/04/29
d: 2017/05/02
e: 2017/05/04
Our goal going forward is to stop storing data this way, we only want to store aggregated data per month. So we want to aggregate data a&b&c in our example for month 04, and aggregate data d&e in month 05.
So in the end we just want 2 pieces of data.
Is it reasonable to do this in a migration, or is it not really the place, or possibly not even possible?
Essentially in [migration enumerateObjects:Data.className block:^(RLMObject *oldObject, RLMObject *newObject) { we would need to go in and figure out the month of the data, and keep a running total. We would need some command letting realm to not migrate that particular piece of data at this moment (as we don't want to until the aggregation is complete). However, the only way we know is when we move from c to d, or month 04 to 05. At that point, we know we have our running-tally/aggregated-data... and I'm guessing it's too late to now migrate.
Does anyone know if something like this is possible? I'm guessing not, it doesn't really make sense... but maybe someone out there knows that it definitely doesn't work or has a way of doing it.
Yes, you should be able to do it in a migration.
You can iterate through all of the objects in a Realm file multiple times, so it should simply be a matter of iterating through all of the objects, aggregating the values of each month, and then iterating through the list a second time to apply the new values. You can also delete objects inside migration blocks, so you could also ensure only one object per month remains:
id migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// Use a dictionary to store the aggregate values.
// Dictionary keys must be unique, so they can be used to aggregate multiple values for a month.
NSMutableDictionary *months = [[NSMutableSet alloc] init];
//Loop through each object to extract and aggregate the data for each month
[migration enumerateObjects:Data.className block:^(RLMObject *oldObject, RLMObject *newObject) {
// Extract the month value from the date in this object
NSDate *date = newObject["date"]; // Properties can be retrieved from RLMObject via KVC
NSInteger month = date.month.intValue; // Extract the month from that date
// Track if this was the first entry for this specific month
BOOL firstTime = ([months.allKeys indexOfObject:#(month)] == NSNotFound);
// Aggregate the value of month with the value stored in the dictionary
NSInteger aggregateValue = months[#(month)].intValue;
aggregateValue += date; // Add the month's information to the aggregate
months[#(month)] = #(aggregateValue);
// If this isn't the first object, we don't need it in Realm anymore, so delete it
if (!firstTime) {
[migration deleteObject:newObject];
}
}];
// At this point, `months` will contain our aggregate values, and the Realm database
// only has one object per month now.
// Loop through all the objects again so we can add in the aggregate values
[migration enumerateObjects:Data.className block:^(RLMObject *oldObject, RLMObject *newObject) {
NSDate *date = newObject["date"];
NSInteger month = date.month.intValue;
// Copy in the aggregate value
newObject["date"] = months[#(month)];
}];
}
That being said, migrations are designed for when the actual schema of your database changes. In this case, it looks like your schema isn't changing, but simply the granularity of the data you want to store is.
If that's the case, it might be more appropriate for you to write your own helper function that runs at the start of your app, checks to see if your data has been aggregated yet, and performs the aggregation if it detects it isn't.
I pass in an NSDate to the following function and want to find the NSDate in the array that is closest in time to the passed in value.
Please note that I do not want to know if the array contains the exact date like this post:
Find NSDate in sorted NSArray
I want to know which date in the array is nearest in time to my reference date.
I must do this frequently. The following works but is slow - how can I speed things up?
// Get the XYZ data nearest to the passed time
- (eciXYZ)eciDataForTime:(NSDate*)time {
// Iterate our list and find the nearest time
float nearestTime = 86400; // Maximum size of dataset ( 2 days )
int index = 0; // Track the index
int smallestDifferenceIndex = index; // Track the index with the smallest index
NSDate *lastListDate; // Track the closest list date
for ( index = 0 ; index < [self.time count]-1 ; index++ ) {
NSDate *listDate = [self.time objectAtIndex:index]; // Get the date - Time is an NSMutableArray of NSDates
// NSTimeInterval is specified in seconds; it yields sub-millisecond precision over a range of 10,000 years.
NSTimeInterval timeDifferenceBetweenDates = [listDate timeIntervalSinceDate:time];
if ( timeDifferenceBetweenDates < nearestTime && timeDifferenceBetweenDates > 0 ) {
nearestTime = timeDifferenceBetweenDates; // Update the tracker
smallestDifferenceIndex = index; // Update the smallest difference tracker
lastListDate = listDate; // Capture the closest date match
//NSLog(#"Time: %f %#",timeDifferenceBetweenDates,listDate);
}
}
Edit: I was under the mistaken impression that NSMutableOrderedSet would automatically maintain order. In the past I probably had used a subclass to achieve this effect. There is no benefit to using it over NSArray unless you want set semantics.
keeping a collection sorted is a good way to keep searches fast. use NSOrderedSet or NSMutableOrderedSet instead of an array object if you can, otherwise you will have to keep the array sorted when you add to it, or if it is only created once then you just sort it then.
Also you can enumerate any collection (that conforms to the protocol) faster by using NSFastEnumeration.
example:
// for an NSOrdered set or NSArray of NSDates
for (NSDate* date in self.times) {
// do something with date
// cleaner, shorter code too
}
because your collection is sorted you now will be able to tell what the closest date is without having to iterate the entire collection (most of the time).
// searching for date closest to Tuesday
[Sunday] <- start search here
[Monday] <- one day before
[Thursday] <- two days after, we now know that Monday is closest
[Friday] <- never have to visit
[Saturday] <- never have to visit
As pointed out by #davecom you can search faster using a binary search. Normally you would achieve this using either CFArrayBSearchValues or the indexOfObject:inSortedRange:options:usingComparator: method on NSArray (it assumes the array is sorted so beware) and passing NSBinarySearchingOptions to the options parameter. In your case this won't work because you don't know the exact object or value you are looking for. You would have to roll your own binary search algorithm.
If this is not fast enough for your purposes we may need more information on context. It may be the best idea to use a C array/C++ list/NSPointerArray of timestamps instead. I feel like your biggest slowdown here is the Objective-C overhead, especially for the dates. If you don't use these as actual date objects anywhere then surely you would be better off using timestamps.
I have a class which must have a property of some kind of timestamp, representing the moment of time when the instance of this class was created. And then when having multiple objects of this class I need to find the time interval between the creation of these objects.
And the usual interval is going to be up to 10 seconds, so I need precision of at least 1 second, but something like 0.1-0.001 second would be much better.
What is the best option to use for this property?
As far as I know, NSDate has precision up to 1 second.
I believe that I need something related to CFTimeInterval. I've used it for view animation with CADisplayLink. It provided the CFTimeInterval value for each moment of screen update and I could calculate the time interval between two CFTimeInterval's very easily.
But how do I assign the value to this CFTimeInterval at any moment of time?
NSDate will work, as Matthias Bauch's comment indicates. You can also just use CFTimeIntervals though:
CFTimeInterval currentTime = CFAbsoluteTimeGetCurrent();
Do note that this function and NSDate are both based on an absolute reference date (Jan 1, 2000 in this case). This means that if the system's clock is changed while your app is running, values obtained before the clock change won't correctly compare to values obtained afterward.
I'm developing an OS X desktop application which will track time for a car racing event.
The difference between pilots can be very small, so the collected data for each lap has a floating point value for the seconds:
bestLap = #"00:01:39.5930000"
But I need to compare each pilot's time and sort it. I'm trying to convert it to a NSDate object, using NSDateFormatter and I couldn't manage to make it work
Is it possible to convert a string like that to a NSDate? If so, how can I compare and sort an array containing NSDates
Thanks
An NSDate is used to represent a date, not a time interval.
Also, if the purpose is just to sort them, there is no need to convert the string into an NSDate or NSTimeInterval since they are already lexicographically ordered if a time interval is shorter than the other in your format.
That means, calling -sortUsingSelector: is enough.
[theMutableArrayOfLaps sortUsingSelector:#selector(compare:)];
As KennyTM says, lexicographic sorting is enough if all you need is ordering. If you really want to get a numeric value for comparison & reporting purposes, you can break the string up into components and convert to a double something like this:
NSArray* parts = [bestLap componentsSeparatedByString:#":"];
double bestLapInSeconds = [[parts objectAtIndex:0] doubleValue] * 3600 // hours
+ [[parts objectAtIndex:1] doubleValue] * 60 // minutes
+ [[parts objectAtIndex:2] doubleValue]; // seconds
(Note that this just blithely assumes that the original string conforms to an "H:M:S" format without any error checking. You should not normally do this in real life!)
The subject is vague because I'm not sure how to articulate in one sentence what I want.
Here goes:
I have an NSArray of NSDictionaries. Each NSDictionary represents one day of the calendar year. Each NSDictionary has a key "date" with a value of NSDate. There should be 365 NSDictionary items in the array. The dictionary is created by a server that I don't control, and it sometimes is missing as many as 100 days.
I need to ensure the array has 365 dictionaries, each one day later than the next.
I currently sort the array by date, iterate through it, copying the NSDictionaries from the current array to a new array. While so doing, I compare the current Dictionary's date value with the date value for the next dictionary. If there is more than one day between the two dates, I add enough new dictionaries to the new array to cover those missing days (and set their dates accordingly), then continue through.
Since the dates are supposed to ordered, I wonder if there is not already a mechanism in the framework or language that I can use to say "Here is an array, and this keypath is supposed to be consecutive. Find and create the elements that are missing, and here's a block or method you can use to initialize them".
Something about my method just feels poorly implemented, so I turn to you. Thoughts?
Thanks.
The way you did it sounds perfectly sane, and there is nothing to my knowledge that will do it automatically in the base framework.
This code will sort them.
NSArray *dates; // wherever you get this...
NSArray *sortedDates = [dates sortedArrayUsingComparator:^(id obj1, id obj2)
{
return [[obj1 valueForKey:#"date"] compare:[obj2 valueForKey:#"date"]];
}];
As for creating the missing entries, you'll have to do that yourself.
You don't need to do the sort:
Create an array with 365 (or 366) placeholder dictionaries (you can possibly use the same one for all slots, or use NSNull)
iterate through the passed in array and figure out which day each of the dictionaries is for. Place each dictionary in its rightful slot in your array.