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.
Related
I have a date time:
Friday, July 8, 2016 at 6:30:00 PM India Standard Time
I want a date time like this:
Friday, July 8, 2016 at 6:30:00 PM Central Daylight Time
I do not want to convert the dates using NSDateFormatter from one timezone to another, because this conversion will give me
Friday, July 8, 2016 at 8:00:00 AM Central Daylight Time
I want the same date time, but in a different timezone. How can I do that?
dasblinkenlight's answer is absolutely correct, but I'll explain a little more fully, and show the steps in ObjC instead of Swift. You don't want the same date in a different time zone; you want the same local time (or wall time): what a person would see when they look at the clock.
The same date in a different zone has a different local time: Friday, July 8, 2016 at 6:30:00 PM India Standard Time is Friday, July 8, 2016 at 8:00:00 AM Central Daylight Time. At the moment that a person in Kolkata looks at the clock and sees 6:30 PM, a person in Omaha looking at her clock would see 8:00 AM.
First, solely for the purpose of the demonstration, we'll create the original date from the string you've given, using a date parser. You would just get the date from your notification object.
NSString * originalDateString = #"Friday, July 8, 2016 at 6:30:00 PM India Standard Time";
NSDateFormatter * parser = [NSDateFormatter new];
[parser setDateStyle:NSDateFormatterFullStyle];
[parser setTimeStyle:NSDateFormatterFullStyle];
NSDate * originalDate = [parser dateFromString:originalDateString];
Although the original string contained time zone information, the date does not. The wall time, however, is dependent on the zone, so we will need a time zone object for the next step. You would get this too from your notification object. Here, we'll just create one from its identifier: NSTimeZone * originalTZ = [NSTimeZone timeZoneWithAbbreviation:#"IST"];
We need an NSDateComponents to represent the local time values for the date in the zone. NSCalendar is responsible for breaking the date into those components:
NSCalendar * cal = [NSCalendar currentCalendar];
NSDateComponents * localComps;
localComps = [cal componentsInTimeZone:originalTZ
fromDate:originalDate];
If you inspect localComps now, you'll see the values encapsulate the wall time you expect: 2016-07-08 18:30:00. Now that we have these values broken out, we can essentially put them into another zone without translation. That is, without reinterpreting the values as an absolute time, which would give 2016-07-08 08:00:00, as discussed above. Simply change the time zone (again, you would get the zone from your notification rather than from its abbreviation):
NSTimeZone * destinationTZ = [NSTimeZone timeZoneWithAbbreviation:#"CDT"];
[localComps setTimeZone:destinationTZ];
Now the calendar can construct a new date object from those components. NSDate * destinationDate = [cal dateFromComponents:localComps];*
This date is the time-zone-independent moment in time that represents Friday, July 8, 2016 at 6:30:00 PM Central Daylight Time. As with any NSDate, if you simply print it out, it will be formatted in GMT. If you want to see the wall time values you expect, you need to use a formatter set to the destination time zone.
*As dasblinkenlight's answer shows, localComps has an associated calendar, so it's capable of creating a date itself, too. I thought this would be a bit clearer.
You can do this by splitting your date into components in the original time zone, setting the target time zone on your components, and retrieving the resultant date (it is not going to be the same date/time)
import Foundation
let calendar = NSCalendar.currentCalendar()
let date = NSDate()
let origTz = NSTimeZone(abbreviation: "IST")!
let destTz = NSTimeZone(abbreviation: "CDT")!
let components = calendar.componentsInTimeZone(origTz, fromDate: date)
components.timeZone = destTz
let res = components.date!
let fmt = NSDateFormatter()
fmt.timeZone = destTz
fmt.locale = NSLocale(localeIdentifier: "en_US_POSIX")
fmt.dateStyle = .FullStyle
fmt.timeStyle = .FullStyle
fmt.timeZone = origTz
print(fmt.stringFromDate(date)) // Sunday, July 10, 2016 at 2:14:21 AM India Standard Time
fmt.timeZone = destTz
print(fmt.stringFromDate(res)) // Sunday, July 10, 2016 at 2:14:21 AM Central Daylight Time
I need to be able to convert the date of the day to an integer so that I can then save it as an integer to use in other areas in my code. I know that there are other ways to save a date in Xcode, but for this specific problem I need to use it as an integer. So my over all question is how would I be able to convert the date into an integer so that I can then use that integer in an NSString? Thanks in advance!
You can do that by using the timeIntervalSinceReferenceDate method.
// Get time from baseline date to now as a double.
double interval = [[NSDate date] timeIntervalSinceReferenceDate];
// Re-apply the time value
NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:interval];
Reference date is a base line date that will always have the same value. So you can safely use it as a reference point. Then you just store the NSTimeInterval (typedef to double) between the reference date and now. That gives you the number of seconds that has taken place between the reference date and now.
Edit:
As mentioned in the comments, if your date is eArlier than 2001, you can use the 1970 reference date.
// Get time from baseline date to now as a double.
double interval = [[NSDate date] timeIntervalSince1970];
// Re-apply the time value
NSDate *date = [NSDate dateWithTimeIntervalSince1970:interval];
If your dates are later than 2001, either method will work for you.
You may convert from double to integer using
int interval = (int)[[NSDate date] timeIntervalSince1970];
Since the double will be truncated to the nearest second, you will want to decide if you want to round up or down. Leaving as is will round down. You can round to the proper nearest second by using:
interval = round(interval);
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'm trying to work with dates and create dates in the future, but daylight savings keeps getting in the way and messing up my times.
Here is my code to move to midnight of the first day of the next month for a date:
+ (NSDate *)firstDayOfNextMonthForDate:(NSDate*)date
{
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
calendar.timeZone = [NSTimeZone systemTimeZone];
calendar.locale = [NSLocale currentLocale];
NSDate *currentDate = [NSDate dateByAddingMonths:1 toDate:date];
NSDateComponents *components = [calendar components:NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit
fromDate:currentDate];
[components setDay:1];
[components setHour:0];
[components setMinute:0];
[components setSecond:0];
return [calendar dateFromComponents:components];
}
+ (NSDate *) dateByAddingMonths: (NSInteger) monthsToAdd toDate:(NSDate*)date
{
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
calendar.timeZone = [NSTimeZone systemTimeZone];
calendar.locale = [NSLocale currentLocale];
NSDateComponents * months = [[NSDateComponents alloc] init];
[months setMonth: monthsToAdd];
return [calendar dateByAddingComponents: months toDate: date options: 0];
}
Which give the dates when I run the method iteratively on a date:
2013-02-01 00:00:00 +0000
2013-03-01 00:00:00 +0000
2013-03-31 23:00:00 +0000 should be 2013-04-01 00:00:00 +0000
2013-04-30 23:00:00 +0000 should be 2013-05-01 00:00:00 +0000
My initial thought was to not use systemTimeZone but that didn't seem to make a difference. Any ideas for how I can make the time constant and not take into account the change in daylight savings?
For a given calendar date/time, it is not possible as a general rule to predict what actual time (seconds since the epoch) that represents. Time zones change and DST rules change. It's a fact of life. DST has a tortured history in Australia. DST rules have been very unpredictable in Israel. DST rules recently changed in the US causing huge headaches for Microsoft who was storing seconds rather than calendar dates.
Never save NSDate when you mean NSDateComponents. If you mean "the first of May 2013 in London," then save "the first of May 2013 in London" in your database. Then calculate an NSDate off of that as close to the actual event as possible. Do all your calendar math using NSDateComponents if you care about calendar things (like months). Only do NSDate math if you really only care about seconds.
EDIT: For lots of very useful background, see the Date and Time Programming Guide.
And one more side note about calendar components: when I say "the first of May 2013 in London," that does not mean "midnight on the first of May." Don't go adding calendar components you don't actually mean.
Remember that what your program is printing to the log is the GMT time, not your local time. Therefore, it's correct for dates after the switch to DST in your local time zone that the GMT will have shifted by one hour.
I had the same problem, and people saying it's not actually a problem (as I saw on some related threads) doesn't help the situation. This kind of problem hurts whenever you have to deal with timezones and DST, and I always feel that I have to re-learn it every time.
I was dealing with epoch times as much as possible (it's a charting application), but there were times when I needed to use NSDate and NSCalendar (namely, for formatting the axis labels, and for marking calendar months, quarters and years). I struggled with this for a day or so, trying to set various timezones on calendars and suchlike.
In the end I found that the following line of code in my app delegate helped immensely:-
// prevent DST bugs by setting default timezone for app
if let utcZone = NSTimeZone(abbreviation: "UTC") {
NSTimeZone.setDefaultTimeZone(utcZone)
}
On top of this, the source data had to be sanitised, so whenever I used an NSDateFormatter on incoming data, I made sure I set its time zone to the correct one for the data source (in my case, it was GMT). This gets rid of nasty DST issues in the data source and ensures all the resultant NSDates can be converted nicely into epoch times without worrying about DST.
Another solution would be to check the hour component of the date, and in case it is 1 or 23 (instead of 0) just change the date by using
Calendar.current.date(byAdding: .hour, value: -1, to: currentDate)
or Calendar.current.date(byAdding: .hour, value: 1, to: currentDate) respectively.
I'm trying to get NSDate from UIDatePicker, but it constantly returns me a date time with trailing 20 seconds. How can I manually set NSDate's second to zero?
NSDate is immutable, so you cannot modify its time. But you can create a new date object that snaps to the nearest minute:
NSTimeInterval time = floor([date timeIntervalSinceReferenceDate] / 60.0) * 60.0;
NSDate *minute = [NSDate dateWithTimeIntervalSinceReferenceDate:time];
Edit to answer Uli's comment
The reference date for NSDate is January 1, 2001, 0:00 GMT. There have been two leap seconds added since then: 2005 and 2010, so the value returned by [NSDate timeIntervalSinceReferenceDate] should be off by two seconds.
This is not the case: timeIntervalSinceReferenceDate is exactly synchronous to the wall time.
When answering the question I did not make sure that this is actually true. I just assumed that Mac OS would behave as UNIX time (1970 epoch) does: POSIX guarantees that each day starts at a multiple of 86,400 seconds.
Looking at the values returned from NSDate this assumption seems to be correct but it sure would be nice to find a definite (documented) statement of that.
You can't directly manipulate the NSTimeInterval since that is the distance in seconds since the reference date, which isn't guaranteed to be a 00-second-time when divided by 60. After all, leap seconds may have been inserted to adjust for differences between solar time and UTC. Each leap second would throw you off by 1. What I do to fix the seconds of my date to 0 is:
NSDate * startDateTime = [NSDate date];
NSDateComponents * startSeconds = [[NSCalendar currentCalendar] components: NSSecondCalendarUnit fromDate: startDateTime];
startDateTime = [NSDate dateWithTimeIntervalSinceReferenceDate: [startDateTime timeIntervalSinceReferenceDate] -[startSeconds second]];
This takes care of leap seconds. I guess an even cleaner way would be to use -dateByAddingComponents:
NSDate * startDateTime = [NSDate date];
NSDateComponents * startSeconds = [[NSCalendar currentCalendar] components: NSSecondCalendarUnit fromDate: startDateTime];
[startSeconds setSecond: -[startSeconds second]];
startDateTime = [[NSCalendar currentCalendar] dateByAddingComponents: startSeconds toDate: startDateTime options: 0];
That way you're guaranteed that whatever special things -dateByAddingComponents: takes care of is accounted for as well.
Here is a Swift extension for anyone who is interested:
extension Date {
public mutating func floorSeconds() {
let calendar = Calendar.current
let components = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: self)
self = calendar.date(from: components) ?? self // you can handle nil however you choose, probably safe to force unwrap in most cases anyway
}
}
Example usage:
let date = Date()
date.floorSeconds()
Using DateComponents is much more robust than adding a time interval to a date.
Although this is an old question and Uli has given the "correct" answer, the simplest solution IMHO is to just subtract the seconds from the date, as obtained from the calendar. Mind that this may still leave milliseconds in place.
NSDate *date = [NSDate date];
NSDateComponents *comp = [[NSCalendar currentCalendar] components:NSCalendarUnitSecond
fromDate:date];
date = [date dateByAddingTimeInterval:-comp.second];
NSDate records a single moment in time. If what you want to do is store a specific day, don't use NSDate. You'll get lots of unexpected head-aches related to time-zones, daylight savings time e.tc.
One alternative solution is to store the day as an integer in quasi-ISO standard format, like 20110915 for the 15th of September, 2011. This is guaranteed to sort in the same way as NSDate would sort.
Here is an extension to do this in Swift:
extension NSDate {
func truncateSeconds() -> NSDate {
let roundedTime = floor(self.timeIntervalSinceReferenceDate / 60) * 60
return NSDate(timeIntervalSinceReferenceDate: roundedTime)
}
}
Also Swift ...
extension Date {
func floored() -> Date {
let flooredSeconds = DateComponents(second: 0, nanosecond: 0)
return Calendar.current.nextDate(after: self,
matching: flooredSeconds,
matchingPolicy: .strict,
direction: Calendar.SearchDirection.backward)!
}
}