How can I determine which date format I've received? - cocoa

In dealing with a wide variety of JSON data being sent from various clients date format standardization is a real problem.
I might get any of these:
2013-10-05
2-6-13
Mon, Jul 13 2013
Sometimes there's hours, minutes and seconds as well as time zone information. For each client I've had to manually set up an NSDateFormatter, or if unable to get it parsing I've done substring searching. What I'd really like to do is get all this combined into a single method and somehow determine which type I'm dealing with. Then have some kind of switch statement that handles the parsing for that particular date.
I'm unable to change their formats, but how can I deal with them better?

This is not exactly the answer to your question but when I'm looking to parse dates without being sure of the format I usually go for NSDataDetector setup for dates only. The detector will often detect dates in multiple languages and format all at once. It's pretty neat.

Okay thanks for the comments. What I ended up doing is going to http://waracle.net/iphone-nsdateformatter-date-formatting-table/ and studying VERY CAREFULLY all the NSDateFormatter options. In particular it was the time zone +0000 at the end of RSS posts which was messing up my format conversions. RSS dates match RFC 822, which means you need to use THREE Z's at the end to properly catch it.
I was attempting things like +ZZZZ which were failing on the strings. That's why I was trimming the strings before. What ended up working was this:
#"Wed, 17 Jul 2013 03:23:18 +0000"
Needs formatter of:
#"EEE, dd MMM yyyy HH:mm:ss ZZZ"
After having correctly set up the DateFormatter for this case I was easily able to figure out others for the other formats. It turns out there's only 3 different date formats being used in the projects and I can easily store the right date format string along side each data source and plug that into the date formatter when needed. No more string searching needed and all data is cleanly converting to the correct NSDate.
So the answer is, make a sample project that converts various date formats, plug in the ones you need and use the DateFormatter codes correctly. Chances are your date problems aren't as bad as you think and a better understanding of them can lead to cleaner code.
Using my example below you will see (null) if your format string is incorrect.
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
#autoreleasepool {
NSArray *dateStrings = #[
// From data source 1.
#[#"2012-04-18",#"yyyy-MM-dd"],
// From data source 2.
#[#"2012-04-19 18:29:35",#"yyyy-MM-dd HH:mm:ss"],
#[#"2011-05-04 11:10:50",#"yyyy-MM-dd HH:mm:ss"],
// From RSS feeds (pubDate)
#[#"Wed, 17 Jul 2013 02:24:23 +0000",#"EEE, dd MMM yyyy HH:mm:ss ZZZ"],
#[#"Tue, 25 Jun 2013 15:04:49 +0000",#"EEE, dd MMM yyyy HH:mm:ss ZZZ"]
];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
for(NSArray *datePair in dateStrings){
[formatter setDateFormat:datePair[1]];
NSDate *thisDate = [formatter dateFromString:datePair[0]];
[formatter setDateStyle:NSDateFormatterMediumStyle];
NSLog(#"%# = %#",datePair[0],[formatter stringFromDate:thisDate]);
}
}
return 0;
}

Related

Plain Ruby: set only timezone for a DateTime instance

All the existing answers either use a lib or Rails. I need to do this in plain Ruby. I also find it hard to imagine that a beautiful language like Ruby would make this so difficult.
I have many date strings of the form: 07 Nov 20. I am able to parse these into a DateTime instance using:
> require 'time'
=> true
> DateTime.strptime("07 Nov 20", "%d %b %y")
=> #<DateTime: 2020-11-07T00:00:00+00:00 ((2459161j,0s,0n),+0s,2299161j)>
> _.new_offset("+05:30")
=> #<DateTime: 2020-11-07T05:30:00+05:30 ((2459161j,0s,0n),+19800s,2299161j)>
As we can see, using DateTime#new_offset changes not only the offset, but also change the time. There doesn't seem to exist any other method to only change the timezone and/or offset either.
If possible, I'd also like to do this for that individual DateTime instance and not all DateTime instances created in the future via some TIMEZONE global or something.
Note: While the input string doesn't contain any time component, I still need my runtime representation to have one. So while using a simpler method like Date#strptime would work, it is not "ideal".
Note 2: I'm guessing there are other complications with simply "setting" the timezone, which is why this function is not present. For example it is not obvious how to handle DST when you "set" the timezone. I'm guessing that changing the time is going to be inevitable if you want to ensure that passing in a valid offset will always return a valid DateTime instance.
Edit 1:
Further digging in the source code for date (date/date_core.c) reveals the set_of function is used to set the offset for a DateTime instance. Unfortunately, it seems that the logic of manipulating the offset is coupled with the manipulation of the time. This explains why there is no method exposed in the Ruby stdlib to only set the offset.
Should they be Date objects instead? Dates don't have times nor time zones.
Date.strptime("07 Nov 20", "%d %b %y")
Otherwise, you can add a time zone to the string and parse it.
string = "07 Nov 20"
p DateTime.strptime("#{string}+0530", "%d %b %y %z")
Or you can subtract the extra hours. DateTime subtracts number of days and will take a fraction.
DateTime.strptime("07 Nov 20", "%d %b %y").new_offset("+05:30") - 5.5/24

How do I obtain the sub-second portion of an NSDate?

Using OS X v10.10.1 (Yosemite) and Xcode 6.1.1.
I feel I must be overlooking something simple and obvious, but I just can't see it. I'm parsing the parts of an NSDate whose value is just the system date.
NSDate *currentDate = [NSDate date];
NSCalendar *systemCalendar = [NSCalendar currentCalendar];
NSDateComponents *dateComponents = [systemCalendar components:(NSYearCalendarUnit|NSMonthCalendarUnit|NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit) fromDate:currentSystemDate];
I can then get the seconds as
NSInteger seconds = [dateComponents second];
but this only gives me the whole number of seconds. I need the decimal portion of the seconds as well. According to the documentation. I can use
NSInteger nanos = [dateComponents nanoseconds];
to get the "number of nanosecond units for the receiver". It doesn't work however. The method is legitimate, but nanos is set to the maximum value of NSInteger. This is (I presume) because I have not supplied a suitable constant in the components method. The problem with that is that there is no NSNanosecondCalendarUnit constant and I cannot find any suitable alternative.
I could construct a new date using the date components and then subtract the two to get an NSTimeInterval which I believe will give me what I need, but I'm finding it hard to believe that there isn't a more straightforward way to get it.
Is there a simple straightforward way in Cocoa to calculate the sub-second portion of an NSDate or do I have to use the approach I just suggested?
You are using the older deprecated names for the components. The new names include NSCalendarUnitNanosecond. Take a look at NSCalendar.h, and you'll see the old and new names.
NSDateComponents *dateComponents = [systemCalendar components:(NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay|NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond|NSCalendarUnitNanosecond) fromDate:currentSystemDate];

Is there a full implementation for ISO-8601 date parsing for Ruby?

The Time.iso8601 method is a restricted subset of ISO-8601.
What are its limitations?
Does anyone know of a full implementation for Ruby? I'm using MRI 1.8.7.
Update
It looks like there isn't a single class that handles all of the various 8601 date and date/time combinations. However, I have managed to work around the problems by using both the Date.parse and Time.iso8601 methods. The downside is that you need to decide in code whether the input looks like a date or a date/time.
Warning : Timezone differences
Time.iso8601 and Time.parse behave differently.
>> Time.parse("2010-09-06T12:27:00.10-05:00")
=> Mon Sep 06 18:27:00 +0100 2010
>> Time.iso8601("2010-09-06T12:27:00.10-05:00")
=> Mon Sep 06 17:27:00 UTC 2010
Differences between Time.iso8601 and ISO-8601
This document touches on the differences between what is in ISO-8601 and what is supported by Ruby. The short answer is that the number of possible formats is restricted.
Yes, but unfortunately it's in Ruby 1.9.
require "date"
Date.iso8601("2010-W32-5").strftime
#=> "2010-08-13"
I don't believe there are any implementations for Ruby 1.8.7 (or at least I couldn't find any). You could either try to upgrade to Ruby 1.9, which is pretty stable as of 1.9.2. Alternatively, you could try to parse the dates yourself.
To convert an ISO8601 date into the local time zone, do this:
require "time"
dt1 = Time.parse("2010-09-06T12:27:00.10-05:00")
To convert an ISO8601 date into UTC, do this:
dt2 = Time.iso8601("2010-09-06T12:27:00.10-05:00")
If you compare the dates returned by the above queries, they will be identical (i.e. dt1 === dt2). However, accessing date components (like year, month, day, hour, etc.) will return values appropriate for the time zone (either UTC or local). The same applies to strftime.

NSDateFormatter parsing date incorrectly

I'm extracting strings from a text that represent dates. They look like this:
Monday August 16, 2010 05:28 AM EST
I'm trying to parse them with a NSDateFormatter. I've set its format to:
[dateFormatter setDateFormat:#"EEEE MMMM d, YYYY h:mm a z"];
However, this doesn't work. For the example I gave above, if I convert the string to a date and then that date to a string, the date formatter returns this:
Monday December 28, 2009 5:28 AM EST
What am I doing wrong?
EDIT: It seems to work when using the format [dateFormatter setDateFormat:#"EEEE MMMM d, y h:mm a z"];. Strange...
Will,
NSDateFormatter uses the Unicode standard for formatting dates. As you've found, "y" works, but "YYYY" doesn't. If you see the spec (referenced several layers deep in the Apple documentation, so not easy to find) you'll see that "Y" has this note: "Year (of "Week of Year"), used in ISO year-week calendar. May differ from calendar year."
Here is the link to the specification.
I know you've fixed it yourself, but this may help explain why "Y" didn't work for you.
"y" or "yyyy" is ok. "YYYY" doesn't work.
Check the following link for detail.
situee.blogspot.com: NSDateFormatter setDateFormat YYYY return wrong year

Paypal date formatting: what is "HH:MM:SS DD Mmm YY, YYYY PST"?

I'm looking at the PayPal IPN docs, and it says the datetime stamps of their strings are formatted as:
HH:MM:SS DD Mmm YY, YYYY PST
So year is specified twice?
Once in double digits, and another with 4 digits?
This looks bizarre.
This seems to be a bug in the documentation. The actual format should be "HH:MM:SS Mmm DD, YYYY PST" (e.g. "08:30:06 Apr 19, 2017 PDT")
Actually in PHP you need to use date("Y-m-d\TH:i:s\Z") . That will result in something that looks like 2012-04-30T00:05:47Z -- I didn't notice a difference between urlencoded and non.
Where are you guys finding this info? This information is elusive in their documentation and cost me an hour or two of hunting and trying stuff. The only place I see this format is in the TIMESTAMP field. Having a hard time with the PayPal NVP API's PROFILESTARTDATE for CreateRecurringPaymentsProfile and a "Subscription start date should be valid" error.
For php, the syntax is date("G:i:s M m, Y T");
this is the correct format according to their documentation - 2010-03-27T12:34:49Z
so it is - YYYY-MM-DDTHH:MM:SSZ (I don't know what the T in the middle and Z is but it's constant for all the dates)
I've created PayPal NVP library in Java, so if you want to check how it works, or use it,
you are more than welcome. it's on sourceforge - payapal-nvp.sourceforge.net
Complete date plus hours, minutes, seconds and a decimal fraction of a
second
YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
Where TZD = time zone designator (Z or +hh:mm or -hh:mm)
Example
1994-11-05T08:15:30-05:00 corresponds to November 5, 1994, 8:15:30 am, US Eastern Standard Time.
1994-11-05T13:15:30Z corresponds to the same instant.
https://www.w3.org/TR/NOTE-datetime
PayPal Format to Any format 100% working and easy copy Paste
$payPalFormat = "18:30:30 Feb 28, 2008 PST";
$subrotoFormat = date('Y-m-d', strtotime($payPalFormat));
Result: 2008-02-29
All Format: https://www.w3schools.com/php/func_date_date.asp
https://gist.github.com/subrotoice/d820863ce65eb0d8434a47a76d005df4 (Subroto Biswas Gist)
Actually, I think the right format is: yyyy-MM-ddTHH:MM:ssZ
The case is important.

Resources