18

I'm struggling to figure out the formatting for the following date:

2011-05-24 19:02:32 Etc/GMT

This date is returned from Apple's receipt validation service and I need to turn it into a NSDate for some comparison operations. The real trouble is related to the timezone.

Here's some code I've already written:

        NSDictionary *receiptData = [info valueForKey:@"expires_date"];

        NSDateFormatter *f = [[NSDateFormatter alloc] init];

        [f setLocale:[NSLocale currentLocale]];
        [f setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
        [f setDateFormat:@"yyyy-MM-dd HH:mm:ss vvvv"];

        NSLog(@"%@", [f stringFromDate:[NSDate date]]);

        NSDate *subPurchaseDate = [f dateFromString:[receiptData valueForKey:@"original_purchase_date"]];

        [f release];

I've tried all combinations of 'v's and 'Z's that I can think of. Any insight?

Roger
  • 15,793
  • 4
  • 51
  • 73
Hyperbole
  • 3,917
  • 4
  • 35
  • 54
  • Did you have any joy with this? I was about to post the same question when I came across your one. – Roger Jun 13 '11 at 18:01
  • 1
    Does anyone else thing it strange that Apple returns this string but the Apple frameworks can't parse it? – Paul de Lange Feb 06 '13 at 10:32
  • 1
    No, but that's largely because I've been working with Apple technology for a few years now. You get used to the pain after a while. – Hyperbole Feb 06 '13 at 17:27
  • Almost 9 years later, and this is still just as painful as it was then. – mm2001 May 23 '20 at 11:09

4 Answers4

16

By looking at the date format documentation I got the correct format, is works well for me:

        NSDateFormatter * formatter = [NSDateFormatter new];
        formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss VV";

        NSDate * purchaseDate = [formatter dateFromString:latestTransaction[@"original_purchase_date"]];
        NSDate * expirationDate = [formatter dateFromString:latestTransaction[@"expires_date"]];

No need to set the time zone on the formatter since it's embedded in the string.
Manipulating the date string to parse it is not a good idea.

Source: http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns

Yariv Nissim
  • 13,273
  • 1
  • 38
  • 44
  • This works i wouldn't remove the timezone like the accepted answer does. You should switch the day and month: yyyy-dd-MM => yyyy-MM-dd – null Jul 09 '14 at 00:48
  • 1
    For some reason, this format does not work for customers who purchase in Israel :O – Jordan H Apr 14 '17 at 20:38
  • I'm not sure what the string is in the receipt, but `[formatter dateFromString:latestTransaction[@"expires_date"]];` returned `NULL` for the customer who purchased in Israel. Worked great in US. – Jordan H Apr 17 '17 at 19:31
  • Interesting. If you can post the string we can figure out if it's localized. Israel time is like Europe - "dd/MM/yyyy" – Yariv Nissim Apr 18 '17 at 20:18
  • > "No need to set the time zone on the formatter since it's embedded in the string." My experience suggests that setting the time zone on the formatter is crucial - otherwise it overrides what is embedded in the string. At least when using Swift in iOS 13.4.1 & iOS 13.5. – mm2001 May 23 '20 at 10:41
4

After doing some more research, the code I'm using here works, but is suspect;

   NSString *purchaseDateString = [@"2011-05-24 19:02:32 Etc/GMT" stringByReplacingOccurrencesOfString:@" Etc/GMT" withString:@""];
   NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
   NSLocale *POSIXLocale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease];
   [formatter setLocale:POSIXLocale];
   [formatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
   [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];

   NSDate *purchaseDate = [formatter dateFromString:purchaseDateString];

I don't like the assumptions I am making about the incoming date string and so I am assuming this code may break for other stores (I'm testing against the UK one). So although this kind of works for my own situation, I'd really like to see a more robust solution that actually parses the timezone string correctly as per the original question.

I can't quite believe Apple have used a deprecated timezone (all the Etc/* ones are officially deprecated) in these important date strings!

EDIT:

I note you are using

NSDictionary *receiptData = [info valueForKey:@"expires_date"];

This is not actually a string according to the documentation it should be "The expiration date of the subscription receipt, expressed as the number of milliseconds since January 1, 1970, 00:00:00 GMT"

However the question is still valid as you have to use the purchase_date field when working with restored subscriptions and this field is in the text format you have described.

Roger
  • 15,793
  • 4
  • 51
  • 73
  • I ended up doing the same find/replace in my code but I replaced the 'Etc/GMT' with 'GMT' to make my life a bit easier. Additionally, I had the same reservations you had about using this solution, but frankly I can't find anything else that would work. Thanks for confirming my solution. – Hyperbole Jun 14 '11 at 23:15
  • I'd add a check that the string ends with "Etc/GMT", and do some kind of logging if it's not true (pop up a MFMailComposeViewController and ask the user to send a bug report?). It's a bit messy, but oh well. – tc. Jun 18 '11 at 15:34
  • 1
    Why remove the Etc/GMT? It's a legit time zone, so it seems. E.g. http://php.net/manual/en/timezones.others.php – Tudor Jun 20 '11 at 15:34
  • 1
    @Tudorizer, primarily because NSDateFormatter doesn't appear to be able to parse it. – Roger Jun 20 '11 at 15:47
  • It can definitely parse it, you just need to use the correct format. Look at my answer below – Yariv Nissim Oct 21 '14 at 05:45
1

The following code can be used to parse RFC 3339 dates found in App Store receipts:

let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
formatter.timeZone = TimeZone(secondsFromGMT: 0)

let date = formatter.date(from: "2020-06-17 10:59:33 Etc/GMT")

Note that it is necessary to set the timeZone as well as the locale. Also setting locale before dateFormat is important according to Technical Q&A QA1480.

Apple recommends using the newer ISO8601DateFormatter API on iOS 10+ and macOS 10.12+.

mm2001
  • 6,427
  • 5
  • 39
  • 37
0

This is what I do on Swift 3 to convert the date from an auto-renewable date from a receipt.

let expirationDate = "2017-08-16 23:10:35 Etc/GMT"
let receiptDateFormat = "yyyy-MM-dd HH:mm:ss VV"
let theDateFormat = DateFormatter.Style.medium
let theTimeFormat = DateFormatter.Style.medium
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = receiptDateFormat
let firstDate = dateFormatter.date(from: stringDate)
dateFormatter.dateStyle = theDateFormat
dateFormatter.timeStyle = theTimeFormat

let result = dateFormatter.string(from: firstDate!)
pableiros
  • 14,932
  • 12
  • 99
  • 105