EKEventStore time zone isn't GMT - objective-c

I'm trying to retrieve all the events for a single day from an instance of EKEventStore using eventsMatchingPredicate:, but as I read, the NSDate objects are by default set to GMT while the EKEventStore isn't. So my question is how do I change the timezone of the EKEventStore or adjust the NSDate objects so that the times aren't off for each timezone?
For example, I'm in GMT -0600, and clicking on January 16th and 17th in the TKCalendarMonthView I'm using for a calendar UI shows Martin Luther King Day on both dates. The start time is 6 AM on 16 January, and the end time is 5:59 AM on 17 January (as a result of my timezone), rather than beginning at 12:00 AM and lasting until 11:59 PM. The code used to retrieve events follows.
- (void)calendarMonthView:(TKCalendarMonthView *)monthView didSelectDate:(NSDate *)d {
// Update tableData with event data from date
[tableData removeAllObjects];
NSArray *a = [systemCalendar eventsMatchingPredicate:[systemCalendar predicateForEventsWithStartDate:d endDate:[NSDate dateWithTimeInterval:84600 sinceDate:d] calendars:nil]];
[tableData addObjectsFromArray:a];
[self.eventsTable reloadData];
}

Given that I'm on a short timeline, I came up with a solution, and it seems to work. My only concern is that I had to multiply the offset by -1 even though the time interval offset itself is negative. This doesn't make sense because we are trying to subtract from the NSDate rather than add to it. A positive number minus a negative number gives us a larger number, so I'm slightly worried about the GMT zones on the other side of the PM and wondering whether I should actually be multiplying all time intervals by -1. Anyone have any thoughts?
- (void)calendarMonthView:(TKCalendarMonthView *)monthView didSelectDate:(NSDate *)d {
[NSTimeZone resetSystemTimeZone];
NSTimeZone *tz = [NSTimeZone systemTimeZone];
NSArray *comps = [[tz description] componentsSeparatedByString:#" "];
NSTimeInterval offset = (NSTimeInterval)[[comps lastObject] floatValue];
if (offset < 0) {
offset *= -1;
}
NSDate *startDate = [d dateByAddingTimeInterval:offset];
NSArray *a = [systemCalendar eventsMatchingPredicate:[systemCalendar predicateForEventsWithStartDate:startDate endDate:[NSDate dateWithTimeInterval:84600 sinceDate:startDate] calendars:nil]];
NSLog(#"Events for the date: %#", a);
[tableData addObjectsFromArray:a];
[self.eventsTable reloadData];
}

Related

NSDateFormatter doesRelativeDateFormatting returns unexpected relative day value

Using NSDateFormatter on macOS 10.13.3, I'm getting incorrect values when using doesRelativeDateFormatting set to YES. I've seen that there may be an issue with relative dates when using a custom format on the formatter but I am using standard dateStyle & timeStyle settings.
As an example, comparing the current date in New York City to the date in Sydney, Australia using appropriately configured date formatters without using doesRelativeDateFormatting, the string output from the date formatters correctly shows the day in Sydney being +1 from the day in NYC. When I enable doesRelativeDateFormatting on the same formatters, the relative date for Sydney returns incorrectly as same day ('Today'). Code that demonstrates these results:
int main(int argc, char *argv[]) {
#autoreleasepool {
NSDate *now = [NSDate date];
NSDateFormatter *localDateFormatter = [NSDateFormatter new];
localDateFormatter.timeZone = [NSTimeZone timeZoneWithName:#"America/New_York"];
localDateFormatter.dateStyle = NSDateFormatterFullStyle;
localDateFormatter.timeStyle = NSDateFormatterFullStyle;
NSDateFormatter *remoteDateFormatter = [NSDateFormatter new];
remoteDateFormatter.timeZone = [NSTimeZone timeZoneWithName:#"Australia/Sydney"];
remoteDateFormatter.dateStyle = NSDateFormatterFullStyle;
remoteDateFormatter.timeStyle = NSDateFormatterFullStyle;
NSLog(#" Now in NYC: %#", [localDateFormatter stringFromDate:now]);
NSLog(#" Now in Sydney: %#", [remoteDateFormatter stringFromDate:now]);
localDateFormatter.doesRelativeDateFormatting = YES;
remoteDateFormatter.doesRelativeDateFormatting = YES;
NSLog(#" Relative now in NYC: %#", [localDateFormatter stringFromDate:now]);
NSLog(#"Relative now in Sydney: %#", [remoteDateFormatter stringFromDate:now]);
}
}
Output:
2018-01-26 14:42:28.478 Untitled[40694:1821879] Now in NYC: Friday, January 26, 2018 at 2:42:28 PM Eastern Standard Time
2018-01-26 14:42:28.479 Untitled[40694:1821879] Now in Sydney: Saturday, January 27, 2018 at 6:42:28 AM Australian Eastern Daylight Time
2018-01-26 14:42:28.479 Untitled[40694:1821879] Relative now in NYC: Today at 2:42:28 PM Eastern Standard Time
2018-01-26 14:42:28.479 Untitled[40694:1821879] Relative now in Sydney: Today at 6:42:28 AM Australian Eastern Daylight Time
Is this a bug in NSDateFormatter or am I doing something wrong in my configuration of the formatter? Thanks.
Your output is correct. Call anyone on the phone anywhere in the world and ask them what the date is and they will all say "today". Just because it's a different day of the week in two parts of the world doesn't mean it isn't "today" locally everywhere.
You are letting yourself get confused by comparing the output of two different timezones that happen to be in two different days when the code is run.
The idea of "relative" date formatting is that the output is a string relative to "now" in the given timezone. It's not relative to any other timezone. Whichever one is set on the date formatter.
Of course, #rmaddy is correct. Enabling doesRelativeDateFormatting and then comparing the date to its own timezone will report 'Today', as it should.
So, the key is to get the date in the remote timezone then use that date relative to the preferred (local) timezone and using the local date formatter's relative date string. Modifying my code to account for the time difference between the two timezones, I calculate the local date offset by the delta and use the local date formatter to get the date string (still using the remote date formatter for the time string because it includes the timezone name).
This isn't perfect because I'm not properly localizing the string (' at ' is being manually inserted without localization) but this modified code basically gets the results for which I'm looking:
int main(int argc, char *argv[]) {
#autoreleasepool {
NSDate *now = [NSDate date];
NSDateFormatter *localDateFormatter = [NSDateFormatter new];
localDateFormatter.timeZone = [NSTimeZone timeZoneWithName:#"America/New_York"];
localDateFormatter.dateStyle = NSDateFormatterFullStyle;
localDateFormatter.timeStyle = NSDateFormatterFullStyle;
NSDateFormatter *remoteDateFormatter = [NSDateFormatter new];
remoteDateFormatter.timeZone = [NSTimeZone timeZoneWithName:#"Australia/Sydney"];
remoteDateFormatter.dateStyle = NSDateFormatterFullStyle;
remoteDateFormatter.timeStyle = NSDateFormatterFullStyle;
NSLog(#" Now in NYC: %#", [localDateFormatter stringFromDate:now]);
NSLog(#" Now in Sydney: %#", [remoteDateFormatter stringFromDate:now]);
localDateFormatter.doesRelativeDateFormatting = YES;
NSLog(#" Relative now in NYC: %#", [localDateFormatter stringFromDate:now]);
NSInteger localSecondsFromGMT = [localDateFormatter.timeZone secondsFromGMTForDate:now];
NSInteger remoteSecondsFromGMT = [remoteDateFormatter.timeZone secondsFromGMTForDate:now];
NSInteger remoteTimeZoneDelta = (remoteSecondsFromGMT - localSecondsFromGMT);
NSDate *remoteDate = [now dateByAddingTimeInterval:(NSTimeInterval)remoteTimeZoneDelta];
localDateFormatter.timeStyle = NSDateFormatterNoStyle;
remoteDateFormatter.dateStyle = NSDateFormatterNoStyle;
NSString *remoteRelativeDate = [localDateFormatter stringFromDate:remoteDate];
NSString *remoteRelativeTime = [remoteDateFormatter stringFromDate:now];
NSLog(#"Relative now in Sydney: %# at %#", remoteRelativeDate, remoteRelativeTime);
}
}
Output:
2018-01-27 16:08:12.856 Untitled[95771:3146343] Now in NYC: Saturday, January 27, 2018 at 4:08:12 PM Eastern Standard Time
2018-01-27 16:08:12.857 Untitled[95771:3146343] Now in Sydney: Sunday, January 28, 2018 at 8:08:12 AM Australian Eastern Daylight Time
2018-01-27 16:08:12.857 Untitled[95771:3146343] Relative now in NYC: Today at 4:08:12 PM Eastern Standard Time
2018-01-27 16:08:12.857 Untitled[95771:3146343] Relative now in Sydney: Tomorrow at 8:08:12 AM Australian Eastern Daylight Time
This is somewhat inelegant but it works for my current needs.

dateWithTimeIntervalSince1970 not returning correct date

I have the following method below that is meant to retrieve and convert a unixTimeStamp from an API call to a NSDate object that I can easily manipulate and use. For some reason, this returns wrong values. An example would be when the unixTimeStamp is 1385152832, the date SHOULD be
Fri, 22 Nov 2013 20:40:31 GMT
November 22, 2013 at 3:40:31 PM EST
but instead spits out: 45852-09-07 08:13:52 EST. Does anyone know why this would happen?
-(NSDate *)messageDate
{
NSTimeInterval unixTimeStamp = [[self messageDateString] doubleValue];
NSDate *messageDate = [NSDate dateWithTimeIntervalSince1970:unixTimeStamp];
NSAssert(messageDate, #"messageDate should not be nil");
return messageDate;
}
The messageDateString method is returning milliseconds since the epoch, not seconds since the epoch. Look at the value of unixTimeStamp in your debugger pane. It's 1384803782032. That is about 1000 times too large to be a current Unix timestamp.
An NSTimeInterval is measured in seconds, not milliseconds. Try this instead:
-(NSDate *)messageDate {
NSTimeInterval unixTimeStamp = [[self messageDateString] doubleValue] / 1000.0;
NSDate *messageDate = [NSDate dateWithTimeIntervalSince1970:unixTimeStamp];
NSAssert(messageDate, #"messageDate should not be nil");
return messageDate;
}

Convert epoch time to NSDate with good timezone with Objective c

how I can convert an epoch time value to NSDate. For example I use this value : 1310412600000. and I am in the EDT time zone.
When I try this :
NSString *bar = [[NSDate dateWithTimeIntervalSince1970:epoch] description];
I got a wrong value...
What is the good way? I spend a lot of time with that....
Thanks
Epoch time (also known as "Unix" and "POSIX" time) is the number of seconds since the start of 1970. You can convert epoch time to NSDate with this algorithm:
Instantiate NSDate object and pass the epoch time as a parameter to NSDate's initWithTimeIntervalSince1970 initializer. You're done. The NSDate object is set to the epoch time. NSDate stores times internally in the UTC time zone. It's up to you how it is displayed.
[Optional] Format your NSDate to the appropriate time zone with an NSDateFormatter.
Here's the code I used:
// Sample string epochTime is number of seconds since 1970
NSString *epochTime = #"1316461149";
// Convert NSString to NSTimeInterval
NSTimeInterval seconds = [epochTime doubleValue];
// (Step 1) Create NSDate object
NSDate *epochNSDate = [[NSDate alloc] initWithTimeIntervalSince1970:seconds];
NSLog (#"Epoch time %# equates to UTC %#", epochTime, epochNSDate);
// (Step 2) Use NSDateFormatter to display epochNSDate in local time zone
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss zzz"];
NSLog (#"Epoch time %# equates to %#", epochTime, [dateFormatter stringFromDate:epochNSDate]);
// (Just for interest) Display your current time zone
NSString *currentTimeZone = [[dateFormatter timeZone] abbreviation];
NSLog (#"(Your local time zone is: %#)", currentTimeZone);
Per the documentation, dateWithTimeIntervalSince1970: runs from January 1, 1970, 00:00 GMT. So you should be getting results four hours later than those you really want.
Hence the simplest thing — if you don't mind hard coding the offset from GMT to EDT — would seem to be:
[[NSDate dateWithTimeIntervalSince1970:epoch] dateByAddingTimeInterval:-240]
// or:
[NSDate dateWithTimeIntervalSince1970:epoch - 240]
Though to eliminate arbitrary constants (and be a little cleaner), you probably want something like:
NSTimeZone *EDTTimeZone = [NSTimeZone timeZoneWithAbbreviation:#"EDT"];
NSInteger secondsDifferenceFromGMT =
[EDTTimeZone secondsFromGMTForDate:[NSDate dateWithTimeIntervalSince1970:0]];
NSDate *startOfEpoch =
[NSDate dateWithTimeIntervalSince1970:secondsDifferenceFromGMT];
...
NSDate *newDate = [startOfEpoch dateByAddingTimeInterval:firstInterval]; // etc
Are you passing the value in seconds?
The method accepts value in seconds , not milliseconds.
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSDate_Class/Reference/Reference.html

2 NSDates that should be equal aren't?

I'm using the JSON library from Stig Brautaset(http://code.google.com/p/json-framework) and I need to serialize an NSDate. I was considering converting it into a string before JSONifying it, however, I ran into this weird behavior:
Why aren't these NSDates considered equal?
NSDate *d = [[NSDate alloc] init];
NSDate *dd = [NSDate dateWithString:[d description]];
NSLog(#"%#", d);
NSLog(#"%#", dd);
if( [d isEqualToDate:dd] ){
NSLog(#"Yay!");
}
When you describe the original date object you lose some sub-second precision from the original object — in other words, -description shaves off fractional seconds, and returns
A string representation of the receiver in the international format YYYY-MM-DD HH:MM:SS ±HHMM, where ±HHMM represents the time zone offset in hours and minutes from GMT
When you create a new date object based on the description, you get it in whole seconds because the string is only precise to a whole second. So -isEqualToDate: returns NO because there is a difference of a fraction of a second between your two date objects, which it's sensitive to.
This method detects sub-second differences between dates. If you want to compare dates with a less fine granularity, use timeIntervalSinceDate: to compare the two dates.
So you'd do something like this instead (NSTimeInterval measures in seconds):
if ([d timeIntervalSinceDate:dd] == 0) {
NSLog(#"Yay!");
}
isEqualToDate detects subseconds differences between dates, but the description method does not include subseconds.
Because they're not equivalent:
NSDate *d = [NSDate date];
NSDate *dd = [NSDate dateWithString:[d description]];
NSLog(#"%f", [d timeIntervalSinceReferenceDate]);
NSLog(#"%f", [dd timeIntervalSinceReferenceDate]);
Produces:
2011-04-28 11:58:11.873 EmptyFoundation[508:903] 325709891.867788
2011-04-28 11:58:11.874 EmptyFoundation[508:903] 325709891.000000
In other words, the +dateWithString: method does not maintain sub-second precision.

NSDate - Offsetting the Time Zone

I must be missing something small her but can't figure it out. Trying to create a date for comparison, but I can't seem to offset currentDate from GMT to EST:
// current date (gmt) //
NSDate *currentDate = [NSDate date];
NSTimeZone *currentDateTimeZone = [NSTimeZone timeZoneWithAbbreviation:#"EST"];
NSDateFormatter *currentDateFormat = [[NSDateFormatter alloc]init];
[currentDateFormat setTimeZone:currentDateTimeZone];
[currentDateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss zzz"];
NSString *currentDateString = [currentDateFormat stringFromDate:currentDate];
NSLog(#"currentDateString: %#", currentDateString); // returns 2011-01-05 13:30:30 EST
NSDate *currentDateWithOffset = [currentDateFormat dateFromString:currentDateString];
NSLog(#"currentDateWithOffset: %#", currentDateWithOffset); // returns 2011-01-05 18:30:30 +0000
Thanks!
Edit:
I'm calling a method in a separate class (trying to make this portable) using the following line:
[Expiration expires:[[NSDate alloc] initWithString:#"2011-01-07 12:00:00 +0000"] within:1.0]
in the expires method, I have these lines:
NSComparisonResult comparison = [currentDateWithOffset compare:expires]; // check for a fixed date to disable the demo
double withinRange = [installDate timeIntervalSinceDate:currentDateWithOffset]; // check for number of seconds between "within" and the install date
I'm then comparing these two values like so:
if(withinRange >= within && withinRange > 0.0) {
// app is expired //
}
else {
// app is still enabled (so far...) //
if(comparison == NSOrderedDescending || comparison == NSOrderedSame) {
// app is expired //
}
else {
// app is still enabled //
}
}
Does this help? Thanks for your patience!
Edit:
Here's the entire expires:within method as it currently stands...
+(BOOL)expire:(NSDate*)expires within:(double)within {
// default expired value //
BOOL expired = NO;
// convert within value from days to seconds //
within *= 24.0 * 60.0 * 60.0;
// current date (gmt) //
NSDate *currentDate = [NSDate date];
// install date //
NSDate *installDate = [[NSUserDefaults standardUserDefaults]objectForKey:#"installDate"];
// check for a value in installDate //
if (nil == installDate) {
// app is running for the first time //
[[NSUserDefaults standardUserDefaults]setObject:currentDate forKey:#"installDate"];
[[NSUserDefaults standardUserDefaults]synchronize];
installDate = currentDate;
}
if([installDate timeIntervalSinceNow] < (-within)) {
expired = YES;
}
else {
if([expires timeIntervalSinceNow] < 0) {
expired = YES;
}
}
NSLog(#"installDate:%#", installDate);
NSLog(#"expires:%#", expires);
NSLog(#"currentDate:%#", currentDate);
return expired;
}
I'm then calling it from another class with
message.text = (YES == [Expiration expire:[[NSDate alloc] initWithString:#"2011-01-07 12:00:00 -0500"] within:(0.015625/2)]) ? #"This App is Expired" : #"This App is Active";
When running in the simulator (fresh app install), NSLog displayed this...
[Session started at 2011-01-06 10:43:46 -0500.]
2011-01-06 10:43:48.146 TimeBasedDemo[14717:207] installDate:2011-01-06 15:43:48 +0000
2011-01-06 10:43:48.147 TimeBasedDemo[14717:207] expires:2011-01-07 17:00:00 +0000
2011-01-06 10:43:48.147 TimeBasedDemo[14717:207] currentDate:2011-01-06 15:43:48 +0000
None of these answers gave me an NSDate object with the current, local date. So here's how I did it:
NSDate *currentDateWithOffset = [NSDate dateWithTimeIntervalSinceNow:[[NSTimeZone localTimeZone] secondsFromGMT]];
An NSDate object represents an instant in time irrespective of time zone and calendar considerations. Time zone info is relevant when you print or parse a date, but it is not stored within the NSDate.
Say you are creating your expiration date like this:
NSDate *exp=[[NSDate alloc] initWithString:#"2011-01-07 12:00:00 +0000"]
That says you want the expiration to occur at noon GMT, on the 7th Jan. If you want it to expire at noon EST, create it with -0500 instead. What you should not have to do is mess with the current time when you do a comparison.
An easy way just to see if the time has passed is then
if ([exp timeIntervalSinceNow]<0) { /* it's expired */ }
and you can see if within seconds have passed since the install date like this:
if ([installDate timeIntervalSinceNow]<(-within)]) { /* it's expired */}
In Cocoa, NSDate is an abstract representation of a date with no time zone information applied. Note that currentDateWithOffset is the same date as the date string, just in a different time zone (five hours ahead). This is expected behavior, as NSDate does not persist the time zone used to create it.
I tinkered around a bit more and found a way to 'cheat' the offset to suit my needs. From other reading, I'm guessing that NSCalendar might be a better long term-solution, but for now I ended up changing
NSDate *currentDateWithOffset = [currentDateFormat dateFromString:currentDateString];
to
NSDate *currentDateWithOffset = [[NSDate alloc] initWithString:[NSString stringWithFormat:#"%# +0000", currentDateString]];
That got me the results I needed and works in the later comparisons I'm using. Thanks for the background info all!