Sort NSFetchedResultsController by closest date ignoring year - ios

I am using Core Data with an NSFetchedResultsController to display dates in a table view. Upon fetch I want these date objects sorted by the closest upcoming month/day to today while ignoring the year, preferably (if possible) without having to store the fetched results in an array and being forced to perform a separate sort operation on that array. That's the basic idea, the rest is context.
My NSManagedObject subclass Date has 2 attributes:
Date.date -> 1986-02-06 08:00:00 +0000 // the stored date of type Date
Date.equalizedDate -> 02/06 // computed from date ignoring year of type String
If I use NSSortDescriptor(key: "date", ascending: true) on the fetched results controller, then the dates in the table view are sorted by year (not what I want):
Date.date 1978-06-23 07:00:00 +0000 // 1978 = #1
Date.date 1986-02-06 08:00:00 +0000 // 1986 = #2
Date.date 1991-07-26 07:00:00 +0000 // 1991 = #3
If I use NSSortDescriptor(key: "equalizedDate", ascending: true) I can get one step closer by ignoring the year and showing proper ascension but it's not compared to today's date:
Date.date 1986-02-06 08:00:00 +0000 // 02/06 = #1 (February 6th)
Date.date 1978-06-23 07:00:00 +0000 // 06/23 = #2 (June 23rd)
Date.date 1991-07-26 07:00:00 +0000 // 07/26 = #3 (July 26th)
If, for example, today was April 5th, then I would want the fetched results to be fetched and displayed as follows:
// Today is April 5th
Date.date 1978-06-23 07:00:00 +0000 // 06/23 = #1 (June 23rd)
Date.date 1991-07-26 07:00:00 +0000 // 07/26 = #2 (July 26th)
Date.date 1986-02-06 08:00:00 +0000 // 02/06 = #3 (February 6th)
Currently, the only way for me to accomplish this is by storing the fetched results controller results in an array, performing a comparison upon the array, and using the array instead of my fetched results controller for the data source of my table view rows and cells:
// fetchedResults = [Date] <- stored Date objects from fetch
for fetchedDate in fetchedResults {
// formatDateIntoString changes 2015-10-26 05:43:22 +0000 into 10/26
if fetchedDate.equalizedDate < NSDate().formatDateIntoString() {
fetchedResults.removeAtIndex(0)
fetchedResults.append(fetchedDate)
} else {
break
}
}
return fetchedResults
I would prefer to omit this extra layer of logic because it not only eliminates the entire purpose of performance of a fetched results controller but it also gives me wonky troubles and wrong index paths when implementing swipe to delete (link for reference). I know that comparators and selectors don't work for fetched result controllers.
Is there any way to work around this issue?

If you do not care about sections, an easy approach is to keep your sorting for equalizedDate (if you have lots of dates, make sure you set that attribute to be indexed). You may also want to consider changing it to an integer rather than a string.
The array will be sorted from 01/01 to 12/31.
You can then, after the initial fetch, do another quick fetch to find the object closest to today's date.
That object now becomes your point of reference (or startingIndex).
In your FRC delegate, just use the starting index as the offset.
So, when asked to return the objet at index 0, you return a value based on the starting index.
something like...
NSUInteger actualIndex = (index + startingIndex) % count;
It's a little more complicated if you are using sections, but not much.
This way, you don't have to do anything special for sorting. It's also easy to change when the date changes in your app.

I think an easier solution is to simply modify your equalizedDate attribute as follows:
First, make it into a transient property.
Second, make it behave dynamically according to the current date. You can make this attribute very simple, but would then need to write more code to interpret it, or you can try to return something you can use directly.
A simple solution is to return just the offset from todays date (a number from 0 to 365). You would have to add functionality to return the correct date string (or date) based in this number and today's date.
An IMO better solution is to return a date with with a correctly normalized year. You would set the current and future dates to the current year, and all past dates to the next year, also resulting in 365 (366) dates.
You could now easily sort by the transient property, or even use it for sectioning.

Related

Check if the value is in range between closest array's elements. (ruby)

I need to know how would I check if my given value is between two closest array's members. For example I have an array of dates with the date of week start in given period of time. And I need to check if my given date is in one of its week. For example:
2015-11-02
2015-11-09
2015-11-16
2015-11-23
And my given value is 2015-11-11 for example. How should I check if it is one of these weeks date? Thanks for help.
%w(2015-11-02 2015-11-09 2015-11-16 2015-11-23).any? do |date|
date.to_date.cweek == Date.today.cweek
end
And here is what this does:
First, you have an array of strings, you use any? to loop through it and check if any fulfils a requirement, then you cast you date strings into actual dates, and cweek gives you the number of a week in the year. Date.today gives you today's date.
Instead of Date.today.cweek you can use '2015-11-11'.to_date.cweek.
The loop above returns boolean; you could also get an array of values that fulfil a condition like this:
new_array = %w(2015-11-02 2015-11-09 2015-11-16 2015-11-23).map do |date|
date.to_date.cweek == '2015-11-11'.to_date.cweek
end.compact
Resources:
Date class on ruby-doc.org
Date & Time in Ruby on tutorialspoint.com
UPDATE
If you want to get from the database only records with a date from particular week, this is how you could do it:
my_date = '2015-11-11'.to_date
matching_records = MyResource.where( date: my_date.beginning_of_week..my_date.end_of_week )
The assumptions are that you have a model MyResource, and that it has a column date. What this does is returns a relation with all the records that have dates from the same week as my_date.
Assuming your dates array is sorted:
date >= dates.first && date <= dates.last
If you're dealing with strings, you can "require 'date'" and transform your strings to dates ("Date.parse('2001-02-03')").
As others have suggested, you can then see if your date is between the first and last entry of your list.
If the real list is sorted and each entry is one week apart, then you can easily find where in the list your guy is.
E.g., say the list is [date_0, date_1, date_2, ..., date_k] (all 1 week apart), and you're given a test_date between date_0 and date_k. Then (test_date.jd - date_0.jd)/7 gives you the index of the date in your list that is <= test_date.

cannot return a utc datetime with moment.js

trying to pass a datetime object that has already been converted to UTC by momentjs to my MVC controller. I'm using a kendo datetimepicker, and for some reason, moment.utc just will not send the controller the value that i want. For example, i type in '9/17/2015 12:00 AM' into my kendo datetimepicker. I get this value like:
var start = $("#startTime").val();
which gives me "9/17/2015 12:00 AM" . great. then i convert this to a date object:
var t1 = new Date(start);
which reads as Thu Sep 17 2015 00:00:00 GMT-0700 (Pacific Daylight Time) {} . Still looking good. Next i try to convert to UTC with moment using:
var t2 = moment.utc(t1);
this gives me
dt {_isAMomentObject: true, _i: Thu Sep 17 2015 00:00:00 GMT-0700 (Pacific Daylight Time), _isUTC: true, _locale: fu, _d: Thu Sep 17 2015 00:00:0...
did NOT convert to utc. instead of converting all it seemed to do was take a date object and let me tell it 'hey, this is utc' and it said 'ok' (by flagging _isUTC:true), even though it's still saving the GMT value in there
even if i decided to make a moment object first, and then run UTC on it? it still comes out the same:
var t2 = moment(t1);
var t3 = moment.utc(t2);
So either of those values, if i run .format() on them (which is what i need to pass to my controller), i always get "2015-09-17T07:00:00+00:00", which is NOT the UTC time. It's the exact time i typed in. What am i doing wrong here?
A few things:
Don't even look at the fields prefixed with underscores. They're part of the internal design of moment.js, and not meant for direct consumption. In many cases, several of the fields have to be combined to get the correct results. This is accounted for in the functions of the public API, such as format.
Don't rely on the Date object to do your parsing. Results can be inconsistent across browsers. Moment has its own parser, which you can use like this:
moment("9/17/2015 12:00 AM","M/D/YYYY h:mm A")
However, in your particular case, you actually don't need to parse any string at all. You said you're using Kendo's DateTimePicker control, so you should use the value function, which already returns a Date object.
Moment has two different functions for working with UTC.
moment.utc(value) - interprets values in terms of UTC
m.utc() - where m is any moment instance, converts the value to UTC
Note that the second one it mutates the existing instance by switching it from "local mode" to "UTC mode". It also returns the instance if you want to chain functions, but it does modify the original instance as well.
You can use format after converting to UTC if you want the output to show the +00:00 offset. However, if you want to show Z (which is usually preferred), you you can just call .toISOString() without explicitly going to UTC first - since that function always outputs UTC.
Additionally, most modern browsers already support .toISOString() directly on the Date object, so unless you're targeting older browsers, you might not need moment at all.
You said "2015-09-17T07:00:00+00:00" was not the UTC time, but actually it is. You started with 00:00 in UTC-7, which is equivalent to 7:00 in UTC+0. So despite jumping through several unnecessary steps, moment still got it right in the end - at least with t3.
You can simplify your code with any of these:
var picker = $("#startTime").data().kendoDateTimePicker;
var dt = picker.value(); // dt is a Date object
var m = moment(dt); // m is a moment object
m.utc(); // m has been converted to UTC
var s = m.format(); // ex: "2015-09-17T07:00:00+00:00"
Or...
var picker = $("#startTime").data().kendoDateTimePicker;
var dt = picker.value(); // dt is a Date object
var m = moment(dt); // m is a moment object
var s = m.toISOString(); // ex: "2015-09-17T07:00:00Z"
Or...
var picker = $("#startTime").data().kendoDateTimePicker;
var dt = picker.value(); // dt is a Date object
var s = dt.toISOString(); // ex: "2015-09-17T07:00:00Z" (requires browser support)

NSFetchRequest search for all objects from last Nth date

My data looks like:
objA date1 objnumber2
objB date2 objnumber1
objC date2 objnumber4
objD date2 objnumber3
objE date1 objnumber7
objF date3 objnumber6
objG date1 objnumber5
I am looking for all the objects which are from the Nth last date. The result objects need to be sorted using objnumber (this should be easy using NSSortDescriptor).
So if I specify N=1 (most recent date), I should get [objF] only. (date3 is most recent)
If N=3 (oldest date), I should get sorted [objA, objG, objF]
The sorting part is easy.
My question is do I really need to firstly search for the latest date (using combination of sortdescriptor and nsfetchrequest searchLimit) in the entire data? Then do a second search to find all objects from that date (using nspredicate) and sort it?
Or is there a better way to perform this type of search? How would you generalize this for Nth date instead of last date? That would be a big performance hit no??
NOTE that the date is not known beforehand.
Edit2: okay this is even more complicated since I am using NSDate. So pretty much all the objects have unique dates lol. Gotta throw nsdateformatter in there in the mix too :(
I figured out a solution to my problem.
Instead of using the date, I added a new Int64 NSNumber attribute. Everyday the obj is added, all the objects are tagged with that number for that particular day.
For retriving all objects from Nth day, I firstly do a "fetchlimit 1" nsfetchrequest for that number in a sorted fetchrequest. That gives me the last number.
Now for the Nth last objects, I subtract N from the last number. Then I perform a "nspredicted" nsfetchrequest for all objects with that number attribute. Then I simply just sort the result array.
This resolves my question :D

Timezone Offset in Angular JS and Rails

Background: I'm building an app with Angular JS as web interface and Rails API. The problem I am having is passing a date from Angular to Rails.
Issue: I have a form with a Date of Birth date field, when a user inputs his DOB say March 1st, 1985, Angular interprets it as 1985-03-01 00:00 +0800 (if you're in Hong Kong or Singapore) and sends a request to Rails. The first thing Rails does with it is to convert it to UTC, which means the datetime is now 1985-02-28 16:00 UTC. Therefore, when the date is saved to the database date column, it becomes Feb 28, 1985.
Solution for now: What I'm doing now is on Angular side, I get the Timezone offset hours and add it to the date, so instead of 1985-03-01 00:00 +0800, it is now 1985-03-01 08:00 +0800. When Rails get it, it converts to 1985-03-01 00:00 UTC and so saves the correct date to db. However, I believe this is a better alternative to tackle this issue.
Thinking about parsing just the date in Rails, yet the params[:dob] I see is already UTC by the time I get it. Would love to know if there is a better practice than my current solution. Thank you for any comment and feedback.
This problem is actually quite common, and stems from two separate but related issues:
The JavaScript Date object is misnamed. It's really a date + time object.
The JavaScript Date object always takes on the characteristics of the time zone for the environment in which it is running in.
For a date-only value like date-of-birth, the best solution to this problem is to not send a full timestamp to your server. Send just the date portion instead.
First, add 12 hours to the time, to use noon instead of midnight. This is to avoid issues with daylight saving time in time zones like Brazil, where the transition occurs right at midnight. (Otherwise, you may run into edge cases where the DOB comes out a day early.)
Then output the date portion of the value, as a string in ISO format (YYYY-MM-DD).
Example:
var dt = // whatever Date object you get from the control
dt.setHours(dt.getHours() + 12); // adjust to noon
var pad = function(n) { return (n < 10 ? '0' : '') + n; }
var dob = dt.getFullYear() + '-' + pad(dt.getMonth()+1) + '-' + pad(dt.getDate());
Another common way to do this is:
var dt = // whatever Date object you get from the control
dt.setHours(dt.getHours() + 12); // adjust to noon
dt.setMinutes(dt.getMinutes() - dt.getTimezoneOffset()); // adjust for the time zone
var dob = dt.toISOString().substring(0,10); // just get the date portion
On the Rails side of things, use a Date object instead of a DateTime. Unlike JavaScript, the Rails Date object is a date-only object - which is perfect for a date-of-birth.

Get the pieces of a date from a string

How would you get the substring of a string that is dynamic? By this I mean that I am retrieving tweets and I want to extract different parts of the return value into a Month String, a Year int, a day int, and a time int. Here is what I am retrieving:
Sun Nov 17 00:15:47 +0000
How would I split it up even if the given value would change over time, as the tweet is on a index path of multiple table view cells, and the value of the creation date would be different for each one of the cells?
Your string represents a date, so the first thing you should do is convert it to an NSDate using an NSDateFormatter.
Then you can get the various elements of the date by creating an NSDateComponents instance via -[NSCalendar components:fromDate:], and accessing its month, day, and whatever other properties you need.

Resources