Ці цікаві регіональні налаштування

Сьогодні ми поговоримо про регіональні налаштуваннях. Але спочатку — невеличка задачка: що виведе нижченаведений код? (Код приведений на мові C#, але розглядається досить загальна проблематика, так що ви можете уявити на його місці будь-який інший мову.)
Console.WriteLine((-42).ToString() == "-42");
Console.WriteLine(double.NaN.ToString() == "NaN");
Console.WriteLine(int.Parse("-42") == -42);
Console.WriteLine(1.1.ToString().Contains("?") == false);
Console.WriteLine(new DateTime(2014, 1, 1).ToString().Contains("2014"));
Console.WriteLine("і".ToUpper() == "І" || "І".ToLower() == "і");
Скільки значень true у вас вийшло? Якщо більше 0, то вам не заважає дізнатися більше про регіональні настройки, т. к. правильну відповідь: «залежить». На жаль, багато програмісти взагалі не замислюються про те, що ці налаштування в різних середовищах можуть відрізнятися. А виставляти для всього коду InvariantCulture цим програмістом ліниво, в результаті чого їх прекрасні додатки ведуть себе дуже дивно, потрапляючи до користувачів з інших стран.Ошибки бувають різні, але найчастіше вони пов'язані з форматуванням і парсингом рядків — досить частотними завданнями для багатьох програмістів. У статті наведена коротка підбірка деяких важливих моментів, на які впливають регіональні налаштування.CultureInfoExplorer Sceenshot
Зовсім небагато теорії: .NET всі відомості про певній мові та регіональних параметрах можна знайти з допомогою класу CultureInfo. Якщо ви раніше не стикалися з культурами, то для первинного ознайомлення добре підійде цей пост. Досвідчений програміст, захоплений вивченням різних існуючих регіональних налаштувань, може стомитися від ручного перегляду всіх CultureInfo. Особисто я в якийсь момент стомився. Тому з'явилося невелике WPF-додаток під назвою CultureInfoExplorer (посилання на GitHub, бінарники), представлена на наведеному вище скріншоті. Воно дозволяє:
  • За даною CultureInfo подивитися значення основних її властивостей і те, як в ній виглядають деякі заздалегідь заготовлені рядкові патерни.
  • За даним властивості подивитися його можливі значення і список всіх CultureInfo, які відповідають кожному значенню.
  • За даним паттерну подивитися можливі варіанти того, у що він може перетворитися, і для кожного варіанту також подивитися список відповідних CultureInfo.
Сподіваюся, знайдуться читачі, яким дана програмка буде корисна. Можна дізнатися багато нового про різні регіональних налаштуваннях. Ну, а тепер перейдемо до прикладів.

За подання чисел у нас відповідає NumberFormatInfo (доступний через CultureInfo.NumberFormat). І маються на увазі не тільки звичайні числа, а також процентні та грошові значення. Зверніть увагу на те, що значення бувають позитивні і негативні: якщо ви працюєте з локалізацією/глобалізацією, то важливо звертати на це увагу. Настійно рекомендую хоча б пробігтися очима по документації і подивитися доступні властивості.

Одне з найпопулярніших властивостей, яке викликає проблеми у людей, називається NumberDecimalSeparator. Воно відповідає за те, що буде при форматуванні числа відділятися ціла частина від дробової. Типовий приклад помилки: програміст зливає масив дробових чисел у рядок, відокремлюючи їх комами. Після цього він намагається розпарсити рядок назад в масив. Якщо NumberDecimalSeparator дорівнює точці, то все буде добре. Скажімо, при виставленої культурі en-US у програміста все запрацювало, він випустив свій продукт. Цей продукт користувач викачує з культурою ru-RU і починає сумувати: адже у нього NumberDecimalSeparator дорівнює комою: масив з елементів 1.2 та 3.4 при такому злитті перетвориться в рядок «1,2,3,4», а її розпарсити буде проблемно. Особисто мені стає ще сумніше тоді, коли зустрів подібну проблему програміст не намагається вирішити її нормально, вказуючи правильний NumberFormatInfo при форматуванні, а починає чаклувати з замінами точок на коми або ком на точки. Потрібно розуміти, що NumberDecimalSeparator, в принципі, може бути будь-якою. Наприклад, у культурі fa-IR (Persian) він дорівнює слешу ('/').

Ще в нашому розпорядженні є аналогічні властивості для відсотків і валют: PercentDecimalSeparator і CurrencyDecimalSeparator. Всі ці три значення зовсім не повинні збігатися. Наприклад, у казахів (kk-KZ) NumberDecimalSeparator PercentDecimalSeparator дорівнюють комою, а CurrencyDecimalSeparator дорівнює знаку мінус (точно такому ж, з допомогою якого позначаються від'ємні числа).

Деякі вважають, що ціле число при конвертації в рядок дає значення, що складається лише з цифр. Але ці цифри можуть розбиватися на групи. За розмір груп відповідає властивість NumberGroupSizes, а за їх роздільник — NumberGroupSeparator (аналогічні властивості є у відсотків і валют, але вони знову-таки не повинні збігатися). Групи можуть бути різного розміру: наприклад, у багатьох культурах (as-IN, bn-BD, gu-IN, hi-IN тощо) NumberGroupSizes одно {3, 2}. Скажімо, число 1234567 в культурі as-IN буде виглядати як «12,34,567». В якості роздільника груп може виступати пробіл \u0020 (наприклад у af-ZA і lt-LT), але, побачивши його, не поспішайте вбивати черговий милицю на парсинг і форматування рядків. Найчастіше замість звичайного пробіл використовується нерозривний пробіл \u00A0 (наша рідна ru-RU).

Знаки для позначення негативних і позитивних чисел також входять в культуру: NegativeSign, PositiveSign. Слава богу, у всіх доступних культурах вони рівні мінуса і плюса, але закладатися на це не варто: оточення можна перевизначити і задати властивостями будь-які значення. А найцікавіше полягає не в знаках, а в шаблони форматування позитивних і негативних значень. Наприклад, форматування від'ємного числа визначається за допомогою NumberNegativePattern, у якого є п'ять можливих значень:
0 (n)
1-n
2 - n
3 n-
4 n -

Наприклад, у культурі ti-ET (Tigrinya (Ethiopia)) значення -5 постане у вигляді (5). З відсотками і валютами (PercentNegativePattern, PercentPositivePattern, CurrencyNegativePattern, CurrencyPositivePattern) справа йде ще веселіше. Наприклад, для CurrencyNegativePattern є цілих шістнадцять можливих значень:
0 ($n)
1 -$n
2 $-n
3 $n-
4 (n$)
5-n$
6 n-$
7 n$-
8-n $
9 -$ n
10 n $-
11 $ n-
12 $ -n
13 n- $
14 ($ n)
15 (n $)

Також є спеціальні властивості для спеціальних символів і спеціальних чисельних значень: PercentSymbol, PerMilleSymbol, NaNSymbol, NegativeInfinitySymbol, PositiveInfinitySymbol. Мені доводилося бачити реальний проект, в якому брався double, форматувався в рядок (зрозуміло, в поточній культурі користувача), а потім у строковому вигляді порівнювався з «-Infinity». А в залежності від цієї самої поточної культури NegativeInfinitySymbol може приймати самі різні значення:
'- безкрайност', '-- អននត', '(-) முடிவிலி', '-∞', '-Anfeidredd', '-Anfin', '-begalybė', '-beskonačnost', 'Éigríoch dhiúltach', '-ifedh', '-INF', '-Infini', '-infinit', '-Infinit', '-Infinito', '-Infinitu', '-infinity', 'Infinity-', '-Infinity', 'miinuslõpmatus', 'mínusz végtelen', '-nekonečno', '-neskončnost', '-nieskończoność', '-njekónčne', '-njeskóńcnje', '-onendlech', '-Sonsuz', '-tükeniksizlik', '-unendlich', '-Unendlich', '-Άπειρο', '-бесконачност', 'терс чексиздик', '-უსასრულობა', 'אינסוף שלילי', '-لا نهاية', 'منهای بی نهایت', 'مەنپىي چەكسىزلىك', '-අනනතය', 'ᠰᠦᠬᠡᠷᠬᠦ ᠬᠢᠵᠠᠭᠠᠷᠭᠦᠢ ᠶᠡᠬᠡ', 'མ་གངས་ཚད་མད་ཆང་བ།', 'ߘߊߒߕߊ-', 'ꀄꊭꌐꀋꉆ', '負無窮大', '负无穷大'

Приклади різних корисних властивостей ми розібрали. А тепер давайте трошки пошалим: трохи змінимо російську культуру, щоб її нове значення псувало нам життя в прикладі з початку поста:
var myCulture = (CultureInfo)new CultureInfo("ru-UA").Clone();
myCulture.NumberFormat.NegativeSign = "!";
myCulture.NumberFormat.PositiveSign = "-";
myCulture.NumberFormat.PositiveInfinitysymbol = "+Inf";
myCulture.NumberFormat.NaNSymbol = "Not a number";
myCulture.NumberFormat.NumberDecimalseparator = "?";
Thread.CurrentThread.CurrentCulture = myCulture;
Console.WriteLine(-42); // !42
Console.WriteLine(double.NaN); // Not a number
Console.WriteLine(int.Parse("-42")); // 42
Console.WriteLine(1.1); // 1?1

Можливо, хтось одразу скаже мені: «Так навіщо такі приклади взагалі розглядати? Жоден програміст таке ніколи писати не буде!». А я відповім: «Ну-ну, ні один не буде, як же». Ситуація стає сумною, коли ви поширюєте деяку бібліотеку, а один з її користувачів вирішив побавитися з культурою. Може, він просто любить розважатися, а може, пише додаток для якоюсь дивиною культури (скажімо, мертвого або вигаданого мови). Але це не важливо. А важливо те, що ваша бібліотека починає поводитися дивно в незвичному для неї оточенні. Тому не варто орієнтуватися на те, що NegativeSign PositiveSign ніколи не змінюються. Краще просто явно вказати потрібну вам культуру і жити щасливо.

Дати і часЗ датами і часом усе особливо важко. За дати у нас відповідає клас DateTimeFormatInfo (властивість CultureInfo.DateTimeFormat), а в ньому є Calendar. Причому є основний календар культури (CultureInfo), а є список доступних для використання календарів (CultureInfo.OptionalCalendar). У нашому розпорядженні є велика пачка стандартних календарів: ChineseLunisolarCalendar, EastAsianLunisolarCalendar, GregorianCalendar, HebrewCalendar, HijriCalendar, JapaneseCalendar, JapaneseLunisolarCalendar, JulianCalendar, KoreanCalendar, KoreanLunisolarCalendar, PersianCalendar, TaiwanCalendar, TaiwanLunisolarCalendar, ThaiBuddhistCalendar, UmAlQuraCalendar (у деяких є ряд додаткових важливих параметрів). Логіка у них, доповім я вам, сама різна. Не будемо зупинятися детально, бо на цю тему докладної інформації в інтернеті достатньо, а матеріалу вистачить на серію самостійних постів. Правила форматування дат і часу ще веселіші, ніж у чисел: купа патернів для різних варіантів форматування дати, нативні назви місяців і днів тижня, для позначення AM/PM, роздільники і т.п. Скажімо, 31 грудня 2014 року може бути представлено (dateTime.ToString(«d»)) в наступних форматах:
09/03/36
10/3/1436
12/31/2014
1436/3/10
2014.12.21.
2014/12/21
2014-12-21
31. 12. 2014
31.12.14
31.12.14 ý.
31.12.2014
31.12.2014 р.
31.12.2014.
31/12/14
31/12/2014
31/12/2557
31-12-14
31-12-2014
31 грудня 14
31-жел-14

І це тільки стандартні значення (без підключення опціональних календарів). Але навіть тут видно різноманітність летоисчислений: у когось на подвір'ї 1436 рік, а у кого-то — 2557 (це відсилання до передостанній сходинці прикладу з початку статті). Якщо ви оперуєте з датами, то слід замислитися: чи варто їх показувати завжди в однаковому форматі або ж підлаштуватися під користувача і відобразити дату в більш звичному для нього вигляді. Ну, а про парсинг дат я взагалі промовчу.

The Turkey TestTurkey flag
Є класичний пост від 2008 року під назвою Does Your Code Pass The Turkey Test?. Докладно переказувати його не буду, краще самостійно прочитати оригінал. Коротка суть The Turkey Test така: поміняйте поточну культуру tr-TR (Turkish (Turkey)) і запустіть ваш додаток. Все нормально працює? У цій культурі вистачає веселощів і з датами, і з числами, і з рядками. Якщо повернутися до нашого першого прикладу, то в даній культурі «i».ToUpper() не дорівнює I, «I».ToLower() не дорівнює i. В кінці посту наводиться чудовий приклад, в якому під регулярний вираз \d{5} підходить рядок складається з арабських цифр "٤٦٠٣٨".

Замість висновкуНаука про регіональні налаштуваннях складна. У цьому пості я ні в якому разі не претендую на те, щоб видати повну інформацію про те, що вони можуть впливати. Є ще дуже багато різних цікавинок, пов'язаних з интернализацией (думаю, тільки про що йде справа наліво текст можна написати окремий пост, та й не один). Мені просто хотілося показати кілька цікавих прикладів того, як CultureInfo.CurrentCulture може вплинути на ваш додаток. Сподіваюся, у плані розширення загальної ерудиції цей матеріал виявиться комусь корисним. Загальна мораль така: якщо ви не хочете думати про те, що у світі існує багато різних культур, то використовуйте скрізь CultureInfo.InvariantCulture (або іншу підходящу вам культуру) — в переважній більшості випадків ви зможете спати спокійно. А якщо ви замислюєтеся про це, то непогано б повивчати цю область більш грунтовно. У цьому може допомогти ось ця гарна книжка: Net Internationalization: The developer's Guide to Building Global Windows and Web Applications.

Вітаються будь-які додаткові факти про те, як CultureInfo може вплинути на роботу різних функцій. Думаю, у багатьох знайдуться власні захоплюючі історії.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.