From 7dd5a7e12bb785fec59a658d937ac9e14b1e470e Mon Sep 17 00:00:00 2001 From: Carl de Billy Date: Mon, 18 Jan 2021 13:51:26 -0500 Subject: [PATCH] feat(calendar): Ensure Calendar & DateTimeFormatter are following ApplicationLanguages state --- .../Given_CalendarFormatter.cs | 46 +++++ .../Windows_Globalization/When_Calendar.cs | 108 +++++----- src/Uno.UWP/Globalization/Calendar.cs | 2 +- .../DateTimeFormatting/DateTimeFormatter.cs | 190 ++++++++++++++---- 4 files changed, 249 insertions(+), 97 deletions(-) create mode 100644 src/Uno.UI.Tests/Windows_Globalization/Given_CalendarFormatter.cs diff --git a/src/Uno.UI.Tests/Windows_Globalization/Given_CalendarFormatter.cs b/src/Uno.UI.Tests/Windows_Globalization/Given_CalendarFormatter.cs new file mode 100644 index 000000000000..1365dcc0b290 --- /dev/null +++ b/src/Uno.UI.Tests/Windows_Globalization/Given_CalendarFormatter.cs @@ -0,0 +1,46 @@ +using System.Globalization; +using System.Linq; +using Windows.Globalization.DateTimeFormatting; +using FluentAssertions; +using FluentAssertions.Execution; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Uno.UI.Tests.Windows_Globalization +{ + [TestClass] + public class Given_CalendarFormatter + { + [TestMethod] + [DataRow("day month year", "en-US", "{month.numeric}/{day.integer}/{year.full}")] + [DataRow("day month year", "en-CA", "{year.full}-{month.numeric}-{day.integer(2)}")] + [DataRow("day month year", "en-GB", "{day.integer(2)}/{month.numeric}/{year.full}")] + [DataRow("day month year", "fr-CA", "{year.full}-{month.numeric}-{day.integer(2)}")] + [DataRow("day month year", "fr-FR", "{day.integer(2)}/{month.numeric}/{year.full}")] + [DataRow("day month year", "hu-HU", "{year.full}. {month.numeric}. {day.integer(2)}.")] + public void When_UsingVariousLanguages(string format, string language, string expectedPattern) + { + var sut = new DateTimeFormatter(format, new[] {language}); + + var firstPattern = sut.Patterns.First(); + + using var _ = new AssertionScope(); + + firstPattern.Should().Be(expectedPattern); + firstPattern.Length.Should().Be(expectedPattern.Length); + } + + [TestMethod] + [DataRow("day", "en-US|fr-CA|ru-RU", "{day.integer}|{day.integer}|{day.integer}")] + [DataRow("day month year", "en-US|fr-CA", "{month.numeric}/{day.integer}/{year.full}|{year.full}-{month.numeric}-{day.integer(2)}")] + [DataRow("month year", "en-US|fr-CA", "{month.full} {year.full}|{month.full}, {year.full}")] + [DataRow("day month", "en-US|fr-CA", "{month.full} {day.integer}|{day.integer} {month.full}")] + [DataRow("hour minute second", "en-US|fr-CA", "{hour}:{minute}:{second} {period.abbreviated}|{hour}:{minute}:{second}")] + [DataRow("hour minute", "en-US|fr-CA", "{hour}:{minute} {period.abbreviated}|{hour}:{minute}")] + public void When_UsingMultipleLanguages(string format, string languages, string expectedPatterns) + { + var sut = new DateTimeFormatter(format, languages.Split('|')); + + sut.Patterns.Should().BeEquivalentTo(expectedPatterns.Split('|')); + } + } +} diff --git a/src/Uno.UI.Tests/Windows_Globalization/When_Calendar.cs b/src/Uno.UI.Tests/Windows_Globalization/When_Calendar.cs index ed3d74b7ab3c..4b7b596577f4 100644 --- a/src/Uno.UI.Tests/Windows_Globalization/When_Calendar.cs +++ b/src/Uno.UI.Tests/Windows_Globalization/When_Calendar.cs @@ -598,43 +598,43 @@ private void Validate( using (new AssertionScope("Calendar Properties")) { - SUT.Day.Should().Be(day, "day"); - SUT.Month.Should().Be(month, "month"); - SUT.Year.Should().Be(year, "year"); - SUT.Hour.Should().Be(hours, "hours"); - SUT.Minute.Should().Be(minutes, "minutes"); - SUT.Second.Should().Be(seconds, "seconds"); - SUT.Nanosecond.Should().Be(milliseconds * 1000, "milliseconds"); - SUT.DayOfWeek.Should().Be(dayOfWeek, "dayOfWeek"); - SUT.Era.Should().Be(era, "era"); - SUT.FirstDayInThisMonth.Should().Be(firstDayInThisMonth, "firstDayInThisMonth"); - SUT.FirstEra.Should().Be(firstEra, "firstEra"); - SUT.FirstHourInThisPeriod.Should().Be(firstHourInThisPeriod, "firstHourInThisPeriod"); - SUT.FirstMinuteInThisHour.Should().Be(firstMinuteInThisHour, "firstMinuteInThisHour"); - SUT.FirstMonthInThisYear.Should().Be(firstMonthInThisYear, "firstMonthInThisYear"); - SUT.FirstPeriodInThisDay.Should().Be(firstPeriodInThisDay, "firstPeriodInThisDay"); - SUT.FirstSecondInThisMinute.Should().Be(firstSecondInThisMinute, "firstSecondInThisMinute"); - SUT.FirstYearInThisEra.Should().Be(firstYearInThisEra, "firstYearInThisEra"); + SUT.Day.Should().Be(day, nameof(day)); + SUT.Month.Should().Be(month, nameof(month)); + SUT.Year.Should().Be(year, nameof(year)); + SUT.Hour.Should().Be(hours, nameof(hours)); + SUT.Minute.Should().Be(minutes, nameof(minutes)); + SUT.Second.Should().Be(seconds, nameof(seconds)); + SUT.Nanosecond.Should().Be(milliseconds * 1000, nameof(milliseconds)); + SUT.DayOfWeek.Should().Be(dayOfWeek, nameof(dayOfWeek)); + SUT.Era.Should().Be(era, nameof(era)); + SUT.FirstDayInThisMonth.Should().Be(firstDayInThisMonth, nameof(firstDayInThisMonth)); + SUT.FirstEra.Should().Be(firstEra, nameof(firstEra)); + SUT.FirstHourInThisPeriod.Should().Be(firstHourInThisPeriod, nameof(firstHourInThisPeriod)); + SUT.FirstMinuteInThisHour.Should().Be(firstMinuteInThisHour, nameof(firstMinuteInThisHour)); + SUT.FirstMonthInThisYear.Should().Be(firstMonthInThisYear, nameof(firstMonthInThisYear)); + SUT.FirstPeriodInThisDay.Should().Be(firstPeriodInThisDay, nameof(firstPeriodInThisDay)); + SUT.FirstSecondInThisMinute.Should().Be(firstSecondInThisMinute, nameof(firstSecondInThisMinute)); + SUT.FirstYearInThisEra.Should().Be(firstYearInThisEra, nameof(firstYearInThisEra)); SUT.Languages.Should().HaveCount(1, "languages count"); - SUT.Languages.Should().HaveElementAt(0, culture, "culture"); - SUT.LastDayInThisMonth.Should().Be(lastDayInThisMonth, "lastDayInThisMonth"); - SUT.LastEra.Should().Be(lastEra, "lastEra"); - SUT.LastHourInThisPeriod.Should().Be(lastHourInThisPeriod, "lastHourInThisPeriod"); - SUT.LastMinuteInThisHour.Should().Be(lastMinuteInThisHour, "lastMinuteInThisHour"); - SUT.LastMonthInThisYear.Should().Be(lastMonthInThisYear, "lastMonthInThisYear"); - SUT.LastPeriodInThisDay.Should().Be(lastPeriodInThisDay, "lastPeriodInThisDay"); - SUT.LastSecondInThisMinute.Should().Be(lastSecondInThisMinute, "lastSecondInThisMinute"); - SUT.LastYearInThisEra.Should().Be(lastYearInThisEra, "lastYearInThisEra"); - SUT.NumberOfDaysInThisMonth.Should().Be(numberOfDaysInThisMonth, "numberOfDaysInThisMonth"); - SUT.NumberOfEras.Should().Be(numberOfEras, "numberOfEras"); - SUT.NumberOfHoursInThisPeriod.Should().Be(numberOfHoursInThisPeriod, "numberOfHoursInThisPeriod"); - SUT.NumberOfMinutesInThisHour.Should().Be(numberOfMinutesInThisHour, "numberOfMinutesInThisHour"); - SUT.NumberOfMonthsInThisYear.Should().Be(numberOfMonthsInThisYear, "numberOfMonthsInThisYear"); - SUT.NumberOfPeriodsInThisDay.Should().Be(numberOfPeriodsInThisDay, "numberOfPeriodsInThisDay"); - SUT.NumberOfSecondsInThisMinute.Should().Be(numberOfSecondsInThisMinute, "numberOfSecondsInThisMinute"); - SUT.NumberOfYearsInThisEra.Should().Be(numberOfYearsInThisEra, "numberOfYearsInThisEra"); - SUT.Period.Should().Be(period, "period"); - SUT.ResolvedLanguage.Should().Be(culture, "culture"); + SUT.Languages.Should().HaveElementAt(0, culture, nameof(culture)); + SUT.LastDayInThisMonth.Should().Be(lastDayInThisMonth, nameof(lastDayInThisMonth)); + SUT.LastEra.Should().Be(lastEra, nameof(lastEra)); + SUT.LastHourInThisPeriod.Should().Be(lastHourInThisPeriod, nameof(lastHourInThisPeriod)); + SUT.LastMinuteInThisHour.Should().Be(lastMinuteInThisHour, nameof(lastMinuteInThisHour)); + SUT.LastMonthInThisYear.Should().Be(lastMonthInThisYear, nameof(lastMonthInThisYear)); + SUT.LastPeriodInThisDay.Should().Be(lastPeriodInThisDay, nameof(lastPeriodInThisDay)); + SUT.LastSecondInThisMinute.Should().Be(lastSecondInThisMinute, nameof(lastSecondInThisMinute)); + SUT.LastYearInThisEra.Should().Be(lastYearInThisEra, nameof(lastYearInThisEra)); + SUT.NumberOfDaysInThisMonth.Should().Be(numberOfDaysInThisMonth, nameof(numberOfDaysInThisMonth)); + SUT.NumberOfEras.Should().Be(numberOfEras, nameof(numberOfEras)); + SUT.NumberOfHoursInThisPeriod.Should().Be(numberOfHoursInThisPeriod, nameof(numberOfHoursInThisPeriod)); + SUT.NumberOfMinutesInThisHour.Should().Be(numberOfMinutesInThisHour, nameof(numberOfMinutesInThisHour)); + SUT.NumberOfMonthsInThisYear.Should().Be(numberOfMonthsInThisYear, nameof(numberOfMonthsInThisYear)); + SUT.NumberOfPeriodsInThisDay.Should().Be(numberOfPeriodsInThisDay, nameof(numberOfPeriodsInThisDay)); + SUT.NumberOfSecondsInThisMinute.Should().Be(numberOfSecondsInThisMinute, nameof(numberOfSecondsInThisMinute)); + SUT.NumberOfYearsInThisEra.Should().Be(numberOfYearsInThisEra, nameof(numberOfYearsInThisEra)); + SUT.Period.Should().Be(period, nameof(period)); + SUT.ResolvedLanguage.Should().Be(culture, nameof(culture)); // Validation is disabled as timezone support is only using the current machine's timezone // SUT.IsDaylightSavingTime.Should().Be(isDaylightSavingTime, "isDaylightSavingTime"); @@ -675,24 +675,24 @@ string dayOfWeekAsString using (new AssertionScope("Calendar Format")) { - SUT.YearAsPaddedString(2).Should().Be(yearAsPaddedString, "yearAsPaddedString"); - SUT.YearAsString().Should().Be(yearAsString, "yearAsString"); - SUT.MonthAsPaddedNumericString(2).Should().Be(monthAsPaddedNumericString, "monthAsPaddedNumericString"); - SUT.MonthAsSoloString().Should().Be(monthAsSoloString, "monthAsSoloString"); - SUT.MonthAsString().Should().Be(monthAsString, "monthAsString"); - SUT.MonthAsNumericString().Should().Be(monthAsNumericString, "monthAsNumericString"); - SUT.DayAsPaddedString(2).Should().Be(dayAsPaddedString, "dayAsPaddedString"); - SUT.DayAsString().Should().Be(dayAsString, "dayAsString"); - SUT.HourAsPaddedString(2).Should().Be(hourAsPaddedString, "hourAsPaddedString"); - SUT.HourAsString().Should().Be(hourAsString, "hourAsString"); - SUT.MinuteAsPaddedString(2).Should().Be(minuteAsPaddedString, "minuteAsPaddedString"); - SUT.MinuteAsString().Should().Be(minuteAsString, "minuteAsString"); - SUT.SecondAsPaddedString(2).Should().Be(secondAsPaddedString, "secondAsPaddedString"); - SUT.SecondAsString().Should().Be(secondAsString, "secondAsString"); - SUT.NanosecondAsPaddedString(2).Should().Be(nanosecondAsPaddedString, "nanosecondAsPaddedString"); - SUT.NanosecondAsString().Should().Be(nanosecondAsString, "nanosecondAsString"); - SUT.DayOfWeekAsSoloString().Should().Be(dayOfWeekAsSoloString, "dayOfWeekAsSoloString"); - SUT.DayOfWeekAsString().Should().Be(dayOfWeekAsString, "dayOfWeekAsString"); + SUT.YearAsPaddedString(2).Should().Be(yearAsPaddedString, nameof(yearAsPaddedString)); + SUT.YearAsString().Should().Be(yearAsString, nameof(yearAsString)); + SUT.MonthAsPaddedNumericString(2).Should().Be(monthAsPaddedNumericString, nameof(monthAsPaddedNumericString)); + SUT.MonthAsSoloString().Should().Be(monthAsSoloString, nameof(monthAsSoloString)); + SUT.MonthAsString().Should().Be(monthAsString, nameof(monthAsString)); + SUT.MonthAsNumericString().Should().Be(monthAsNumericString, nameof(monthAsNumericString)); + SUT.DayAsPaddedString(2).Should().Be(dayAsPaddedString, nameof(dayAsPaddedString)); + SUT.DayAsString().Should().Be(dayAsString, nameof(dayAsString)); + SUT.HourAsPaddedString(2).Should().Be(hourAsPaddedString, nameof(hourAsPaddedString)); + SUT.HourAsString().Should().Be(hourAsString, nameof(hourAsString)); + SUT.MinuteAsPaddedString(2).Should().Be(minuteAsPaddedString, nameof(minuteAsPaddedString)); + SUT.MinuteAsString().Should().Be(minuteAsString, nameof(minuteAsString)); + SUT.SecondAsPaddedString(2).Should().Be(secondAsPaddedString, nameof(secondAsPaddedString)); + SUT.SecondAsString().Should().Be(secondAsString, nameof(secondAsString)); + SUT.NanosecondAsPaddedString(2).Should().Be(nanosecondAsPaddedString, nameof(nanosecondAsPaddedString)); + SUT.NanosecondAsString().Should().Be(nanosecondAsString, nameof(nanosecondAsString)); + SUT.DayOfWeekAsSoloString().Should().Be(dayOfWeekAsSoloString, nameof(dayOfWeekAsSoloString)); + SUT.DayOfWeekAsString().Should().Be(dayOfWeekAsString, nameof(dayOfWeekAsString)); } } } diff --git a/src/Uno.UWP/Globalization/Calendar.cs b/src/Uno.UWP/Globalization/Calendar.cs index 701afdb4d789..3a36b1e9cd26 100644 --- a/src/Uno.UWP/Globalization/Calendar.cs +++ b/src/Uno.UWP/Globalization/Calendar.cs @@ -89,7 +89,7 @@ private static string GetClock(string clock) public Calendar() { - _languages = new string[1] { CultureInfo.CurrentCulture.IetfLanguageTag }; + _languages = ApplicationLanguages.Languages; _resolvedCulture = CultureInfo.CurrentCulture; _calendar = CultureInfo.CurrentCulture.Calendar; _timeZone = TimeZoneInfo.Local; diff --git a/src/Uno.UWP/Globalization/DateTimeFormatting/DateTimeFormatter.cs b/src/Uno.UWP/Globalization/DateTimeFormatting/DateTimeFormatter.cs index 348baa1562eb..3904c360544f 100644 --- a/src/Uno.UWP/Globalization/DateTimeFormatting/DateTimeFormatter.cs +++ b/src/Uno.UWP/Globalization/DateTimeFormatting/DateTimeFormatter.cs @@ -1,20 +1,18 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; +using System.Security.Principal; +using System.Text.RegularExpressions; +using Uno.Extensions; namespace Windows.Globalization.DateTimeFormatting { public sealed partial class DateTimeFormatter { - private static readonly IReadOnlyList _defaultPatterns = - new List - { - "‎{month.full}‎ ‎{day.integer}‎, ‎{year.full}", - "‎{day.integer}‎ ‎{month.full}‎, ‎{year.full}", - }.AsReadOnly(); + private static readonly IReadOnlyList _defaultPatterns; - private static readonly IReadOnlyList _emptyLanguages = - new List(0).AsReadOnly(); + private static readonly IReadOnlyList _emptyLanguages; public string NumeralSystem { get; set; } @@ -36,7 +34,7 @@ public sealed partial class DateTimeFormatter public YearFormat IncludeYear { get; } - public IReadOnlyList Languages { get; } = _emptyLanguages; + public IReadOnlyList Languages { get; } = ApplicationLanguages.Languages; public string Calendar { get; } @@ -48,13 +46,32 @@ public sealed partial class DateTimeFormatter public string Template { get; } - public static DateTimeFormatter LongDate { get; } = new DateTimeFormatter("longdate"); + public static DateTimeFormatter LongDate { get; } + + public static DateTimeFormatter LongTime { get; } + + public static DateTimeFormatter ShortDate { get; } + + public static DateTimeFormatter ShortTime { get; } + + static DateTimeFormatter() + { + _map_cache = new Dictionary>(); + _patterns_cache = new Dictionary<(string language, string template), string>(); - public static DateTimeFormatter LongTime { get; } = new DateTimeFormatter("longtime"); + _defaultPatterns = new [] + { + "{month.full}‎ ‎{day.integer}‎, ‎{year.full}", + "{day.integer}‎ ‎{month.full}‎, ‎{year.full}", + }; - public static DateTimeFormatter ShortDate { get; } = new DateTimeFormatter("shortdate"); + _emptyLanguages = new string[0]; - public static DateTimeFormatter ShortTime { get; } = new DateTimeFormatter("shorttime"); + LongDate = new DateTimeFormatter("longdate"); + LongTime = new DateTimeFormatter("longtime"); + ShortDate = new DateTimeFormatter("shortdate"); + ShortTime = new DateTimeFormatter("shorttime"); + } public DateTimeFormatter(string formatTemplate) :this(formatTemplate, languages: null) @@ -66,12 +83,40 @@ public DateTimeFormatter( IEnumerable languages) { Template = formatTemplate ?? throw new ArgumentNullException(nameof(formatTemplate)); - if (languages != null) + + var languagesArray = languages?.Distinct().ToArray(); + + if (languagesArray == null || languagesArray.Length == 0) + { + var currentUiLanguage = CultureInfo.CurrentUICulture.Name; + var currentLanguage = CultureInfo.CurrentCulture.Name; + + if (currentUiLanguage != currentLanguage) + { + Languages = languagesArray = new[] + { + currentUiLanguage, + currentLanguage + }; + } + else + { + Languages = languagesArray = new[] + { + currentUiLanguage + }; + } + } + else { - Languages = new List(languages).AsReadOnly(); + Languages = languagesArray; } - BuildLookup(); + _firstCulture = new CultureInfo(languagesArray[0]); + + _maps = languagesArray.SelectToArray(BuildLookup); + + Patterns = BuildPatterns().ToArray(); } public DateTimeFormatter( @@ -122,28 +167,27 @@ public DateTimeFormatter(YearFormat yearFormat, MonthFormat monthFormat, DayForm global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.Globalization.DateTimeFormatting.DateTimeFormatter", "DateTimeFormatter.DateTimeFormatter(YearFormat yearFormat, MonthFormat monthFormat, DayFormat dayFormat, DayOfWeekFormat dayOfWeekFormat, HourFormat hourFormat, MinuteFormat minuteFormat, SecondFormat secondFormat, IEnumerable languages, string geographicRegion, string calendar, string clock)"); } - private static void BuildLookup() + private IDictionary BuildLookup(string language) { - _info = DateTimeFormatInfo.CurrentInfo; - if (_info == _static_info) + if(_map_cache.TryGetValue(language, out var map)) { - _map = _static_map; - return; + return map; } - _static_info = _info; + var info = new CultureInfo(language).DateTimeFormat; - _map = _static_map = new Dictionary + map = new Dictionary { - { "longdate" , _info.LongDatePattern } , - { "shortdate" , _info.ShortDatePattern } , - { "longtime" , _info.LongTimePattern } , - { "shorttime" , _info.ShortTimePattern } , - { "dayofweek day month year" , "D" } , + { "longdate" , info.LongDatePattern } , + { "shortdate" , info.ShortDatePattern } , + { "longtime" , info.LongTimePattern } , + { "shorttime" , info.ShortTimePattern } , + { "dayofweek day month year" , info.FullDateTimePattern } , { "dayofweek day month" , "D" } , - { "day month year" , "MMMM dd, yyyy" } , - { "day month" , "MMMM dd" } , - { "month year" , "MMMM yyyy" } , + { "day month year" , info.ShortDatePattern } , + { "day month.full year" , info.ShortDatePattern } , + { "day month" , info.MonthDayPattern } , + { "month year" , info.YearMonthPattern } , { "dayofweek.full" , "dddd" } , { "dayofweek.abbreviated" , "ddd" } , { "month.full" , "MMMM" } , @@ -151,20 +195,22 @@ private static void BuildLookup() { "month.numeric" , "%M" } , { "year.abbreviated" , "yy" } , { "year.full" , "yyyy" } , - { "hour minute second" , "T" }, - { "hour minute" , "t" }, + { "hour minute second" , info.LongTimePattern }, + { "hour minute" , info.ShortTimePattern }, { "timezone.abbreviated" , "zz" }, { "timezone.full" , "zzz" }, { "dayofweek" , "dddd" } , { "day" , "%d" } , { "month" , "MMMM" } , { "year" , "yyyy" } , - { "hour" , "h tt" } , + { "hour" , "H tt" } , { "minute" , "m" }, { "second" , "s" }, { "timezone" , "%z" }, - // { "year month day hour" , "" } , - }; + // { "year month day hour" , "" } , + }; + + return _map_cache[language] = map; } public string Format(DateTimeOffset value) @@ -172,7 +218,7 @@ public string Format(DateTimeOffset value) var format = GetSystemTemplate(); try { - return value.ToString(format, _info); + return value.ToString(format, _firstCulture.DateTimeFormat); } catch (Exception e) { @@ -180,22 +226,82 @@ public string Format(DateTimeOffset value) } } - private static DateTimeFormatInfo _static_info; - private static Dictionary _static_map; + private static readonly IDictionary> _map_cache; + private static readonly IDictionary<(string language, string template), string> _patterns_cache; - private static DateTimeFormatInfo _info; - private static Dictionary _map; + private readonly CultureInfo _firstCulture; + + private readonly IDictionary[] _maps; private string GetSystemTemplate() { var result = Template.Replace("{", "").Replace("}", ""); - foreach (var p in _map) + var map = _maps[0]; + + foreach (var p in map) { result = result.Replace(p.Key, p.Value); } return result; } + + private static readonly (Regex pattern, string replacement)[] PatternsReplacements = + new (string pattern, string replacement)[] + { + (@"\bMMMM\b", "{month.full}"), + (@"\bMMM\b", "{month.abbreviated}"), + (@"\bMM\b", "{month.numeric}"), + (@"%M\b", "{month.numeric}"), + (@"\bM\b", "{month.numeric}"), + (@"\bdddd\b", "{dayofweek.full}"), + (@"\bddd\b", "{dayofweek.abbreviated}"), + (@"\byyyy\b", "{year.full}"), + (@"\byy\b", "{year.abbreviated}"), + (@"\b(z|zz)\b", "{timezone.abbreviated}"), + (@"\byyyy\b", "{year.full}"), + (@"\bMMMM\b", "{month.full}"), + (@"\bdd\b", "{day.integer(2)}"), + (@"%d\b", "{day.integer}"), + (@"\bd\b", "{day.integer}"), + (@"\bzzz\b", "{timezone.full}"), + (@"\bzz\b", "{timezone.abbreviated}"), + (@"%z\b", "{timezone}"), + (@"\b(HH|hh|H|h)\b", "{hour}"), + (@"\b(mm|m)\b", "{minute}"), + (@"\b(ss|s)\b", "{second}"), + (@"\btt\b", "{period.abbreviated}"), + } + .SelectToArray(x => + (new Regex(x.pattern, RegexOptions.CultureInvariant | RegexOptions.Compiled), + x.replacement)); + + private IEnumerable BuildPatterns() + { + var format = Template; + + for (var i = 0; i < Languages.Count; i++) + { + var language = Languages[i]; + if (_patterns_cache.TryGetValue((language, format), out var pattern)) + { + yield return pattern; + } + else + { + var map = _maps[i]; + if (map.TryGetValue(format, out var r)) + { + foreach (var p in PatternsReplacements) + { + r = p.pattern.Replace(r, p.replacement); + } + + yield return _patterns_cache[(language, format)] = r; + } + } + } + } } }