mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
Changed from IEnumerable<string> to string[] on some of the properties on IDateTimeFormatProvider because they are naturally accessed by index. Incremental work on formatting implementation in DefaultDateFormatter.
This commit is contained in:
@@ -227,6 +227,55 @@ namespace Orchard.Framework.Tests.Localization {
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Date formatting works correctly for all combinations of months, format strings and cultures.")]
|
||||
public void FormatDateTest01() {
|
||||
var allCases = new ConcurrentBag<string>();
|
||||
var failedCases = new ConcurrentDictionary<string, Exception>();
|
||||
var maxFailedCases = 0;
|
||||
|
||||
var options = new ParallelOptions();
|
||||
if (Debugger.IsAttached) {
|
||||
options.MaxDegreeOfParallelism = 1;
|
||||
}
|
||||
|
||||
var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
|
||||
Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine.
|
||||
var container = InitializeContainer(culture.Name, "GregorianCalendar");
|
||||
var formats = container.Resolve<IDateTimeFormatProvider>();
|
||||
var target = container.Resolve<IDateFormatter>();
|
||||
|
||||
foreach (var dateFormat in formats.AllDateFormats) { // All date formats supported by the culture.
|
||||
for (var month = 1; month <= 12; month++) { // All months in the year.
|
||||
|
||||
DateTime date = new DateTime(1998, month, 1);
|
||||
DateParts dateParts = new DateParts(1998, month, 1);
|
||||
|
||||
// Print reference string using Gregorian calendar to avoid calendar conversion.
|
||||
var cultureGregorian = (CultureInfo)culture.Clone();
|
||||
cultureGregorian.DateTimeFormat.Calendar = cultureGregorian.OptionalCalendars.OfType<GregorianCalendar>().First();
|
||||
|
||||
var caseKey = String.Format("{0}___{1}___{2}", culture.Name, dateFormat, dateParts);
|
||||
allCases.Add(caseKey);
|
||||
//Debug.WriteLine(String.Format("{0} cases tested so far. Testing case {1}...", allCases.Count, caseKey));
|
||||
|
||||
try {
|
||||
var result = target.FormatDate(dateParts, dateFormat);
|
||||
var expected = date.ToString(dateFormat, cultureGregorian);
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
failedCases.TryAdd(caseKey, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (failedCases.Count > maxFailedCases) {
|
||||
throw new AggregateException(String.Format("Format tests failed for {0} of {1} cases. Expected {2} failed cases or less.", failedCases.Count, allCases.Count, maxFailedCases), failedCases.Values);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Description("Time parsing throws a FormatException for unparsable time strings.")]
|
||||
[ExpectedException(typeof(FormatException))]
|
||||
|
||||
@@ -20,43 +20,43 @@ namespace Orchard.Localization.Services {
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public IEnumerable<string> MonthNames {
|
||||
public string[] MonthNames {
|
||||
get {
|
||||
return T("January, February, March, April, May, June, July, August, September, October, November, December").Text.Split(new string[] {", "}, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> MonthNamesGenitive {
|
||||
public virtual string[] MonthNamesGenitive {
|
||||
get {
|
||||
return MonthNames;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> MonthNamesShort {
|
||||
public string[] MonthNamesShort {
|
||||
get {
|
||||
return T("Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec").Text.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> MonthNamesShortGenitive {
|
||||
public virtual string[] MonthNamesShortGenitive {
|
||||
get {
|
||||
return MonthNamesShort;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> DayNames {
|
||||
public string[] DayNames {
|
||||
get {
|
||||
return T("Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday").Text.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> DayNamesShort {
|
||||
public string[] DayNamesShort {
|
||||
get {
|
||||
return T("Sun, Mon, Tue, Wed, Thu, Fri, Sat").Text.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> DayNamesMin {
|
||||
public string[] DayNamesMin {
|
||||
get {
|
||||
return T("Su, Mo, Tu, We, Th, Fr, Sa").Text.Split(new string[] { ", " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
@@ -160,7 +160,7 @@ namespace Orchard.Localization.Services {
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> AmPmDesignators {
|
||||
public string[] AmPmDesignators {
|
||||
get {
|
||||
var t = T("AM;PM").Text;
|
||||
var parts = t.Split(';');
|
||||
|
||||
@@ -31,6 +31,14 @@ namespace Orchard.Localization.Models {
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime ToDateTime() {
|
||||
return new DateTime(
|
||||
_year > 0 ? _year : DateTime.MinValue.Year,
|
||||
_month > 0 ? _month : DateTime.MinValue.Month,
|
||||
_day > 0 ? _day : DateTime.MinValue.Day
|
||||
);
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
return String.Format("{0}-{1}-{2}", _year, _month, _day);
|
||||
}
|
||||
|
||||
@@ -23,43 +23,43 @@ namespace Orchard.Localization.Services {
|
||||
_calendarManager = calendarManager;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> MonthNames {
|
||||
public virtual string[] MonthNames {
|
||||
get {
|
||||
return DateTimeFormat.MonthNames;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> MonthNamesGenitive {
|
||||
public virtual string[] MonthNamesGenitive {
|
||||
get {
|
||||
return DateTimeFormat.MonthGenitiveNames;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> MonthNamesShort {
|
||||
public virtual string[] MonthNamesShort {
|
||||
get {
|
||||
return DateTimeFormat.AbbreviatedMonthNames;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> MonthNamesShortGenitive {
|
||||
public virtual string[] MonthNamesShortGenitive {
|
||||
get {
|
||||
return DateTimeFormat.AbbreviatedMonthGenitiveNames;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> DayNames {
|
||||
public virtual string[] DayNames {
|
||||
get {
|
||||
return DateTimeFormat.DayNames;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> DayNamesShort {
|
||||
public virtual string[] DayNamesShort {
|
||||
get {
|
||||
return DateTimeFormat.AbbreviatedDayNames;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> DayNamesMin {
|
||||
public virtual string[] DayNamesMin {
|
||||
get {
|
||||
return DateTimeFormat.ShortestDayNames;
|
||||
}
|
||||
@@ -111,9 +111,6 @@ namespace Orchard.Localization.Services {
|
||||
patterns.AddRange(DateTimeFormat.GetAllDateTimePatterns('D'));
|
||||
patterns.AddRange(DateTimeFormat.GetAllDateTimePatterns('m'));
|
||||
patterns.AddRange(DateTimeFormat.GetAllDateTimePatterns('y'));
|
||||
// The standard format strings 'M' (month/day pattern) and 'Y' (year/month
|
||||
// pattern) are excluded because they can not be round-tripped with full
|
||||
// date fidelity.
|
||||
return patterns.Distinct();
|
||||
}
|
||||
}
|
||||
@@ -175,9 +172,9 @@ namespace Orchard.Localization.Services {
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> AmPmDesignators {
|
||||
public virtual string[] AmPmDesignators {
|
||||
get {
|
||||
return new string[] { DateTimeFormat.AMDesignator, DateTimeFormat.PMDesignator };
|
||||
return new [] { DateTimeFormat.AMDesignator, DateTimeFormat.PMDesignator };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
@@ -112,8 +113,20 @@ namespace Orchard.Localization.Services {
|
||||
}
|
||||
|
||||
public virtual string FormatDate(DateParts parts, string format) {
|
||||
// TODO: Mahsa should implement!
|
||||
throw new NotImplementedException();
|
||||
var replacements = GetDateFormatReplacements();
|
||||
var formatString = ConvertToFormatString(format, replacements);
|
||||
var calendar = CurrentCalendar;
|
||||
|
||||
var dateTime = parts.ToDateTime();
|
||||
|
||||
var yearString = parts.Year.ToString("00", System.Globalization.CultureInfo.InvariantCulture);
|
||||
var twoDigitYear = Int32.Parse(yearString.Substring(yearString.Length - 2));
|
||||
var monthName = parts.Month > 0 ? _dateTimeFormatProvider.MonthNamesGenitive[parts.Month - 1] : null;
|
||||
var monthNameShort = parts.Month > 0 ? _dateTimeFormatProvider.MonthNamesShortGenitive[parts.Month - 1] : null;
|
||||
var dayName = parts.Day > 0 ? _dateTimeFormatProvider.DayNames[(int)calendar.GetDayOfWeek(dateTime)] : null;
|
||||
var dayNameShort = parts.Day > 0 ? _dateTimeFormatProvider.DayNamesShort[(int)calendar.GetDayOfWeek(parts.ToDateTime())] : null;
|
||||
|
||||
return String.Format(formatString, parts.Year, twoDigitYear, parts.Month, monthName, monthNameShort, parts.Day, dayName, dayNameShort);
|
||||
}
|
||||
|
||||
public virtual string FormatTime(TimeParts parts) {
|
||||
@@ -127,7 +140,7 @@ namespace Orchard.Localization.Services {
|
||||
}
|
||||
|
||||
protected virtual DateTimeParts? TryParseDateTime(string dateTimeString, string format, IDictionary<string, string> replacements) {
|
||||
var dateTimePattern = ConvertFormatStringToRegexPattern(format, replacements);
|
||||
var dateTimePattern = ConvertToRegexPattern(format, replacements);
|
||||
Match m = Regex.Match(dateTimeString, dateTimePattern, RegexOptions.IgnoreCase);
|
||||
if (m.Success) {
|
||||
return new DateTimeParts(ExtractDateParts(m), ExtractTimeParts(m));
|
||||
@@ -136,7 +149,7 @@ namespace Orchard.Localization.Services {
|
||||
}
|
||||
|
||||
protected virtual DateParts? TryParseDate(string dateString, string format, IDictionary<string, string> replacements) {
|
||||
var datePattern = ConvertFormatStringToRegexPattern(format, replacements);
|
||||
var datePattern = ConvertToRegexPattern(format, replacements);
|
||||
Match m = Regex.Match(dateString, datePattern, RegexOptions.IgnoreCase);
|
||||
if (m.Success) {
|
||||
return ExtractDateParts(m);
|
||||
@@ -145,7 +158,7 @@ namespace Orchard.Localization.Services {
|
||||
}
|
||||
|
||||
protected virtual TimeParts? TryParseTime(string timeString, string format, IDictionary<string, string> replacements) {
|
||||
var timePattern = ConvertFormatStringToRegexPattern(format, replacements);
|
||||
var timePattern = ConvertToRegexPattern(format, replacements);
|
||||
Match m = Regex.Match(timeString, timePattern, RegexOptions.IgnoreCase);
|
||||
if (m.Success) {
|
||||
return ExtractTimeParts(m);
|
||||
@@ -210,8 +223,8 @@ namespace Orchard.Localization.Services {
|
||||
if (!m.Groups["amPm"].Success) {
|
||||
throw new FormatException("The string was not recognized as a valid time. The hour is in 12-hour notation but no AM/PM designator was found.");
|
||||
}
|
||||
var isPm = m.Groups["amPm"].Value.Equals(_dateTimeFormatProvider.AmPmDesignators.ToArray()[1], StringComparison.InvariantCultureIgnoreCase);
|
||||
hour = ConvertHour12ToHour24(Int32.Parse(m.Groups["hour12"].Value), isPm);
|
||||
var isPm = m.Groups["amPm"].Value.Equals(_dateTimeFormatProvider.AmPmDesignators[1], StringComparison.InvariantCultureIgnoreCase);
|
||||
hour = ConvertToHour24(Int32.Parse(m.Groups["hour12"].Value), isPm);
|
||||
}
|
||||
|
||||
if (m.Groups["minute"].Success) {
|
||||
@@ -264,31 +277,31 @@ namespace Orchard.Localization.Services {
|
||||
{"fff", "(?<millisecond>[0-9]{3})"},
|
||||
{"ff", "(?<millisecond>[0-9]{2})"},
|
||||
{"f", "(?<millisecond>[0-9]{1})"},
|
||||
{"tt", String.Format(@"\s*(?<amPm>{0}|{1})\s*", EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators.ToArray()[0]), EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators.ToArray()[1]))},
|
||||
{"t", String.Format(@"\s*(?<amPm>{0}|{1})\s*", EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators.ToArray()[0]), EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators.ToArray()[1]))},
|
||||
{" tt", String.Format(@"\s*(?<amPm>{0}|{1})\s*", EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators.ToArray()[0]), EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators.ToArray()[1]))},
|
||||
{" t", String.Format(@"\s*(?<amPm>{0}|{1})\s*", EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators.ToArray()[0]), EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators.ToArray()[1]))},
|
||||
{"tt", String.Format(@"\s*(?<amPm>{0}|{1})\s*", EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators[0]), EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators[1]))},
|
||||
{"t", String.Format(@"\s*(?<amPm>{0}|{1})\s*", EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators[0]), EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators[1]))},
|
||||
{" tt", String.Format(@"\s*(?<amPm>{0}|{1})\s*", EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators[0]), EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators[1]))},
|
||||
{" t", String.Format(@"\s*(?<amPm>{0}|{1})\s*", EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators[0]), EscapeForRegex(_dateTimeFormatProvider.AmPmDesignators[1]))},
|
||||
{"K", @"(?<timezone>Z|(\+|-)[0-9]{2}:[0-9]{2})*"},
|
||||
};
|
||||
}
|
||||
|
||||
//protected virtual Dictionary<string, string> GetDateFormatReplacements() {
|
||||
// return new Dictionary<string, string>() {
|
||||
// {"dddd", "{5:dddd}"},
|
||||
// {"ddd", "{6:ddd}"},
|
||||
// {"dd", "{2:00}"},
|
||||
// {"d", "{2:##}"},
|
||||
// {"MMMM", "{3:MMMM}"},
|
||||
// {"MMM", "{4:MMM}"},
|
||||
// {"MM", "{1:00}"},
|
||||
// {"M", "{1:##}"},
|
||||
// {"yyyyy", "{1:00000}"},
|
||||
// {"yyyy", "{1:0000}"},
|
||||
// {"yyy", "{1:000}"},
|
||||
// {"yy", "{1:00}"},
|
||||
// {"y", "{1:0}"}
|
||||
// };
|
||||
//}
|
||||
protected virtual Dictionary<string, string> GetDateFormatReplacements() {
|
||||
return new Dictionary<string, string>() {
|
||||
{"dddd", "{6:dddd}"},
|
||||
{"ddd", "{7:ddd}"},
|
||||
{"dd", "{5:00}"},
|
||||
{"d", "{5:##}"},
|
||||
{"MMMM", "{3:MMMM}"},
|
||||
{"MMM", "{4:MMM}"},
|
||||
{"MM", "{2:00}"},
|
||||
{"M", "{2:##}"},
|
||||
{"yyyyy", "{0:00000}"},
|
||||
{"yyyy", "{0:0000}"},
|
||||
{"yyy", "{0:000}"},
|
||||
{"yy", "{1:00}"},
|
||||
{"y", "{1:0}"}
|
||||
};
|
||||
}
|
||||
|
||||
//protected virtual Dictionary<string, string> GetTimeFormatReplacements() {
|
||||
// return new Dictionary<string, string>() {
|
||||
@@ -313,7 +326,7 @@ namespace Orchard.Localization.Services {
|
||||
// };
|
||||
//}
|
||||
|
||||
protected virtual string ConvertFormatStringToRegexPattern(string format, IDictionary<string, string> replacements) {
|
||||
protected virtual string ConvertToRegexPattern(string format, IDictionary<string, string> replacements) {
|
||||
string result = format;
|
||||
|
||||
// Transform the / and : characters into culture-specific date and time separators.
|
||||
@@ -323,7 +336,6 @@ namespace Orchard.Localization.Services {
|
||||
result = EscapeForRegex(result);
|
||||
|
||||
// Transform all literals to corresponding wildcard matches.
|
||||
//result = Regex.Replace(result, @"(?<!\\)'(.*?)(?<!\\)'|(?<!\\)""(.*?)(?<!\\)""", m => EscapeForRegex(m.Value.Trim('\'', '"')));
|
||||
result = Regex.Replace(result, @"(?<!\\)'(.*?)(?<!\\)'|(?<!\\)""(.*?)(?<!\\)""", m => String.Format("(?:.{{{0}}})", m.Value.Replace("\\", "").Length - 2));
|
||||
|
||||
// Transform all DateTime format specifiers into corresponding Regex captures.
|
||||
@@ -335,7 +347,29 @@ namespace Orchard.Localization.Services {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual int ConvertHour12ToHour24(int hour12, bool isPm) {
|
||||
protected virtual string ConvertToFormatString(string format, IDictionary<string, string> replacements) {
|
||||
string result = format;
|
||||
|
||||
// Transform the / and : characters into culture-specific date and time separators.
|
||||
result = Regex.Replace(result, @"\/|:", m => m.Value == "/" ? _dateTimeFormatProvider.DateSeparator : _dateTimeFormatProvider.TimeSeparator);
|
||||
|
||||
//// Transform all literals to corresponding text.
|
||||
//var literals = new List<string>();
|
||||
//result = Regex.Replace(result, @"(?<!\\)'(.*?)(?<!\\)'|(?<!\\)""(.*?)(?<!\\)""", m => {
|
||||
// literals.Add(m.Value.Trim('\'', '"'));
|
||||
// return String.Format("{{{0}}}", literals.Count - 1);
|
||||
//});
|
||||
|
||||
// Transform all literals to corresponding text.
|
||||
result = Regex.Replace(result, @"(?<!\\)'(.*?)(?<!\\)'|(?<!\\)""(.*?)(?<!\\)""", m => m.Value.Trim('\'', '"'));
|
||||
|
||||
// Transform all DateTime format specifiers into corresponding format string placeholders.
|
||||
result = result.ReplaceAll(replacements);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual int ConvertToHour24(int hour12, bool isPm) {
|
||||
if (isPm) {
|
||||
return hour12 == 12 ? 12 : hour12 + 12;
|
||||
}
|
||||
|
||||
@@ -41,8 +41,12 @@ struct DateLocalizationOptions {
|
||||
}
|
||||
|
||||
TODO:
|
||||
* Implement formatting in addition to parsing
|
||||
* Test for proper handling of fraction (f) format specifier - suspect it does not work properly in current state
|
||||
* Rewrite DefaultDateLocalizationServices
|
||||
* Write unit tests for DefaultDateLocalizationServices
|
||||
* Add warning message when saving unsupported combination in settings
|
||||
* Add support for the different Gregorian calendar types
|
||||
* Improve DateTimeField:
|
||||
- Surface the field mode (date, time or both)
|
||||
- Do not perform time-zone conversion in date-only mode
|
||||
|
||||
@@ -12,49 +12,49 @@ namespace Orchard.Localization.Services {
|
||||
/// <summary>
|
||||
/// Gets a list of month names.
|
||||
/// </summary>
|
||||
IEnumerable<string> MonthNames {
|
||||
string[] MonthNames {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of genitive month names (used in contexts when a day is involved).
|
||||
/// </summary>
|
||||
IEnumerable<string> MonthNamesGenitive {
|
||||
string[] MonthNamesGenitive {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of abbreviated month names.
|
||||
/// </summary>
|
||||
IEnumerable<string> MonthNamesShort {
|
||||
string[] MonthNamesShort {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of abbreviated genivite month names (used in contexts when a day is involved).
|
||||
/// </summary>
|
||||
IEnumerable<string> MonthNamesShortGenitive {
|
||||
string[] MonthNamesShortGenitive {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of weekday names.
|
||||
/// </summary>
|
||||
IEnumerable<string> DayNames {
|
||||
string[] DayNames {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of abbreviated weekday names.
|
||||
/// </summary>
|
||||
IEnumerable<string> DayNamesShort {
|
||||
string[] DayNamesShort {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of maximally abbreviated weekday names.
|
||||
/// </summary>
|
||||
IEnumerable<string> DayNamesMin {
|
||||
string[] DayNamesMin {
|
||||
get;
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace Orchard.Localization.Services {
|
||||
/// <summary>
|
||||
/// Gets a list of strings used as display text for the AM and PM designators.
|
||||
/// </summary>
|
||||
IEnumerable<string> AmPmDesignators {
|
||||
string[] AmPmDesignators {
|
||||
get;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user