Refactored the IDateFormatter abstraction and its implementation.

This commit is contained in:
Daniel Stolt
2014-07-27 04:55:27 +02:00
parent f622d48f43
commit 6e74d40b2d
7 changed files with 333 additions and 191 deletions

View File

@@ -1,8 +1,11 @@
using System;
using System.Globalization;
using Autofac;
using Moq;
using NUnit.Framework;
using Orchard.Framework.Localization.Models;
using Orchard.Framework.Localization.Services;
using Orchard.Localization.Services;
namespace Orchard.Framework.Tests.Localization {
@@ -10,59 +13,136 @@ namespace Orchard.Framework.Tests.Localization {
public class DefaultDateFormatterTests {
[Test]
[Description("Correct Swedish date is parsed correctly.")]
[Description("Correct en-US date is parsed correctly.")]
public void ParseTest01() {
IDateFormatter target = new DefaultDateFormatter();
var cultureInfo = CultureInfo.GetCultureInfo("sv-SE");
var dateTimeString = "2014-05-31 10:00:00";
var result = target.ParseDateTime(dateTimeString, cultureInfo);
var container = InitializeContainer("en-US");
var culture = CultureInfo.GetCultureInfo("en-US");
var formats = container.Resolve<IDateTimeFormatProvider>();
var target = container.Resolve<IDateFormatter>();
var value = new DateTime(2014, 5, 31, 10, 0, 0).ToString(formats.ShortDateTimeFormat, culture);
var result = target.ParseDateTime(value);
var expected = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0);
Assert.AreEqual(expected, result);
}
[Test]
[Description("Correct US English date is parsed correctly.")]
public void ParseTest02() {
IDateFormatter target = new DefaultDateFormatter();
var cultureInfo = CultureInfo.GetCultureInfo("en-US");
var dateString = "5/31/2014 10:00:00 AM";
var result = target.ParseDateTime(dateString, cultureInfo);
var expected = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0);
Assert.AreEqual(expected, result);
}
[Test]
[Description("Incorrect US English date yields a FormatException.")]
[Description("Incorrect en-US date yields an exception.")]
[ExpectedException(typeof(FormatException))]
public void ParseTest03() {
IDateFormatter target = new DefaultDateFormatter();
var cultureInfo = CultureInfo.GetCultureInfo("en-US");
var dateString = "blablabla";
public void ParseTest02() {
var container = InitializeContainer("en-US");
var target = container.Resolve<IDateFormatter>();
target.ParseDateTime(dateString, cultureInfo);
var value = "BlaBlaBla";
var result = target.ParseDateTime(value);
}
[Test]
[Description("Loop through all cultures. Test Parse method by all possible DateTimeFormats.")]
[Description("Correct sv-SE date is parsed correctly.")]
public void ParseTest03() {
var container = InitializeContainer("sv-SE");
var culture = CultureInfo.GetCultureInfo("sv-SE");
var formats = container.Resolve<IDateTimeFormatProvider>();
var target = container.Resolve<IDateFormatter>();
var value = new DateTime(2014, 5, 31, 10, 0, 0).ToString(formats.ShortDateTimeFormat, culture);
var result = target.ParseDateTime(value);
var expected = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0);
Assert.AreEqual(expected, result);
}
[Test]
[Description("Incorrect sv-SE date yields an exception.")]
[ExpectedException(typeof(FormatException))]
public void ParseTest04() {
IDateFormatter target = new DefaultDateFormatter();
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
foreach (CultureInfo cultureInfo in cultures) {
// Due to a bug in .NET 4.5 in combination with updated Upper Sorbian culture in Windows 8.
if (System.Environment.OSVersion.Version.ToString().CompareTo("6.2.0.0") >= 0 && cultureInfo.Name.StartsWith("hsb")) {
continue;
}
DateTime dateTime = new DateTime(2014, 12, 31, 10, 20, 40, 567);
cultureInfo.DateTimeFormat.Calendar = new GregorianCalendar();
var dateString = dateTime.ToString("G", cultureInfo);
var result = target.ParseDateTime(dateString, cultureInfo);
var millisecond = DateTime.Parse(dateString, cultureInfo.DateTimeFormat).Millisecond;
var expected = new DateTimeParts(2014, 12, 31, 10, 20, 40, millisecond);
Assert.AreEqual(expected, result);
var container = InitializeContainer("sv-SE");
var target = container.Resolve<IDateFormatter>();
var value = "BlaBlaBla";
var result = target.ParseDateTime(value);
}
//[Test]
//[Description("Loop through all cultures. Test Parse method by all possible DateTimeFormats.")]
//public void ParseTest04() {
// IDateFormatter target = new DefaultDateFormatter();
// var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
// foreach (CultureInfo cultureInfo in cultures) {
// // Due to a bug in .NET 4.5 in combination with updated Upper Sorbian culture in Windows 8.
// if (System.Environment.OSVersion.Version.ToString().CompareTo("6.2.0.0") >= 0 && cultureInfo.Name.StartsWith("hsb")) {
// continue;
// }
// DateTime dateTime = new DateTime(2014, 12, 31, 10, 20, 40, 567);
// cultureInfo.DateTimeFormat.Calendar = new GregorianCalendar();
// var dateString = dateTime.ToString("G", cultureInfo);
// var result = target.ParseDateTime(dateString, cultureInfo);
// var millisecond = DateTime.Parse(dateString, cultureInfo.DateTimeFormat).Millisecond;
// var expected = new DateTimeParts(2014, 12, 31, 10, 20, 40, millisecond);
// Assert.AreEqual(expected, result);
// }
//}
private IContainer InitializeContainer(string cultureName) {
var builder = new ContainerBuilder();
builder.RegisterInstance<WorkContext>(new StubWorkContext(cultureName));
builder.RegisterType<StubWorkContextAccessor>().As<IWorkContextAccessor>();
builder.RegisterType<CultureDateTimeFormatProvider>().As<IDateTimeFormatProvider>();
builder.RegisterType<DefaultDateFormatter>().As<IDateFormatter>();
builder.RegisterInstance(new Mock<ICalendarManager>().Object);
return builder.Build();
}
private class StubWorkContext : WorkContext {
private string _cultureName;
public StubWorkContext(string cultureName) {
_cultureName = cultureName;
}
public override T Resolve<T>() {
throw new NotImplementedException();
}
public override bool TryResolve<T>(out T service) {
throw new NotImplementedException();
}
public override T GetState<T>(string name) {
if (name == "CurrentCulture") return (T)((object)_cultureName);
if (name == "CurrentCalendar") return (T)default(object);
throw new NotImplementedException(String.Format("Property '{0}' is not implemented.", name));
}
public override void SetState<T>(string name, T value) {
throw new NotImplementedException();
}
}
private class StubWorkContextAccessor : IWorkContextAccessor {
private WorkContext _workContext;
public StubWorkContextAccessor(WorkContext workContext) {
_workContext = workContext;
}
public WorkContext GetContext(System.Web.HttpContextBase httpContext) {
throw new NotImplementedException();
}
public IWorkContextScope CreateWorkContextScope(System.Web.HttpContextBase httpContext) {
throw new NotImplementedException();
}
public WorkContext GetContext() {
return _workContext;
}
public IWorkContextScope CreateWorkContextScope() {
throw new NotImplementedException();
}
}
}

View File

@@ -13,55 +13,57 @@ namespace Orchard.Localization.Services {
/// </summary>
public class CultureDateTimeFormatProvider : IDateTimeFormatProvider {
private readonly IOrchardServices _orchardServices;
// TODO: This implementation should probably also depend on the current calendar, because DateTimeFormatInfo returns different strings depending on the calendar.
public CultureDateTimeFormatProvider(IOrchardServices orchardServices) {
_orchardServices = orchardServices;
private readonly IWorkContextAccessor _workContextAccessor;
public CultureDateTimeFormatProvider(IWorkContextAccessor workContextAccessor) {
_workContextAccessor = workContextAccessor;
}
public IEnumerable<string> MonthNames {
public virtual IEnumerable<string> MonthNames {
get {
return CurrentCulture.DateTimeFormat.MonthNames;
}
}
public IEnumerable<string> MonthNamesShort {
public virtual IEnumerable<string> MonthNamesShort {
get {
return CurrentCulture.DateTimeFormat.AbbreviatedMonthNames;
}
}
public IEnumerable<string> DayNames {
public virtual IEnumerable<string> DayNames {
get {
return CurrentCulture.DateTimeFormat.DayNames;
}
}
public IEnumerable<string> DayNamesShort {
public virtual IEnumerable<string> DayNamesShort {
get {
return CurrentCulture.DateTimeFormat.AbbreviatedDayNames;
}
}
public IEnumerable<string> DayNamesMin {
public virtual IEnumerable<string> DayNamesMin {
get {
return CurrentCulture.DateTimeFormat.ShortestDayNames;
}
}
public string ShortDateFormat {
public virtual string ShortDateFormat {
get {
return CurrentCulture.DateTimeFormat.ShortDatePattern;
}
}
public string ShortTimeFormat {
public virtual string ShortTimeFormat {
get {
return CurrentCulture.DateTimeFormat.ShortTimePattern;
}
}
public string ShortDateTimeFormat {
public virtual string ShortDateTimeFormat {
get {
// From empirical testing I am fairly certain this invariably evaluates to
// the pattern actually used when printing using the 'g' (i.e. general date/time
@@ -70,25 +72,25 @@ namespace Orchard.Localization.Services {
}
}
public string LongDateFormat {
public virtual string LongDateFormat {
get {
return CurrentCulture.DateTimeFormat.LongDatePattern;
}
}
public string LongTimeFormat {
public virtual string LongTimeFormat {
get {
return CurrentCulture.DateTimeFormat.LongTimePattern;
}
}
public string LongDateTimeFormat {
public virtual string LongDateTimeFormat {
get {
return CurrentCulture.DateTimeFormat.FullDateTimePattern;
}
}
public IEnumerable<string> AllDateFormats {
public virtual IEnumerable<string> AllDateFormats {
get {
var patterns = new List<string>();
patterns.AddRange(CurrentCulture.DateTimeFormat.GetAllDateTimePatterns('d'));
@@ -100,7 +102,7 @@ namespace Orchard.Localization.Services {
}
}
public IEnumerable<string> AllTimeFormats {
public virtual IEnumerable<string> AllTimeFormats {
get {
var patterns = new List<string>();
patterns.AddRange(CurrentCulture.DateTimeFormat.GetAllDateTimePatterns('t'));
@@ -109,7 +111,7 @@ namespace Orchard.Localization.Services {
}
}
public IEnumerable<string> AllDateTimeFormats {
public virtual IEnumerable<string> AllDateTimeFormats {
get {
var patterns = new List<string>();
patterns.AddRange(CurrentCulture.DateTimeFormat.GetAllDateTimePatterns('f'));
@@ -125,13 +127,13 @@ namespace Orchard.Localization.Services {
}
}
public int FirstDay {
public virtual int FirstDay {
get {
return Convert.ToInt32(CurrentCulture.DateTimeFormat.FirstDayOfWeek);
}
}
public bool Use24HourTime {
public virtual bool Use24HourTime {
get {
if (ShortTimeFormat.Contains("H")) // Capital H is the format specifier for the hour using a 24-hour clock.
return true;
@@ -139,33 +141,34 @@ namespace Orchard.Localization.Services {
}
}
public string DateSeparator {
public virtual string DateSeparator {
get {
return CurrentCulture.DateTimeFormat.DateSeparator;
}
}
public string TimeSeparator {
public virtual string TimeSeparator {
get {
return CurrentCulture.DateTimeFormat.TimeSeparator;
}
}
public string AmPmPrefix {
public virtual string AmPmPrefix {
get {
return " "; // No way to get this from CultureInfo unfortunately, so assume a single space.
}
}
public IEnumerable<string> AmPmDesignators {
public virtual IEnumerable<string> AmPmDesignators {
get {
return new string[] { CurrentCulture.DateTimeFormat.AMDesignator, CurrentCulture.DateTimeFormat.PMDesignator };
}
}
private CultureInfo CurrentCulture {
protected virtual CultureInfo CurrentCulture {
get {
return CultureInfo.GetCultureInfo(_orchardServices.WorkContext.CurrentCulture);
var workContext = _workContextAccessor.GetContext();
return CultureInfo.GetCultureInfo(workContext.CurrentCulture);
}
}
}

View File

@@ -4,101 +4,134 @@ using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Orchard.Framework.Localization.Models;
using Orchard.Localization.Services;
using Orchard.Utility.Extensions;
namespace Orchard.Framework.Localization.Services {
public class DefaultDateFormatter : IDateFormatter {
DateTimeParts IDateFormatter.ParseDateTime(string dateTimeString, CultureInfo culture) {
DateParts date = ((IDateFormatter)this).ParseDate(dateTimeString, culture);
TimeParts time = ((IDateFormatter)this).ParseTime(dateTimeString, culture);
return new DateTimeParts(date, time);
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IDateTimeFormatProvider _dateTimeFormatProvider;
private readonly ICalendarManager _calendarManager;
public DefaultDateFormatter(
IWorkContextAccessor workContextAccessor,
IDateTimeFormatProvider dateTimeFormatProvider,
ICalendarManager calendarManager) {
_workContextAccessor = workContextAccessor;
_dateTimeFormatProvider = dateTimeFormatProvider;
_calendarManager = calendarManager;
}
DateParts IDateFormatter.ParseDate(string dateString, CultureInfo culture) {
var dateFormatString = Regex.Replace(culture.DateTimeFormat.ShortDatePattern, @"\.|\$|\^|\{|\[|\(|\||\)|\*|\+|\?|\\", m => String.Format(@"\{0}", m.Value));
dateFormatString = Regex.Replace(dateFormatString, @"(?<!\\)'(.*?)((?<!\\)')", m => String.Format("(.{{{0}}})", m.Value.Replace("\\", "").Length - 2));
public virtual DateTimeParts ParseDateTime(string dateTimeString) {
var replacements = GetDateParseReplacements().Union(GetTimeParseReplacements()).ToDictionary(item => item.Key, item => item.Value);
var dateTimePattern = ConvertFormatStringToRegExPattern(_dateTimeFormatProvider.ShortDateTimeFormat, replacements);
var dateFormat = ReplaceAll(dateFormatString, GetDateParseReplacements(culture));
Match m = Regex.Match(dateTimeString, dateTimePattern, RegexOptions.IgnoreCase);
if (!m.Success) {
throw new FormatException("The string was not recognized as a valid date and time.");
}
if (!Regex.IsMatch(dateString, dateFormat, RegexOptions.IgnoreCase))
throw new FormatException("Invalid date format.");
return new DateTimeParts(ExtractDateParts(m), ExtractTimeParts(m));
}
Match dateMatch = Regex.Match(dateString, dateFormat, RegexOptions.IgnoreCase);
public virtual DateParts ParseDate(string dateString) {
var replacements = GetDateParseReplacements();
var datePattern = ConvertFormatStringToRegExPattern(_dateTimeFormatProvider.ShortDateFormat, replacements);
Match m = Regex.Match(dateString, datePattern, RegexOptions.IgnoreCase);
if (!m.Success) {
throw new FormatException("The string was not recognized as a valid date.");
}
return ExtractDateParts(m);
}
public virtual TimeParts ParseTime(string timeString) {
var replacements = GetTimeParseReplacements();
var timePattern = ConvertFormatStringToRegExPattern(_dateTimeFormatProvider.LongTimeFormat, replacements);
Match m = Regex.Match(timeString, timePattern, RegexOptions.IgnoreCase);
if (!m.Success) {
throw new FormatException("The string was not recognized as a valid time.");
}
return ExtractTimeParts(m);
}
public virtual string FormatDateTime(DateTimeParts parts) {
// TODO: Mahsa should implement!
throw new NotImplementedException();
}
public virtual string FormatDateTime(DateTimeParts parts, string format) {
// TODO: Mahsa should implement!
throw new NotImplementedException();
}
public virtual string FormatDate(DateParts parts) {
// TODO: Mahsa should implement!
throw new NotImplementedException();
}
public virtual string FormatDate(DateParts parts, string format) {
// TODO: Mahsa should implement!
throw new NotImplementedException();
}
public virtual string FormatTime(TimeParts parts) {
// TODO: Mahsa should implement!
throw new NotImplementedException();
}
public virtual string FormatTime(TimeParts parts, string format) {
// TODO: Mahsa should implement!
throw new NotImplementedException();
}
protected virtual DateParts ExtractDateParts(Match m) {
int year = 0,
month = 0,
day = 0;
if (dateMatch.Groups["year"].Success) {
int.TryParse(dateMatch.Groups["year"].Value, out year);
year = culture.DateTimeFormat.Calendar.ToFourDigitYear(year);
}
if (dateMatch.Groups["month"].Success) {
int.TryParse(dateMatch.Groups["month"].Value, out month);
}
if (dateMatch.Groups["day"].Success) {
int.TryParse(dateMatch.Groups["day"].Value, out day);
}
year = CurrentCalendar.ToFourDigitYear(Int32.Parse(m.Groups["year"].Value));
month = Int32.Parse(m.Groups["month"].Value);
day = Int32.Parse(m.Groups["day"].Value);
// TODO: Also extract month names, not just numbers.
return new DateParts(year, month, day);
}
TimeParts IDateFormatter.ParseTime(string timeString, CultureInfo culture) {
var timeFormatString = Regex.Replace(culture.DateTimeFormat.LongTimePattern, @"\.|\$|\^|\{|\[|\(|\||\)|\*|\+|\?|\\", m => String.Format(@"\{0}", m.Value));
timeFormatString = Regex.Replace(timeFormatString, @"(?<!\\)'(.*?)((?<!\\)')", m => String.Format("(.{{{0}}})", m.Value.Replace("\\", "").Length - 2));
var timeFormat = ReplaceAll(timeFormatString, GetTimeParseReplacements(culture));
if (!Regex.IsMatch(timeString, timeFormat, RegexOptions.IgnoreCase))
throw new FormatException("Invalid time format.");
Match timeMatch = Regex.Match(timeString, timeFormat, RegexOptions.IgnoreCase);
protected virtual TimeParts ExtractTimeParts(Match m) {
int hour = 0,
minute = 0,
second = 0,
millisecond = 0;
if (timeMatch.Groups["hour"].Success) {
int.TryParse(timeMatch.Groups["hour"].Value, out hour);
hour = Int32.Parse(m.Groups["hour"].Value);
minute = Int32.Parse(m.Groups["minute"].Value);
if (m.Groups["second"].Success) {
second = Int32.Parse(m.Groups["second"].Value);
}
if (timeMatch.Groups["minute"].Success) {
int.TryParse(timeMatch.Groups["minute"].Value, out minute);
}
if (timeMatch.Groups["second"].Success) {
int.TryParse(timeMatch.Groups["second"].Value, out second);
}
if (timeMatch.Groups["millisecond"].Success) {
int.TryParse(timeMatch.Groups["millisecond"].Value, out millisecond);
if (m.Groups["millisecond"].Success) {
second = Int32.Parse(m.Groups["millisecond"].Value);
}
// TODO: We must also handle 12-hour time with AM/PM designator.
return new TimeParts(hour, minute, second, millisecond);
}
string IDateFormatter.FormatDateTime(DateTimeParts parts, CultureInfo culture) {
// TODO: Mahsa should implement!
throw new NotImplementedException();
}
string IDateFormatter.FormatDate(DateParts parts, CultureInfo culture) {
//var dateFormatString =
//return string.Format(dateFormatString,parts.Year,parts.Month,parts.Day);
throw new NotImplementedException();
}
string IDateFormatter.FormatTime(TimeParts parts, CultureInfo culture) {
// TODO: Mahsa should implement!
throw new NotImplementedException();
}
private Dictionary<string, string> GetDateParseReplacements(CultureInfo culture) {
protected virtual Dictionary<string, string> GetDateParseReplacements() {
return new Dictionary<string, string>() {
{"dddd", String.Format("(?<day>{0})", String.Join("|", culture.DateTimeFormat.DayNames))},
{"ddd", String.Format("(?<day>{0})", String.Join("|", culture.DateTimeFormat.AbbreviatedDayNames))},
{"dddd", String.Format("(?<day>{0})", String.Join("|", _dateTimeFormatProvider.DayNames))},
{"ddd", String.Format("(?<day>{0})", String.Join("|", _dateTimeFormatProvider.DayNamesShort))},
{"dd", "(?<day>[0-9]{2})"},
{"d", "(?<day>[0-9]{1,2})"},
{"MMMM", String.Format("(?<month>{0})", String.Join("|", culture.DateTimeFormat.MonthNames.Where(x => !String.IsNullOrEmpty(x))))},
{"MMM", String.Format("(?<month>{0})", String.Join("|", culture.DateTimeFormat.AbbreviatedMonthNames.Where(x => !String.IsNullOrEmpty(x))))},
{"MMMM", String.Format("(?<month>{0})", String.Join("|", _dateTimeFormatProvider.MonthNames.Where(x => !String.IsNullOrEmpty(x))))},
{"MMM", String.Format("(?<month>{0})", String.Join("|", _dateTimeFormatProvider.MonthNamesShort.Where(x => !String.IsNullOrEmpty(x))))},
{"MM", "(?<month>[0-9]{2})"},
{"M", "(?<month>[0-9]{1,2})"},
{"yyyyy", "(?<year>[0-9]{5})"},
@@ -109,14 +142,12 @@ namespace Orchard.Framework.Localization.Services {
};
}
private Dictionary<string, string> GetTimeParseReplacements(CultureInfo culture) {
protected virtual Dictionary<string, string> GetTimeParseReplacements() {
return new Dictionary<string, string>() {
{"HH", "(?<hour>[0-9]{2})"},
{"H", "(?<hour>[0-9]{1,2})"},
{"hh", "(?<hour>[0-9]{2})"},
{"h", "(?<hour>[0-9]{1,2})"},
{"MM", "(?<minute>[0-9]{2})"},
{"M", "(?<minute>[0-9]{1,2})"},
{"mm", "(?<minute>[0-9]{2})"},
{"m", "(?<minute>[0-9]{1,2})"},
{"ss", "(?<second>[0-9]{2})"},
@@ -127,59 +158,76 @@ namespace Orchard.Framework.Localization.Services {
{"ffff", "(?<millisecond>[0-9]{4})"},
{"fffff", "(?<millisecond>[0-9]{5})"},
{"ffffff", "(?<millisecond>[0-9]{6})"},
{"tt", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)},
{"t", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)},
{" tt", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)},
{" t", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)}
{"tt", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])},
{"t", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])},
{" tt", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])},
{" t", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])}
};
}
private Dictionary<string, string> GetDateFormatReplacements(CultureInfo culture) {
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", "{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> GetTimeFormatReplacements() {
// return new Dictionary<string, string>() {
// {"HH", "(?<hour>[0-9]{2})"},
// {"H", "(?<hour>[0-9]{1,2})"},
// {"hh", "(?<hour>[0-9]{2})"},
// {"h", "(?<hour>[0-9]{1,2})"},
// {"mm", "(?<minute>[0-9]{2})"},
// {"m", "(?<minute>[0-9]{1,2})"},
// {"ss", "(?<second>[0-9]{2})"},
// {"s", "(?<second>[0-9]{1,2})"},
// {"f", "(?<millisecond>[0-9]{1})"},
// {"ff", "(?<millisecond>[0-9]{2})"},
// {"fff", "(?<millisecond>[0-9]{3})"},
// {"ffff", "(?<millisecond>[0-9]{4})"},
// {"fffff", "(?<millisecond>[0-9]{5})"},
// {"ffffff", "(?<millisecond>[0-9]{6})"},
// {"tt", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])},
// {"t", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])},
// {" tt", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])},
// {" t", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])}
// };
//}
protected virtual string ConvertFormatStringToRegExPattern(string format, IDictionary<string, string> replacements) {
string result = null;
result = Regex.Replace(format, @"\.|\$|\^|\{|\[|\(|\||\)|\*|\+|\?|\\", m => String.Format(@"\{0}", m.Value));
result = Regex.Replace(result, @"(?<!\\)'(.*?)((?<!\\)')", m => String.Format("(.{{{0}}})", m.Value.Replace("\\", "").Length - 2));
result = result.ReplaceAll(replacements);
return result;
}
private Dictionary<string, string> GetTimeFormatReplacements(CultureInfo culture) {
return new Dictionary<string, string>() {
{"HH", "(?<hour>[0-9]{2})"},
{"H", "(?<hour>[0-9]{1,2})"},
{"hh", "(?<hour>[0-9]{2})"},
{"h", "(?<hour>[0-9]{1,2})"},
{"MM", "(?<minute>[0-9]{2})"},
{"M", "(?<minute>[0-9]{1,2})"},
{"mm", "(?<minute>[0-9]{2})"},
{"m", "(?<minute>[0-9]{1,2})"},
{"ss", "(?<second>[0-9]{2})"},
{"s", "(?<second>[0-9]{1,2})"},
{"f", "(?<millisecond>[0-9]{1})"},
{"ff", "(?<millisecond>[0-9]{2})"},
{"fff", "(?<millisecond>[0-9]{3})"},
{"ffff", "(?<millisecond>[0-9]{4})"},
{"fffff", "(?<millisecond>[0-9]{5})"},
{"ffffff", "(?<millisecond>[0-9]{6})"},
{"tt", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)},
{"t", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)},
{" tt", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)},
{" t", String.Format("\\s*(?<AMPM>{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)}
};
protected virtual CultureInfo CurrentCulture {
get {
var workContext = _workContextAccessor.GetContext();
return CultureInfo.GetCultureInfo(workContext.CurrentCulture);
}
}
private string ReplaceAll(string original, IDictionary<string, string> replacements) {
var pattern = String.Format("{0}", String.Join("|", replacements.Keys.ToArray()));
return Regex.Replace(original, pattern, (match) => replacements[match.Value]);
protected virtual Calendar CurrentCalendar {
get {
var workContext = _workContextAccessor.GetContext();
if (!String.IsNullOrEmpty(workContext.CurrentCalendar))
return _calendarManager.GetCalendarByName(workContext.CurrentCalendar);
return CurrentCulture.Calendar;
}
}
}
}

View File

@@ -9,15 +9,15 @@ namespace Orchard.Localization.Services {
public class DefaultDateLocalizationServices : IDateLocalizationServices {
private readonly IWorkContextAccessor _workContextAccessor;
private readonly IDateTimeFormatProvider _dateTimeLocalization;
private readonly IDateTimeFormatProvider _dateTimeFormatProvider;
private readonly ICalendarManager _calendarManager;
public DefaultDateLocalizationServices(
IWorkContextAccessor workContextAccessor,
IDateTimeFormatProvider dateTimeLocalization,
IDateTimeFormatProvider dateTimeFormatProvider,
ICalendarManager calendarManager) {
_workContextAccessor = workContextAccessor;
_dateTimeLocalization = dateTimeLocalization;
_dateTimeFormatProvider = dateTimeFormatProvider;
_calendarManager = calendarManager;
}
@@ -70,7 +70,7 @@ namespace Orchard.Localization.Services {
public virtual string ConvertToLocalString(DateTime date, string nullText = null) {
return ConvertToLocalString(ToNullable(date), _dateTimeLocalization.LongDateTimeFormat, nullText);
return ConvertToLocalString(ToNullable(date), _dateTimeFormatProvider.LongDateTimeFormat, nullText);
}
public virtual string ConvertToLocalString(DateTime date, string format, string nullText = null) {
@@ -106,7 +106,7 @@ namespace Orchard.Localization.Services {
}
public virtual string ConvertToLocalDateString(DateTime? date, string nullText = null) {
return ConvertToLocalString(date, _dateTimeLocalization.ShortDateFormat, nullText);
return ConvertToLocalString(date, _dateTimeFormatProvider.ShortDateFormat, nullText);
}
public virtual string ConvertToLocalTimeString(DateTime date, string nullText = null) {
@@ -114,7 +114,7 @@ namespace Orchard.Localization.Services {
}
public virtual string ConvertToLocalTimeString(DateTime? date, string nullText = null) {
return ConvertToLocalString(date, _dateTimeLocalization.ShortTimeFormat, nullText);
return ConvertToLocalString(date, _dateTimeFormatProvider.ShortTimeFormat, nullText);
}
public virtual DateTime? ConvertFromLocalString(string dateString) {

View File

@@ -6,11 +6,14 @@ using Orchard.Framework.Localization.Models;
namespace Orchard.Framework.Localization.Services {
public interface IDateFormatter : IDependency {
DateTimeParts ParseDateTime(string dateTimeString, CultureInfo culture);
DateParts ParseDate(string dateString, CultureInfo culture);
TimeParts ParseTime(string timeString, CultureInfo culture);
string FormatDateTime(DateTimeParts parts, CultureInfo culture);
string FormatDate(DateParts parts, CultureInfo culture);
string FormatTime(TimeParts parts, CultureInfo culture);
DateTimeParts ParseDateTime(string dateTimeString);
DateParts ParseDate(string dateString);
TimeParts ParseTime(string timeString);
string FormatDateTime(DateTimeParts parts);
string FormatDateTime(DateTimeParts parts, string format);
string FormatDate(DateParts parts);
string FormatDate(DateParts parts, string format);
string FormatTime(TimeParts parts);
string FormatTime(TimeParts parts, string format);
}
}

View File

@@ -38,4 +38,12 @@ struct DateLocalizationOptions {
bool ConvertTimeZone;
bool ConvertCalendar;
string NullText;
}
}
TODO:
* Consider current calendar in CultureDateTimeFormatProvider
* Handle multiple allowed formats when parsing in DefaultDateFormatter
* Handle month names as well as numbers in DefaultDateFormatter
* Fix TODOs in DefaultDateFormatter
* Rewrite DefaultDateLocalizationServices
* Write unit tests for DefaultDateLocalizationServices

View File

@@ -321,7 +321,7 @@ namespace Orchard.Utility.Extensions {
}
public static string ReplaceAll(this string original, IDictionary<string, string> replacements) {
var pattern = String.Format("({0})", String.Join("|", replacements.Keys.ToArray()));
var pattern = String.Format("{0}", String.Join("|", replacements.Keys.ToArray()));
return Regex.Replace(original, pattern, (match) => replacements[match.Value]);
}
}