Edit: My solution is below.
I have a sort of calendar-related app where the user is able to jump backward and forward certain integer numbers of days. In testing the app around midnight, I realized there is a DST-related problem, and I'm looking for the best solution.
The app is not really based on any calendar in iOS. However, I display the Gregorian date and time so the user has a reference to a familiar date and time.
One of the built in jumps in time is 73 days, which is about 1/5 year. Common dates used will be March equinox +/- integer multiples of 73 days. The problem comes, for example, when jumping from March equinox to a date 73 days previous, because the March equinox is likely to be in DST, whereas the other is not. It depends on the timezone setting on the system. The app, however, has nothing to do with timezone, so I'd rather display times as if there were no such thing as DST, yet still using the local timezone. I hope that makes sense.
Any suggestions on how to achieve this? If the question is too vague, maybe I can add more detail, but I don't want to confuse things by adding too much.
EDIT: I've decide to link sample screen prints from the app to illustrate what's going on. This picture:
http://victorspictures.com/trollart/h37a90c77#h56122c20
shows the configuration for this year's equinox. The caption explains how to read the device. Follow to the next two pictures in the gallery to see how to use the device to calculate the date of Easter.
Anyway, swipe gestures allow the user to move +/- one day or +/- 73 days, which is where the problem arises. I'm adding time to the date display, and maybe that is really all I need to do.
Solution: Celeda's suggestion
Added this method:
- (NSDate *)setNoon: (NSDate *)date
{
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *components = [calendar components:NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit fromDate:date];
[components setHour:12];
NSDate *todayNoon = [calendar dateFromComponents:components];
return todayNoon;
}
Then, everywhere a jump was made, it was simply encapsulated in the new method:
newDate = [self setNoon:[previousCode]];
It appears, based on your comment to Hot Licks's answer, that you have some number d representing days since the 1975 vernal equinox (which was at 1975-03-21 05:57:00 UTC), and you want figure out the corresponding day on the Gregorian calendar.
We can use the NSDate, NSCalendar, and NSDateComponents classes to perform this computation. To avoid the effects of DST, we need to set the time zones of the calendar and formatter to UTC.
NSInteger d = 73 * 189; // days since the 1975 vernal equinox
NSTimeZone *utcTimeZone = [NSTimeZone timeZoneWithName:#"UTC"];
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
calendar.timeZone = utcTimeZone;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = #"yyyy-MM-dd";
formatter.timeZone = utcTimeZone;
NSDateComponents *epochComponents = [[NSDateComponents alloc] init];
epochComponents.year = 1975;
epochComponents.month = 3;
epochComponents.day = 21;
epochComponents.hour = 5;
epochComponents.minute = 57;
epochComponents.timeZone = utcTimeZone;
NSDate *epochDate = [calendar dateFromComponents:epochComponents];
NSLog(#"epochDate=%#", epochDate);
NSLog(#"epochDate from formatter=%#", [formatter stringFromDate:epochDate]);
NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];
offsetComponents.day = d;
NSDate *computedDate = [calendar dateByAddingComponents:offsetComponents toDate:epochDate options:0];
NSLog(#"computedDate=%#", computedDate);
NSLog(#"computedDate from formatter=%#", [formatter stringFromDate:computedDate]);
Try commenting out the lines that set the time zone to UTC on the calendar and/or the formatter to see how DST changes the results.
For more information, read the Date and Time Programming Guide.
Related
The common question on stackoverflow is how to get the Day of Year from a date but how to you get the date from the Day of Year?
I'm using the following code to generate the Day of Year but how to I do the converse?
// Calculate Day of the Year
NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSUInteger dayOfYear = [gregorian ordinalityOfUnit:NSDayCalendarUnit inUnit:NSYearCalendarUnit forDate:[NSDate date]];
setDayOfYear = dayOfYear;
return setDayOfYear;
Assuming you know the year you want and the actual time of the date doesn't matter and you have the correct locale and time zones set up, you can just convert the day of the year and year from components back into a date. Again, you may need to decide what your application needs to do about the time component and you have to make sure that the day and year actually make sense for the calendar, but assuming the gregorian calendar you listed in your example, and the 200th day of 2013 (19 July, 2013), you could so something like this:
NSDateComponents* components = [[NSDateComponents alloc] init];
[components setDay:200];
[components setYear:2013];
NSDate* july19_2013 = [gregorian dateFromComponents:components];
If I were to run this, I'd get a date that's 19 July, 2013 # 07:00 since my current locale is in a time zone equiv of America/Los_Angeles and I didn't configure the calendar.
I am making a countdown timer and I want it to globally countdown to Christmas. Therefore I have my timestamp value of 1387929600 which is dec 25 00:00:00. I am adjusting that value according to the different time zones and I am doing it like this:
NSDate *firstDate = [NSDate dateWithTimeIntervalSince1970:1387954801];
NSTimeZone *tz = [NSTimeZone localTimeZone];
int tzInt = [tz secondsFromGMTForDate:firstDate];
int timeDifference = tzInt+1387954801;
xmasDate = [NSDate dateWithTimeIntervalSince1970:timeDifference];
I take the firstDate and do secondsFromGMTForDate and then add it to the original timestamp number to get the xmasDate adjusted for time zones.
This general approach seems to work however the secondsFromGMTForDate is not calculating correctly.
For example when I set my phone to the current time zone it still wants to subtract "-25200" from the timestamp time when it should be zero since its the current time zone.
Am I correct in saying that? I might be missing something in the calculation and maybe I have to add to the timestamp number but I'm not sure.
Any help would be greatly appreciated.
By adding tzInt to a timestamp in GMT, you're doubling the effect of time zone difference instead of cancelling it.
For example, if your timezone is 25200 seconds behind GMT (tzInt=-25200), you need to add 25200 to 1387929600, as Christmas will come later in your time zone.
So the correct calculation of timeDifference is the following:
int timeDifference = 1387954801 - tzInt;
Otherwise the code looks correct, but I'd use the following code instead, which, I think, is easier to understand:
NSDateComponents *components = [[NSDateComponents alloc] init];
[components setDay:25];
[components setMonth:12];
[components setYear:2013];
NSDate *xmasDate = [[NSCalendar currentCalendar] dateFromComponents:components];
NSTimeInterval secondsLeft = [xmasDate timeIntervalSinceNow];
This is probably a simple solution but does anyone know how to delay the NSDate change past midnight? Any insight would be really helpful, thanks!
Edit:
I am currently getting the date this way and displaying a locations data based on that day. But, much like the NSDate is logically supposed to work, it switches to the next day at midnight. I want the date to change at 3am instead of at 12am.
NSDateFormatter *f = [[NSDateFormatter alloc] init];
[f setDateFormat:#"EEEE"];
today = [f stringFromDate:[NSDate date]];
Should I just use the time instead of using NSDate? I am a bit of a noob to iOS so any insight would be helpful. Thanks for your responses already.
I must admit that I do not yet understand why you want to display a "wrong" weekday
name, but the following code should do what you want. This is only one of various ways
to achieve your task.
Convert date to "date components:"
NSCalendar *cal = [NSCalendar currentCalendar];
NSUInteger unitFlags = NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit;
NSDateComponents *comp = [cal components:unitFlags fromDate:[NSDate date]];
Subtract one day if necessary:
if (comp.hour < 3) {
comp.day -= 1;
}
Convert adjusted components back to date:
NSDate *adjustedDate = [cal dateFromComponents:comp];
Your original code, now using the adjusted date:
NSDateFormatter *f = [[NSDateFormatter alloc] init];
[f setDateFormat:#"EEEE"];
NSString *today = [f stringFromDate:adjustedDate];
I'm trying to write an application that will send the user an alert in the Notification Center 60 hours before the date arrives. Here is the code:
localNotif.fireDate = [eventDate dateByAddingTimeInterval:-60*60*60];
I was wondering if the -60*60*60 formula will work to alert them 60 hours prior to the date? I'm not sure at all how the formula works, I would like to set it up to alert 10 minutes before the date for testing purposes, then change it back to 60 hours once I confirm that everything is correct. Does any one know the formula to use for both of these?
Any help is much appreciated, thank you!
A crude but easy-to-code way is to add/subtract seconds from an NSDate directly:
NSDate *hourLaterDate = [date dateByAddingTimeInterval: 60*60];
NSDate *hourEarlierDate = [date dateByAddingTimeInterval: -60*60];
You can see how it works by logging the dates:
NSDate *now = [NSDate date];
NSDate *hourLaterDate = [now dateByAddingTimeInterval: 60*60];
NSLog(#"%# => %#", now, hourLaterDate);
In this approach a date is interpreted as a number of seconds since the reference date. So, internally it's just a big number of type double.
A tedious-to-code but pedantically correct way to do these calculations is by interpreting dates as dates expressed in a calendar system. The same thing achieved as calendrical calculations:
NSDateComponents *hour = [[NSDateComponents alloc] init];
[hour setHour: 1];
NSCalendar *calendar= [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];
NSDate *hourLaterDate = [calendar dateByAddingComponents: hour
toDate: date
options: 0];
[hour release];
[calendar release];
These calculations take into account time zones, daylight saving time, leap years, etc. They can also be more expressive in terms of what you're calculating.
Before using any of these approaches you have to decide what exactly you need: a timestamp or a full-blown calendar date.
I have been racking my brains on this one looking for a quick solution.
I need to fire off a NSTime at a specific time based on the current time in a specific time zone.
For example, I get the current time in PST:
NSDate *now = [NSDate date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"HH:mm:ss";
NSCalendar * cal = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar];
[cal setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"PST"]];
NSDateComponents * comps = [cal components:NSHourCalendarUnit fromDate:now];
I see that the time is 2 (14) getting [comps hour] and I need to fire a timer at 2:30PM (14:30) on that day. I need help converting it back and doing the math to say how long it is before the timer fires. I will be setting up several different timers based on the current time. While I am looking at the time in PST I know that the timers and setting of the time is done in GMT. That's another twist...
Thanks in advance...
I solved the problem myself.. But thanks for the tips...
if ([date earlierDate:newDate] == date) // it is before 7AM... fire at 7AM
and so on....