NSDate - Offsetting the Time Zone - objective-c

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!

Related

How to display the time whenever value of the current minute changes (with Cocoa)?

Ok this one really has me stumped. The challenge seems simple but has a few restrictions that are really throwing me off.
Gist of the program:
(1) When the minute changes the application should output the current time with the format HH:mm:ss.
(2) When the second changes the application should output a character for every second and a different character for every tenth second (i.e. ".........|.........|"). Here it is important to note that I cannot just count seconds; rather, the program must know the value of the second of the current time and output the corresponding character only if it is divisible by 10.
Restrictions:
(1) The program can have 2 classes -- one for the minute display and one for the second display.
(2) Each class can contain only one void method that takes a timer as a parameter.
(3) The main function will create an object of each class and 2 NSTimers. The first timer must have its interval set at 0.5 and must target the minutes object. The second timer must have its interval set at 0.1 and must target the seconds object.
I would love to just set the first time to fire every minute and the second timer to fire ever second, but I am not allowed to tamper with that. This is as far as I got before I was stumped:
main.m:
MinuteDisplay *minutes = [[MinuteDisplay alloc] init];
__unused NSTimer *minutesTimer =
[NSTimer scheduledTimerWithTimeInterval:0.5
target:minutes
selector:#selector(minuteChange:)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] run];
MinuteDisplay.m:
- (void)minuteChange:(NSTimer *)timer
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss"];
NSDateFormatter *testFormatter = [[NSDateFormatter alloc] init];
[testFormatter setDateFormat:#"ss"];
NSDate *now = [NSDate date];
NSString *nowString = [dateFormatter stringFromDate:now];
NSString *testString = [testFormatter stringFromDate:now];
if ([testString isEqual:#"00"]) {
NSLog(#"%#", nowString];
}
}
Example output:
01:09:00
//.5 second interval
01:09:00
I haven't even bothered with the second half of this challenge yet because I got stumped here. This technically works, but I know I'm doing it wrong, since the output isn't dependent upon the program knowing the value of the minute has changed. Also, because the timer is set to fire every .5 seconds, I get two lines of output every minute instead of just one (which does not satisfy the specs).
Can anyone suggest where to go from here? This one really has my mind boggled.
If you can not modify the timer interval, the other alternative is to use an instance variable in your MinuteDisplay class, for example, displayString. And modify the minuteChange method as follows:
- (void)minuteChange:(NSTimer *)timer
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss"];
NSDateFormatter *testFormatter = [[NSDateFormatter alloc] init];
[testFormatter setDateFormat:#"ss"];
NSDate *now = [NSDate date];
NSString *nowString = [dateFormatter stringFromDate:now];
NSString *testString = [testFormatter stringFromDate:now];
if ([testString isEqual:#"00"]) {
if(![nowString isEqualToString:dispString]) {
dispString = nowString;
NSLog(#"*************** %#", dispString);
}
}
}
This will make sure that it is logged only once a minute
Working minuteChange method:
- (void)minuteChange:(NSTimer *)timer
{
NSDate *now = [NSDate date];
if (!self.lastTime) {
self.lastTime = [NSDate date];
}
if (now > self.lastTime) {
if ([[self.seconds stringFromDate:now] isEqualToString:#"00"]) {
printf("\n%s", [[self.output stringFromDate:now] UTF8String]);
self.lastTime = now;
}
}
self.output = [[NSDateFormatter alloc] init];
[self.output setDateFormat:#"HH:mm:ss"];
self.seconds = [[NSDateFormatter alloc] init];
[self.seconds setDateFormat:#"ss"];
}
Working secondChange method:
- (void)secondChange:(NSTimer *)timer
{
long secondsSince1970 = time(NULL);
if (!self.lastSecond) {
self.lastSecond = secondsSince1970;
}
if (secondsSince1970 > self.lastSecond) {
if (secondsSince1970 % 10 == 0) {
printf("|");
self.lastSecond = secondsSince1970;
} else {
printf(".");
self.lastSecond = secondsSince1970;
}
}
}
Changes to minuteChange:
The most important change was the addition of the lastTime ivar to the class. Now it's simple...if lastTime is null, assign it the value of the current date. If current time is greater than last time, test if it's a new minute and then output the time if it is. ONLY update lastTime if a new minute was output (this ensures that the time is only displayed once per minute).
secondChange:
Took a different approach with this one and now I'm wishing I had gone with straight C the first time around. This one is so simple! Check if the current second is greater than the last second. If so, check if it's divisible by 10. If so, print "|". If not, print ".". Super easy and actually more accurate than setting the timer to fire every second. Sheesh.

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;
}

NSTimer time increment

I'm a beginner in obj-C for iOS platform and am trying to build a few simple project to build my foundation.
I have a button which increase the NSTimer time for the label, but when I use NSLog to log the time, it uses the value before time increment was implemented. I need to be able to log a updated time (after increment), as I require that value and am implementing more function into the IBAction after I solve this portion.
E.g at 15min I press, the NSLog will read it as "00:15:00.0" rather than "00:35:00.0".
- (IBAction)onSkipPressed:(id)sender {
startDate = [startDate dateByAddingTimeInterval:-1200];
NSLog(#"%#",self.timeLabel.text);
}
Any one know the reason for this issue? And how should I solve it such that NSLog will read it as "00:35:00.0" if I invoke this IBAction at 15min.
EDIT - The start button will start the timer and timeLabel will get the string. Sorry for missing out such a important detail. I don't think there are any other code in the project which is related to this functionality already. Thank you for pointing it out to me.
- (void)updateTimer
{
NSDate *currentDate = [NSDate date];
NSTimeInterval timeInterval = [currentDate timeIntervalSinceDate:startDate];
NSDate *timerDate = [NSDate dateWithTimeIntervalSince1970:timeInterval];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"HH:mm:ss.S"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0.0]];
NSString *timeString=[dateFormatter stringFromDate:timerDate];
timeLabel.text = timeString;
}
my IBAction to fire the timer
- (IBAction)onStartPressed:(id)sender {
startDate = [NSDate date];
gameTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/10.0
target:self
selector:#selector(updateTimer)
userInfo:nil
repeats:YES];
//hide start button and show timeLabel
startButton.hidden=true;
timeLabel.hidden=false;
}
I went back to do a few revision with tutorials involving NSTimer. And turns out all I was missing was 1 line [self updateTimer]
- (IBAction)onSkipPressed:(id)sender {
startDate = [startDate dateByAddingTimeInterval:-1200];
[self updateTimer];
NSLog(#"%#",self.timeLabel.text);
}
This solve my issue and the timeLabel.text is updated for me to log the information.
Um, why are you passing in negative 1200?
// this subtracts 1200 seconds from your date, no?
startDate = [startDate dateByAddingTimeInterval:-1200];
Shouldn't you do:
// add 30 minutes (60 seconds a minute x 30 minutes) to your time interval
startDate = [startDate dateByAddingTimeInterval:(60 * 30)];
...
NSLog(#"%#",self.timeLabel.text);
Or am I misunderstanding something?

EKEventStore time zone isn't GMT

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];
}

Frames per second timecode display with NSTimer

I am working on an iPhone/iPad app that needs to display a running timecode clock. I have gotten it to display the correct hours, minutes, and seconds with no problem using this code:
- (void) viewDidLoad {
// Start the Timer method for here to start it when the view loads.
runTimer = [NSTimer scheduledTimerWithTimeInterval: .01 target: self selector: #selector(updateDisplay) userInfo: nil repeats: YES];
}
- (void)updateDisplay {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
NSDate *date = [NSDate date];
// Display each hour, minute, second and frame.
[formatter setDateFormat:#"hh"];
[timecodeHourLabel setText:[formatter stringFromDate:date]];
[formatter setDateFormat:#"mm"];
[timecodeMinuteLabel setText:[formatter stringFromDate:date]];
[formatter setDateFormat:#"ss"];
[timecodeSecondLabel setText:[formatter stringFromDate:date]];
}
The issue is when I need to display frames per second. I know that calculating 1/24 * 1000 gives me how many milliseconds are in one frame. I just don't know how to make the NSDate and NSTimer functions work with this code and allow it to update a UILabel as quickly as needed for running timecode.
Any suggestions?
If your timer is running with the period of 0.01 sec, then it's frequency is 100 frames/sec (well, it's better to say it has 100 function calls per second). But if you need to display the precise time period (cause sometimes the timer may be delayed), then you need to store the previous call date and then use
NSDate* new_date = [NSDate date];
double freq = 1.0 / [new_date timeIntervalSinceDate: old_date];
[old_date release];
old_date = [new_date retain];
Here's a Processing/Java equivalent that's fairly straightforward to repurpose.
String timecodeString(int fps) {
float ms = millis();
return String.format("%02d:%02d:%02d+%02d", floor(ms/1000/60/60), // H
floor((ms/1000/60)%60), // M (edit: added %60)
floor(ms/1000%60), // S
floor(ms/1000*fps%fps)); // F
}