Cách tốt nhất để đối phó với địa điểm NSDateFormatter tại địa phương là gì?


167

Dường như NSDateFormattercó một "tính năng" khiến bạn bất ngờ cắn: Nếu bạn thực hiện một thao tác định dạng "cố định" đơn giản như:

NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyyMMddHHmmss"];
NSString* dateStr = [fmt stringFromDate:someDate];
[fmt release];

Sau đó, nó hoạt động tốt ở Hoa Kỳ và hầu hết các địa phương UNTIL ... ai đó với điện thoại của họ được đặt thành khu vực 24 giờ sẽ đặt công tắc 12/24 giờ trong cài đặt thành 12. Sau đó, phần trên bắt đầu ghép "AM" hoặc "PM" lên kết thúc chuỗi kết quả.

(Xem, ví dụ: NSDateFormatter, tôi đang làm gì đó sai hay đây là lỗi? )

(Và xem https://developer.apple.com/l Library / content / qa / qa1480 / _index.html )

Rõ ràng Apple đã tuyên bố đây là "BAD" - Hỏng như được thiết kế và họ sẽ không sửa nó.

Việc lách luật rõ ràng là đặt ngôn ngữ của trình định dạng ngày cho một khu vực cụ thể, nói chung là Hoa Kỳ, nhưng điều này hơi lộn xộn:

NSLocale *loc = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[df setLocale: loc];
[loc release];

Không quá tệ trong các phần hai mươi, nhưng tôi đang xử lý khoảng mười ứng dụng khác nhau và ứng dụng đầu tiên tôi nhìn thấy có 43 trường hợp của kịch bản này.

Vì vậy, bất kỳ ý tưởng thông minh cho một lớp vĩ mô / ghi đè / bất cứ điều gì để giảm thiểu nỗ lực thay đổi mọi thứ, mà không làm cho mã bị che khuất? (Bản năng đầu tiên của tôi là ghi đè NSDateFormatter bằng một phiên bản sẽ đặt ngôn ngữ trong phương thức init. Yêu cầu thay đổi hai dòng - dòng alloc / init và nhập thêm.)

Thêm

Đây là những gì tôi đã nghĩ ra cho đến nay - dường như hoạt động trong tất cả các tình huống:

@implementation BNSDateFormatter

-(id)init {
static NSLocale* en_US_POSIX = nil;
NSDateFormatter* me = [super init];
if (en_US_POSIX == nil) {
    en_US_POSIX = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
}
[me setLocale:en_US_POSIX];
return me;
}

@end

Tiền thưởng!

Tôi sẽ trao phần thưởng cho đề xuất / phê bình tốt nhất (hợp pháp) mà tôi thấy vào giữa ngày thứ ba. [Xem bên dưới - thời hạn được gia hạn.]

Cập nhật

Đề xuất của OMZ, đây là những gì tôi đang tìm kiếm -

Đây là phiên bản thể loại - tập tin h:

#import <Foundation/Foundation.h>


@interface NSDateFormatter (Locale)
- (id)initWithSafeLocale;
@end

Tập tin loại m:

#import "NSDateFormatter+Locale.h"


@implementation NSDateFormatter (Locale)

- (id)initWithSafeLocale {
static NSLocale* en_US_POSIX = nil;
self = [super init];
if (en_US_POSIX == nil) {
    en_US_POSIX = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
}
NSLog(@"Category's locale: %@ %@", en_US_POSIX.description, [en_US_POSIX localeIdentifier]);
[self setLocale:en_US_POSIX];
return self;    
}

@end

Mật mã:

NSDateFormatter* fmt;
NSString* dateString;
NSDate* date1;
NSDate* date2;
NSDate* date3;
NSDate* date4;

fmt = [[NSDateFormatter alloc] initWithSafeLocale];
[fmt setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
dateString = [fmt stringFromDate:[NSDate date]];
NSLog(@"dateString = %@", dateString);
date1 = [fmt dateFromString:@"2001-05-05 12:34:56"];
NSLog(@"date1 = %@", date1.description);
date2 = [fmt dateFromString:@"2001-05-05 22:34:56"];
NSLog(@"date2 = %@", date2.description);
date3 = [fmt dateFromString:@"2001-05-05 12:34:56PM"];  
NSLog(@"date3 = %@", date3.description);
date4 = [fmt dateFromString:@"2001-05-05 12:34:56 PM"]; 
NSLog(@"date4 = %@", date4.description);
[fmt release];

fmt = [[BNSDateFormatter alloc] init];
[fmt setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
dateString = [fmt stringFromDate:[NSDate date]];
NSLog(@"dateString = %@", dateString);
date1 = [fmt dateFromString:@"2001-05-05 12:34:56"];
NSLog(@"date1 = %@", date1.description);
date2 = [fmt dateFromString:@"2001-05-05 22:34:56"];
NSLog(@"date2 = %@", date2.description);
date3 = [fmt dateFromString:@"2001-05-05 12:34:56PM"];  
NSLog(@"date3 = %@", date3.description);
date4 = [fmt dateFromString:@"2001-05-05 12:34:56 PM"]; 
NSLog(@"date4 = %@", date4.description);
[fmt release];

Kết quả:

2011-07-11 17:44:43.243 DemoApp[160:307] Category's locale: <__NSCFLocale: 0x11a820> en_US_POSIX
2011-07-11 17:44:43.257 DemoApp[160:307] dateString = 2011-07-11 05:44:43 PM
2011-07-11 17:44:43.264 DemoApp[160:307] date1 = (null)
2011-07-11 17:44:43.272 DemoApp[160:307] date2 = (null)
2011-07-11 17:44:43.280 DemoApp[160:307] date3 = (null)
2011-07-11 17:44:43.298 DemoApp[160:307] date4 = 2001-05-05 05:34:56 PM +0000
2011-07-11 17:44:43.311 DemoApp[160:307] Extended class's locale: <__NSCFLocale: 0x11a820> en_US_POSIX
2011-07-11 17:44:43.336 DemoApp[160:307] dateString = 2011-07-11 17:44:43
2011-07-11 17:44:43.352 DemoApp[160:307] date1 = 2001-05-05 05:34:56 PM +0000
2011-07-11 17:44:43.369 DemoApp[160:307] date2 = 2001-05-06 03:34:56 AM +0000
2011-07-11 17:44:43.380 DemoApp[160:307] date3 = (null)
2011-07-11 17:44:43.392 DemoApp[160:307] date4 = (null)

Điện thoại [làm cho iPod Touch] được đặt thành Vương quốc Anh, với công tắc 12/24 được đặt thành 12. Có sự khác biệt rõ ràng trong hai kết quả và tôi đánh giá phiên bản danh mục là sai. Lưu ý rằng nhật ký trong phiên bản danh mục IS đang được thực thi (và các điểm dừng được đặt trong mã được nhấn), do đó, đây không chỉ là trường hợp mã không được sử dụng.

Cập nhật tiền thưởng:

Vì tôi chưa nhận được bất kỳ câu trả lời thích hợp nào nên tôi sẽ gia hạn thời hạn tiền thưởng thêm một hoặc hai ngày nữa.

Tiền thưởng kết thúc sau 21 giờ - nó sẽ đến với bất cứ ai nỗ lực nhất để giúp đỡ, ngay cả khi câu trả lời không thực sự hữu ích trong trường hợp của tôi.

Một quan sát tò mò

Sửa đổi việc thực hiện danh mục một chút:

#import "NSDateFormatter+Locale.h"

@implementation NSDateFormatter (Locale)

- (id)initWithSafeLocale {
static NSLocale* en_US_POSIX2 = nil;
self = [super init];
if (en_US_POSIX2 == nil) {
    en_US_POSIX2 = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
}
NSLog(@"Category's locale: %@ %@", en_US_POSIX2.description, [en_US_POSIX2 localeIdentifier]);
[self setLocale:en_US_POSIX2];
NSLog(@"Category's object: %@ and object's locale: %@ %@", self.description, self.locale.description, [self.locale localeIdentifier]);
return self;    
}

@end

Về cơ bản chỉ cần thay đổi tên của biến cục bộ tĩnh (trong trường hợp có một số xung đột với khai báo tĩnh trong lớp con) và thêm NSLog bổ sung. Nhưng hãy nhìn những gì NSLog in:

2011-07-15 16:35:24.322 DemoApp[214:307] Category's locale: <__NSCFLocale: 0x160550> en_US_POSIX
2011-07-15 16:35:24.338 DemoApp[214:307] Category's object: <NSDateFormatter: 0x160d90> and object's locale: <__NSCFLocale: 0x12be70> en_GB
2011-07-15 16:35:24.345 DemoApp[214:307] dateString = 2011-07-15 04:35:24 PM
2011-07-15 16:35:24.370 DemoApp[214:307] date1 = (null)
2011-07-15 16:35:24.378 DemoApp[214:307] date2 = (null)
2011-07-15 16:35:24.390 DemoApp[214:307] date3 = (null)
2011-07-15 16:35:24.404 DemoApp[214:307] date4 = 2001-05-05 05:34:56 PM +0000

Như bạn có thể thấy, setLocale đơn giản là không. Vị trí của trình định dạng vẫn là en_GB. Dường như có một cái gì đó "lạ" về một phương thức init trong một thể loại.

Câu trả lời cuối cùng

Xem câu trả lời được chấp nhận dưới đây.


5
Moshe, tôi không biết tại sao bạn chọn chỉnh sửa tiêu đề. "F336ur" là một thuật ngữ hợp pháp trong nghệ thuật (và đã được 30 năm hoặc lâu hơn), có nghĩa là một khía cạnh hoặc tính năng của một số phần mềm đủ bị coi là một lỗi, mặc dù các tác giả từ chối thừa nhận.
Licks nóng

1
khi chuyển đổi một chuỗi thành ngày, chuỗi phải khớp chính xác với mô tả định dạng - đây là một vấn đề tiếp tuyến với địa phương của bạn.
bshirley

Các chuỗi ngày khác nhau có mặt để kiểm tra các cấu hình khác nhau có thể, chính xác và sai. Tôi biết rằng một số trong số họ là không hợp lệ, đưa ra chuỗi định dạng.
Licks nóng

Bạn đã thử nghiệm với các giá trị khác nhau của - (NSDateFormatterBehavior)formatterBehavior?
bshirley

Chưa thử nghiệm với nó. Thông số kỹ thuật này trái ngược nhau về việc nó thậm chí có thể được thay đổi trong iOS hay không. Mô tả chính cho biết "Lưu ý iOS: iOS chỉ hỗ trợ hành vi 10,4+", trong khi phần NSDateFormatterBehavior cho biết cả hai chế độ đều khả dụng (nhưng có thể chỉ nói về các hằng số).
Licks nóng

Câu trả lời:


67

Tât nhiên!!

Đôi khi bạn có một "Aha !!" Khoảnh khắc, đôi khi nó giống như một "Duh !!" Đây là cái sau. Trong danh mục cho initWithSafeLocale"siêu" initđược mã hóa là self = [super init];. Điều này tạo ra SUPERCLASS củaNSDateFormatter nhưng không initnhững NSDateFormatterđối tượng riêng của mình.

Rõ ràng khi khởi tạo này bị bỏ qua, setLocale"bật ra", có lẽ là do một số cấu trúc dữ liệu bị thiếu trong đối tượng. Thay đổi initthànhself = [self init]; nguyên nhân sự NSDateFormatterkhởi xảy ra, và setLocalelà hạnh phúc một lần nữa.

Đây là nguồn "cuối cùng" cho .m của danh mục:

#import "NSDateFormatter+Locale.h"

@implementation NSDateFormatter (Locale)

- (id)initWithSafeLocale {
    static NSLocale* en_US_POSIX = nil;
    self = [self init];
    if (en_US_POSIX == nil) {
        en_US_POSIX = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
    }
    [self setLocale:en_US_POSIX];
    return self;    
}

@end

định dạng ngày sẽ là gì cho "NSString * dateStr = @" 2014-04-05T04: 00: 00.000Z ";" ?
Đại lý Chocks.


@tbag - Không phải câu hỏi của bạn là về NSDateFormatter sao?
Licks nóng

@HotLicks có xấu của tôi. Tôi thịt NSDateFormatter.
tbag

@tbag - Thông số kỹ thuật nói gì?
Licks nóng

41

Thay vì phân lớp, bạn có thể tạo một NSDateFormatterdanh mục với một trình khởi tạo bổ sung, đảm nhiệm việc gán miền địa phương và cũng có thể là một chuỗi định dạng, vì vậy bạn có một trình định dạng sẵn sàng sử dụng ngay sau khi khởi tạo nó.

@interface NSDateFormatter (LocaleAdditions)

- (id)initWithPOSIXLocaleAndFormat:(NSString *)formatString;

@end

@implementation NSDateFormatter (LocaleAdditions)

- (id)initWithPOSIXLocaleAndFormat:(NSString *)formatString {
    self = [super init];
    if (self) {
        NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
        [self setLocale:locale];
        [locale release];
        [self setFormat:formatString];
    }
    return self;
}

@end

Sau đó, bạn có thể sử dụng NSDateFormatterbất cứ nơi nào trong mã của mình chỉ bằng:

NSDateFormatter* fmt = [[NSDateFormatter alloc] initWithPOSIXLocaleAndFormat:@"yyyyMMddHHmmss"];

Bạn có thể muốn tiền tố phương thức danh mục của mình bằng cách nào đó để tránh xung đột tên, chỉ trong trường hợp Apple quyết định thêm phương thức đó vào phiên bản tương lai của HĐH.

Trong trường hợp bạn luôn sử dụng (các) định dạng ngày giống nhau, bạn cũng có thể thêm các phương thức danh mục trả về các thể hiện đơn lẻ với một số cấu hình nhất định (đại loại như +sharedRFC3339DateFormatter). Tuy nhiên, hãy lưu ý rằng điều đó NSDateFormatterkhông an toàn cho luồng và bạn phải sử dụng khóa hoặc @synchronizedchặn khi bạn sử dụng cùng một thể hiện từ nhiều luồng.


Có một NSLocale tĩnh (như trong đề xuất của tôi) sẽ hoạt động trong một danh mục không?
Licks nóng

Vâng, đó cũng nên làm việc trong một thể loại. Tôi để nó ra để làm cho ví dụ đơn giản hơn.
omz

Thật kỳ lạ, cách tiếp cận danh mục không hoạt động. Phương thức thể loại được thực thi và nó nhận được chính xác Địa điểm giống như phiên bản khác (trước tiên tôi thực hiện chúng trở lại, phiên bản danh mục). Chỉ bằng cách nào đó, setLocale dường như không "lấy".
Licks nóng

Sẽ rất thú vị khi tìm hiểu tại sao phương pháp này không hiệu quả. Nếu không ai nghĩ ra điều gì tốt hơn, tôi sẽ thưởng tiền thưởng cho lời giải thích tốt nhất về lỗi rõ ràng này.
Licks nóng

Chà, tôi đang trao tiền thưởng cho OMZ, vì anh ấy là người duy nhất đã nỗ lực rõ ràng về việc này.
Licks nóng

7

Tôi có thể đề nghị một cái gì đó hoàn toàn khác bởi vì thành thật mà nói tất cả điều này là phần nào chạy xuống một lỗ thỏ.

Bạn nên sử dụng một bộ NSDateFormatterdateFormatcài đặt và localebuộc phải en_US_POSIXnhận ngày (từ máy chủ / API).

Sau đó, bạn nên sử dụng một NSDateFormattergiao diện người dùng khác cho giao diện người dùng mà bạn sẽ đặt timeStyle/ dateStylethuộc tính - theo cách này bạn không có thông tin rõ ràngdateFormat bộ do chính mình giả định, do đó giả định rằng định dạng đó sẽ được sử dụng.

Điều này có nghĩa là UI được điều khiển bởi tùy chọn của người dùng (sáng / chiều so với 24 giờ và chuỗi ngày được định dạng chính xác theo lựa chọn của người dùng - từ cài đặt iOS), trong khi ngày "đến" ứng dụng của bạn luôn được "phân tích cú pháp" chính xác NSDatecho bạn sử dụng


Đôi khi chương trình này hoạt động, đôi khi không. Một điều nguy hiểm là phương pháp của bạn có thể cần phải sửa đổi định dạng ngày của trình định dạng và, khi làm như vậy, thay đổi định dạng được đặt bởi mã gọi cho bạn, khi nó ở giữa các hoạt động định dạng ngày. Có những kịch bản khác trong đó múi giờ phải được thay đổi liên tục.
Hot Licks

Tôi không biết tại sao việc thay đổi timeZonegiá trị của trình định dạng sẽ cản trở sơ đồ này, bạn có thể nói rõ hơn không? Ngoài ra, để rõ ràng, bạn sẽ tránh thay đổi định dạng. Nếu bạn cần làm như vậy thì điều này sẽ xảy ra với một trình định dạng "nhập", vì vậy một trình định dạng riêng biệt.
Daniel

Bất cứ khi nào bạn thay đổi trạng thái của một đối tượng toàn cầu, điều đó thật nguy hiểm. Dễ quên rằng những người khác cũng đang sử dụng nó.
Hot Licks

3

Đây là giải pháp cho vấn đề đó trong phiên bản swift. Trong swift chúng ta có thể sử dụng phần mở rộng thay vì danh mục. Vì vậy, ở đây tôi đã tạo tiện ích mở rộng cho DateFormatter và bên trong initWithSafeLocale trả về DateFormatter với Locale có liên quan, Ở đây, trong trường hợp của chúng tôi là en_US_POSIX, ngoài ra còn cung cấp một vài phương thức hình thành ngày.

  • Swift 4

    extension DateFormatter {
    
    private static var dateFormatter = DateFormatter()
    
    class func initWithSafeLocale(withDateFormat dateFormat: String? = nil) -> DateFormatter {
    
        dateFormatter = DateFormatter()
    
        var en_US_POSIX: Locale? = nil;
    
        if (en_US_POSIX == nil) {
            en_US_POSIX = Locale.init(identifier: "en_US_POSIX")
        }
        dateFormatter.locale = en_US_POSIX
    
        if dateFormat != nil, let format = dateFormat {
            dateFormatter.dateFormat = format
        }else{
            dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        }
        return dateFormatter
    }
    
    // ------------------------------------------------------------------------------------------
    
    class func getDateFromString(string: String, fromFormat dateFormat: String? = nil) -> Date? {
    
        if dateFormat != nil, let format = dateFormat {
            dateFormatter = DateFormatter.initWithSafeLocale(withDateFormat: format)
        }else{
            dateFormatter = DateFormatter.initWithSafeLocale()
        }
        guard let date = dateFormatter.date(from: string) else {
            return nil
        }
        return date
    }
    
    // ------------------------------------------------------------------------------------------
    
    class func getStringFromDate(date: Date, fromDateFormat dateFormat: String? = nil)-> String {
    
        if dateFormat != nil, let format = dateFormat {
            dateFormatter = DateFormatter.initWithSafeLocale(withDateFormat: format)
        }else{
            dateFormatter = DateFormatter.initWithSafeLocale()
        }
    
        let string = dateFormatter.string(from: date)
    
        return string
    }   }
  • mô tả sử dụng:

    let date = DateFormatter.getDateFromString(string: "11-07-2001”, fromFormat: "dd-MM-yyyy")
    print("custom date : \(date)")
    let dateFormatter = DateFormatter.initWithSafeLocale(withDateFormat: "yyyy-MM-dd HH:mm:ss")
    let dt = DateFormatter.getDateFromString(string: "2001-05-05 12:34:56")
    print("base date = \(dt)")
    dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    let dateString = dateFormatter.string(from: Date())
    print("dateString = " + dateString)
    let date1 = dateFormatter.date(from: "2001-05-05 12:34:56")
    print("date1 = \(String(describing: date1))")
    let date2 = dateFormatter.date(from: "2001-05-05 22:34:56")
    print("date2 = \(String(describing: date2))")
    let date3 = dateFormatter.date(from: "2001-05-05 12:34:56PM")
    print("date3 = \(String(describing: date3))")
    let date4 = dateFormatter.date(from: "2001-05-05 12:34:56 PM")
    print("date4 = \(String(describing: date4))")
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.