I have a bunch of hours of operation I am I want to determine whether the store is open, closed or is closing in 30,29,28,27... minutes I am doing this in Xcode/ Objectic-C. Now I have to do this for lets say 50 different hours of operation. I have made a function that does this but it is not very efficient and involves a lot of if-else statements. Here is a sample hours of operation
Monday - Thursday
7:30am - Midnight
Friday
7:30am - 10:00pm
Saturday
9:00am - 10:00pm
Sunday
9:00am - Midnight
And here is my function and how I handle it
-(BOOL) dateAndTime:(NSDate*)date getStartDay:(NSInteger)startDay getStartHour:(NSInteger)startHour getStartMin:(NSInteger)startMin getEndDay:(NSInteger)endDay getEndHour:(NSInteger)endHour getEndMin:(NSInteger)endMin{
NSCalendar *calendar = [NSCalendar currentCalendar];
const NSCalendarUnit units = NSWeekdayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;
NSDateComponents *comps = [calendar components:units fromDate:date];
if (comps.weekday == 1) {
comps.weekday = 7;
}
else comps.weekday = comps.weekday - 2;
NSDate *startOfToday;
[[NSCalendar currentCalendar] rangeOfUnit:NSDayCalendarUnit startDate:&startOfToday interval:NULL forDate:date];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSTimeZone *timeZone = [NSTimeZone localTimeZone];
[dateFormatter setDateFormat:#"HH:mm"];
[dateFormatter setTimeZone:timeZone];
NSString *dateString = [dateFormatter stringFromDate:date];
NSDate *startDate = [dateFormatter dateFromString:[NSString stringWithFormat:#"%ld:%ld", (long)startHour, (long)startMin]];
NSDate *endDate = [dateFormatter dateFromString:[NSString stringWithFormat:#"%ld:%ld", (long)endHour, (long)endMin]];
NSString *startDateString = [dateFormatter stringFromDate:startDate];
NSString *endDateString = [dateFormatter stringFromDate:endDate];
if ([startDateString compare:dateString] == NSOrderedAscending && [dateString compare:endDateString] == NSOrderedAscending && startDay <= comps.weekday && comps.weekday <= endDay) {
return YES;
}
else return NO;
}
Now I pass in the day, from 0-6 (0 being monday) and then the time in 24-hour time. And then use it like this:
if ([self dateAndTime:date getStartDay:0 getStartHour:7 getStartMin:30 getEndDay:3 getEndHour:23 getEndMin:30] == YES)
text = #"Open";
else if ([self dateAndTime:date getStartDay:0 getStartHour:23 getStartMin:30 getEndDay:3 getEndHour:24 getEndMin:0] == YES)
text = [NSString stringWithFormat:#"Closes in %# min", countdownNumber];
else text = #"Closed";
As you can see to do this for all the days of the week and hours on each day it requires a lot of if statements and is very bad. Just for this one example it requires 8 if-else statements (VERY TIME CONSUMING)
Now the basis of this question is how can I make this much more efficient/ what is a better way to do this, while sill being able to have the countdown for the last 30 minutes?
I have done some research and can't find anything that has a countdown to when something is closing and is efficient.
Here is the full if-else statements for the example if you need it or want to see https://gist.github.com/spennyf/b0b18e31c3e9deaa0455
Thanks for the help and/or advice in advance :)
EDIT
This is at the top of my .m file I have this
#interface HomeTableViewController () {
ShopHours WeekSchedule[];
}
#end
But this gives me the compile error I talked about in the comments, how can I set this variable so it can be used throughout this one .m file? Right now I am just passing it is as an extra parapet for the function which should be fine. :)
And what would be the best way to set up the if statement for if the place is closing in 30 minutes once I have determined that it is open? And could you add the set up for if a place is open for parts of a day/ over midnight.
Thanks for all your help :)
Try tackling the problem from a different direction. Take the following as a starting point, not everything is explained - if you say don't know what a dictionary is then you should research it.
Looking At The Problem
You have a table of opening and closing times, checking whether your shop is open should be a lookup into this table - just as you would in "real life". To know if the shop is open you need to know the weekday - which tells you which line of your table to consult, and the time - which you compare against the two times in that line of the table.
To represent a table in a program you typically use an array, to represent two associated times - such as the open & close times - you might use a record, object or dictionary etc.
How can you represent a time of day? Well general time & date calculations are complicated, but after the weekday all you need to know is the time in that day, and assuming you are not worried about leap seconds (you're not) you can assume there are 24 hours of 60 minutes each in a day so you can store the time as the number of minutes since midnight - giving you a single number. If you use the number of minutes since midnight to determine whether the shop is open or closed you can avoid complicated date comparisons.
Some Code
As your code already already shows you can use NSCalendar to obtain the weekeday, hours and minutes of any NSDate.
How to get the hours and minutes to minutes since midnight, well that is simple arithmetic but you'll want to do it a few times so maybe a simple macro to convert a time in hours and minutes:
#define TO_MINUTES(hour, min) (hour * 60 + min)
How to represent the table of opening times?
Well you could use an NSArray, indexed by the weekday, where each element is an NSDictionary containing two key/value pairs - for the open and closing times. However your times are just integers, the number of minutes since midnight, and storing integers in an NSDictionary requires wrapping them as a NSNumber objects. (If that doesn't make sense, time to do some research!)
Another approach would be to use a C-style array and structure for your table - this will work quite well as you are only storing integers.
Here is a C structure definition to represent the opening and closing times:
typedef struct
{
NSInteger openTime;
NSInteger closeTime;
} ShopHours;
and with that and the above macro you can easily define a constant array representing the shop hours:
ShopHours WeekSchedule[] =
{
{0, 0}, // index 0 - ignore
{ TO_MINUTES(9, 0), TO_MINUTES(24, 0) }, // index 1 - Sunday
{ TO_MINUTES(7, 30), TO_MINUTES(24, 0) }, // index 2 - Monday
...
{ TO_MINUTES(9, 0), TO_MINUTES(22, 0) }, // index 7 - Saturday
};
In real code you might read this in from a data file, but the above global array will do for now.
(Note that index 0 is ignored - NSDateComponents number the days starting from 1 for Sunday and arrays (both C-style and NSArray) are indexed from 0, simply ignoring the zeroth element avoids do - 1's in your code.)
You already have code to break an NSDate into NSDateComponents, using that you can get the weekday and minutes since midnight easily:
NSInteger weekday = comps.weekday;
NSInteger minutes = TO_MINUTES(comps.hour, comps.minute);
use weekday to index the WeekSchedule table, and compare minutes to the two entries and you are done, e.g. is the shop open:
if (minutes >= WeekSchedule[weekday].openTime && minutes <= WeekSchedule[weekday].closeTime)
{
// shop is open...
}
else
{
// shop is closed...
}
You can wrap the above into a method which given a date tells you the state of the shop:
- (NSString *) shopState:(NSDate *)dateAndTime
{
// break out the weekday, hours and minutes...
// your code from the question
NSInteger weekday = comps.weekday;
NSInteger minutes = TO_MINUTES(comps.hour, comps.minute);
if (minutes >= WeekSchedule[weekday].openTime && minutes <= WeekSchedule[weekday].closeTime)
{
// shop is open...
// determine if its closing within 30 mins and return an appropriate string
}
else
{
// shop is closed...
return #"Closed";
}
}
As you'll see this solution is a lot shorter than the approach you took.
HTH
Addendum - Refinements
As Rob Napier has pointed out in comments and in case its not obvious, the above outline of a solution is just that and omits cases such as shops being open over midnight. Here are some things you might want to consider:
Shops open for more than one period per day: Some shops close over lunch, restaurants may open for lunch and evenings, etc. To handle this you need a list of open/close times per day rather than just a single pair. Once you have determined the weekday testing is a matter of iterating over such a list.
Shops open across midnight: This is just a special case of (1), think about it...
Time zones: In your code in the question and the code in this answer it is assumed that the open/close times and the time being tested are all from the same time zone. If you wish to support, say, a person in Canada determining whether a shop in Germany is currently open and can be phoned you need to allow for the time difference.
Daylight Saving Time: This is the corner case Rob mentions in the comments. When a DST change occurs an hour might be skipped or repeat. This is only an issue if you support shops which open/close in that hour - shops which are open right across the period of change need no special handling. NSCalendar will give the correct hour/min from the time you are testing, you need to handle any adjustments to open/close times. For example consider a shop which closes at 2am, a DST change jumps 2am back to 1am, is the shop open at 1:30am? Yes the first time it comes around, but what about the second? Deciding this is an issue beyond time calculations.
You need to decide whether and how to address these.
Some More Hints
OK, so its Christmas (take that how you like - over eating slowing brains, time for gifts, etc. ;-))
I see you've asked in another question how to create the table dynamically rather than using a static one, so you have that covered.
Let's consider the multiple opening times day & open over midnight:
Arrays of arrays would work, but you could instead just keep an array of opening times for a whole week. E.g. change the TO_MINUTES macro to take the day number as well and store all the times as the number of minutes since Sunday 0000hrs. Now instead of indexing the array to find the day you iterate or search it - the array is ordered so you could binary search if you wish, but a simple iteration is probably fast enough (how many open/close periods are there in a week?)
By setting the closing time to the next day (1) covers opening over midnight for everything but Sat -> Sun, including closing within 30 min calculations.
To handle Sat -> Sun first split the period into Sat night and Sun morning parts. Add them to your array, they will be the first (early Sunday morning) and last (late Sat night) entries. Now when you go to determine the minutes till closing check if the closing time is Sat midnight (e.g. TO_MINUTES(7, 24, 0)), if so check if the first entry's opening time is Sunday 0000hrs, if so you want to adjust the closing time to do the 30 min check (add in the length of the first period).
That will handle multiple periods and open over midnight. It doesn't handle DST, shop holidays, etc. - you need to decided how much to handle. For DST use NSTimeZone to find out when and by how much the times changes (its not always by 1 hour) to figure out the "repeated" and "missing" times - but remember this is only an issue of your shop actually opens/closes during those times.
It's New Year ;-)
Seriously Rob decided to give almost the complete code but using Objective-C objects and a number of methods so I thought I'd add my code for comparison because it raises and interesting issue.
What should be noted first is the similarity, algorithmically the two solutions are close - the wraparound is handled differently but either approach could do it either way so that is not significant.
The difference comes to the choice of data structure - should you use C structures and arrays for something this simple or Objective-C objects? The frameworks themselves have plenty of structure types - e.g. NSRect et al - there is nothing wrong with using them in Objective-C code. The choice isn't black and white, there is a gray area where either might be suitable, and this problem probably falls in that gray area. So here's the multiple openings times/day solution:
// convenience macro
// day 1 = Sunday, ... 7 = Saturday
#define TO_MINUTES(day, hour, min) ((day * 24 + hour) * 60 + min)
#define WEEK_START TO_MINUTES(1, 0, 0)
#define WEEK_FINISH TO_MINUTES(7, 24, 0)
typedef struct
{ NSInteger openTime;
NSInteger closeTime;
} ShopHours;
// Opening hours
ShopHours WeekSchedule[] =
{ { TO_MINUTES(1, 0, 0), TO_MINUTES(1, 0, 15) }, // Sat night special, part of Sat 11:30pm - Sun 0:15am
{ TO_MINUTES(1, 9, 0), TO_MINUTES(1, 24, 0) }, // Sun 9am - Midnight
{ TO_MINUTES(2, 7, 30), TO_MINUTES(2, 24, 0) }, // Mon 7:30am - Midnight
{ TO_MINUTES(3, 7, 30), TO_MINUTES(3, 24, 0) },
{ TO_MINUTES(4, 7, 30), TO_MINUTES(5, 2, 0) }, // Midweek madness, Wed 7:30am - Thursday 2am
{ TO_MINUTES(5, 7, 30), TO_MINUTES(5, 24, 0) },
{ TO_MINUTES(6, 7, 30), TO_MINUTES(6, 22, 0) }, // Fri 7:30am - 10pm
{ TO_MINUTES(7, 9, 0), TO_MINUTES(7, 22, 0) }, // Sat 9am - 10pm
{ TO_MINUTES(7, 23, 30),TO_MINUTES(7, 24, 0) }, // Sat night special, part of Sat 11:30pm - Sun 0:15am
};
- (NSString *) shopState:(NSDate *)dateAndTime
{ NSCalendar *calendar = [NSCalendar currentCalendar];
const NSCalendarUnit units = NSWeekdayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;
NSDateComponents *comps = [calendar components:units fromDate:dateAndTime];
NSInteger minutes = TO_MINUTES(comps.weekday, comps.hour, comps.minute);
NSLog(#"%ld (%ld, %ld, %ld)", minutes, comps.weekday, comps.hour, comps.minute);
unsigned periods = sizeof(WeekSchedule)/sizeof(ShopHours);
for (unsigned ix = 0; ix < periods; ix++)
{ if (minutes >= WeekSchedule[ix].openTime)
{ if (minutes < WeekSchedule[ix].closeTime)
{
// shop is open, how long till close time?
NSInteger closeTime = WeekSchedule[ix].closeTime;
// handle Sat -> Sun wraparound
if (closeTime == WEEK_FINISH && WeekSchedule[0].openTime == WEEK_START)
closeTime += WeekSchedule[0].closeTime - WEEK_START;
NSInteger closingIn = closeTime - minutes;
if (closingIn <= 30)
return [NSString stringWithFormat:#"Closes in %ld min", closingIn];
else
return #"Open";
}
}
else // minutes < WeekSchedule[ix].openTime
break;
}
return #"Closed";
}
The DST Issue
I first though this was a non-issue, Rob raised it in comments, and now he thinks its a non-issue, but it isn't - though its somewhat academic maybe.
It is a non-issue if you don't use NSDate to represent the time being queried, and Rob's solution takes that route.
The original question and the code above does use NSDate and breaks out the weekday, hour and minute from it as NSDateComponents. Consider the situation where 2am becomes 1am due to DST change and the shop usually closes at 1:30am. If you start with an NSDate value before 1am and increment, say by 10min each time, until you get a hour value from components:fromDate: greater than 2 you'll see values like: 00:50, 01:00, 01:10, ..., 01:50, 01:00, 01:10, ..., 01:50, 02:00, 02:10. Testing each of these times will report the shop as closed for 30 mins after the first 01:30 is passed, then it will re-open for 30 mins until the next one is passed!
This issue only occurs if you start with an NSDate, if you simple take a weekday/hour/min as your input then it does not occur. Either approach (struct or object) can operate either way, you just have to decide whether it is an issue you wish to address.
Storing your times as C structures creates a number of memory management headaches. I'd avoid that if you can (and I think you can). Instead, I recommend creating some new classes to help us out here.
First, we should think about these times as "nominal times within a week." By making these nominal times, we can explicitly get rid of DST concerns by saying that "1:30am on Sunday" means "the first instant that we would call 1:30am, no matter what DST transitions might happen to create another 1:30a." That's how shops usually work anyway, but it's important to be precise when thinking about time functions.
The great thing about "nominal times within a week" is that we can start counting minutes from Sunday at midnight, and we know that there will be exactly 60*24*7 (10,080) minutes in the week. We only really care here about minutes, so we just need to keep track of a number between 0 and 10,079. But we must remember that these numbers are subject to modular arithmetic (they "wrap around"). It is meaningless to ask whether Tuesday is before or after Wednesday. Tuesday is before and after Wednesday. But it's meaningful to ask whether Tuesday is between Monday (as a starting point) and Wednesday (as an ending point). We can determine if moving forward from Tuesday we will encounter Wednesday before we encounter Monday. If that's true, it's between. That's how you have to think about modular time.
OK, way too much theory. Let's look at some code (all the code is here). First we'd like a WeekTime to represent some time within a week:
typedef enum {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday
} Weekday;
#interface WeekTime : NSObject
#property (nonatomic, readonly) NSInteger minutesSinceStartOfWeek;
- (instancetype)initWithWeekday:(Weekday)weekday hour:(NSInteger)hour minute:(NSInteger)minute;
#end
The impl of this should be obvious so I won't paste it here.
Now we want to think about ranges of nominal week times. We'll make these open ranges, so they include their start time but not their end time. If a place closes at 9p, you want to return "Closed" at 9p, not "Closing in 1m."
#interface WeekTimeRange : NSObject
#property (nonatomic, readonly) WeekTime *start;
#property (nonatomic, readonly) WeekTime *end;
- (instancetype)initWithStart:(WeekTime *)start end:(WeekTime *)end;
- (BOOL)contains:(WeekTime *)time;
#end
Most of this should be obvious, but contains: has one little trick to it. Since we're in modular arithmetic, it's legal for start to be "greater" than end (remembering that "greater" doesn't mean anything). That's easy to deal with:
- (BOOL)contains:(WeekTime *)time {
NSInteger queryMinutes = time.minutesSinceStartOfWeek;
NSInteger startMinutes = self.start.minutesSinceStartOfWeek;
NSInteger endMinutes = self.end.minutesSinceStartOfWeek;
if (startMinutes < endMinutes) {
return (startMinutes <= queryMinutes &&
queryMinutes < endMinutes);
} else {
return (queryMinutes < endMinutes ||
queryMinutes >= startMinutes);
}
}
(In my example I've chosen to make the range where start==end be undefined. If you want to assign some meaning to that, either meaning "all times" or "no times," then you may need to tweak this. Be careful of "all times," though. That really would mean the shop closes and immediately reopens, so you'll get "closing in..." messages. That's why I just made start==end be undefined.)
OK, finally we'll want a few helpers:
WeekTimeRange *WTMakeRange(Weekday startDay, NSInteger startHour, NSInteger startMinute,
Weekday endDay, NSInteger endHour, NSInteger endMinute) {
return [[WeekTimeRange alloc]
initWithStart:[[WeekTime alloc] initWithWeekday:startDay hour:startHour minute:startMinute]
end:[[WeekTime alloc] initWithWeekday:endDay hour:endHour minute:endMinute]];
}
WeekTimeRange *WTFindOpenRangeIncluding(NSArray *ranges, WeekTime *time) {
for (WeekTimeRange *range in ranges) {
if ([range contains:time]) {
return range;
}
}
return nil;
}
NSString *WTStatus(WeekTimeRange *range, WeekTime *requestedTime) {
if (range != nil) {
NSInteger minutesLeft = range.end.minutesSinceStartOfWeek - requestedTime.minutesSinceStartOfWeek;
if (minutesLeft < 0) {
minutesLeft += [WeekTime maxMinute];
}
if (minutesLeft <= 30) {
return [NSString stringWithFormat:#"Closing in %ld minutes", (long)minutesLeft];
} else {
return #"Open";
}
} else {
return #"Closed";
}
}
These shouldn't require much explanation. Let's see it in practice. You already seem to know how to turn dates into their components, so I'm going to just create a WeekTime directly and not worry about converting it from an NSDate.
NSArray *shopHours = #[WTMakeRange(Monday, 7, 30, Tuesday, 0, 0),
WTMakeRange(Tuesday, 7, 30, Wednesday, 0, 0),
WTMakeRange(Wednesday, 7, 30, Thursday, 0, 0),
WTMakeRange(Thursday, 7, 30, Friday, 0, 0),
WTMakeRange(Friday, 7, 30, Friday, 22, 0),
WTMakeRange(Saturday, 9, 0, Saturday, 22, 0),
WTMakeRange(Sunday, 9, 0, Monday, 2, 0)
];
WeekTime *mondayElevenPM = [[WeekTime alloc] initWithWeekday:Monday hour:23 minute:00];
WeekTimeRange *openRange = WTFindOpenRangeIncluding(shopHours, mondayElevenPM);
NSString *result = WTStatus(openRange, mondayElevenPM);
NSLog(#"%#", result);
I haven't tried tons of test cases, but my ad hoc tests seem to work. All the code is in the gist. One case I don't test for is whether your ranges overlap. The above algorithm doesn't care if the ranges are in order, but you may get incorrect "closing in..." messages if the ranges overlap. Resolving that (either by throwing an error, or by merging ranges) should be an pretty easy enhancement.
Instead of having the method dateAndTime with all of those arguments create a custom date object and set the properties you create on that object with values like so:
#interface MyDateModelObject : NSObject
#property (nonatomic, strong) NSDate *startDay;
#property (nonatomic, assign) NSInteger startHour;
And so on...
Turning the dateAndTime method to:
- (BOOL)dateAndTime:(MyDateModelObject *)myDateModelObject
Now getting into dateAndTime's code. This method is doing too much. To follow the Single Responsibility Principle I would remove code like the NSDateFormatter and the date comparison code into their own methods into a utility methods.
With that said I would go further and rename dateAndTime to the following and add your gist code inside it:
- (NSString *)retrieveStoreState:(MyDateModelObject *)date
Now in your code you can create a model object and set it's properties, pass it to retrieveStoreState and make it return just the state based on the logic you have in your gist.
Hopefully this gives you a good direction to go in. If you follow the SRP you should be able to clean your code and spruce your gist up a bit making it more readable.
This question already has answers here:
iOS: Compare two dates
(6 answers)
Closed 9 years ago.
I want to compare two dates.This my code i wrote
NSDate *c_date=[NSDate date];
NSDate *newDate = [c_date dateByAddingTimeInterval:300];
This code is not working?What i am missing?
From NSDate, you can use
- (NSComparisonResult)compare:(NSDate *)anotherDate
You can use
- (NSComparisonResult)compare:(NSDate *)other;
which will yield a
typedef NS_ENUM(NSInteger, NSComparisonResult) {NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending};
in your example you're just creating two different NSDate objects with a known NSTimeInterval (300), so there is no comparison.
Use [NSDate timeIntervalSince1970] which will return a simple double value that can be used for comparison just like any other.
NSDate *c_date=[NSDate date];
NSDate *newDate = [c_date dateByAddingTimeInterval:300];
NSTimeInterval c_ti = [c_date timeIntervalSince1970];
NSTimeInterval new_ti = [newDate timeIntervalSince1970];
if (c_ti < new_ti) {
// c_date is before newDate
} else if (c_ti > new_ti) {
// c_date is after newDate
} else {
// c_date and newDate are the same
}
There are also the [NSDate compare:] method, that you might find more convenient.
Here's the thing (well, it might be the thing, it's not completely 100% clear from your question). NSDate represents an interval in seconds since 1st January 1970. Internally, it uses a floating point number (a double in OS X, not sure in iOS). This means that comparing two NSDates for equality is dry hit and miss, actually it's mostly miss.
If you want to make sure one date is within, say, 1/2 a second of another date, try:
fabs([firstDate timeIntervalSinceDate: secondDate]) < 0.5
If you just want both dates to be on the same day, you'll need to muck about with NSCalendar and date components.
See also this SO answer.
https://stackoverflow.com/a/6112384/169346
I need the ability to convert an NSDate value to a GMT Date.
How can I go about converting an NSDate value to a GMT formatted NSDate value, independent of whatever date locale settings the iPhone device is using?
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"yyyy-MM-dd'T'HH:mm";
NSTimeZone *gmt = [NSTimeZone timeZoneWithAbbreviation:#"GMT"];
[dateFormatter setTimeZone:gmt];
NSString *timeStamp = [dateFormatter stringFromDate:[NSDate date]];
[dateFormatter release];
Working with time in Cocoa can be complicated. When you get an NSDate object, it's in the local time zone. [[NSTimeZone defaultTimeZone] secondsFromGMT] gives you the offset of the current time zone from GMT. Then you can do this:
NSDate *localDate = // get the date
NSTimeInterval timeZoneOffset = [[NSTimeZone defaultTimeZone] secondsFromGMT]; // You could also use the systemTimeZone method
NSTimeInterval gmtTimeInterval = [localDate timeIntervalSinceReferenceDate] - timeZoneOffset;
NSDate *gmtDate = [NSDate dateWithTimeIntervalSinceReferenceDate:gmtTimeInterval];
Now gmtDate should have the correct date in GMT for you. In order to display it, look at NSDateFormatter, specifically the setDateStyle and setTimeStyle methods. You create an NSDateFormatter, configure it the way you want, and then call stringFromDate: to get a nicely formatted string.
Howard's Answer is correct and please vote it up and accept it.
For reference I think it is useful to explain the difference between date objects and localised date representations are.
In many programming languages date objects are used to represent unique points in time. Ignoring Relativistic arguments it can be assumed that at any instance we can define a point in time which is equal universally for every one, regardless of how we measure time.
If for each point in time we could construct a unique label, that label could be passed around and referenced unambiguously. The purpose of date objects is to act as a unique universal label for a given point in time.
One could come up with any number of techniques to construct such a labelling scheme and how each date object chooses to do so is immaterial to anyone using them.
An example may be to use a numeric offset from a universal event (X seconds since the sun exploded).
It is only when we wish to take a time point and serialize it into a human readable string that we must deal with the complexities of time zones, locales, etc...
(Local Date String) + (Date Formatter) => Time Point
Time Point + (Date Formatter) => (Local Date String)
Every time point is universal... there is no such thing as a new york time point, or gmt time point, only once you convert a time point to a local string (using a date formatter) does any association to a time zone appear.
Note: I'm sure there are many blogs/articles on this very issue, but my google foo is failing me at this hour. If anyone has the enthusiasm to expand on this issue please feel free to do so.
Swift 4:
//UTC or GMT ⟺ Local
extension Date {
// Convert local time to UTC (or GMT)
func toGlobalTime() -> Date {
let timezone = TimeZone.current
let seconds = -TimeInterval(timezone.secondsFromGMT(for: self))
return Date(timeInterval: seconds, since: self)
}
// Convert UTC (or GMT) to local time
func toLocalTime() -> Date {
let timezone = TimeZone.current
let seconds = TimeInterval(timezone.secondsFromGMT(for: self))
return Date(timeInterval: seconds, since: self)
}
}
While Alex's answer was a good start, it didn't deal with DST (daylight savings time) and added an unnecessary conversion to/from the reference date. The following works for me:
To convert from a localDate to GMT, taking DST into account:
NSDate *localDate = <<your local date>>
NSTimeInterval timeZoneOffset = [[NSTimeZone systemTimeZone] secondsFromGMTForDate:localDate];
NSDate *gmtDate = [localDate dateByAddingTimeInterval:-timeZoneOffset]; // NOTE the "-" sign!
To convert from a GMT date to a localDate, taking DST into account:
NSDate *gmtDate = <<your gmt date>>
NSTimeInterval timeZoneOffset = [[NSTimeZone systemTimeZone] secondsFromGMTForDate:gmtDate];
NSDate *localDate = [gmtDate dateByAddingTimeInterval:timeZoneOffset];
One small note: I used dateByAddingTimeInterval, which is iOS 4 only. If you are on OS 3 or earlier, use addTimerInterval.
Have you tried looking at the documentation for NSDateFormatter?
NSDateFormatter
NSDateFormatter appears to have some methods for playing with TimeZones, particularly
-setTimeZone:
I haven't tested it myself, but I imagine that if you set GMT as the timezone on a date that is originally represented in another timezone, it will display the date with the correct adjustments to match the new timezone.