From af587e4cabadd04930d15d78f03831aa7df56791 Mon Sep 17 00:00:00 2001 From: Daniel Stolt Date: Thu, 7 Aug 2014 02:46:47 +0200 Subject: [PATCH] Added correct formatting and parsing of milliseconds using the fraction specifiers, and updated unit tests to cover it. --- .../Localization/DefaultDateFormatterTests.cs | 16 ++-- .../Services/DefaultDateFormatter.cs | 85 +++++++++++-------- .../Services/IDateLocalizationServices.txt | 6 -- src/Orchard/Orchard.Framework.csproj | 3 - 4 files changed, 57 insertions(+), 53 deletions(-) delete mode 100644 src/Orchard/Localization/Services/IDateLocalizationServices.txt diff --git a/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs b/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs index 5417cdcfd..302053922 100644 --- a/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs +++ b/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs @@ -42,7 +42,7 @@ namespace Orchard.Tests.Localization { foreach (var dateTimeFormat in formats.AllDateTimeFormats) { // All date and time formats supported by the culture. for (var month = 1; month <= 12; month++) { // All months in the year. - DateTime dateTime = new DateTime(1998, month, 1, 10, 30, 30, DateTimeKind.Utc); + DateTime dateTime = new DateTime(1998, month, 1, 10, 30, 30, 678, DateTimeKind.Utc); // Print string using Gregorian calendar to avoid calendar conversion. var cultureGregorian = (CultureInfo)culture.Clone(); @@ -329,8 +329,8 @@ namespace Orchard.Tests.Localization { foreach (var dateTimeFormat in formats.AllDateTimeFormats) { // All date/time formats supported by the culture. for (var month = 1; month <= 12; month++) { // All months in the year. - DateTime dateTime = new DateTime(1998, month, 1, 10, 30, 30); - DateTimeParts dateTimeParts = new DateTimeParts(1998, month, 1, 10, 30, 30, 0, DateTimeKind.Unspecified, offset: TimeSpan.Zero); + DateTime dateTime = new DateTime(1998, month, 1, 10, 30, 30, 678); + DateTimeParts dateTimeParts = new DateTimeParts(1998, month, 1, 10, 30, 30, 678, DateTimeKind.Unspecified, offset: TimeSpan.Zero); // Print reference string using Gregorian calendar to avoid calendar conversion. var cultureGregorian = (CultureInfo)culture.Clone(); @@ -392,8 +392,8 @@ namespace Orchard.Tests.Localization { foreach (var dateTimeFormat in formats.AllDateTimeFormats) { // All date/time formats supported by the culture. foreach (var hour in new[] { 0, 6, 12, 18 }) { // Enough hours to cover all code paths (AM/PM, 12<->00, 1/2 digits, etc). - DateTime dateTime = new DateTime(1998, 1, 1, hour, 30, 30); - DateTimeParts dateTimeParts = new DateTimeParts(1998, 1, 1, hour, 30, 30, 0, DateTimeKind.Unspecified, offset: TimeSpan.Zero); + DateTime dateTime = new DateTime(1998, 1, 1, hour, 30, 30, 678); + DateTimeParts dateTimeParts = new DateTimeParts(1998, 1, 1, hour, 30, 30, 678, DateTimeKind.Unspecified, offset: TimeSpan.Zero); // Print reference string using Gregorian calendar to avoid calendar conversion. var cultureGregorian = (CultureInfo)culture.Clone(); @@ -471,7 +471,7 @@ namespace Orchard.Tests.Localization { kind = DateTimeKind.Local; } - var dateTime = new DateTime(1998, 1, 1, 10, 30, 30, kind); + var dateTime = new DateTime(1998, 1, 1, 10, 30, 30, 678, kind); var dateTimeOffset = new DateTimeOffset(dateTime, timeZone.BaseUtcOffset); var dateTimeParts = DateTimeParts.FromDateTime(dateTime, offset); @@ -609,8 +609,8 @@ namespace Orchard.Tests.Localization { foreach (var timeFormat in formats.AllTimeFormats) { // All time formats supported by the culture. for (var hour = 0; hour <= 23; hour++) { // All hours in the day. - DateTime date = new DateTime(1998, 1, 1, hour, 30, 30); - TimeParts timeParts = new TimeParts(hour, 30, 30, 0, DateTimeKind.Unspecified, offset: TimeSpan.Zero); + DateTime date = new DateTime(1998, 1, 1, hour, 30, 30, 678); + TimeParts timeParts = new TimeParts(hour, 30, 30, 678, DateTimeKind.Unspecified, offset: TimeSpan.Zero); var caseKey = String.Format("{0}___{1}___{2}", culture.Name, timeFormat, timeParts); allCases.Add(caseKey); diff --git a/src/Orchard/Localization/Services/DefaultDateFormatter.cs b/src/Orchard/Localization/Services/DefaultDateFormatter.cs index 4b19bbe41..5a79cad86 100644 --- a/src/Orchard/Localization/Services/DefaultDateFormatter.cs +++ b/src/Orchard/Localization/Services/DefaultDateFormatter.cs @@ -110,11 +110,11 @@ namespace Orchard.Localization.Services { int twoDigitYear, hour12, offsetHours, offsetMinutes; bool isPm; - string monthName, monthNameShort, monthNameGenitive, monthNameShortGenitive, dayName, dayNameShort, amPm, amPmShort, timeZone, offsetSign; + string monthName, monthNameShort, monthNameGenitive, monthNameShortGenitive, dayName, dayNameShort, amPm, amPmShort, timeZone, offsetSign, fraction1Zero, fraction2Zero, fraction3Zero, fraction1Digit, fraction2Digit, fraction3Digit; GetDateFormatValues(parts.Date, calendar, out twoDigitYear, out monthName, out monthNameShort, out monthNameGenitive, out monthNameShortGenitive, out dayName, out dayNameShort); - GetTimeFormatValues(parts.Time, out isPm, out hour12, out amPm, out amPmShort, out timeZone, out offsetSign, out offsetHours, out offsetMinutes); + GetTimeFormatValues(parts.Time, out isPm, out hour12, out amPm, out amPmShort, out timeZone, out offsetSign, out offsetHours, out offsetMinutes, out fraction1Zero, out fraction2Zero, out fraction3Zero, out fraction1Digit, out fraction2Digit, out fraction3Digit); - return String.Format(formatString, parts.Date.Year, twoDigitYear, parts.Date.Month, monthName, monthNameShort, monthNameGenitive, monthNameShortGenitive, parts.Date.Day, dayName, dayNameShort, parts.Time.Hour, hour12, parts.Time.Minute, parts.Time.Second, parts.Time.Millisecond, amPm, amPmShort, timeZone, offsetSign, offsetHours, offsetMinutes); + return String.Format(formatString, parts.Date.Year, twoDigitYear, parts.Date.Month, monthName, monthNameShort, monthNameGenitive, monthNameShortGenitive, parts.Date.Day, dayName, dayNameShort, parts.Time.Hour, hour12, parts.Time.Minute, parts.Time.Second, fraction1Zero, fraction2Zero, fraction3Zero, fraction1Digit, fraction2Digit, fraction3Digit, amPm, amPmShort, timeZone, offsetSign, offsetHours, offsetMinutes); } public virtual string FormatDate(DateParts parts) { @@ -145,10 +145,10 @@ namespace Orchard.Localization.Services { bool isPm; int hour12, offsetHours, offsetMinutes; - string amPm, amPmShort, timeZone, offsetSign; - GetTimeFormatValues(parts, out isPm, out hour12, out amPm, out amPmShort, out timeZone, out offsetSign, out offsetHours, out offsetMinutes); + string amPm, amPmShort, timeZone, offsetSign, fraction1Zero, fraction2Zero, fraction3Zero, fraction1Digit, fraction2Digit, fraction3Digit; + GetTimeFormatValues(parts, out isPm, out hour12, out amPm, out amPmShort, out timeZone, out offsetSign, out offsetHours, out offsetMinutes, out fraction1Zero, out fraction2Zero, out fraction3Zero, out fraction1Digit, out fraction2Digit, out fraction3Digit); - return String.Format(formatString, null, null, null, null, null, null, null, null, null, null, parts.Hour, hour12, parts.Minute, parts.Second, parts.Millisecond, amPm, amPmShort, timeZone, offsetSign, offsetHours, offsetMinutes); + return String.Format(formatString, null, null, null, null, null, null, null, null, null, null, parts.Hour, hour12, parts.Minute, parts.Second, fraction1Zero, fraction2Zero, fraction3Zero, fraction1Digit, fraction2Digit, fraction3Digit, amPm, amPmShort, timeZone, offsetSign, offsetHours, offsetMinutes); } protected virtual DateTimeParts? TryParseDateTime(string dateTimeString, string format, IDictionary replacements) { @@ -250,8 +250,8 @@ namespace Orchard.Localization.Services { second = Int32.Parse(m.Groups["second"].Value); } - if (m.Groups["millisecond"].Success) { - millisecond = Int32.Parse(m.Groups["millisecond"].Value); + if (m.Groups["fraction"].Success) { + millisecond = Convert.ToInt32(Decimal.Parse(String.Format(CultureInfo.InvariantCulture, "0.{0}", m.Groups["fraction"].Value), CultureInfo.InvariantCulture) * 1000); } if (m.Groups["timeZone"].Success) { @@ -301,7 +301,7 @@ namespace Orchard.Localization.Services { dayNameShort = parts.Day > 0 ? _dateTimeFormatProvider.DayNamesShort[(int)calendar.GetDayOfWeek(parts.ToDateTime(calendar))] : null; } - protected virtual void GetTimeFormatValues(TimeParts parts, out bool isPm, out int hour12, out string amPm, out string amPmShort, out string timeZone, out string offsetSign, out int offsetHours, out int offsetMinutes) { + protected virtual void GetTimeFormatValues(TimeParts parts, out bool isPm, out int hour12, out string amPm, out string amPmShort, out string timeZone, out string offsetSign, out int offsetHours, out int offsetMinutes, out string fraction1Zero, out string fraction2Zero, out string fraction3Zero, out string fraction1Digit, out string fraction2Digit, out string fraction3Digit) { hour12 = ConvertToHour12(parts.Hour, out isPm); amPm = _dateTimeFormatProvider.AmPmDesignators[isPm ? 1 : 0]; amPmShort = String.IsNullOrEmpty(amPm) ? "" : amPm[0].ToString(); @@ -317,6 +317,12 @@ namespace Orchard.Localization.Services { timeZone = String.Format("{0}{1:00}:{2:00}", offsetSign, offsetHours, offsetMinutes); break; } + fraction1Zero = (((decimal)parts.Millisecond) / 1000).ToString("0.0", CultureInfo.InvariantCulture).Substring(2); + fraction2Zero = (((decimal)parts.Millisecond) / 1000).ToString("0.00", CultureInfo.InvariantCulture).Substring(2); + fraction3Zero = (((decimal)parts.Millisecond) / 1000).ToString("0.000", CultureInfo.InvariantCulture).Substring(2); + fraction1Digit = parts.Millisecond > 0 ? (((decimal)parts.Millisecond) / 1000).ToString("0.#", CultureInfo.InvariantCulture).Substring(2) : ""; + fraction2Digit = parts.Millisecond > 0 ? (((decimal)parts.Millisecond) / 1000).ToString("0.##", CultureInfo.InvariantCulture).Substring(2) : ""; + fraction3Digit = parts.Millisecond > 0 ? (((decimal)parts.Millisecond) / 1000).ToString("0.###", CultureInfo.InvariantCulture).Substring(2) : ""; } protected virtual bool GetUseGenitiveMonthName(string format) { @@ -356,13 +362,20 @@ namespace Orchard.Localization.Services { {"m", "(?[0-9]{1,2})"}, {"ss", "(?[0-9]{2})"}, {"s", "(?[0-9]{1,2})"}, - {"fffffff", "(?[0-9]{7})"}, - {"ffffff", "(?[0-9]{6})"}, - {"fffff", "(?[0-9]{5})"}, - {"ffff", "(?[0-9]{4})"}, - {"fff", "(?[0-9]{3})"}, - {"ff", "(?[0-9]{2})"}, - {"f", "(?[0-9]{1})"}, + {"fffffff", "(?[0-9]{7})"}, + {"ffffff", "(?[0-9]{6})"}, + {"fffff", "(?[0-9]{5})"}, + {"ffff", "(?[0-9]{4})"}, + {"fff", "(?[0-9]{3})"}, + {"ff", "(?[0-9]{2})"}, + {"f", "(?[0-9]{1})"}, + {"FFFFFFF", "(?[0-9]{7})"}, + {"FFFFFF", "(?[0-9]{6})"}, + {"FFFFF", "(?[0-9]{5})"}, + {"FFFF", "(?[0-9]{4})"}, + {"FFF", "(?[0-9]{3})"}, + {"FF", "(?[0-9]{2})"}, + {"F", "(?[0-9]{1})"}, {"tt", String.Format(@"\s*(?{0}|{1})\s*", EscapeForRegex(amDesignator), EscapeForRegex(pmDesignator))}, {"t", String.Format(@"\s*(?{0}|{1})\s*", String.IsNullOrEmpty(amDesignator) ? "" : EscapeForRegex(amDesignator[0].ToString()), String.IsNullOrEmpty(pmDesignator) ? "" : EscapeForRegex(pmDesignator[0].ToString()))}, {"K", @"(?Z|(\+|-)[0-9]{2}:[0-9]{2})*"}, @@ -402,26 +415,26 @@ namespace Orchard.Localization.Services { {"m", "{12:#0}"}, {"ss", "{13:00}"}, {"s", "{13:#0}"}, - {"fffffff", "{14:0000000}"}, - {"ffffff", "{14:000000}"}, - {"fffff", "{14:00000}"}, - {"ffff", "{14:0000}"}, - {"fff", "{14:000}"}, - {"ff", "{14:00}"}, - {"f", "{14:0}"}, - {"FFFFFFF", "{14:#######}"}, - {"FFFFFF", "{14:######}"}, - {"FFFFF", "{14:#####}"}, - {"FFFF", "{14:####}"}, - {"FFF", "{14:###}"}, - {"FF", "{14:##}"}, - {"F", "{14:#}"}, - {"tt", "{15}"}, - {"t", "{16}"}, - {"K", "{17}"}, - {"zzz", "{18}{19:00}:{20:00}"}, - {"zz", "{18}{19:00}"}, - {"z", "{18}{19:#0}"} + {"fffffff", "{16}0000"}, + {"ffffff", "{16}000"}, + {"fffff", "{16}00"}, + {"ffff", "{16}0"}, + {"fff", "{16}"}, + {"ff", "{15}"}, + {"f", "{14}"}, + {"FFFFFFF", "{19}"}, + {"FFFFFF", "{19}"}, + {"FFFFF", "{19}"}, + {"FFFF", "{19}"}, + {"FFF", "{19}"}, + {"FF", "{18}"}, + {"F", "{17}"}, + {"tt", "{20}"}, + {"t", "{21}"}, + {"K", "{22}"}, + {"zzz", "{23}{24:00}:{25:00}"}, + {"zz", "{23}{24:00}"}, + {"z", "{23}{24:#0}"} }; } diff --git a/src/Orchard/Localization/Services/IDateLocalizationServices.txt b/src/Orchard/Localization/Services/IDateLocalizationServices.txt deleted file mode 100644 index d8f9ba140..000000000 --- a/src/Orchard/Localization/Services/IDateLocalizationServices.txt +++ /dev/null @@ -1,6 +0,0 @@ -TODO: - * Test for proper handling of fraction (f) format specifier - suspect it does not work properly in current state - * Write remaining unit tests for DefaultDateLocalizationServices - -BREAKING: - * DateTokens "Date.Format:" and "Date.Local.Format:" only supports custom date/time format strings. \ No newline at end of file diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 29041ca91..27b6e7b71 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -984,9 +984,6 @@ true - - -