Skip to content

Instantly share code, notes, and snippets.

@davedelong
Last active October 15, 2022 08:02
Show Gist options
  • Save davedelong/2a2ae3c4682586278b14d0d5131fe050 to your computer and use it in GitHub Desktop.
Save davedelong/2a2ae3c4682586278b14d0d5131fe050 to your computer and use it in GitHub Desktop.

Revisions

  1. davedelong revised this gist Oct 12, 2019. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions NSLocale.m
    Original file line number Diff line number Diff line change
    @@ -62,7 +62,7 @@ + (NSLocale *)localeIncludingOverridesWithLocaleIdentifier:(NSString *)localeIde

    struct __DDLocale {
    void *_base; // The CF version of an "isa" pointer
    void *_field1; // maybe the locale identifier?
    void *_field1; // maybe the locale identifier?
    void *_field2; // maybe a cache
    void *_field3; // no idea
    CFDictionaryRef _prefs; // The Secret Sauce
    @@ -82,10 +82,10 @@ + (NSLocale *)localeIncludingOverridesWithLocaleIdentifier:(NSString *)localeIde
    NSMutableDictionary *components = [[NSLocale componentsFromLocaleIdentifier:localeIdentifier] mutableCopy];

    // So instead we'll "force" a new instance of a locale by creating a never-before-seen
    // locale identifier, thanks to the improbably magic of NSUUID
    // locale identifier, thanks to the improbable magic of NSUUID
    [components setObject:[[NSUUID UUID] UUIDString] forKey:@"custom"];

    // Turn the components back into a locale identifier. It's not "guaranteed"
    // Turn the components back into a locale identifier. It's now "guaranteed"
    // to be unique
    NSString *newID = [NSLocale localeIdentifierFromComponents:components];

  2. davedelong created this gist Oct 12, 2019.
    114 changes: 114 additions & 0 deletions NSLocale.m
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,114 @@
    /*
    ======================================================
    THIS CODE IS FOR EDUCATIONAL PURPOSES ONLY.
    I'M NOT RESPONSIBLE IF YOU SHIP THIS AND IT BLOWS UP IN YOUR FACE.
    IF IT DOES AND YOU COMPLAIN TO ME I WILL LAUGH AT YOU.
    ======================================================
    This code is based on the following premise:
    • You want to show things in a particular locale
    • You want to honor any overrides the user has set
    iOS will only format dates using the +[NSLocale currentLocale] if the app is localized to support that locale.
    That means that if your app is running on:
    • a device set to a language your app does not support AND
    • the user has custom overrides for 12/24 hour, date/time formats, etc
    THEN:
    • the user will see dates formatted in another language, and WITHOUT their overrides.
    This is because you end up getting a locale that's just the "default" locale for a particular language,
    with no overrides applied.
    Enter this code.
    This code attempts to work around this (admittedly rather edge case) scenario.
    The overrides on a locale are stored inside the "_prefs" field. For the current locale,
    this is basically copied out of NSUserDefaults and is how the current locale knows about the
    12/24 hour override, among other things.
    For custom locales, this is NULL. And there is no way to change it.
    Also working against us is the fact that _prefs IS NOT AN OBJC IVAR FIELD. NSLocale is toll-free
    bridged to CFLocaleRef, which means we're really dealing with pointers to C structs, and the fields
    on those structs are not described to the Objective-C runtime. So the Runtime doesn't even help us.
    So instead, we get to drop down to the raw memory layout and fudge things around.
    Please don't use this in shipping code.
    */

    @interface NSLocale (Voodoo)

    // hide all the nastiness behind a nice, pleasant method
    + (NSLocale *)localeIncludingOverridesWithLocaleIdentifier:(NSString *)localeIdentifier;

    @end


    // this is the (rough) layout of a CFLocaleRef
    // I figured this out based on:
    // - https://github.com/apple/swift-corelibs-foundation/blob/155f1ce1965effe55289477507a6f9fbdc8fe333/CoreFoundation/Locale.subproj/CFLocale.c#L144-L151
    // - too much time in the debugger

    struct __DDLocale {
    void *_base; // The CF version of an "isa" pointer
    void *_field1; // maybe the locale identifier?
    void *_field2; // maybe a cache
    void *_field3; // no idea
    CFDictionaryRef _prefs; // The Secret Sauce
    void *_lock; // maybe some thread safety thing
    Boolean _nullLocale; // who knows
    };

    @implementation NSLocale (Voodoo)

    + (NSLocale *)localeIncludingOverridesWithLocaleIdentifier:(NSString *)localeIdentifier {

    // First, rip apart the locale identifier in to its components
    // Why? because if you create a locale w/ an identifier, and another locale instance
    // already exists that has the same identifier, you'll just get a pointer back
    // to the pre-existing object. For this class, that's probably what you want
    // most of the time. For this scenario, it's not.
    NSMutableDictionary *components = [[NSLocale componentsFromLocaleIdentifier:localeIdentifier] mutableCopy];

    // So instead we'll "force" a new instance of a locale by creating a never-before-seen
    // locale identifier, thanks to the improbably magic of NSUUID
    [components setObject:[[NSUUID UUID] UUIDString] forKey:@"custom"];

    // Turn the components back into a locale identifier. It's not "guaranteed"
    // to be unique
    NSString *newID = [NSLocale localeIdentifierFromComponents:components];

    // Construct a brand-spanking-new NSLocale
    NSLocale *copy = [[NSLocale alloc] initWithLocaleIdentifier:newID];

    // If, for some reason, it failed, early return
    // This is because we're about to do pointer magic, and doing pointer
    // magic with a NULL pointer is a great way to crash. Let's not crash.
    if (copy == nil) { return nil; }

    // Cast (lie to the compiler) that these pointers are actually pointers
    // to our struct definition that we worked out before
    struct __DDLocale* this = (struct __DDLocale *)[NSLocale currentLocale];
    struct __DDLocale* that = (struct __DDLocale *)copy;

    // Copy over the dictionary of overrides from the +currentLocale
    // to our new copy. This is how our copy will get to know about the
    // overrides as well
    that->_prefs = CFDictionaryCreateMutableCopy(NULL, 0, this->_prefs);

    // Share And Enjoy.
    return copy;
    }

    @end