Format Duration as ISO8601 Period with NSDateIntervalFormatter - objective-c

I am trying to convert the duration between two dates to the format "'P'yyyy'Y'M'M'd'DT'H'H'm'M's.S'S'". An example of my expected output is: P0Y0M0DT0H0M0.002S (this is port of an Android project that uses DurationFormatUtils).
My test case:
NSDateIntervalFormatter *durationFormatter = [NSDateIntervalFormatter durationFormatter];
NSDate *date = [NSDate date];
NSDate *end = [date dateByAddingTimeInterval:25];
NSString *duration = [durationFormatter stringFromDate:date toDate:end];
XCTAssertTrue( [duration hasPrefix:#"P"], #"Actual String: %#", duration );
My formatter is defined as:
+ (NSDateIntervalFormatter*) durationFormatter {
static NSDateIntervalFormatter *formatter = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [[NSDateIntervalFormatter alloc] init];
formatter.dateTemplate = #"'P'yyyy'Y'M'M'd'DT'H'H'm'M's.S'S'";
});
return formatter;
}
When I run this, duration is 3/5/2015, 16:46:20.3. If I set the locale on the formatter to nil and set the date and time format to NSDateIntervalFormatterFullStyle, the duration has the following format: Thursday, March 5, 2015, 4:51:36 PM GMT-05:00 - Thursday, March 5, 2015, 4:52:01 PM GMT-05:00.
How can I format the duration between two dates with ISO8601 period format (or at least as "'P'yyyy'Y'M'M'd'DT'H'H'm'M's.S'S'")?

The class you are using, NSDateIntervalFormatter, is designed to produce intervals of the form date1 - date2, not format the time difference between two dates.
To get what you are after you need to use NSCalendar, NSDateComponents and stringWithFormat: (or similar).
First you need a calendar, [NSCalendar currentCalendar] is a good choice. (The difference between two absolute points in times can be a different when expressed in the units of a particular calendar - if that doesn't make sense don't worry, just use currentCalendar!)
Next use the components:fromDate:toDate:options: method to obtain an instance of NSDateComponents which contains the number of years, days, hours etc. between your two dates.
Finally use stringWithFormat: to format the components as you wish.
HTH

You have misunderstood what NSDateIntervalFormatter is for. It's for formatting an interval as a string of the form start - end.
It's not for formatting a duration.
You can use NSDateComponentsFormatter to format a duration. However, I don't know if it supports the specific format you want.
It has the convenience methods -stringFromDate:toDate: and -stringFromTimeInterval:.

Related

How can I get the amound of seconds from a NSDate ("hh:mm:ss") and vice versa

I am trying to make a timer and yes I have looked at NSTimer and it is not what I'm looking for, I basically need a way to convert a NSDate in the format of ("hh:mm:ss") into the amount of seconds that would be in this. And then I need a way to do the opposite so convert the seconds to the amount of hours, minutes and leftover seconds from that.
You can extract components from NSDate using the NSCalendar class. The following example extracts a single component, NSCalendarUnitSecond, from the current date.
NSDate *today = [NSDate date];
NSCalendar *localCalendar = [NSCalendar currentCalendar];
NSUInteger seconds = [localCalendar component:NSCalendarUnitSecond fromDate:today];
You could have a place holder date e.g. 00:00:00 and then use the NSDate function timeIntervalSinceDate: to get the seconds. Converting back again you could use the dateWithTimeInterval: method.
Obviously both these dates would have to be the same year month and day and you could use any arbitrary values for these as long as they are the same.

Remove time from NSDate object and save it in NSDate not in string?

There are lot of questions about date and time, but my question is a bit different.
Why NSDate comes with times added to it?
Why can not time be removed from NSDate? I can remove the time but it needs to be saved in String, Why it is not allowed to save it in NSDate?
NSDate actually stores a number of seconds from reference date (Jan 1, 2001). Everything rest are calendar calculations based on this amount of seconds. If you truncate time components and store result as 'NSDate' you will have different dates on different time zones.
You should consider using NSDateFormatter to convert NSDate values to string. Use:
[formatter setDateStyle:NSDateFormatterMediumStyle];
[formatter setTimeStyle:NSDateFormatterNoStyle];
to setup date formatter to ignore time.
Is this what you were looking for?
func dayOf(date: NSDate) -> NSDate {
return NSCalendar.currentCalendar().dateBySettingHour(0, minute: 0, second: 0, ofDate: date, options: NSCalendarOptions())!
}
A reusable way to solve this problem in swift is writing an extension on NSDate to trim the time off of the object.
import Foundation
extension NSDate {
func trimTime() -> NSDate {
let cal = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let components = cal.components([.Day, .Month, .Year], fromDate: self)
return cal.dateFromComponents(components)!
}
}
This is not possible to remove TimeStamp from NSDate. NSDate is always packed with timestamp.
NSDate is based on the UTC time zone. If it is 1AM in US, it will be 12:30 PM in some other country and the date will be different. It will become trouble to get who entered when if different dates are there. So to make the date consistent timestamp-ing is required.
EDIT:
UTC update as suggested by Zaph :)
tiemstamp as suggested by Daij-Djan
NSDate is a presentation way of time stamp, you can get different date with different timezone of the same NSDate object, so you cannot just save the "date" part of NSDate object, that's not the way NSDate works.
If you don't want time present in date string, just format it without time.
My suggestion is save time stamp in your database, if you need to find certain date, use a range query, that way you can deal with timezone problem.
Timezone function is hard to implement with date field.

NSDateFormatter dateFromString returns incorrect date [duplicate]

This question already has answers here:
Get NSDate from NSDate adjusted with timezone
(2 answers)
Closed 9 years ago.
I am trying to use NSDateFormatter in my app which takes a date string and formats it to an NSDate so that I can do Date Comparisons, however I am finding when I use dateFromString and format it the date is losing one day.
NSString *dateString = #"02-06-2012";
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"dd-MM-yyyy"];
NSDate *dateFromString = [[NSDate alloc] init];
dateFromString = [dateFormatter dateFromString:dateString];
NSLog(#"My Date = %#", dateFromString);
[dateFormatter release];
This outputs to the console:
My Date = 2012-06-01 23:00:00 +0000
Try adding this lines to your code,
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:#"GMT+0:00"]];
or
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:#"GMT"]];
SWIFT update :
Code from quetion,
let dateString = "02-06-2012"
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
var dateFromString : NSDate = dateFormatter.dateFromString(dateString)!
println("My Date \(dateFromString)")
And Solution ,
dateFormatter.timeZone = NSTimeZone(name: "GMT")
OR
dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT+0:00")
I don't believe that Dhruv's answer is correct. In fact, it's not clear there's any problem at all. You just seem to have an incorrect expectation of what should happen and/or interpretation of what's happening.
NSDate represents a moment in time. This moment does not have one unique name. It will be known by different names in different places and under different naming systems (time zones, calendars). NSDate doesn't deal with any of this, except lamely in its -description method, where it has to produce a string representation of that moment.
Second, a string like "02-06-2012" doesn't specify a precise moment in time. First of all, it's just a date with no time information, so NSDateFormatter just defaults to the first moment for that date. Second, it doesn't specify the time zone. The first moment of the calendar day is a different moment in each time zone. Unless you specify a time zone with -setTimeZone: or the string itself carries time zone information, NSDateFormatter assumes that any date strings you ask it to parse are in the current time zone.
So, your dateFromString object represents the first moment of the specified date, 02-06-2012, in your time zone. I expect this is what you wanted. However, you then got confused by the way that NSDate describes itself when logged. As I said, NSDate has to pick some "name" (string representation) for the moment it represents and which name it picks is fairly arbitrary. These days it is picking the name that the moment is known by in UTC. I gather from the log output shown in your question that you are located at UTC+0100. So, the date may look like it's one day earlier but it really is the same moment you specified. In other words, "2012-06-01 23:00:00 +0000" and "2012-06-02 00:00:00 +0100" are two equivalent names for exactly the same moment in time. You just aren't used to seeing the first one and misinterpreted it.
The lesson is that you have to stop relying on NSDate's self-description to be in any particular time zone. Really, you have to not rely on anything about it, since it's not documented. In fact, the docs for -[NSDate description] state, "The representation is not guaranteed to remain constant across different releases of the operating system."
Dhruv's solution seems to help merely because it causes NSDateFormatter and -[NSDate description] to agree on the time zone. But that's unreliable. It wouldn't work on Snow Leopard, for example, because -[NSDate description] used the local time zone instead of UTC in that version of the frameworks.
More importantly, though, it alters the actual moment represented by the NSDate object you get from NSDateFormatter's interpretation of your date string. I suspect you really want that to have a specific meaning – you want the string to be interpreted as being in the local time zone – and his solution thwarts your intent.
tl;dr: you were getting the date you wanted all along; don't rely on -[NSDate description]; don't use Dhruv's solution

In Objective-C, to get the current hour and minute as integers, we need to use NSDateComponents and NSCalendar?

I'd like to get the current hour and minute as integers. So if right now is 3:16am, I'd like to get the two integers: 3 and 16.
But it looks like [NSDate date] will give the number of seconds since 1970, or it can give a string of the current time representation, but there is no easy way to get them as integers?
I see a post in Getting current time, but it involved NSDateComponents and NSCalendar? That's way too complicated... all that was need is something like
NSDate *date = [NSDate date];
int hour = [date getHour]; // which is not possible
Is there a simpler way than using 3 classes NSDate, NSDateComponents, and NSCalendar to get the current hour as an integer, or typically, in Objective-C, would we typically still use C language's localtime and tm to get the hour as an integer?
How you interpret the seconds since 1970 depends on the calendar that you are using. There is simply no other option. Fortunately it is not that difficult to set up. See the 'Data and Time Programming Guide' for lots of examples. In your case:
// Assume you have a 'date'
NSCalendar *gregorianCal = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSDateComponents *dateComps = [gregorianCal components: (NSHourCalendarUnit | NSMinuteCalendarUnit)
fromDate: date];
// Then use it
[dateComps minute];
[dateComps hour];
So it really isn't that complicated.
Also note that you could create a 'Class Category' to encapsulate this as:
#interface NSDate (MyGregorianDateComponents)
- (NSInteger) getGregorianHour;
- (NSInteger) getGregorianMinute;
#end
NSDate just holds the time that has passed since a certain reference date, to get more meaningful numbers out of this (eg. after taking care of DST, leap years and all the other stupid time stuff), you have to use NSDateComponents with the appropriate NSCalendar.
My class can help.
https://github.com/TjeerdVurig/Vurig-Calendar/blob/master/Vurig%20Calendar/NSDate%2Bconvenience.m
I'm sure you can figure out the minute part :)

Objective-C and sqlite's DATETIME type

I have a sqlite3 table that I'm trying to map to an object in objective-C. One attribute of the table is 'completed_at' which is stored as a DATETIME.
I want to create a property on my objective-C class (which inherits from NSObject) that will map well to the 'completed_at' attribute.
Objective-C has an NSDate type but I'm not sure if that will map directly?
I am sharing here just the core things regarding date formatting for saving and retrieving the data for presentation. If you have any problem with this code snippet then I will share the full code that I used for my project.
When you save your data, bind your date value in the sql statement like this way:
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSString *dateString=[dateFormat stringFromDate:[NSDate date]];
sqlite3_bind_text(saveStmt, 1, [dateString UTF8String] , -1, SQLITE_TRANSIENT);
and when you retrieve data you have to write this code:
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
NSDate *myDate =[dateFormat dateFromString:[NSString stringWithUTF8String:(char *)sqlite3_column_text(selectstmt, 1)]];
now you have a variable myDate of NSDate type which you can render in your way:
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"dd-MM-yyyy hh:mm:ss a"];
NSLog(#"My Date was : %#", [formatter stringFromDate:myDate]);
You must have to remember three things:
In your SQLite date field type should be DATETIME
Date format should be same when you store and when you retrieve
Now you can show in your own way but following the format. Below the format details is given.
Format:
'dd' = Day 01-31
'MM' = Month 01-12
'yyyy' = Year 2000
'HH' = Hour in 24 hour
'hh' = Hour in 12 hour
'mm' = Minute 00-59
'ss' = Second 00-59
'a' = AM / PM
I have zero experience with Objective-C, but I found Apple's NSDate Class Reference with a google search. With the information provided on the linked page you should be able to figure out how to manipulate 32-bit epoch times in Objective-C, and this would work well in SQLite. I would probably create the completed_at column as type INTEGER for 32-bit times.
SQLite really prefers Julian dates, which are floats. I haven't found any documentation explaining how one might coerce the NSDate class into working with Julians.
timeIntervalSince1970 looks very interesting.
This came up a couple of weeks ago:
Persisting Dates to SQLite3 in an iPhone Application
The formatter is important if you are trying to effect the presentation but if you use if for internal storage, you are defining a string which can defeat the DB-engine's ability to use the value for computation, comparison, sorting, etc. Also, if you are going to have different clients inserting the date value into the DB you would have to write conversion functions everywhere. I used the following and it worked as expected (schema's column defined as DATETIME):
dateExpires = [NSDate dateWithTimeIntervalSinceNow: sqlite3_column_double(queryStmt, 5)];
I inserted into the SQLITE3 db with the Firefox add-on as "4/12/2010" here in Central time zone. Viewing the value of 'dateExpires' in XCode-debugger displayed as:
2010-04-12 23:19:48 -0500
Sure enough, that is the correct time.
Also, to insert into the SQLITE DB you will put the value [NSDate date]