CalEvent with IsAllDay=TRUE gets me in trouble with timezone - objective-c

I have a TimeZone problem with allday events in CalCalendar:
- (BOOL)setDayType:(NSString *)type
forDay:(NSDate *)date;
{
NSError *err = nil;
CalEvent *e = [CalEvent event];
[e setTitle:type];
[e setIsAllDay:YES];
[e setStartDate:date];
[e setEndDate:[date dateByAddingTimeInterval:1]];
[e setCalendar:[self calendar]];
if ( ![[CalCalendarStore defaultCalendarStore] saveEvent:e span:CalSpanThisEvent error:&err] )
{
...
Let's say I set the date at January 2nd, 2012, with a time of 00:00:00. I am at GMT+2, so timezone +0200. My date object reports 1-1-2012, 22:00:00, at GMT, as per this NSLog output:
"Set daytype ptd on 2012-01-01 22:00:00 +0000"
and also generates the allday even on January 1st!!
How can I make sure that I set the allday event in the correct day, for all timezones? I could easily add three hours but that will not work worldwide!

As long as date is on the correct day in the current time zone, you can use it as-in.
The "problem" that you see is caused because if you print the stored date via NSLog (or in the debugger) it will always show you GMT time because NSDate has no knowledge of which timezone that you are in.
However, since date is a specific point in time, when you format it for the user (using a date formatter), it will show the correct date and time, no matter what the time zone is, as long as it stays consistent.
The only situation where you will need to worry about adding/subtracting time like you mention is if you need a point in time which is the same date across multiple time zone's.
EDIT:
So it looks like iCal uses GMT time to store everything, so you need to create your date in the GMT timezone. If you need help with this, post your code where you create your NSDate and I'll be happy to point you in the right direction.

Related

Convert time to local time in Objective-C

I'm having this issue where the correct time is passed but once it hits the [formatter setDateFormat:date] it comes out in a different time zone. Here is a snippet
NSDate* date = user.startDate;
NSDateFormatter *format = [[NSDateFormatter alloc] init];
NSLog(#"-------> (1) time is %#", date);
NSLog(#"-------> (2) time is %#", date);
[format setDateFormat:#"h:mm a"];
NSLog(#"-------> (3) time is %#", date);
NSLog(#"-------> (4) time is %#", [format stringFromDate:date]);
Here is the debugger output for the log messages
-------> (1) time is 2016-06-30 09:25:17 +0000
-------> (2) time is 2016-06-30 09:25:17 +0000
-------> (3) time is 2016-06-30 09:25:17 +0000
-------> (4) time is 2:25 AM
I'm expecting the output for (4) to be time is 9:25 AM
Your time zone is apparently UTC-7000.
NSLog prints NSDate objects always in UTC, NSDateFormatter considers the current time zone. The printed dates are actually the same.
If you really need all dates in UTC, set the time zone of the formatter.
format.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
By the way: There are built-in methods to check if a date is in today or yesterday.
Here's what is going on: the log of a date will always be GMT. (I don't think it makes sense for me to get into details as there is a great explanation here.) That means that log 1, 2 and 3 will be “wrong” (not in your timezone) and that should be expected.
If, for instance, you want to log the string as GMT, you can change the property timeZone of your date formatter.
Dealing with dates can be extremely hard. Please take the time to read if not all, some important parts of this document (fundamentals/basics).

Applying NSDate from server with localtimezone get NSDate

I'm returning an NSDate from my server (running on GMT+2)
I'm calculating few things with the server's date and the device current date.
The problem (obviously) is when the device is running on different timezone then my server.
How can I apply and change the server's NSDate to return current device NSDate for my calculation will be exact for every time zone.
I simply need a function that will get my server NSDate with my server's timezone (gmt+2) and will return the correct device NSDate.
hope someone can help me on that
Server returns Ticks (running on c#) and manipulating to nsdate with this code
double tickFactor = 10000000;
double ticksDoubleValue = [ticks doubleValue];
double seconds = ((ticksDoubleValue - 621355968000000000)/ tickFactor);
NSDate *returnDate = [NSDate dateWithTimeIntervalSince1970:seconds];
return returnDate;
The NSDate class stores an absolute time. To facilitate this, it represents time in UTC, which is time-zone-agnostic. However you create an NSDate, it is assumed the time is in UTC. Even when you use NSDateFormatter to "read" a date from a string, it's just doing the math on your behalf, before creating the NSDate.
If you have a time representation that includes a time zone offset, you need to account for that when you do the conversion. As mentioned above, given a proper format string, NSDateFormatter will do that for you. If your representation is numeric (typically number of seconds from some date), you need to add or subtract the time zone offset. Add if the offset is negative, subtract if it's positive.
To adjust a server date provided in the server's local time, adjust based on the server's time zone. For GMT+2, subtract 3600 * 2 (number of seconds per hour * offset). This gives the seconds in UTC. When you create the NSDate using `[NSDate dateWithTimeIntervalSince1970:]. it will be the expected time.

Why does NSDateFormatter return nil date for these 4 time zones?

Try running this in iOS6 (haven't tested pre iOS6):
NSDateFormatter *julianDayDateFormatter = nil;
julianDayDateFormatter = [[NSDateFormatter alloc] init];
[julianDayDateFormatter setDateFormat:#"g"];
for (NSString *timeZone in [NSTimeZone knownTimeZoneNames]) {
julianDayDateFormatter.timeZone = [NSTimeZone timeZoneWithName: timeZone];
NSDate *date = [julianDayDateFormatter dateFromString:[NSString stringWithFormat:#"%d", 2475213]];
if (date == nil)
NSLog(#"timeZone = %#", timeZone);
}
and you get the following output:
America/Bahia
America/Campo_Grande
America/Cuiaba
America/Sao_Paulo
Can anyone explain why these four time zones behave like this with NSDateFormatter set to julian day numbers? All other time zones makes NSDateFormatter return actual NSDates.
I have a suspicion. Only a suspicion, but a pretty strong one.
That value represents October 19th 2064. The Brazilian time zones observe daylight saving time starting at local midnight - that's when their clocks go forward, so midnight itself doesn't exist. October 19th is one of those transitions.
Here's some sample code using Noda Time, my .NET date/time API. It checks whether the start of the day in every time zone it knows about is actually midnight:
using System;
using NodaTime;
class Test
{
static void Main()
{
var localDate = new LocalDate(2064, 10, 19);
var provider = DateTimeZoneProviders.Tzdb;
foreach (var id in provider.Ids)
{
var zone = provider[id];
var startOfDay = zone.AtStartOfDay(localDate).LocalDateTime.TimeOfDay;
if (startOfDay != LocalTime.Midnight)
{
Console.WriteLine(id);
}
}
}
}
That produces a very similar list:
America/Bahia
America/Campo_Grande
America/Cuiaba
America/Sao_Paulo
Brazil/East
I suspect Brazil/East may be an alias for America/Sao_Paolo, which is why it's not on your list.
Anyway, to get back to your Julian day issue - I suspect the formatter always wants to return an NSDate * which is at the local midnight. That doesn't exist for October 19th 2064 in those time zones... hence it returns nil. Personally I'd suggest it should return the 1am value instead, but hey...
Credits to Jon Skeet for putting me on the right track. However, I just want to clarify his answer in an iOS context.
When you ask NSDateFormatter to convert a julian day number into an NSDate, you can only specify whole numbers (usually you can specify a decimal part for the hours/minutes/secs of the day) in the string to be parsed.
Because Apple demarcates julian days at midnight (as opposed to noon in astronomy, read more here: http://www.unicode.org/reports/tr35/#Date_Field_Symbol_Table) and some midnights simply doesn't exists (thanks for pointing that out #JonSkeet) NSDateFormatter identifies that that particular point in time doesn't exist in that time zone and returns nil.
For the record, iOS5 does not behave like this and I agree with Jon Skeet, that NSDateFormatter should return an NSDate adjusted for DST instead of nil, as that particular julian day in fact exists! I filed a bug with Apple.

Unix timestamp is not getting converted correctly to NSDate

I have parsed an api which returns a unixtimestamp for last updated value. I am trying to convert it into NSDate. The code is below
NSDate *date = [NSDate dateWithTimeIntervalSince1970:[[[feedCount objectAtIndex:i] objectForKey:#"firstitemmsec"] intValue]];
NSLog(#"%#", date);
When I NSLog the date, I am getting date like this:
2038-01-19 03:14:07 +0000
The above date is obviously wrong
What is the mistake in the above code?
EDIT: my unixtimestamp is
"firstitemmsec":"1264396813500"
This value is obviously bigger for int. So how best can I handle this situation
Unix timestamps are in seconds, the value you have looks like a number of milliseconds since 1st January 1970. If you divide by 1000, you get 1264396813, which according to this converter is:
Mon, 25 Jan 2010 05:20:13 GMT
Check the value of [[[feedCount objectAtIndex:i] objectForKey:#"firstitemmsec"] intValue]--odds seem good that it's not a valid UNIX timestamp (okay, it is since any int value is on iOS, but obviously it's not the right value.)
I think there's another to do. Erase the "500" at the end of your number wich is an error code from the server and you have a right unix time ;)
There was probably an error or a bug on your server... ???

Date since 1600 to NSDate?

I have a date that's stored as a number of days since January 1, 1600 that I need to deal with. This is a legacy date format that I need to read many, many times in my application.
Previously, I'd been creating a calendar, empty date components and root date like this:
self.gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar
] autorelease];
id rootComponents = [[[NSDateComponents alloc] init] autorelease];
[rootComponents setYear: 1600];
[rootComponents setMonth: 1];
[rootComponents setDay: 1];
self.rootDate = [gregorian dateFromComponents: rootComponents];
self.offset = [[[NSDateComponents alloc] init] autorelease];
Then, to convert the integer later to a date, I use this:
[offset setDay: theLegacyDate];
id eventDate = [gregorian dateByAddingComponents: offset
toDate: rootDate
options: 0];
(I never change any values in offset anywhere else.)
The problem is I'm getting a different time for rootDate on iOS vs. Mac OS X. On Mac OS X, I'm getting midnight. On iOS, I'm getting 8:12:28. (So far, it seems to be consistent about this.) When I add my number of days later, the weird time stays.
OS | legacyDate | rootDate | eventDate
======== | ========== | ==========================|==========================
Mac OS X | 143671 | 1600-01-01 00:00:00 -0800 | 1993-05-11 00:00:00 -0700
iOS | 143671 | 1600-01-01 08:12:28 +0000 | 1993-05-11 07:12:28 +0000
In the previous release of my product, I didn't care about the time; now I do. Why the weird time on iOS, and what should I do about it? (I'm assuming the hour difference is DST.)
I've tried setting the hour, minute and second of rootComponents to 0. This has no impact. If I set them to something other than 0, it adds them to 8:12:28. I've been wondering if this has something to do with leap seconds or other cumulative clock changes.
Or is this entirely the wrong approach to use on iOS?
I imagine you're right about the leap seconds/cumulative clock changes accounting for the time issue. Are the dates you're dealing with actually in the past, or is it purely an arbitrary epoch?
In either case, you could try defining a new epoch that's much closer to present day (say, the Cocoa epoch). Calculate a day delta between the new epoch and the old and save it as a constant. When you need to process a date, apply this delta to the date and then use your existing NSCalendar technique, but with your new epoch instead of the old. That will hopefully avoid the clock drift issue you're seeing.
It looks like the right answer is to make things simpler. Instead of making a rootDate, I just build the date from components every time. This should be no slower, and is still keeps the code really close to the idea.
Initial setup:
self.gregorian = [[[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar
] autorelease];
self.components = [[[NSDateComponents alloc] init] autorelease];
[components setYear: 1600];
[components setMonth: 1];
(Obviously, properties and ivars are adjusted.)
Later, to actually convert a legacy date to a NSDate:
[components setDay: 1 + theLegacyDate];
id eventDate = [gregorian dateFromComponents: components];
This has these advantages for me:
It users fewer ivars.
It's less code.
It always returns midnight on that day, regardless of whether DST is in effect.
Note that iOS takes into account very obscure rules for various time zones. It is most likely that midnight, Jan 1st. 1600 in your timezone actually was at 7:12:28 UTC. There have been many cases where people complained about bugs in date conversions and then someone figured out that actually they are in a time zone that made some strange calendar change many years ago.
You need to find out first what exact NSDate your data represents. "Number of days since Jan 1st 1600" is nonsense, because you need a time zone! What you should do: Find a "legacy" number where you know what day it is supposed to represent. For example, if you "know" that 143671 is supposed to be 11th May 1993 in your time zone, then start with that date as the root date and add (x - 143671) days to it.