Jak Microsoft zwalcza polską kulturę?
To trzecia i (mam nadzieję) ostatnia część moich przygód z kontrolką daty w PizzaMVC. Tym razem musiałem naprawić problem związany z polskim zapisem daty wynikający z ewidentych błędów Microsoftu w .NET Framework. A przy okazji napisałem najdłuższy program w Javie w swoim życiu.
Jak zapisuje się daty po polsku?
Aby się tego dowiedzieć, wróćmy na chwilę do szkoły podstawowej. To takie miejsce, w którym młodzi ludzie uczą się róznych przydatnych rzeczy, m.in. pisania. Jedną z często zapisywanych w szkole informacji jest data. (Za moich czasów robiło się to nawet kilka razy dziennie - na marginesie, obok tematu lekcji.) Jak daty zapisywało się w podstawówce?
- 5 kwietnia 2016 r
- 5 IV 2016
- 5.04.2016
Pierwsza wersja jest najbardziej opisowa, druga moim zdaniem najładniejsza (i sam chyba najczęściej ją stosowałem), a trzecia najbardziej skrótowa. W jej przypadku można też opcjonalnie dopisywać zero dla pierwszych dziewięciu dni miesiąca. Tyle - nie ma innych form zapisu daty w języku polskim.
Co ważne dla pierwszego zapisu, należy używać formy “kwietnia”, nie “kwiecień”. Nie mamy pięciu kwietniów w roku, żeby mówić “piąty kwiecień”. I nie stawia się kropki po zapisie cyfrowym liczby, jak myślą niektórzy. Nie w takim przypadku.
Parę słów ISO 8601 i Polskiej Normie
Ponieważ w różnych krajach składowe zapisu daty występują w różnej kolejności (np. dd/mm/rrrr
używane w Wielkiej Brytanii i absurdalne mm/dd/rrrr
stosowane w USA) oraz używa się różnych separatorów (duński -
, brytyjski /
, węgierski .
), wynikają z tego różne niejasności. Aby im zapobiec organizacja ISO opracowała międzynardowy standard zapisu daty i czasu - ISO 8601 (Data elements and interchange formats – Information interchange – Representation of dates and times). Jest on bardzo prosty i sensowny - najbardziej znaczące elementy są najbliżej lewej strony, a więc data w nim wyglada tak: 2016-04-05
. Dodatkowo, zapis ten znakomicie ułatwia to porównywanie i sortowanie dat. Ten standard ma być stosowany w systemach informatycznych działających globalnie, wymieniających informacje między państwami. Nigdzie nie stwierdzono, że to ma być nowy standard do użycia dla ludzi, ani też, że w ogóle ma byc dla nich czytelny.
W Polsce ten standard został wprowadzony przez Polską Normę PN-90/N-01204, później zastąpioną przez PN-EN 28601:2002, która (jak można przeczytać na stronie Polskiego Komitetu Normalizacyjnego) jest obecnie wycofana i nie została zastąpiona żadną inną. Dziwna sytuacja i trochę dla mnie niezrozumiała, ale oczywistym jest, że i bez formalnych ustaleń w postaci PN warto się stosować do standardów ISO.
Jak polskie daty zapisuje Microsoft?
Z nieznanych przyczyn Microsoft, zarówno w Windowsie, jak i w .NET zdecydował się na zapis zgodny z ISO lub PN, czyli 2016-04-05
. Nie jest to poprawny polski zapis daty. Inne kraje też stosują ISO 8601, ale mimo tego, formaty dat dla ich ustawień regionalnych są respektowane przez Microsoft. Dla Polski nie. Czy Microsoft nie lubi naszego kraju? Czy po prostu pracują tam ignoranci? To drugie wyjaśniałoby zarówno użycie niepoprawnego formatu zapisu dat, jak i np. kuriozalne tłumaczenie Relationships
na Relacje
w Accessie.
Dla porównania, największy konkurent .NET, też nie wyświetla polskich dat do końca poprawnie. O ile separator jest dobry, to już rok zbyt krótki. Oto dowód: http://ideone.com/HjMy5F
To teraz trochę o kodzie
To przydługie wprowadzenie było potrzebne, aby zrozumieć źródło problemu, z którym się borykałem. Otóż, używany przeze mnie bootstrap-datepicker stosuje poprawny polski zapis dd.mm.yyyy
, natomiast ustawienia pl-PL
w .NET to yyyy-mm-dd
. W związku z tym, oczywistym był konflikt w interpretacji tekstowego zapisu między kodem .NET a JS. Wartości renderowane przez serwer w kodzie strony nie były rozpoznawane przez kontrolkę, zaś te wybrane przez użytkownika za pomocą datepickera, przez serwer były uznawane za nieprawidłowe.
Naprawa wyświetlania po stronie przeglądarki
W pliku cshtml
nie było dużo roboty. Już wcześniej korzystałem z ustawień regionalnych, teraz musiałem dodać małego hacka na polskie ustawienia:
I zmienić sposób wyświetlania Modelu:
Jak widać teraz używam ściśle wybranego formatu, zamiast ToShortDateString
.
A co z serwerem?
Zmiana z poprzedniego akapitu sprawia, że data wyświetla się prawidłowo, można ją zmieniać przy użyciu kontrolki, i nie powoduje ona błędów walidacji po stronie przeglądarki. Niestety, po wysłaniu formularza na serwer, zwracany jest błąd dotyczący brakującej wartości w polu daty. Czyli walidacja serwerowa działa poprawnie, ale nie tak, jak w tym konkretnym przypadku trzeba. Pomysłów na modyfikację miałem kilka:
- Podmiana wartości pola w kodzie JS w zdarzeniu przed wysłaniem formularza.
- Podmiana wartości pola już po stronie backendu.
- Filtry w ASP.NET MVC.
- Dziedziczenie z
DefaultModelBinder
i nadpisanie metodyBindProperty
alboGetPropertyValue
. - Własny model binder.
Rozwiązanie pierwsze jest dość łatwe do wykonania, ale wydaje mi się, że takie coś może działać niestabilnie w jakichś bardziej skompliokwanych przypadkach. Nie jest to dla mnie pewna droga, więc odpada. Drugi sposób też odpada, bo wartości w żądaniu (Request.Forms
) nie da się podmienić, gdyż kolekcja ta jest tylko do odczytu. (Słusznie zresztą!). Filtry odpadają dlatego, że wykonywane są po bindowaniu modelu, czyli za późno.
Najwięcej czasu spędziłem na próbie dziedziczenia z DefaultModelBinder
. Jak się okazało podczas debugowania, nie jest to takie łatwe. Metoda konwertująca string do typu właściwości modelu jest statyczna, więc nie da się jej nadpisać. Wartości właściwości z requestu są wyciągane przez bindingContext.ValueProvider
, którego nie bardzo miałem jak zastąpić ani nadpisać. W ogóle debugowanie DefaultModelBindera
nie jest przyjemne. Odniosłem wrażenie, że jest dość chaotyczny, a do tego działa rekurencyjnie i naprawdę można się pogubić w call stacku.
Została ostatnia możliwość - dodatkowy model binder. Oto i jego kod:
Ponieważ kultura de-DE
ma interesujący nas format zapisu daty dd.mm.yyyy
, to zdecydowałem się właśnie jej użyć. Reszta kodu chyba nie wymaga komentarza.