diff --git a/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs b/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs index 52f99462a..7d3bf2299 100644 --- a/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs +++ b/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs @@ -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(); + var target = container.Resolve(); + 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(); - 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(); + var target = container.Resolve(); + + 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(); + + 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(new StubWorkContext(cultureName)); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterInstance(new Mock().Object); + return builder.Build(); + } + + private class StubWorkContext : WorkContext { + + private string _cultureName; + + public StubWorkContext(string cultureName) { + _cultureName = cultureName; + } + + public override T Resolve() { + throw new NotImplementedException(); + } + + public override bool TryResolve(out T service) { + throw new NotImplementedException(); + } + + public override T GetState(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(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(); } } } diff --git a/src/Orchard/Localization/Services/CultureDateTimeFormatProvider.cs b/src/Orchard/Localization/Services/CultureDateTimeFormatProvider.cs index 26f0fdf2b..17753a083 100644 --- a/src/Orchard/Localization/Services/CultureDateTimeFormatProvider.cs +++ b/src/Orchard/Localization/Services/CultureDateTimeFormatProvider.cs @@ -13,55 +13,57 @@ namespace Orchard.Localization.Services { /// 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 MonthNames { + public virtual IEnumerable MonthNames { get { return CurrentCulture.DateTimeFormat.MonthNames; } } - public IEnumerable MonthNamesShort { + public virtual IEnumerable MonthNamesShort { get { return CurrentCulture.DateTimeFormat.AbbreviatedMonthNames; } } - public IEnumerable DayNames { + public virtual IEnumerable DayNames { get { return CurrentCulture.DateTimeFormat.DayNames; } } - public IEnumerable DayNamesShort { + public virtual IEnumerable DayNamesShort { get { return CurrentCulture.DateTimeFormat.AbbreviatedDayNames; } } - public IEnumerable DayNamesMin { + public virtual IEnumerable 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 AllDateFormats { + public virtual IEnumerable AllDateFormats { get { var patterns = new List(); patterns.AddRange(CurrentCulture.DateTimeFormat.GetAllDateTimePatterns('d')); @@ -100,7 +102,7 @@ namespace Orchard.Localization.Services { } } - public IEnumerable AllTimeFormats { + public virtual IEnumerable AllTimeFormats { get { var patterns = new List(); patterns.AddRange(CurrentCulture.DateTimeFormat.GetAllDateTimePatterns('t')); @@ -109,7 +111,7 @@ namespace Orchard.Localization.Services { } } - public IEnumerable AllDateTimeFormats { + public virtual IEnumerable AllDateTimeFormats { get { var patterns = new List(); 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 AmPmDesignators { + public virtual IEnumerable 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); } } } diff --git a/src/Orchard/Localization/Services/DefaultDateFormatter.cs b/src/Orchard/Localization/Services/DefaultDateFormatter.cs index 98da1a2b4..76ff46c64 100644 --- a/src/Orchard/Localization/Services/DefaultDateFormatter.cs +++ b/src/Orchard/Localization/Services/DefaultDateFormatter.cs @@ -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, @"(? 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, @"(? 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 GetDateParseReplacements(CultureInfo culture) { + protected virtual Dictionary GetDateParseReplacements() { return new Dictionary() { - {"dddd", String.Format("(?{0})", String.Join("|", culture.DateTimeFormat.DayNames))}, - {"ddd", String.Format("(?{0})", String.Join("|", culture.DateTimeFormat.AbbreviatedDayNames))}, + {"dddd", String.Format("(?{0})", String.Join("|", _dateTimeFormatProvider.DayNames))}, + {"ddd", String.Format("(?{0})", String.Join("|", _dateTimeFormatProvider.DayNamesShort))}, {"dd", "(?[0-9]{2})"}, {"d", "(?[0-9]{1,2})"}, - {"MMMM", String.Format("(?{0})", String.Join("|", culture.DateTimeFormat.MonthNames.Where(x => !String.IsNullOrEmpty(x))))}, - {"MMM", String.Format("(?{0})", String.Join("|", culture.DateTimeFormat.AbbreviatedMonthNames.Where(x => !String.IsNullOrEmpty(x))))}, + {"MMMM", String.Format("(?{0})", String.Join("|", _dateTimeFormatProvider.MonthNames.Where(x => !String.IsNullOrEmpty(x))))}, + {"MMM", String.Format("(?{0})", String.Join("|", _dateTimeFormatProvider.MonthNamesShort.Where(x => !String.IsNullOrEmpty(x))))}, {"MM", "(?[0-9]{2})"}, {"M", "(?[0-9]{1,2})"}, {"yyyyy", "(?[0-9]{5})"}, @@ -109,14 +142,12 @@ namespace Orchard.Framework.Localization.Services { }; } - private Dictionary GetTimeParseReplacements(CultureInfo culture) { + protected virtual Dictionary GetTimeParseReplacements() { return new Dictionary() { {"HH", "(?[0-9]{2})"}, {"H", "(?[0-9]{1,2})"}, {"hh", "(?[0-9]{2})"}, {"h", "(?[0-9]{1,2})"}, - {"MM", "(?[0-9]{2})"}, - {"M", "(?[0-9]{1,2})"}, {"mm", "(?[0-9]{2})"}, {"m", "(?[0-9]{1,2})"}, {"ss", "(?[0-9]{2})"}, @@ -127,59 +158,76 @@ namespace Orchard.Framework.Localization.Services { {"ffff", "(?[0-9]{4})"}, {"fffff", "(?[0-9]{5})"}, {"ffffff", "(?[0-9]{6})"}, - {"tt", String.Format("\\s*(?{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)}, - {"t", String.Format("\\s*(?{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)}, - {" tt", String.Format("\\s*(?{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)}, - {" t", String.Format("\\s*(?{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)} + {"tt", String.Format("\\s*(?{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])}, + {"t", String.Format("\\s*(?{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])}, + {" tt", String.Format("\\s*(?{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])}, + {" t", String.Format("\\s*(?{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])} }; } - private Dictionary GetDateFormatReplacements(CultureInfo culture) { - return new Dictionary() { - {"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 GetDateFormatReplacements() { + // return new Dictionary() { + // {"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 GetTimeFormatReplacements() { + // return new Dictionary() { + // {"HH", "(?[0-9]{2})"}, + // {"H", "(?[0-9]{1,2})"}, + // {"hh", "(?[0-9]{2})"}, + // {"h", "(?[0-9]{1,2})"}, + // {"mm", "(?[0-9]{2})"}, + // {"m", "(?[0-9]{1,2})"}, + // {"ss", "(?[0-9]{2})"}, + // {"s", "(?[0-9]{1,2})"}, + // {"f", "(?[0-9]{1})"}, + // {"ff", "(?[0-9]{2})"}, + // {"fff", "(?[0-9]{3})"}, + // {"ffff", "(?[0-9]{4})"}, + // {"fffff", "(?[0-9]{5})"}, + // {"ffffff", "(?[0-9]{6})"}, + // {"tt", String.Format("\\s*(?{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])}, + // {"t", String.Format("\\s*(?{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])}, + // {" tt", String.Format("\\s*(?{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])}, + // {" t", String.Format("\\s*(?{0}|{1})\\s*", _dateTimeFormatProvider.AmPmDesignators.ToArray()[0], _dateTimeFormatProvider.AmPmDesignators.ToArray()[1])} + // }; + //} + + protected virtual string ConvertFormatStringToRegExPattern(string format, IDictionary replacements) { + string result = null; + result = Regex.Replace(format, @"\.|\$|\^|\{|\[|\(|\||\)|\*|\+|\?|\\", m => String.Format(@"\{0}", m.Value)); + result = Regex.Replace(result, @"(? String.Format("(.{{{0}}})", m.Value.Replace("\\", "").Length - 2)); + result = result.ReplaceAll(replacements); + return result; } - private Dictionary GetTimeFormatReplacements(CultureInfo culture) { - return new Dictionary() { - {"HH", "(?[0-9]{2})"}, - {"H", "(?[0-9]{1,2})"}, - {"hh", "(?[0-9]{2})"}, - {"h", "(?[0-9]{1,2})"}, - {"MM", "(?[0-9]{2})"}, - {"M", "(?[0-9]{1,2})"}, - {"mm", "(?[0-9]{2})"}, - {"m", "(?[0-9]{1,2})"}, - {"ss", "(?[0-9]{2})"}, - {"s", "(?[0-9]{1,2})"}, - {"f", "(?[0-9]{1})"}, - {"ff", "(?[0-9]{2})"}, - {"fff", "(?[0-9]{3})"}, - {"ffff", "(?[0-9]{4})"}, - {"fffff", "(?[0-9]{5})"}, - {"ffffff", "(?[0-9]{6})"}, - {"tt", String.Format("\\s*(?{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)}, - {"t", String.Format("\\s*(?{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)}, - {" tt", String.Format("\\s*(?{0}|{1})\\s*", culture.DateTimeFormat.AMDesignator, culture.DateTimeFormat.PMDesignator)}, - {" t", String.Format("\\s*(?{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 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; + } } } } diff --git a/src/Orchard/Localization/Services/DefaultDateLocalizationServices.cs b/src/Orchard/Localization/Services/DefaultDateLocalizationServices.cs index 59301cffc..beeba2216 100644 --- a/src/Orchard/Localization/Services/DefaultDateLocalizationServices.cs +++ b/src/Orchard/Localization/Services/DefaultDateLocalizationServices.cs @@ -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) { diff --git a/src/Orchard/Localization/Services/IDateFormatter.cs b/src/Orchard/Localization/Services/IDateFormatter.cs index a9261a2eb..1dc182e6f 100644 --- a/src/Orchard/Localization/Services/IDateFormatter.cs +++ b/src/Orchard/Localization/Services/IDateFormatter.cs @@ -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); } } diff --git a/src/Orchard/Localization/Services/IDateLocalizationServices.txt b/src/Orchard/Localization/Services/IDateLocalizationServices.txt index d6adb7626..48d974dda 100644 --- a/src/Orchard/Localization/Services/IDateLocalizationServices.txt +++ b/src/Orchard/Localization/Services/IDateLocalizationServices.txt @@ -38,4 +38,12 @@ struct DateLocalizationOptions { bool ConvertTimeZone; bool ConvertCalendar; string NullText; -} \ No newline at end of file +} + +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 \ No newline at end of file diff --git a/src/Orchard/Utility/Extensions/StringExtensions.cs b/src/Orchard/Utility/Extensions/StringExtensions.cs index 7fe13c987..79a87cd93 100644 --- a/src/Orchard/Utility/Extensions/StringExtensions.cs +++ b/src/Orchard/Utility/Extensions/StringExtensions.cs @@ -321,7 +321,7 @@ namespace Orchard.Utility.Extensions { } public static string ReplaceAll(this string original, IDictionary 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]); } }