From 43af10e99663f3619b71f859556f04de31636c68 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sun, 5 Jan 2025 08:35:46 +1100 Subject: [PATCH] Fix Date format length checks (#1379) --- docs/dates.md | 18 +- docs/guids.md | 10 +- docs/members-throw.md | 8 +- docs/named-tuples.md | 4 +- docs/obsolete-members.md | 6 +- docs/scrubbers.md | 12 +- docs/serializer-settings.md | 46 +- src/Verify.Tests/CultureToDateBuilder.cs | 128 -- .../DateFormatLengthCalculatorTests.cs | 100 + ...ScrubberTests.GetCultureDates.verified.txt | 48 +- src/Verify.Tests/DateScrubberTests.cs | 32 +- ...Tests.ScrubInlineDateTimesUsG.verified.txt | 1 + ...ubInlineDateTimesWithLongAmPm.verified.txt | 1 + .../Serialization/SerializationTests.cs | 18 + .../Serialization/Scrubbers/CultureDate.cs | 34 +- .../Scrubbers/DateFormatExpander.cs | 31 + .../Scrubbers/DateFormatLengthCalculator.cs | 365 ++++ .../Serialization/Scrubbers/DateScrubber.cs | 40 +- .../Scrubbers/DateScrubber_Generated.cs | 1711 ----------------- 19 files changed, 675 insertions(+), 1938 deletions(-) delete mode 100644 src/Verify.Tests/CultureToDateBuilder.cs create mode 100644 src/Verify.Tests/DateFormatLengthCalculatorTests.cs create mode 100644 src/Verify.Tests/Serialization/SerializationTests.ScrubInlineDateTimesUsG.verified.txt create mode 100644 src/Verify.Tests/Serialization/SerializationTests.ScrubInlineDateTimesWithLongAmPm.verified.txt create mode 100644 src/Verify/Serialization/Scrubbers/DateFormatExpander.cs create mode 100644 src/Verify/Serialization/Scrubbers/DateFormatLengthCalculator.cs delete mode 100644 src/Verify/Serialization/Scrubbers/DateScrubber_Generated.cs diff --git a/docs/dates.md b/docs/dates.md index 2c13c668ed..57db9686a5 100644 --- a/docs/dates.md +++ b/docs/dates.md @@ -70,7 +70,7 @@ settings.DontScrubDateTimes(); return Verify(target, settings); ``` -snippet source | anchor +snippet source | anchor @@ -87,7 +87,7 @@ var target = new return Verify(target) .DontScrubDateTimes(); ``` -snippet source | anchor +snippet source | anchor @@ -100,7 +100,7 @@ return Verify(target) public static void ModuleInitializer() => VerifierSettings.DontScrubDateTimes(); ``` -snippet source | anchor +snippet source | anchor @@ -124,7 +124,7 @@ settings.DisableDateCounting(); return Verify(target, settings); ``` -snippet source | anchor +snippet source | anchor @@ -141,7 +141,7 @@ var target = new return Verify(target) .DisableDateCounting(); ``` -snippet source | anchor +snippet source | anchor @@ -154,7 +154,7 @@ return Verify(target) public static void ModuleInitializer() => VerifierSettings.DisableDateCounting(); ``` -snippet source | anchor +snippet source | anchor @@ -201,7 +201,7 @@ public Task ScrubInlineDateTimesInstance() settings); } ``` -snippet source | anchor +snippet source | anchor @@ -215,7 +215,7 @@ public Task ScrubInlineDateTimesFluent() => Verify("content 2020-10-20 content") .ScrubInlineDateTimes("yyyy-MM-dd"); ``` -snippet source | anchor +snippet source | anchor @@ -231,7 +231,7 @@ public static class ModuleInitializer VerifierSettings.ScrubInlineDateTimes("yyyy-MM-dd"); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/guids.md b/docs/guids.md index 5fc1e008bc..7cb85719c8 100644 --- a/docs/guids.md +++ b/docs/guids.md @@ -23,7 +23,7 @@ var target = new GuidTarget await Verify(target); ``` -snippet source | anchor +snippet source | anchor Results in the following: @@ -79,7 +79,7 @@ await Verify(target) ```cs VerifierSettings.DontScrubGuids(); ``` -snippet source | anchor +snippet source | anchor @@ -103,7 +103,7 @@ public Task ScrubInlineGuidsInstance() settings); } ``` -snippet source | anchor +snippet source | anchor @@ -117,7 +117,7 @@ public Task ScrubInlineGuidsFluent() => Verify("content 651ad409-fc30-4b12-a47e-616d3f953e4c content") .ScrubInlineGuids(); ``` -snippet source | anchor +snippet source | anchor @@ -133,7 +133,7 @@ public static class ModuleInitializer VerifierSettings.ScrubInlineGuids(); } ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/members-throw.md b/docs/members-throw.md index cd8732cf7a..72f9e74352 100644 --- a/docs/members-throw.md +++ b/docs/members-throw.md @@ -35,7 +35,7 @@ public Task CustomExceptionPropFluent() .IgnoreMembersThatThrow(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -45,7 +45,7 @@ Or globally: ```cs VerifierSettings.IgnoreMembersThatThrow(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -82,7 +82,7 @@ public Task ExceptionMessagePropFluent() .IgnoreMembersThatThrow(_ => _.Message == "Ignore"); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -92,7 +92,7 @@ Or globally: ```cs VerifierSettings.IgnoreMembersThatThrow(_ => _.Message == "Ignore"); ``` -snippet source | anchor +snippet source | anchor Result: diff --git a/docs/named-tuples.md b/docs/named-tuples.md index c81efe638e..b57a17d806 100644 --- a/docs/named-tuples.md +++ b/docs/named-tuples.md @@ -19,7 +19,7 @@ Given a method that returns a named tuple: static (bool Member1, string Member2, string Member3) MethodWithNamedTuple() => (true, "A", "B"); ``` -snippet source | anchor +snippet source | anchor Can be verified: @@ -29,7 +29,7 @@ Can be verified: ```cs await VerifyTuple(() => MethodWithNamedTuple()); ``` -snippet source | anchor +snippet source | anchor Resulting in: diff --git a/docs/obsolete-members.md b/docs/obsolete-members.md index c0b96fbf7e..182142c41b 100644 --- a/docs/obsolete-members.md +++ b/docs/obsolete-members.md @@ -31,7 +31,7 @@ public Task WithObsoleteProp() return Verify(target); } ``` -snippet source | anchor +snippet source | anchor Result: @@ -79,7 +79,7 @@ public Task WithObsoletePropIncludedFluent() .IncludeObsoletes(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -89,7 +89,7 @@ Or globally: ```cs VerifierSettings.IncludeObsoletes(); ``` -snippet source | anchor +snippet source | anchor Result: diff --git a/docs/scrubbers.md b/docs/scrubbers.md index 2f9145714f..1652067dc3 100644 --- a/docs/scrubbers.md +++ b/docs/scrubbers.md @@ -59,7 +59,7 @@ For example remove lines containing `text`: ```cs verifySettings.ScrubLines(line => line.Contains("text")); ``` -snippet source | anchor +snippet source | anchor @@ -74,7 +74,7 @@ For example remove lines containing `text1` or `text2` ```cs verifySettings.ScrubLinesContaining("text1", "text2"); ``` -snippet source | anchor +snippet source | anchor Case insensitive by default (StringComparison.OrdinalIgnoreCase). @@ -86,7 +86,7 @@ Case insensitive by default (StringComparison.OrdinalIgnoreCase). ```cs verifySettings.ScrubLinesContaining(StringComparison.Ordinal, "text1", "text2"); ``` -snippet source | anchor +snippet source | anchor @@ -101,7 +101,7 @@ For example converts lines to upper case: ```cs verifySettings.ScrubLinesWithReplace(line => line.ToUpper()); ``` -snippet source | anchor +snippet source | anchor @@ -114,7 +114,7 @@ Replaces `Environment.MachineName` with `TheMachineName`. ```cs verifySettings.ScrubMachineName(); ``` -snippet source | anchor +snippet source | anchor @@ -127,7 +127,7 @@ Replaces `Environment.UserName` with `TheUserName`. ```cs verifySettings.ScrubUserName(); ``` -snippet source | anchor +snippet source | anchor diff --git a/docs/serializer-settings.md b/docs/serializer-settings.md index bf76b5786b..91bc398bea 100644 --- a/docs/serializer-settings.md +++ b/docs/serializer-settings.md @@ -204,7 +204,7 @@ To disable this behavior globally use: ```cs VerifierSettings.DontIgnoreEmptyCollections(); ``` -snippet source | anchor +snippet source | anchor @@ -305,7 +305,7 @@ public Task ScopedSerializerFluent() .AddExtraSettings(_ => _.TypeNameHandling = TypeNameHandling.All); } ``` -snippet source | anchor +snippet source | anchor Result: @@ -433,7 +433,7 @@ public Task IgnoreTypeFluent() .IgnoreMembersWithType(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -443,7 +443,7 @@ Or globally: ```cs VerifierSettings.IgnoreMembersWithType(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -580,7 +580,7 @@ public Task ScrubTypeFluent() .ScrubMembersWithType(); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -590,7 +590,7 @@ Or globally: ```cs VerifierSettings.ScrubMembersWithType(); ``` -snippet source | anchor +snippet source | anchor Result: @@ -669,7 +669,7 @@ public Task AddIgnoreInstanceFluent() .IgnoreInstance(_ => _.Property == "Ignore"); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -679,7 +679,7 @@ Or globally: ```cs VerifierSettings.IgnoreInstance(_ => _.Property == "Ignore"); ``` -snippet source | anchor +snippet source | anchor Result: @@ -741,7 +741,7 @@ public Task AddScrubInstanceFluent() .ScrubInstance(_ => _.Property == "Ignore"); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -751,7 +751,7 @@ Or globally: ```cs VerifierSettings.ScrubInstance(_ => _.Property == "Ignore"); ``` -snippet source | anchor +snippet source | anchor Result: @@ -814,7 +814,7 @@ public Task IgnoreMemberByExpressionFluent() _ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally @@ -829,7 +829,7 @@ VerifierSettings.IgnoreMembers( _ => _.GetOnlyProperty, _ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -889,7 +889,7 @@ public Task ScrubMemberByExpressionFluent() _ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally @@ -904,7 +904,7 @@ VerifierSettings.ScrubMembers( _ => _.GetOnlyProperty, _ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -983,7 +983,7 @@ public Task IgnoreMemberByNameFluent() .IgnoreMember(_ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1003,7 +1003,7 @@ VerifierSettings.IgnoreMember("Field"); // For a specific type with expression VerifierSettings.IgnoreMember(_ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1078,7 +1078,7 @@ public Task ScrubMemberByNameFluent() .ScrubMember(_ => _.PropertyThatThrows); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1098,7 +1098,7 @@ VerifierSettings.ScrubMember("Field"); // For a specific type with expression VerifierSettings.ScrubMember(_ => _.PropertyThatThrows); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1191,7 +1191,7 @@ public Task IgnoreDictionaryByPredicate() return Verify(target, settings); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1203,7 +1203,7 @@ VerifierSettings.IgnoreMembers( _=>_.DeclaringType == typeof(TargetClass) && _.Name == "Proprty"); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1292,7 +1292,7 @@ public Task ScrubDictionaryByPredicate() return Verify(target, settings); } ``` -snippet source | anchor +snippet source | anchor Or globally: @@ -1304,7 +1304,7 @@ VerifierSettings.ScrubMembers( _=>_.DeclaringType == typeof(TargetClass) && _.Name == "Proprty"); ``` -snippet source | anchor +snippet source | anchor Result: @@ -1469,7 +1469,7 @@ public Task MemberConverterByExpression() return Verify(input); } ``` -snippet source | anchor +snippet source | anchor diff --git a/src/Verify.Tests/CultureToDateBuilder.cs b/src/Verify.Tests/CultureToDateBuilder.cs deleted file mode 100644 index f0800a3aa5..0000000000 --- a/src/Verify.Tests/CultureToDateBuilder.cs +++ /dev/null @@ -1,128 +0,0 @@ -#if NET9_0 -public class CultureToDateBuilder -{ - const string monthDayFormat = "MMMM MMM dddd ddd"; - - [Fact] - public Task BuildCultureToDate() - { - if (Environment.OSVersion.Version.Build < 22000) - { - return Task.CompletedTask; - } - var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures); - var builder = new StringBuilder( - """ - // - static partial class DateScrubber - { - static IReadOnlyDictionary cultureDates = new Dictionary() - { - - """); - foreach (var culture in cultures) - { - var (longDate, shortDate) = FindDates(culture); - if (culture.Name.Contains('-')) - { - var (parentLongDate, parentShortDate) = FindDates(culture.Parent); - if (longDate == parentLongDate && shortDate == parentShortDate) - { - continue; - } - } - builder.AppendLine( - $$""" - { - "{{culture.Name}}", - new( - new(2023, {{longDate.Month}}, {{longDate.Day}}, {{longDate.Hour}}, 10, 10, 10), - new(2023, {{shortDate.Month}}, {{shortDate.Day}}, {{shortDate.Hour}}, 0, 0)) - }, - """); - } - - builder.AppendLine( - """ - }.ToFrozenDictionary(); - } - """); - var file = Path.Combine(AttributeReader.GetSolutionDirectory(), "Verify/Serialization/Scrubbers/DateScrubber_Generated.cs"); - File.Delete(file); - return File.WriteAllTextAsync(file, builder.ToString()); - } - - static (DateTime longDate, DateTime shortDate) FindDates(CultureInfo culture) - { - var formatInfo = culture.DateTimeFormat; - var longDate = FindLongDate(formatInfo); - var shortDate = FindShortDate(formatInfo); - return (longDate, shortDate); - } - - static DateTime FindLongDate(DateTimeFormatInfo formatInfo) - { - DateTime longDate = default; - var longFormatted = ""; - var amLength = formatInfo.AMDesignator.Length; - var pmLength = formatInfo.PMDesignator.Length; - for (var month = 1; month <= 12; month++) - { - for (var day = 20; day <= 27; day++) - { - DateTime date; - if (amLength > pmLength) - { - date = new(2023, month, day, 1, 0, 0, 0); - } - else - { - date = new(2023, month, day, 12, 0, 0, 0); - } - - var formatted = date.ToString(monthDayFormat, formatInfo); - if (formatted.Length > longFormatted.Length) - { - longFormatted = formatted; - longDate = date; - } - } - } - - return longDate; - } - - static DateTime FindShortDate(DateTimeFormatInfo formatInfo) - { - DateTime shortDate = default; - string? shortFormatted = null; - var amLength = formatInfo.AMDesignator.Length; - var pmLength = formatInfo.PMDesignator.Length; - for (var month = 1; month <= 12; month++) - { - for (var day = 1; day <= 7; day++) - { - DateTime date; - if (amLength <= pmLength) - { - date = new(2023, month, day, 1, 0, 0, 0); - } - else - { - date = new(2023, month, day, 13, 0, 0, 0); - } - - var formatted = date.ToString(monthDayFormat, formatInfo); - if (shortFormatted == null || - formatted.Length < shortFormatted.Length) - { - shortFormatted = formatted; - shortDate = date; - } - } - } - - return shortDate; - } -} -#endif \ No newline at end of file diff --git a/src/Verify.Tests/DateFormatLengthCalculatorTests.cs b/src/Verify.Tests/DateFormatLengthCalculatorTests.cs new file mode 100644 index 0000000000..2a82c19868 --- /dev/null +++ b/src/Verify.Tests/DateFormatLengthCalculatorTests.cs @@ -0,0 +1,100 @@ +public class DateFormatLengthCalculatorTests +{ + [Theory] + [InlineData("y", 4, 1)] + [InlineData("%y", 4, 1)] + [InlineData("yy", 4, 2)] + [InlineData("yyy", 4, 3)] + [InlineData("yyyy", 4, 4)] + [InlineData("yyyyy", 5, 5)] + [InlineData("M", 2, 1)] + [InlineData("MM", 2, 2)] + [InlineData("MMM", 3, 3)] + [InlineData("MMMM", 9, 3)] + [InlineData("MMMMM", 9, 3)] + [InlineData("d", 2, 1)] + [InlineData("dd", 2, 2)] + [InlineData("ddd", 3, 3)] + [InlineData("dddd", 9, 6)] + [InlineData("ddddd", 9, 6)] + [InlineData("h", 2, 1)] + [InlineData("hh", 2, 2)] + [InlineData("hhh", 2, 2)] + [InlineData("m", 2, 1)] + [InlineData("mm", 2, 2)] + [InlineData("mmm", 2, 2)] + [InlineData("s", 2, 1)] + [InlineData("ss", 2, 2)] + [InlineData("sss", 2, 2)] + [InlineData("f", 1, 1)] + [InlineData("ff", 2, 2)] + [InlineData("fff", 3, 3)] + [InlineData("ffff", 4, 4)] + [InlineData("fffff", 5, 5)] + [InlineData("ffffff", 6, 6)] + [InlineData("fffffff", 7, 7)] + [InlineData("g", 4, 4)] + [InlineData("gg", 4, 4)] + [InlineData("ggg", 4, 4)] + [InlineData("t", 1, 1)] + [InlineData("tt", 2, 2)] + [InlineData("ttt", 2, 2)] + [InlineData("z", 3, 2)] + [InlineData("zz", 3, 3)] + [InlineData("zzz", 6, 6)] + [InlineData("zzzz", 6, 6)] + [InlineData("K", 6, 6)] + [InlineData("KK", 12, 12)] + [InlineData(":", 1, 1)] + [InlineData("':'", 1, 1)] + [InlineData("/", 1, 1)] + [InlineData("'/'", 1, 1)] + [InlineData("yyyy-MM-dd", 10, 10)] + [InlineData("yyyy/MM/dd", 10, 10)] + [InlineData("yyyy'/'MM'/'dd", 10, 10)] + public void Combos(string format, int max, int min) + { + var culture = CultureInfo.InvariantCulture; + + var length = DateFormatLengthCalculator.InnerGetLength(format.AsSpan(), culture); + Assert.Equal(max, length.max); + Assert.Equal(min, length.min); + + if (format.Length > 1) + { + var result = DateTime.Now.ToString(format, culture); + Assert.True(result.Length <= max, $"{result.Length} <= {max}"); + Assert.True(result.Length >= min, $"{result.Length} >= {min}"); + } + + var padded = $" {format} "; + length = DateFormatLengthCalculator.InnerGetLength(padded.AsSpan(), culture); + Assert.Equal(max + 2, length.max); + Assert.Equal(min + 2, length.min); + + var prefixed = $" {format}"; + length = DateFormatLengthCalculator.InnerGetLength(prefixed.AsSpan(), culture); + Assert.Equal(max + 1, length.max); + Assert.Equal(min + 1, length.min); + + var suffixed = $"{format} "; + length = DateFormatLengthCalculator.InnerGetLength(suffixed.AsSpan(), culture); + Assert.Equal(max + 1, length.max); + Assert.Equal(min + 1, length.min); + + var escapedPrefixed = $@"\d{format}"; + length = DateFormatLengthCalculator.InnerGetLength(escapedPrefixed.AsSpan(), culture); + Assert.Equal(max + 1, length.max); + Assert.Equal(min + 1, length.min); + + var escapedSuffixed = $@"{format}\d"; + length = DateFormatLengthCalculator.InnerGetLength(escapedSuffixed.AsSpan(), culture); + Assert.Equal(max + 1, length.max); + Assert.Equal(min + 1, length.min); + + var escapedWrapped = $@"\d{format}\d"; + length = DateFormatLengthCalculator.InnerGetLength(escapedWrapped.AsSpan(), culture); + Assert.Equal(max + 2, length.max); + Assert.Equal(min + 2, length.min); + } +} \ No newline at end of file diff --git a/src/Verify.Tests/DateScrubberTests.GetCultureDates.verified.txt b/src/Verify.Tests/DateScrubberTests.GetCultureDates.verified.txt index 150c697919..9deaadd878 100644 --- a/src/Verify.Tests/DateScrubberTests.GetCultureDates.verified.txt +++ b/src/Verify.Tests/DateScrubberTests.GetCultureDates.verified.txt @@ -1,14 +1,50 @@ { invarient: { - Long: DateTime_1, - Short: DateTime_2 + AmPmLong: 2, + AmPmShort: 2, + MonthNameLong: 9, + MonthNameShort: 3, + AbbreviatedMonthNameLong: 3, + AbbreviatedMonthNameShort: 3, + DayNameLong: 9, + DayNameShort: 6, + AbbreviatedDayNameLong: 3, + AbbreviatedDayNameShort: 3, + DateSeparator: 1, + TimeSeparator: 1, + EraLong: 4, + EraShort: 4 }, parent: { - Long: DateTime_3, - Short: DateTime_2 + AmPmLong: 2, + AmPmShort: 2, + MonthNameLong: 9, + MonthNameShort: 3, + AbbreviatedMonthNameLong: 3, + AbbreviatedMonthNameShort: 3, + DayNameLong: 10, + DayNameShort: 6, + AbbreviatedDayNameLong: 2, + AbbreviatedDayNameShort: 2, + DateSeparator: 1, + TimeSeparator: 1, + EraLong: 7, + EraShort: 7 }, child: { - Long: DateTime_3, - Short: DateTime_2 + AmPmLong: 2, + AmPmShort: 2, + MonthNameLong: 9, + MonthNameShort: 3, + AbbreviatedMonthNameLong: 3, + AbbreviatedMonthNameShort: 3, + DayNameLong: 10, + DayNameShort: 6, + AbbreviatedDayNameLong: 2, + AbbreviatedDayNameShort: 2, + DateSeparator: 1, + TimeSeparator: 1, + EraLong: 7, + EraShort: 7 } } \ No newline at end of file diff --git a/src/Verify.Tests/DateScrubberTests.cs b/src/Verify.Tests/DateScrubberTests.cs index 0d1511b67e..3e4e27e8cf 100644 --- a/src/Verify.Tests/DateScrubberTests.cs +++ b/src/Verify.Tests/DateScrubberTests.cs @@ -20,9 +20,9 @@ public Task GetCultureDates() => Verify( new { - invarient = DateScrubber.GetCultureDates(CultureInfo.InvariantCulture), - parent = DateScrubber.GetCultureDates(CultureInfo.GetCultureInfo("de")), - child = DateScrubber.GetCultureDates(CultureInfo.GetCultureInfo("de-DE")) + invarient = DateFormatLengthCalculator.GetCultureLengthInfo(CultureInfo.InvariantCulture), + parent = DateFormatLengthCalculator.GetCultureLengthInfo(CultureInfo.GetCultureInfo("de")), + child = DateFormatLengthCalculator.GetCultureLengthInfo(CultureInfo.GetCultureInfo("de-DE")) }); [Theory] @@ -112,6 +112,32 @@ await Verify(builder) } } + [Fact] + public void ReplaceDateTimes_AllCultures() + { + var format = "yyyy MMMM MMM MM dddd ddd dd d HH H mm m ss s fffff tt"; + foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures)) + { + var counter = Counter.Start(); + var dateTime = DateTime.Now; + var value = dateTime.ToString(format, culture); + var builder = new StringBuilder(value); + DateScrubber.ReplaceDateTimes(builder, format, counter, culture); + var result = builder.ToString(); + if (result == "DateTime_1") + { + continue; + } + + throw new( + $""" + {culture.DisplayName} {culture.Name} + {result} + {value} + """); + } + } + [Theory] [InlineData("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "no match")] [InlineData("aaaa", "no match short")] diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubInlineDateTimesUsG.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubInlineDateTimesUsG.verified.txt new file mode 100644 index 0000000000..6114d03610 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubInlineDateTimesUsG.verified.txt @@ -0,0 +1 @@ +DateTime_1 \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubInlineDateTimesWithLongAmPm.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubInlineDateTimesWithLongAmPm.verified.txt new file mode 100644 index 0000000000..6114d03610 --- /dev/null +++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubInlineDateTimesWithLongAmPm.verified.txt @@ -0,0 +1 @@ +DateTime_1 \ No newline at end of file diff --git a/src/Verify.Tests/Serialization/SerializationTests.cs b/src/Verify.Tests/Serialization/SerializationTests.cs index 7350a4f2cb..291a42772a 100644 --- a/src/Verify.Tests/Serialization/SerializationTests.cs +++ b/src/Verify.Tests/Serialization/SerializationTests.cs @@ -1552,6 +1552,24 @@ public Task ScrubInlineDateTimes() .ScrubInlineDateTimes("f"); } + [Fact] + public Task ScrubInlineDateTimesUsG() + { + var settings = new VerifySettings(); + settings.ScrubInlineDateTimes("G", new("en-US")); + + return Verify("12/11/2024 10:36:43 AM", settings); + } + [Fact] + public Task ScrubInlineDateTimesWithLongAmPm() + { + var settings = new VerifySettings(); + settings.ScrubInlineDateTimes("yyyy MM dd HH:mm tt", new("am-ET")); + + return Verify("2025 01 04 18:19 ከሰዓት", settings); + } + + // ReSharper disable once UnusedMember.Local static void DontIgnoreEmptyCollections() => diff --git a/src/Verify/Serialization/Scrubbers/CultureDate.cs b/src/Verify/Serialization/Scrubbers/CultureDate.cs index 78f975744c..970432e5ed 100644 --- a/src/Verify/Serialization/Scrubbers/CultureDate.cs +++ b/src/Verify/Serialization/Scrubbers/CultureDate.cs @@ -1,5 +1,31 @@ -readonly struct CultureDate(DateTime longDate, DateTime shortDate) +readonly struct CultureDate( + int amPmLong, + int amPmShort, + int monthNameLong, + int monthNameShort, + int abbreviatedMonthNameLong, + int abbreviatedMonthNameShort, + int dayNameLong, + int dayNameShort, + int abbreviatedDayNameLong, + int abbreviatedDayNameShort, + int dateSeparator, + int timeSeparator, + int eraLong, + int eraShort) { - public DateTime Long { get; } = longDate; - public DateTime Short { get; } = shortDate; -} \ No newline at end of file + public int AmPmLong { get; } = amPmLong; + public int AmPmShort { get; } = amPmShort; + public int MonthNameLong { get; } = monthNameLong; + public int MonthNameShort { get; } = monthNameShort; + public int AbbreviatedMonthNameLong { get; } = abbreviatedMonthNameLong; + public int AbbreviatedMonthNameShort { get; } = abbreviatedMonthNameShort; + public int DayNameLong { get; } = dayNameLong; + public int DayNameShort { get; } = dayNameShort; + public int AbbreviatedDayNameLong { get; } = abbreviatedDayNameLong; + public int AbbreviatedDayNameShort { get; } = abbreviatedDayNameShort; + public int DateSeparator { get; } = dateSeparator; + public int TimeSeparator { get; } = timeSeparator; + public int EraLong { get; } = eraLong; + public int EraShort { get; } = eraShort; +} diff --git a/src/Verify/Serialization/Scrubbers/DateFormatExpander.cs b/src/Verify/Serialization/Scrubbers/DateFormatExpander.cs new file mode 100644 index 0000000000..8a6332efb9 --- /dev/null +++ b/src/Verify/Serialization/Scrubbers/DateFormatExpander.cs @@ -0,0 +1,31 @@ +static class DateFormatExpander +{ + internal static string ExpandFormat(this DateTimeFormatInfo info, string format) + { + if (format.Length != 1) + { + return format; + } + + var ch = format[0]; + return ch switch + { + 'd' => info.ShortDatePattern, + 'D' => info.LongDatePattern, + 'f' => $"{info.LongDatePattern} {info.ShortTimePattern}", + 'F' => info.FullDateTimePattern, + 'g' => $"{info.ShortDatePattern} {info.ShortTimePattern}", + 'G' => $"{info.ShortDatePattern} {info.LongTimePattern}", + 'm' or 'M' => info.MonthDayPattern, + 'o' or 'O' => "yyyy'-'MM'-'dd'T'HH':'mm':'ss.fffffffK", + 'r' or 'R' => info.RFC1123Pattern, + 's' => info.SortableDateTimePattern, + 't' => info.ShortTimePattern, + 'T' => info.LongTimePattern, + 'u' => info.UniversalSortableDateTimePattern, + 'U' => info.FullDateTimePattern, + 'y' or 'Y' => info.YearMonthPattern, + _ => throw new($"Invalid format: {format}"), + }; + } +} \ No newline at end of file diff --git a/src/Verify/Serialization/Scrubbers/DateFormatLengthCalculator.cs b/src/Verify/Serialization/Scrubbers/DateFormatLengthCalculator.cs new file mode 100644 index 0000000000..b4dc66de1f --- /dev/null +++ b/src/Verify/Serialization/Scrubbers/DateFormatLengthCalculator.cs @@ -0,0 +1,365 @@ +static class DateFormatLengthCalculator +{ + const int maxSecondsFractionDigits = 7; + + static ConcurrentDictionary cache = new(); + + public static (int max, int min) GetLength(string format, Culture culture) + { + var key = $"{culture.Name}_{format}"; + return cache.GetOrAdd( + key, + _ => + { + format = culture.DateTimeFormat.ExpandFormat(format); + var length = InnerGetLength(format.AsSpan(), culture); + return length; + }); + } + + public static (int max, int min) InnerGetLength(scoped CharSpan format, Culture culture) + { + var cultureDates = GetCultureLengthInfo(culture); + + var index = 0; + + var minLength = 0; + var maxLength = 0; + while (index < format.Length) + { + var ch = format[index]; + int nextChar; + int tokenLen; + switch (ch) + { + case 'g': + tokenLen = ParseRepeatPattern(format, index, ch); + minLength += cultureDates.EraShort; + maxLength += cultureDates.EraLong; + break; + case 'h': + case 'H': + case 'm': + case 's': + tokenLen = ParseRepeatPattern(format, index, ch); + minLength += Math.Min(tokenLen, 2); + maxLength += 2; + break; + case 'f': + case 'F': + tokenLen = ParseRepeatPattern(format, index, ch); + if (tokenLen > maxSecondsFractionDigits) + { + throw new FormatException("Too many second fraction digits"); + } + + minLength += tokenLen; + maxLength += tokenLen; + + break; + case 't': + tokenLen = ParseRepeatPattern(format, index, ch); + maxLength += cultureDates.AmPmLong; + minLength += cultureDates.AmPmShort; + + break; + case 'd': + // tokenLen == 1 : Day of month as digits with no leading zero. + // tokenLen == 2 : Day of month as digits with leading zero for single-digit months. + // tokenLen == 3 : Day of week as a three-letter abbreviation. + // tokenLen >= 4 : Day of week as its full name. + tokenLen = ParseRepeatPattern(format, index, ch); + if (tokenLen == 1) + { + minLength += 1; + maxLength += 2; + } + else if (tokenLen == 2) + { + minLength += 2; + maxLength += 2; + } + else if (tokenLen == 3) + { + minLength += cultureDates.AbbreviatedDayNameShort; + maxLength += cultureDates.AbbreviatedDayNameLong; + } + else + { + minLength += cultureDates.DayNameShort; + maxLength += cultureDates.DayNameLong; + } + + break; + case 'M': + // tokenLen == 1 : Month as digits with no leading zero. + // tokenLen == 2 : Month as digits with leading zero for single-digit months. + // tokenLen == 3 : Month as a three-letter abbreviation. + // tokenLen >= 4 : Month as its full name. + tokenLen = ParseRepeatPattern(format, index, ch); + if (tokenLen == 1) + { + minLength += 1; + maxLength += 2; + } + else if (tokenLen == 2) + { + minLength += 2; + maxLength += 2; + } + else if (tokenLen == 3) + { + minLength += cultureDates.AbbreviatedMonthNameShort; + maxLength += cultureDates.AbbreviatedMonthNameLong; + } + else + { + minLength += cultureDates.MonthNameShort; + maxLength += cultureDates.MonthNameLong; + } + + break; + case 'y': + // Notes about OS behavior: + // y: Always print (year % 100). No leading zero. + // yy: Always print (year % 100) with leading zero. + // yyy/yyyy/yyyyy/... : Print year value. With leading zeros. + tokenLen = ParseRepeatPattern(format, index, ch); + if (tokenLen == 1) + { + minLength += 1; + maxLength += 4; + } + else if (tokenLen == 2) + { + minLength += 2; + maxLength += 4; + } + else + { + minLength += tokenLen; + maxLength += Math.Max(tokenLen, 4); + } + + break; + case 'z': + tokenLen = ParseRepeatPattern(format, index, ch); + if (tokenLen == 1) + { + minLength += 2; + maxLength += 3; + } + else if (tokenLen == 2) + { + minLength += 3; + maxLength += 3; + } + else + { + minLength += 6; + maxLength += 6; + } + + break; + case 'K': + tokenLen = 1; + minLength += 6; + maxLength += 6; + break; + case ':': + minLength += cultureDates.TimeSeparator; + maxLength += cultureDates.TimeSeparator; + tokenLen = 1; + break; + case '/': + minLength += cultureDates.DateSeparator; + maxLength += cultureDates.DateSeparator; + tokenLen = 1; + break; + case '\'': + case '\"': + tokenLen = ParseQuoteString(format, index); + var unwrapped = tokenLen - 2; + minLength += unwrapped; + maxLength += unwrapped; + break; + case '%': + // Optional format character. + // For example, format string "%d" will print day of month + // without leading zero. Most of the cases, "%" can be ignored. + nextChar = ParseNextChar(format, index); + // nextChar will be -1 if we have already reached the end of the format string. + // Besides, we will not allow "%%" to appear in the pattern. + if (nextChar is < 0 or '%') + { + throw new FormatException("Detected '%' at the end of the format string or '%%' appears in the format string"); + } + + var nextCharChar = (char) nextChar; + var innerLength = InnerGetLength([nextCharChar], culture); + maxLength += innerLength.max; + minLength += innerLength.min; + tokenLen = 2; + + break; + case '\\': + // Escaped character. Can be used to insert a character into the format string. + // For example, "\d" will insert the character 'd' into the string. + // + // NOTE: we can remove this format character if we enforce the enforced quote + // character rule. + // That is, we ask everyone to use single quote or double quote to insert characters, + // then we can remove this character. + nextChar = ParseNextChar(format, index); + if (nextChar < 0) + { + // This means that '\' is at the end of the formatting string. + throw new FormatException("Detected a '\\' at the end of the formatting string"); + } + + minLength++; + maxLength++; + tokenLen = 2; + + break; + default: + minLength++; + maxLength++; + tokenLen = 1; + break; + } + + index += tokenLen; + } + + return (maxLength, minLength); + } + + // Get the next character at the index of 'pos' in the 'format' string. + // Return value of -1 means 'pos' is already at the end of the 'format' string. + // Otherwise, return value is the int value of the next character. + static int ParseNextChar(CharSpan format, int pos) + { + if (pos + 1 >= format.Length) + { + return -1; + } + + return format[pos + 1]; + } + + static int ParseRepeatPattern(CharSpan format, int pos, char patternChar) + { + var index = pos + 1; + while (index < format.Length && format[index] == patternChar) + { + index++; + } + + return index - pos; + } + + // The pos should point to a quote character. This method will + // append to the result StringBuilder the string enclosed by the quote character. + static int ParseQuoteString(scoped CharSpan format, int pos) + { + // pos will be the index of the quote character in the 'format' string. + var formatLen = format.Length; + var beginPos = pos; + // Get the character used to quote the following string. + var quoteChar = format[pos++]; + + var foundQuote = false; + while (pos < formatLen) + { + var ch = format[pos++]; + if (ch == quoteChar) + { + foundQuote = true; + break; + } + + if (ch != '\\') + { + continue; + } + + if (pos >= formatLen) + { + throw new FormatException("Invalid that '\\' is at the end of the formatting string."); + } + + // The following are used to support escaped character. + // Escaped character is also supported in the quoted string. + // Therefore, someone can use a format like "'minute:' mm\"" to display: + // minute: 45" + // because the second double quote is escaped. + pos++; + } + + if (foundQuote) + { + // Return the character count including the begin/end quote characters and enclosed string. + return pos - beginPos; + } + + throw new FormatException($"Can't find the matching quote: {quoteChar}"); + } + + internal static CultureDate GetCultureLengthInfo(Culture culture) + { + var info = culture.DateTimeFormat; + + var calendar = culture.Calendar; + + var am = info.AMDesignator; + var pm = info.PMDesignator; + string amPmLong; + string amPmShort; + if (am.Length < pm.Length) + { + amPmLong = pm; + amPmShort = am; + } + else + { + amPmLong = am; + amPmShort = pm; + } + + var monthNames = Lengths(info.MonthNames); + var abbreviatedMonthNames = Lengths(info.AbbreviatedMonthNames); + var dayNames = Lengths(info.DayNames); + var abbreviatedDayNames = Lengths(info.AbbreviatedDayNames); + var eras = Lengths(calendar.Eras.Select(_ => info.GetEraName(_))); + var timeSeparator = info.TimeSeparator; + var dateSeparator = info.DateSeparator; + return new( + amPmLong: amPmLong.Length, + amPmShort: amPmShort.Length, + monthNameLong: monthNames.Long, + monthNameShort: monthNames.Short, + abbreviatedMonthNameLong: abbreviatedMonthNames.Long, + abbreviatedMonthNameShort: abbreviatedMonthNames.Short, + dayNameLong: dayNames.Long, + dayNameShort: dayNames.Short, + abbreviatedDayNameLong: abbreviatedDayNames.Long, + abbreviatedDayNameShort: abbreviatedDayNames.Short, + dateSeparator: dateSeparator.Length, + timeSeparator: timeSeparator.Length, + eraLong: eras.Long, + eraShort: eras.Short); + } + + static (int Long, int Short) Lengths(IEnumerable names) + { + var lengths = names + .Where(_ => _.Length > 0) + .OrderBy(_ => _.Length) + .ToList(); + var max = lengths.Last(); + var min = lengths.First(); + Debug.Assert(max.Length >= min.Length); + return (max.Length, min.Length); + } +} \ No newline at end of file diff --git a/src/Verify/Serialization/Scrubbers/DateScrubber.cs b/src/Verify/Serialization/Scrubbers/DateScrubber.cs index 8b3d14adaf..e20ce4aab8 100644 --- a/src/Verify/Serialization/Scrubbers/DateScrubber.cs +++ b/src/Verify/Serialization/Scrubbers/DateScrubber.cs @@ -59,7 +59,6 @@ public static void ReplaceDates( format, counter, culture, - _ => Date.FromDateTime(_), TryConvertDate); #endif @@ -112,7 +111,6 @@ public static void ReplaceDateTimeOffsets( format, counter, culture, - _ => new DateTimeOffset(_), TryConvertDateTimeOffset); static bool TryConvertDateTime( @@ -158,41 +156,25 @@ public static void ReplaceDateTimes(StringBuilder builder, string format, Counte format, counter, culture, - _ => _, TryConvertDateTime); - static void ReplaceInner(StringBuilder builder, string format, Counter counter, Culture culture, Func toDate, TryConvert tryConvertDate) + static void ReplaceInner(StringBuilder builder, string format, Counter counter, Culture culture, TryConvert tryConvertDate) { - int Length(DateTime dateTime) - { - var date = toDate(dateTime); - try - { - return date.ToString(format, culture).Length; - } - catch (Exception exception) - { - throw new($"Failed to get length for {date.GetType()} {date} using format '{format}' and culture {culture}.", exception); - } - } + var (max, min) = DateFormatLengthCalculator.GetLength(format, culture); - var cultureDate = GetCultureDates(culture); - var shortest = Length(cultureDate.Short); - var longest = Length(cultureDate.Long); - - if (builder.Length < shortest) + if (builder.Length < min) { return; } - if (shortest == longest) + if (min == max) { - ReplaceFixedLength(builder, format, counter, culture, tryConvertDate, longest); + ReplaceFixedLength(builder, format, counter, culture, tryConvertDate, max); return; } - ReplaceVariableLength(builder, format, counter, culture, tryConvertDate, longest, shortest); + ReplaceVariableLength(builder, format, counter, culture, tryConvertDate, max, min); } static void ReplaceVariableLength(StringBuilder builder, string format, Counter counter, Culture culture, TryConvert tryConvertDate, int longest, int shortest) @@ -250,14 +232,4 @@ static void ReplaceFixedLength(StringBuilder builder, string format, Counter cou } } } - internal static CultureDate GetCultureDates(Culture culture) - { - if (cultureDates.TryGetValue(culture.Name, out var cultureDate) || - cultureDates.TryGetValue(culture.TwoLetterISOLanguageName, out cultureDate)) - { - return cultureDate; - } - - throw new($"Could not find culture {culture.Name}"); - } } \ No newline at end of file diff --git a/src/Verify/Serialization/Scrubbers/DateScrubber_Generated.cs b/src/Verify/Serialization/Scrubbers/DateScrubber_Generated.cs deleted file mode 100644 index 097bf051d2..0000000000 --- a/src/Verify/Serialization/Scrubbers/DateScrubber_Generated.cs +++ /dev/null @@ -1,1711 +0,0 @@ -// -static partial class DateScrubber -{ - static IReadOnlyDictionary cultureDates = new Dictionary() - { - { - "", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "aa", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "af", - new( - new(2023, 2, 23, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "agq", - new( - new(2023, 9, 23, 12, 10, 10, 10), - new(2023, 5, 4, 1, 0, 0)) - }, - { - "ak", - new( - new(2023, 5, 20, 12, 10, 10, 10), - new(2023, 9, 1, 1, 0, 0)) - }, - { - "am", - new( - new(2023, 9, 26, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "ar", - new( - new(2023, 2, 21, 12, 10, 10, 10), - new(2023, 3, 4, 1, 0, 0)) - }, - { - "ar-DZ", - new( - new(2023, 7, 25, 12, 10, 10, 10), - new(2023, 5, 6, 1, 0, 0)) - }, - { - "ar-IQ", - new( - new(2023, 1, 24, 12, 10, 10, 10), - new(2023, 8, 5, 1, 0, 0)) - }, - { - "ar-JO", - new( - new(2023, 1, 24, 12, 10, 10, 10), - new(2023, 8, 5, 1, 0, 0)) - }, - { - "ar-LB", - new( - new(2023, 1, 24, 12, 10, 10, 10), - new(2023, 8, 5, 1, 0, 0)) - }, - { - "ar-MA", - new( - new(2023, 2, 21, 12, 10, 10, 10), - new(2023, 5, 6, 1, 0, 0)) - }, - { - "ar-PS", - new( - new(2023, 1, 24, 12, 10, 10, 10), - new(2023, 8, 5, 1, 0, 0)) - }, - { - "ar-SA", - new( - new(2023, 11, 21, 12, 10, 10, 10), - new(2023, 2, 4, 1, 0, 0)) - }, - { - "ar-SY", - new( - new(2023, 1, 24, 12, 10, 10, 10), - new(2023, 8, 5, 1, 0, 0)) - }, - { - "ar-TN", - new( - new(2023, 7, 25, 12, 10, 10, 10), - new(2023, 5, 6, 1, 0, 0)) - }, - { - "arn", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "as", - new( - new(2023, 2, 23, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "asa", - new( - new(2023, 2, 20, 1, 10, 10, 10), - new(2023, 5, 5, 13, 0, 0)) - }, - { - "ast", - new( - new(2023, 9, 20, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "az", - new( - new(2023, 9, 26, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "ba", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "bas", - new( - new(2023, 12, 25, 1, 10, 10, 10), - new(2023, 4, 4, 13, 0, 0)) - }, - { - "be", - new( - new(2023, 10, 23, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "bem", - new( - new(2023, 2, 25, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "bez", - new( - new(2023, 12, 23, 12, 10, 10, 10), - new(2023, 6, 1, 1, 0, 0)) - }, - { - "bg", - new( - new(2023, 9, 25, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "bgc", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 7, 1, 0, 0)) - }, - { - "bho", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 6, 1, 0, 0)) - }, - { - "bin", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "bm", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "bn", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "bo", - new( - new(2023, 11, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "br", - new( - new(2023, 2, 22, 12, 10, 10, 10), - new(2023, 11, 5, 1, 0, 0)) - }, - { - "brx", - new( - new(2023, 2, 23, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "bs", - new( - new(2023, 9, 25, 1, 10, 10, 10), - new(2023, 5, 5, 13, 0, 0)) - }, - { - "bs-Cyrl", - new( - new(2023, 9, 25, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "byn", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "ca", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 3, 2, 1, 0, 0)) - }, - { - "ccp", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "ce", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 5, 4, 1, 0, 0)) - }, - { - "ceb", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "cgg", - new( - new(2023, 11, 20, 12, 10, 10, 10), - new(2023, 4, 2, 1, 0, 0)) - }, - { - "chr", - new( - new(2023, 1, 21, 12, 10, 10, 10), - new(2023, 2, 1, 1, 0, 0)) - }, - { - "ckb", - new( - new(2023, 1, 25, 12, 10, 10, 10), - new(2023, 8, 4, 1, 0, 0)) - }, - { - "ckb-IR", - new( - new(2023, 10, 25, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "co", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "cs", - new( - new(2023, 7, 20, 12, 10, 10, 10), - new(2023, 2, 3, 1, 0, 0)) - }, - { - "cu", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "cv", - new( - new(2023, 1, 22, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "cy", - new( - new(2023, 7, 26, 12, 10, 10, 10), - new(2023, 5, 4, 1, 0, 0)) - }, - { - "da", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "dav", - new( - new(2023, 11, 20, 12, 10, 10, 10), - new(2023, 2, 4, 1, 0, 0)) - }, - { - "de", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "dje", - new( - new(2023, 2, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "doi", - new( - new(2023, 10, 20, 12, 10, 10, 10), - new(2023, 5, 7, 1, 0, 0)) - }, - { - "dsb", - new( - new(2023, 9, 25, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "dua", - new( - new(2023, 7, 22, 12, 10, 10, 10), - new(2023, 3, 5, 1, 0, 0)) - }, - { - "dv", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "dyo", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 8, 6, 1, 0, 0)) - }, - { - "dz", - new( - new(2023, 11, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "ebu", - new( - new(2023, 12, 23, 12, 10, 10, 10), - new(2023, 4, 2, 1, 0, 0)) - }, - { - "ee", - new( - new(2023, 11, 25, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "el", - new( - new(2023, 2, 24, 12, 10, 10, 10), - new(2023, 5, 2, 1, 0, 0)) - }, - { - "en", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "eo", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "es", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "et", - new( - new(2023, 2, 20, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "eu", - new( - new(2023, 1, 23, 12, 10, 10, 10), - new(2023, 10, 1, 1, 0, 0)) - }, - { - "ewo", - new( - new(2023, 11, 21, 12, 10, 10, 10), - new(2023, 1, 6, 1, 0, 0)) - }, - { - "fa", - new( - new(2023, 4, 26, 1, 10, 10, 10), - new(2023, 1, 6, 13, 0, 0)) - }, - { - "ff", - new( - new(2023, 10, 21, 12, 10, 10, 10), - new(2023, 8, 6, 1, 0, 0)) - }, - { - "ff-Adlm", - new( - new(2023, 3, 23, 12, 10, 10, 10), - new(2023, 8, 4, 1, 0, 0)) - }, - { - "fi", - new( - new(2023, 3, 22, 12, 10, 10, 10), - new(2023, 8, 1, 1, 0, 0)) - }, - { - "fil", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "fo", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 5, 2, 1, 0, 0)) - }, - { - "fr", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "fr-CM", - new( - new(2023, 9, 20, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "fur", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "fy", - new( - new(2023, 1, 26, 12, 10, 10, 10), - new(2023, 6, 2, 1, 0, 0)) - }, - { - "ga", - new( - new(2023, 10, 22, 12, 10, 10, 10), - new(2023, 7, 3, 1, 0, 0)) - }, - { - "gd", - new( - new(2023, 1, 21, 12, 10, 10, 10), - new(2023, 3, 6, 1, 0, 0)) - }, - { - "gl", - new( - new(2023, 2, 22, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "gn", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 4, 1, 0, 0)) - }, - { - "gsw", - new( - new(2023, 9, 21, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "gu", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "guz", - new( - new(2023, 1, 22, 1, 10, 10, 10), - new(2023, 5, 5, 13, 0, 0)) - }, - { - "gv", - new( - new(2023, 12, 20, 12, 10, 10, 10), - new(2023, 3, 4, 1, 0, 0)) - }, - { - "ha", - new( - new(2023, 2, 20, 1, 10, 10, 10), - new(2023, 5, 2, 13, 0, 0)) - }, - { - "haw", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 4, 1, 0, 0)) - }, - { - "he", - new( - new(2023, 10, 22, 1, 10, 10, 10), - new(2023, 3, 4, 13, 0, 0)) - }, - { - "hi", - new( - new(2023, 10, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "hi-Latn", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "hr", - new( - new(2023, 1, 23, 12, 10, 10, 10), - new(2023, 9, 1, 1, 0, 0)) - }, - { - "hsb", - new( - new(2023, 9, 24, 12, 10, 10, 10), - new(2023, 3, 3, 1, 0, 0)) - }, - { - "hu", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 2, 1, 0, 0)) - }, - { - "hy", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 3, 4, 1, 0, 0)) - }, - { - "ia", - new( - new(2023, 2, 22, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "ibb", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "id", - new( - new(2023, 9, 24, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "ig", - new( - new(2023, 2, 22, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "ii", - new( - new(2023, 11, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "is", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "it", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 3, 4, 1, 0, 0)) - }, - { - "iu", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "ja", - new( - new(2023, 10, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "jgo", - new( - new(2023, 6, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "jmc", - new( - new(2023, 2, 20, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "jv", - new( - new(2023, 9, 26, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "ka", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 3, 5, 1, 0, 0)) - }, - { - "kab", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 8, 2, 1, 0, 0)) - }, - { - "kam", - new( - new(2023, 11, 20, 1, 10, 10, 10), - new(2023, 1, 3, 13, 0, 0)) - }, - { - "kde", - new( - new(2023, 11, 24, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "kea", - new( - new(2023, 9, 25, 12, 10, 10, 10), - new(2023, 5, 6, 1, 0, 0)) - }, - { - "kgp", - new( - new(2023, 10, 26, 12, 10, 10, 10), - new(2023, 1, 7, 1, 0, 0)) - }, - { - "khq", - new( - new(2023, 2, 23, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "ki", - new( - new(2023, 11, 25, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "kk", - new( - new(2023, 12, 20, 12, 10, 10, 10), - new(2023, 2, 3, 1, 0, 0)) - }, - { - "kkj", - new( - new(2023, 6, 24, 12, 10, 10, 10), - new(2023, 8, 3, 1, 0, 0)) - }, - { - "kl", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 3, 5, 1, 0, 0)) - }, - { - "kln", - new( - new(2023, 12, 21, 12, 10, 10, 10), - new(2023, 6, 3, 1, 0, 0)) - }, - { - "km", - new( - new(2023, 11, 23, 12, 10, 10, 10), - new(2023, 1, 4, 1, 0, 0)) - }, - { - "kn", - new( - new(2023, 9, 22, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "ko", - new( - new(2023, 10, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "kok", - new( - new(2023, 2, 23, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "kr", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "ks", - new( - new(2023, 7, 24, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "ks-Deva", - new( - new(2023, 10, 23, 12, 10, 10, 10), - new(2023, 5, 2, 1, 0, 0)) - }, - { - "ksb", - new( - new(2023, 2, 20, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "ksf", - new( - new(2023, 11, 22, 12, 10, 10, 10), - new(2023, 3, 2, 1, 0, 0)) - }, - { - "ksh", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "kw", - new( - new(2023, 7, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "ky", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "la", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "lag", - new( - new(2023, 5, 20, 12, 10, 10, 10), - new(2023, 10, 6, 1, 0, 0)) - }, - { - "lb", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "lg", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 4, 3, 1, 0, 0)) - }, - { - "lkt", - new( - new(2023, 6, 24, 12, 10, 10, 10), - new(2023, 4, 6, 1, 0, 0)) - }, - { - "ln", - new( - new(2023, 11, 21, 1, 10, 10, 10), - new(2023, 10, 1, 13, 0, 0)) - }, - { - "lo", - new( - new(2023, 5, 23, 12, 10, 10, 10), - new(2023, 3, 1, 1, 0, 0)) - }, - { - "lrc", - new( - new(2023, 4, 21, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "lrc-IQ", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "lt", - new( - new(2023, 11, 23, 1, 10, 10, 10), - new(2023, 3, 4, 13, 0, 0)) - }, - { - "lu", - new( - new(2023, 7, 22, 12, 10, 10, 10), - new(2023, 4, 6, 1, 0, 0)) - }, - { - "luo", - new( - new(2023, 12, 21, 12, 10, 10, 10), - new(2023, 3, 4, 1, 0, 0)) - }, - { - "luy", - new( - new(2023, 2, 24, 12, 10, 10, 10), - new(2023, 5, 2, 1, 0, 0)) - }, - { - "lv", - new( - new(2023, 9, 21, 1, 10, 10, 10), - new(2023, 3, 7, 13, 0, 0)) - }, - { - "mai", - new( - new(2023, 10, 26, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "mas", - new( - new(2023, 5, 20, 1, 10, 10, 10), - new(2023, 2, 3, 13, 0, 0)) - }, - { - "mer", - new( - new(2023, 2, 20, 12, 10, 10, 10), - new(2023, 5, 4, 1, 0, 0)) - }, - { - "mfe", - new( - new(2023, 2, 22, 12, 10, 10, 10), - new(2023, 5, 4, 1, 0, 0)) - }, - { - "mg", - new( - new(2023, 8, 21, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "mgh", - new( - new(2023, 6, 22, 12, 10, 10, 10), - new(2023, 7, 2, 1, 0, 0)) - }, - { - "mgo", - new( - new(2023, 3, 20, 12, 10, 10, 10), - new(2023, 11, 1, 1, 0, 0)) - }, - { - "mi", - new( - new(2023, 4, 26, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "mk", - new( - new(2023, 9, 25, 1, 10, 10, 10), - new(2023, 5, 3, 13, 0, 0)) - }, - { - "ml", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 6, 3, 1, 0, 0)) - }, - { - "mn", - new( - new(2023, 12, 20, 12, 10, 10, 10), - new(2023, 9, 3, 1, 0, 0)) - }, - { - "mn-Mong", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "mn-Mong-MN", - new( - new(2023, 11, 21, 12, 10, 10, 10), - new(2023, 1, 2, 1, 0, 0)) - }, - { - "mni", - new( - new(2023, 2, 21, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "moh", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "mr", - new( - new(2023, 2, 24, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "ms", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 3, 1, 1, 0, 0)) - }, - { - "mt", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 2, 4, 1, 0, 0)) - }, - { - "mua", - new( - new(2023, 6, 22, 12, 10, 10, 10), - new(2023, 1, 4, 1, 0, 0)) - }, - { - "my", - new( - new(2023, 10, 22, 1, 10, 10, 10), - new(2023, 5, 6, 13, 0, 0)) - }, - { - "mzn", - new( - new(2023, 4, 21, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "naq", - new( - new(2023, 9, 21, 1, 10, 10, 10), - new(2023, 1, 2, 13, 0, 0)) - }, - { - "nb", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "nd", - new( - new(2023, 1, 21, 12, 10, 10, 10), - new(2023, 11, 2, 1, 0, 0)) - }, - { - "nds", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "ne", - new( - new(2023, 9, 22, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "nl", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 7, 1, 0, 0)) - }, - { - "nmg", - new( - new(2023, 11, 22, 12, 10, 10, 10), - new(2023, 4, 1, 1, 0, 0)) - }, - { - "nn", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "nnh", - new( - new(2023, 7, 20, 1, 10, 10, 10), - new(2023, 4, 1, 13, 0, 0)) - }, - { - "no", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "nqo", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 3, 2, 1, 0, 0)) - }, - { - "nr", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "nso", - new( - new(2023, 1, 23, 12, 10, 10, 10), - new(2023, 6, 1, 1, 0, 0)) - }, - { - "nus", - new( - new(2023, 12, 22, 12, 10, 10, 10), - new(2023, 2, 5, 1, 0, 0)) - }, - { - "nyn", - new( - new(2023, 11, 20, 12, 10, 10, 10), - new(2023, 4, 2, 1, 0, 0)) - }, - { - "oc", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "om", - new( - new(2023, 10, 20, 12, 10, 10, 10), - new(2023, 4, 5, 1, 0, 0)) - }, - { - "or", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "os", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 5, 6, 1, 0, 0)) - }, - { - "pa", - new( - new(2023, 4, 22, 12, 10, 10, 10), - new(2023, 5, 7, 1, 0, 0)) - }, - { - "pa-Arab", - new( - new(2023, 7, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "pap", - new( - new(2023, 9, 24, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "pcm", - new( - new(2023, 2, 22, 1, 10, 10, 10), - new(2023, 5, 2, 13, 0, 0)) - }, - { - "pl", - new( - new(2023, 10, 23, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "prg", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "ps", - new( - new(2023, 5, 25, 12, 10, 10, 10), - new(2023, 3, 3, 1, 0, 0)) - }, - { - "ps-PK", - new( - new(2023, 2, 23, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "pt", - new( - new(2023, 2, 20, 12, 10, 10, 10), - new(2023, 5, 6, 1, 0, 0)) - }, - { - "qu", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "quc", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "raj", - new( - new(2023, 9, 22, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "rm", - new( - new(2023, 6, 26, 12, 10, 10, 10), - new(2023, 3, 4, 1, 0, 0)) - }, - { - "rn", - new( - new(2023, 8, 26, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "ro", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 4, 1, 0, 0)) - }, - { - "rof", - new( - new(2023, 12, 20, 1, 10, 10, 10), - new(2023, 5, 5, 13, 0, 0)) - }, - { - "ru", - new( - new(2023, 9, 24, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "rw", - new( - new(2023, 2, 25, 12, 10, 10, 10), - new(2023, 4, 6, 1, 0, 0)) - }, - { - "rwk", - new( - new(2023, 2, 20, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "sa", - new( - new(2023, 10, 20, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "sah", - new( - new(2023, 8, 20, 12, 10, 10, 10), - new(2023, 7, 5, 1, 0, 0)) - }, - { - "saq", - new( - new(2023, 12, 26, 1, 10, 10, 10), - new(2023, 1, 1, 13, 0, 0)) - }, - { - "sat", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "sbp", - new( - new(2023, 5, 25, 12, 10, 10, 10), - new(2023, 4, 7, 1, 0, 0)) - }, - { - "sc", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "sd", - new( - new(2023, 2, 21, 12, 10, 10, 10), - new(2023, 5, 7, 1, 0, 0)) - }, - { - "sd-Deva", - new( - new(2023, 9, 20, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "se", - new( - new(2023, 1, 22, 12, 10, 10, 10), - new(2023, 4, 1, 1, 0, 0)) - }, - { - "se-FI", - new( - new(2023, 1, 22, 12, 10, 10, 10), - new(2023, 4, 4, 1, 0, 0)) - }, - { - "seh", - new( - new(2023, 2, 24, 12, 10, 10, 10), - new(2023, 5, 4, 1, 0, 0)) - }, - { - "ses", - new( - new(2023, 2, 23, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "sg", - new( - new(2023, 2, 22, 12, 10, 10, 10), - new(2023, 6, 2, 1, 0, 0)) - }, - { - "shi", - new( - new(2023, 9, 23, 12, 10, 10, 10), - new(2023, 3, 1, 1, 0, 0)) - }, - { - "shi-Latn", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 3, 1, 1, 0, 0)) - }, - { - "si", - new( - new(2023, 4, 20, 1, 10, 10, 10), - new(2023, 1, 1, 13, 0, 0)) - }, - { - "sk", - new( - new(2023, 9, 25, 12, 10, 10, 10), - new(2023, 5, 2, 1, 0, 0)) - }, - { - "sl", - new( - new(2023, 9, 25, 12, 10, 10, 10), - new(2023, 5, 2, 1, 0, 0)) - }, - { - "sma", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "smj", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "smn", - new( - new(2023, 10, 20, 12, 10, 10, 10), - new(2023, 6, 3, 1, 0, 0)) - }, - { - "sms", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "sn", - new( - new(2023, 8, 25, 12, 10, 10, 10), - new(2023, 1, 5, 1, 0, 0)) - }, - { - "so", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 7, 1, 0, 0)) - }, - { - "sq", - new( - new(2023, 6, 21, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "sr", - new( - new(2023, 9, 25, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "sr-Cyrl-BA", - new( - new(2023, 9, 25, 1, 10, 10, 10), - new(2023, 5, 5, 13, 0, 0)) - }, - { - "sr-Cyrl-ME", - new( - new(2023, 9, 25, 1, 10, 10, 10), - new(2023, 5, 5, 13, 0, 0)) - }, - { - "sr-Latn-BA", - new( - new(2023, 9, 25, 1, 10, 10, 10), - new(2023, 5, 5, 13, 0, 0)) - }, - { - "sr-Latn-ME", - new( - new(2023, 9, 25, 1, 10, 10, 10), - new(2023, 5, 5, 13, 0, 0)) - }, - { - "ss", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "ssy", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "st", - new( - new(2023, 5, 26, 12, 10, 10, 10), - new(2023, 4, 6, 1, 0, 0)) - }, - { - "su", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 5, 3, 1, 0, 0)) - }, - { - "sv", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "sw", - new( - new(2023, 2, 20, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "syr", - new( - new(2023, 11, 20, 12, 10, 10, 10), - new(2023, 8, 5, 1, 0, 0)) - }, - { - "ta", - new( - new(2023, 9, 26, 12, 10, 10, 10), - new(2023, 5, 6, 1, 0, 0)) - }, - { - "te", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "teo", - new( - new(2023, 6, 26, 1, 10, 10, 10), - new(2023, 2, 1, 13, 0, 0)) - }, - { - "tg", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "th", - new( - new(2023, 2, 23, 12, 10, 10, 10), - new(2023, 1, 4, 1, 0, 0)) - }, - { - "ti", - new( - new(2023, 9, 24, 12, 10, 10, 10), - new(2023, 1, 2, 1, 0, 0)) - }, - { - "tig", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "tk", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "tn", - new( - new(2023, 5, 26, 12, 10, 10, 10), - new(2023, 7, 2, 1, 0, 0)) - }, - { - "to", - new( - new(2023, 4, 20, 1, 10, 10, 10), - new(2023, 5, 1, 13, 0, 0)) - }, - { - "tr", - new( - new(2023, 6, 24, 12, 10, 10, 10), - new(2023, 1, 3, 1, 0, 0)) - }, - { - "ts", - new( - new(2023, 12, 25, 12, 10, 10, 10), - new(2023, 11, 5, 1, 0, 0)) - }, - { - "tt", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "twq", - new( - new(2023, 2, 23, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "tzm", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 3, 1, 1, 0, 0)) - }, - { - "tzm-DZ", - new( - new(2023, 1, 23, 12, 10, 10, 10), - new(2023, 5, 6, 1, 0, 0)) - }, - { - "tzm-Tfng", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 3, 1, 1, 0, 0)) - }, - { - "ug", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "uk", - new( - new(2023, 3, 20, 12, 10, 10, 10), - new(2023, 2, 1, 1, 0, 0)) - }, - { - "ur", - new( - new(2023, 7, 20, 12, 10, 10, 10), - new(2023, 5, 1, 1, 0, 0)) - }, - { - "uz", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "uz-Arab", - new( - new(2023, 4, 26, 12, 10, 10, 10), - new(2023, 1, 6, 1, 0, 0)) - }, - { - "vai", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 3, 1, 1, 0, 0)) - }, - { - "vai-Latn", - new( - new(2023, 11, 20, 12, 10, 10, 10), - new(2023, 3, 1, 1, 0, 0)) - }, - { - "ve", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "vi", - new( - new(2023, 10, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "vo", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "vun", - new( - new(2023, 2, 20, 12, 10, 10, 10), - new(2023, 5, 5, 1, 0, 0)) - }, - { - "wae", - new( - new(2023, 9, 20, 12, 10, 10, 10), - new(2023, 3, 3, 1, 0, 0)) - }, - { - "wal", - new( - new(2023, 1, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "wo", - new( - new(2023, 9, 21, 12, 10, 10, 10), - new(2023, 8, 5, 1, 0, 0)) - }, - { - "xh", - new( - new(2023, 1, 25, 12, 10, 10, 10), - new(2023, 5, 7, 1, 0, 0)) - }, - { - "xog", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 4, 3, 1, 0, 0)) - }, - { - "yav", - new( - new(2023, 1, 26, 12, 10, 10, 10), - new(2023, 6, 2, 1, 0, 0)) - }, - { - "yi", - new( - new(2023, 9, 21, 1, 10, 10, 10), - new(2023, 5, 6, 13, 0, 0)) - }, - { - "yo", - new( - new(2023, 1, 21, 12, 10, 10, 10), - new(2023, 4, 3, 1, 0, 0)) - }, - { - "yrl", - new( - new(2023, 10, 25, 12, 10, 10, 10), - new(2023, 5, 6, 1, 0, 0)) - }, - { - "zgh", - new( - new(2023, 9, 22, 12, 10, 10, 10), - new(2023, 3, 1, 1, 0, 0)) - }, - { - "zh", - new( - new(2023, 11, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "zh-Hant", - new( - new(2023, 10, 20, 12, 10, 10, 10), - new(2023, 1, 1, 1, 0, 0)) - }, - { - "zu", - new( - new(2023, 2, 22, 12, 10, 10, 10), - new(2023, 5, 7, 1, 0, 0)) - }, - }.ToFrozenDictionary(); -}