Browse Source

date translation with tests

oxpa 9 years ago
parent
commit
e9257e6500
2 changed files with 189 additions and 59 deletions
  1. 84 53
      src/filter_lib/erlydtl_dateformat.erl
  2. 105 6
      test/erlydtl_test_defs.erl

+ 84 - 53
src/filter_lib/erlydtl_dateformat.erl

@@ -74,7 +74,7 @@ format(DateTime, FormatString) ->
 %% replacing atom with a stub function (it's easier to do it this way)
 format(FormatString, TransFun, Locale) when is_binary(FormatString) ->
     format(binary_to_list(FormatString), TransFun, Locale);
-format(FormatString, none, Locale) -> 
+format(FormatString, none, _Locale) -> 
     format(FormatString, fun stub_tran/2, <<>>);
 format(FormatString, TransFun, Locale) ->
     {Date, Time} = erlang:localtime(),
@@ -82,12 +82,12 @@ format(FormatString, TransFun, Locale) ->
 
 format(DateTime, FormatString, TransFun, Locale) when is_binary(FormatString) ->
     format(DateTime, binary_to_list(FormatString), TransFun, Locale);
-format(DateTime, FormatString, none, Locale) ->
+format(DateTime, FormatString, none, _Locale) ->
     format(DateTime, FormatString, fun stub_tran/2, <<>>);
 format({{_,_,_} = Date,{_,_,_} = Time}, FormatString, TransFun, Locale) ->
     replace_tags(Date, Time, FormatString, TransFun, Locale );
 
-format({_,_,_} = Date, FormatString, none, Locale) ->
+format({_,_,_} = Date, FormatString, none, _Locale) ->
     replace_tags(Date, {0,0,0}, FormatString, fun stub_tran/2, <<>>);
 format({_,_,_} = Date, FormatString, TransFun, Locale) ->
     replace_tags(Date, {0,0,0}, FormatString, TransFun, Locale);
@@ -100,8 +100,14 @@ replace_tags(Date, Time, Input, TransFun, Locale) ->
 replace_tags(_Date, _Time, [], Out, _State, _TransFun, _Locale) ->
     lists:reverse(Out);
 replace_tags(Date, Time, [C|Rest], Out, noslash, TransFun, Locale) when ?TAG_SUPPORTED(C) ->
-    replace_tags(Date, Time, Rest,
-                 lists:reverse(tag_to_value(C, Date, Time, TransFun, Locale)) ++ Out, noslash, TransFun, Locale);
+    case tag_to_value(C, Date, Time, TransFun, Locale) of
+        V when is_binary(V) -> replace_tags(Date, Time, Rest, 
+                                            [V] ++ Out, noslash, 
+                                            TransFun, Locale);
+        V when is_list(V) ->   replace_tags(Date, Time, Rest, 
+                                            lists:reverse(V) ++ Out, 
+                                            noslash, TransFun, Locale)
+    end;
 replace_tags(Date, Time, [$\\|Rest], Out, noslash, TransFun, Locale) ->
     replace_tags(Date, Time, Rest, Out, slash, TransFun, Locale);
 replace_tags(Date, Time, [C|Rest], Out, slash, TransFun, Locale) ->
@@ -115,15 +121,19 @@ replace_tags(Date, Time, [C|Rest], Out, _State, TransFun, Locale) ->
 %%-----------------------------------------------------------
 
 %% 'a.m.' or 'p.m.'
-tag_to_value($a, _, {H, _, _}, TransFun, Locale) when H > 11 -> "p.m.";
-tag_to_value($a, _, _, TransFun, Locale) -> "a.m.";
+tag_to_value($a, _, {H, _, _}, TransFun, Locale) when H > 11 -> 
+    erlang:apply(TransFun, ["p.m.", Locale]);
+tag_to_value($a, _, _, TransFun, Locale) -> 
+    erlang: apply(TransFun, ["a.m.", Locale]);
 
 %% 'AM' or 'PM'
-tag_to_value($A, _, {H, _, _}, TransFun, Locale) when H > 11 -> "PM";
-tag_to_value($A, _, _, TransFun, Locale) -> "AM";
+tag_to_value($A, _, {H, _, _}, TransFun, Locale) when H > 11 -> 
+    erlang:apply(TransFun, ["PM", Locale]);
+tag_to_value($A, _, _, TransFun, Locale) -> 
+    erlang:apply(TransFun, ["AM", Locale]);
 
 %% Swatch Internet time
-tag_to_value($B, _, _, TransFun, Locale) ->
+tag_to_value($B, _, _, _TransFun, _Locale) ->
     ""; %% NotImplementedError
 
 %% ISO 8601 Format.
@@ -152,37 +162,39 @@ tag_to_value($f, Date, Time, TransFun, Locale) ->
         ++ ":" ++ tag_to_value($i, Date, Time, TransFun, Locale);
 
 %% Hour, 12-hour format without leading zeros; i.e. '1' to '12'
-tag_to_value($g, _, {H,_,_}, TransFun, Locale) ->
+tag_to_value($g, _, {H,_,_}, _TransFun, _Locale) ->
     integer_to_list(hour_24to12(H));
 
 %% Hour, 24-hour format without leading zeros; i.e. '0' to '23'
-tag_to_value($G, _, {H,_,_}, TransFun, Locale) ->
+tag_to_value($G, _, {H,_,_}, _TransFun, _Locale) ->
     integer_to_list(H);
 
 %% Hour, 12-hour format; i.e. '01' to '12'
-tag_to_value($h, _, {H,_,_}, TransFun, Locale) ->
+tag_to_value($h, _, {H,_,_}, _TransFun, _Locale) ->
     integer_to_list_zerofill(hour_24to12(H));
 
 %% Hour, 24-hour format; i.e. '00' to '23'
-tag_to_value($H, _, {H,_,_}, TransFun, Locale) ->
+tag_to_value($H, _, {H,_,_}, _TransFun, _Locale) ->
     integer_to_list_zerofill(H);
 
 %% Minutes; i.e. '00' to '59'
-tag_to_value($i, _, {_,M,_}, TransFun, Locale) ->
+tag_to_value($i, _, {_,M,_}, _TransFun, _Locale) ->
     integer_to_list_zerofill(M);
 
 %% Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off
 %% if they're zero and the strings 'midnight' and 'noon' if appropriate.
 %% Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.'
 %% Proprietary extension.
-tag_to_value($P, _, {0,  0, _}, TransFun, Locale) -> "midnight";
-tag_to_value($P, _, {12, 0, _}, TransFun, Locale) -> "noon";
+tag_to_value($P, _, {0,  0, _}, TransFun, Locale) -> 
+    erlang:apply(TransFun, ["midnight", Locale]);
+tag_to_value($P, _, {12, 0, _}, TransFun, Locale) -> 
+    erlang:apply(TransFun, ["noon", Locale]);
 tag_to_value($P, Date, Time, TransFun, Locale) ->
     tag_to_value($f, Date, Time, TransFun, Locale)
         ++ " " ++ tag_to_value($a, Date, Time, TransFun, Locale);
 
 %% Seconds; i.e. '00' to '59'
-tag_to_value($s, _, {_,_,S}, TransFun, Locale) ->
+tag_to_value($s, _, {_,_,S}, _TransFun, _Locale) ->
     integer_to_list_zerofill(S);
 
 %%-----------------------------------------------------------
@@ -191,71 +203,87 @@ tag_to_value($s, _, {_,_,S}, TransFun, Locale) ->
 
 %% Month, textual, 3 letters, lowercase; e.g. 'jan'
 tag_to_value($b, {_,M,_}, _, TransFun, Locale) ->
-    string:sub_string(monthname(M), 1, 3);
+    erlang:apply(TransFun,[string:sub_string(monthname(M), 1, 3), Locale]);
 
 %% Day of the month, 2 digits with leading zeros; i.e. '01' to '31'
-tag_to_value($d, {_, _, D}, _, TransFun, Locale) ->
+tag_to_value($d, {_, _, D}, _, _TransFun, _Locale) ->
     integer_to_list_zerofill(D);
 
 %% Day of the week, textual, 3 letters; e.g. 'Fri'
 tag_to_value($D, Date, _, TransFun, Locale) ->
     Dow = calendar:day_of_the_week(Date),
-    ucfirst(string:sub_string(dayname(Dow), 1, 3));
+    erlang:apply(TransFun, [
+                 ucfirst(string:sub_string(dayname(Dow), 1, 3)),
+                 Locale ]);
 
 %% Month, textual, long, alternative; e.g. 'Listopada'
-tag_to_value($F, {_,M,_}, _, TransFun, Locale) ->
-    ucfirst(monthname(M));
+tag_to_value($E, {_,M,_}, _, TransFun, Locale) ->
+    erlang:apply(TransFun, [
+                 ucfirst(monthname(M)),
+                 {Locale, <<"alt. month">>}]);
 
 
 %% Month, textual, long; e.g. 'January'
 tag_to_value($F, {_,M,_}, _, TransFun, Locale) ->
-    ucfirst(monthname(M));
+    erlang:apply(TransFun, [
+                 ucfirst(monthname(M)),
+                 Locale]);
 
 %% '1' if Daylight Savings Time, '0' otherwise.
-tag_to_value($I, _, _, TransFun, Locale) ->
+tag_to_value($I, _, _, _TransFun, _Locale) ->
     "TODO";
 
 %% Day of the month without leading zeros; i.e. '1' to '31'
-tag_to_value($j, {_, _, D}, _, TransFun, Locale) ->
+tag_to_value($j, {_, _, D}, _, _TransFun, _Locale) ->
     integer_to_list(D);
 
 %% Day of the week, textual, long; e.g. 'Friday'
 tag_to_value($l, Date, _, TransFun, Locale) ->
-    ucfirst(dayname(calendar:day_of_the_week(Date)));
+    erlang:apply(TransFun, [
+                 ucfirst(dayname(calendar:day_of_the_week(Date))),
+                 Locale]);
 
 %% Boolean for whether it is a leap year; i.e. True or False
-tag_to_value($L, {Y,_,_}, _, TransFun, Locale) ->
+tag_to_value($L, {Y,_,_}, _, _TransFun, _Locale) ->
     case calendar:is_leap_year(Y) of
         true -> "True";
         _ -> "False"
     end;
 
 %% Month; i.e. '01' to '12'
-tag_to_value($m, {_, M, _}, _, TransFun, Locale) ->
+tag_to_value($m, {_, M, _}, _, _TransFun, _Locale) ->
     integer_to_list_zerofill(M);
 
 %% Month, textual, 3 letters; e.g. 'Jan'
 tag_to_value($M, {_,M,_}, _, TransFun, Locale) ->
-    ucfirst(string:sub_string(monthname(M), 1, 3));
+    erlang:apply(TransFun, [
+                 ucfirst(string:sub_string(monthname(M), 1, 3)),
+                 Locale]);
 
 %% Month without leading zeros; i.e. '1' to '12'
-tag_to_value($n, {_, M, _}, _, TransFun, Locale) ->
+tag_to_value($n, {_, M, _}, _, _TransFun, _Locale) ->
     integer_to_list(M);
 
 %% Month abbreviation in Associated Press style. Proprietary extension.
 tag_to_value($N, {_,M,_}, _, TransFun, Locale) when M =:= 9 ->
     %% Special case - "Sept."
-    ucfirst(string:sub_string(monthname(M), 1, 4)) ++ ".";
+    erlang:apply(TransFun, [
+                 ucfirst(string:sub_string(monthname(M), 1, 4)) ++ ".",
+                 {Locale, <<"abbrev. month">>}]);
 tag_to_value($N, {_,M,_}, _, TransFun, Locale) when M < 3 orelse M > 7 ->
     %% Jan, Feb, Aug, Oct, Nov, Dec are all
     %% abbreviated with a full-stop appended.
-    ucfirst(string:sub_string(monthname(M), 1, 3)) ++ ".";
+    erlang:apply(TransFun, [
+                 ucfirst(string:sub_string(monthname(M), 1, 3)) ++ ".",
+                 {Locale, <<"abbrev. month">>}]);
 tag_to_value($N, {_,M,_}, _, TransFun, Locale) ->
     %% The rest are the fullname.
-    ucfirst(monthname(M));
+    erlang:apply(TransFun, [
+                 ucfirst(monthname(M)),
+                 {Locale, <<"abbrev. month">>}]);
 
 %% Difference to Greenwich time in hours; e.g. '+0200'
-tag_to_value($O, Date, Time, TransFun, Locale) ->
+tag_to_value($O, Date, Time, _TransFun, _Locale) ->
     Diff = utc_diff(Date, Time),
     Offset = if
                  Diff < 0 ->
@@ -266,68 +294,68 @@ tag_to_value($O, Date, Time, TransFun, Locale) ->
     lists:flatten(Offset);
 
 %% RFC 2822 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'
-tag_to_value($r, Date, Time, TransFun, Locale) ->
+tag_to_value($r, Date, Time, _TransFun, _Locale) ->
     % afaik, date should not be translated in case RFC format is specified.
     replace_tags(Date, Time, "D, j M Y H:i:s O", fun stub_tran/2, <<>> );
 
 %% English ordinal suffix for the day of the month, 2 characters;
 %% i.e. 'st', 'nd', 'rd' or 'th'
-tag_to_value($S, {_, _, D}, _, TransFun, Locale) when
+tag_to_value($S, {_, _, D}, _, _TransFun, _Locale) when
       D rem 100 =:= 11 orelse
       D rem 100 =:= 12 orelse
       D rem 100 =:= 13 -> "th";
-tag_to_value($S, {_, _, D}, _, TransFun, Locale) when D rem 10 =:= 1 -> "st";
-tag_to_value($S, {_, _, D}, _, TransFun, Locale) when D rem 10 =:= 2 -> "nd";
-tag_to_value($S, {_, _, D}, _, TransFun, Locale) when D rem 10 =:= 3 -> "rd";
-tag_to_value($S, _, _, TransFun, Locale) -> "th";
+tag_to_value($S, {_, _, D}, _, _TransFun, _Locale) when D rem 10 =:= 1 -> "st";
+tag_to_value($S, {_, _, D}, _, _TransFun, _Locale) when D rem 10 =:= 2 -> "nd";
+tag_to_value($S, {_, _, D}, _, _TransFun, _Locale) when D rem 10 =:= 3 -> "rd";
+tag_to_value($S, _, _, _TransFun, _Locale) -> "th";
 
 %% Number of days in the given month; i.e. '28' to '31'
-tag_to_value($t, {Y,M,_}, _, TransFun, Locale) ->
+tag_to_value($t, {Y,M,_}, _, _TransFun, _Locale) ->
     integer_to_list(calendar:last_day_of_the_month(Y,M));
 
 %% Time zone of this machine; e.g. 'EST' or 'MDT'
-tag_to_value($T, _, _, TransFun, Locale) ->
+tag_to_value($T, _, _, _TransFun, _Locale) ->
     "TODO";
 
 %% Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)
-tag_to_value($U, Date, Time, TransFun, Locale) ->
+tag_to_value($U, Date, Time, _TransFun, _Locale) ->
     EpochSecs = calendar:datetime_to_gregorian_seconds({Date, Time})
         - calendar:datetime_to_gregorian_seconds({{1970,1,1},{0,0,0}}),
     integer_to_list(EpochSecs);
 
 %% Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)
-tag_to_value($w, Date, _, TransFun, Locale) ->
+tag_to_value($w, Date, _, _TransFun, _Locale) ->
     %% Note: calendar:day_of_the_week returns
     %%   1 | .. | 7. Monday = 1, Tuesday = 2, ..., Sunday = 7
     integer_to_list(calendar:day_of_the_week(Date) rem 7);
 
 %% ISO-8601 week number of year, weeks starting on Monday
-tag_to_value($W, {Y,M,D}, _, TransFun, Locale) ->
+tag_to_value($W, {Y,M,D}, _, _TransFun, _Locale) ->
     integer_to_list(year_weeknum(Y,M,D));
 
 %% Year, 2 digits; e.g. '99'
-tag_to_value($y, {Y, _, _}, _, TransFun, Locale) ->
+tag_to_value($y, {Y, _, _}, _, _TransFun, _Locale) ->
     string:sub_string(integer_to_list(Y), 3);
 
 %% Year, 4 digits; e.g. '1999'
-tag_to_value($Y, {Y, _, _}, _, TransFun, Locale) ->
+tag_to_value($Y, {Y, _, _}, _, _TransFun, _Locale) ->
     integer_to_list(Y);
 
 %% Day of the year; i.e. '0' to '365'
-tag_to_value($z, {Y,M,D}, _, TransFun, Locale) ->
+tag_to_value($z, {Y,M,D}, _, _TransFun, _Locale) ->
     integer_to_list(day_of_year(Y,M,D));
 
 %% Time zone offset in seconds (i.e. '-43200' to '43200'). The offset for
 %% timezones west of UTC is always negative, and for those east of UTC is
 %% always positive.
-tag_to_value($Z, _, _, TransFun, Locale) ->
+tag_to_value($Z, _, _, _TransFun, _Locale) ->
     "TODO";
 
 %% o – the ISO 8601 year number
-tag_to_value($o, {Y,M,D}, _, TransFun, Locale) ->
+tag_to_value($o, {Y,M,D}, _, _TransFun, _Locale) ->
     integer_to_list(weeknum_year(Y,M,D));
 
-tag_to_value(C, Date, Time, TransFun, Locale) ->
+tag_to_value(C, Date, Time, _TransFun, _Locale) ->
     io:format("Unimplemented tag : ~p [Date : ~p] [Time : ~p]",
               [C, Date, Time]),
     "".
@@ -418,4 +446,7 @@ ucfirst([First | Rest]) when First >= $a, First =< $z ->
 ucfirst(Other) ->
     Other.
 
-stub_tran(A,_) -> A.
+stub_tran(A,_) -> 
+    % userful for test debuggging
+    % io:format("calling stub translation!!!",[]),
+    A.

+ 105 - 6
test/erlydtl_test_defs.erl

@@ -1,3 +1,4 @@
+%% -*- coding: utf-8 -*-
 -module(erlydtl_test_defs).
 
 -export([tests/0, extra_reader/2]).
@@ -203,7 +204,11 @@ all_test_defs() ->
      {"now",
       [{"now functional",
         <<"It is the {% now \"jS \\o\\f F Y\" %}.">>, [{var1, ""}], generate_test_date()}
-      ]},
+     ]},
+      {"now",
+      [{"now function with translation", % notice, that only date output is traslated. While you might want to transle the whole format string ('F'->'E')
+        <<"It is the {% now \"jS \\o\\f F Y\" %}.">>, [{var1, ""}], [{locale, <<"ru">>}, {translation_fun, fun date_translation/2}], generate_test_date(russian)}
+     ]},
      {"if",
       [{"If/else",
         <<"{% if var1 %}boo{% else %}yay{% endif %}">>, [{var1, ""}], <<"yay">>},
@@ -560,6 +565,48 @@ all_test_defs() ->
         <<"{{ var1|date }}">>,
         [{var1, {{1975,7,24}, {7,13,1}}}],
         <<"July 24, 1975">>},
+        % I doubt someone need first two, but test we support it
+        {"|date a translation",
+        <<"{{ var1|date:\"a\" }}">>,
+        [{var1, {{1975,7,24},{12,00,00}}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}],
+        <<"п.п."/utf8>>},
+        {"|date A translation",
+        <<"{{ var1|date:\"A\" }}">>,
+        [{var1, {{1975,7,24},{12,00,00}}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}],
+        <<"ПП"/utf8>>},
+        {"|date b translation", 
+        <<"{{ var1|date:\"b\" }}">>,
+        [{var1, {1975,7,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}],
+        <<"июл"/utf8>>},
+        {"|date D translation",
+        <<"{{ var1|date:\"D\" }}">>,
+        [{var1, {1975,7,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}],
+        <<"Чтв"/utf8>>},
+        {"|date E translation",
+        <<"{{ var1|date:\"E\" }}">>,
+        [{var1, {1975,7,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}],
+        <<"Июля"/utf8>>},
+        {"|date F translation",
+        <<"{{ var1|date:\"F\" }}">>,
+        [{var1, {1975,7,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}],
+        <<"Июль"/utf8>>},
+        {"|date l translation",
+        <<"{{ var1|date:\"l\" }}">>,
+        [{var1, {1975,7,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}],
+        <<"Четверг"/utf8>>},
+        {"|date M translation",
+        <<"{{ var1|date:\"M\" }}">>,
+        [{var1, {1986,9,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}],
+        <<"Сен"/utf8>>},
+        {"|date N translation",
+        <<"{{ var1|date:\"N\" }}">>,
+        [{var1, {1986,9,24}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}],
+        <<"Сен."/utf8>>},
+        {"|date P translation",
+        <<"{{ var1|date:\"P\" }}">>,
+        [{var1, {{1986,9,24},{12,0,0}}}],[{translation_fun, fun date_translation/2},{locale, <<"ru">>}],
+        <<"полдень"/utf8>>},
+
        {"|default:\"foo\" 1",
         <<"{{ var1|default:\"foo\" }}">>, [], <<"foo">>},
        {"|default:\"foo\" 2",
@@ -1797,13 +1844,65 @@ def_to_test(Group, {Name, DTL, Vars, RenderOpts, CompilerOpts, Output, Warnings}
       }.
 
 
+date_translation(Val, LC) when is_list(Val) ->
+    io:format("Translating ~p~n", [Val]),
+    date_translation(list_to_binary(Val),LC);
+% date a
+date_translation(<<"p.m.">>, <<"ru">>) ->
+    <<"п.п."/utf8>>;
+% date A
+date_translation(<<"PM">>, <<"ru">>) ->
+    <<"ПП"/utf8>>;
+% date b
+date_translation(<<"jul">>, <<"ru">>) ->
+    <<"июл"/utf8>>;
+% date D
+date_translation(<<"Thu">>, <<"ru">>) ->
+    <<"Чтв"/utf8>>;
+% date E
+date_translation(<<"July">>, {<<"ru">>, <<"alt. month">>}) ->
+    <<"Июля"/utf8>>;
+% date F
+date_translation(<<"July">>, <<"ru">>) ->
+    <<"Июль"/utf8>>;
+% date l
+date_translation(<<"Thursday">>, <<"ru">>) ->
+    <<"Четверг"/utf8>>;
+% date M
+date_translation(<<"Sep">>, <<"ru">>) ->
+    <<"Сен"/utf8>>;
+% date N
+date_translation(<<"Sept.">>, {<<"ru">>, <<"abbrev. month">>}) ->
+    <<"Сен."/utf8>>;
+% date P
+date_translation(<<"noon">>, <<"ru">>) ->
+    <<"полдень"/utf8>>;
+date_translation(Text, <<"ru">>) ->
+    proplists:get_value(Text,
+                        lists:zip(
+                              lists:map(fun list_to_binary/1, en_months()),
+                              ru_months()),
+                        Text);
+date_translation(Text, _) ->
+    Text.
+
+ru_months() -> [ <<"Январь"/utf8>>, <<"Февраль"/utf8>>, <<"Март"/utf8>>, <<"Апрель"/utf8>>,
+             <<"Май"/utf8>>, <<"Июнь"/utf8>>, <<"Июль"/utf8>>, <<"Август"/utf8>>, <<"Сентябрь"/utf8>>,
+             <<"Октябрь"/utf8>>, <<"Ноябрь"/utf8>>, <<"Декабрь"/utf8>>].
+en_months() -> ["January", "February", "March", "April",
+             "May", "June", "July", "August", "September",
+             "October", "November", "December"].
+
+
+
 generate_test_date() ->
+    generate_test_date(false).
+generate_test_date(Translation) ->
     {{Y,M,D}, _} = erlang:localtime(),
-    MonthName = [
-                 "January", "February", "March", "April",
-                 "May", "June", "July", "August", "September",
-                 "October", "November", "December"
-                ],
+    MonthName = case Translation of
+                    russian -> ru_months();
+                    _ -> en_months()
+                end,
     OrdinalSuffix = [
                      "st","nd","rd","th","th","th","th","th","th","th", % 1-10
                      "th","th","th","th","th","th","th","th","th","th", % 10-20