Integrated the IDateFormatter abstraction and a first draft implementation and unit tests.

This commit is contained in:
Daniel Stolt
2014-07-25 16:51:29 +02:00
parent 53c449e20f
commit 7d62d40a69
9 changed files with 404 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
using System;
using NUnit.Framework;
using Orchard.Framework.Localization.Models;
namespace Orchard.Framework.Tests.Localization {
[TestFixture]
public class DateParseResultTests {
[Test]
[Description("Equal instances return equality.")]
public void EqualsTest01() {
var target = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0);
var other = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0);
var result = target.Equals(other);
Assert.IsTrue(result);
}
[Test]
[Description("Different instances do not return equality.")]
public void EqualsTest02() {
var target = new DateTimeParts(2014, 5, 31, 10, 0, 0, 0);
var other = new DateTimeParts(2014, 5, 31, 10, 0, 0, 1);
var result = target.Equals(other);
Assert.IsFalse(result);
}
}
}

View File

@@ -0,0 +1,65 @@
using System;
using System.Globalization;
using NUnit.Framework;
using Orchard.Framework.Localization.Models;
using Orchard.Framework.Localization.Services;
namespace Orchard.Framework.Tests.Localization {
[TestFixture]
public class DefaultDateFormatterTests {
[Test]
[Description("Correct Swedish 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 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.")]
[ExpectedException(typeof(FormatException))]
public void ParseTest03() {
IDateFormatter target = new DefaultDateFormatter();
var cultureInfo = CultureInfo.GetCultureInfo("en-US");
var dateString = "blablabla";
target.ParseDateTime(dateString, cultureInfo);
}
[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) {
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);
}
}
}
}

View File

@@ -259,6 +259,8 @@
<Compile Include="FileSystems\Dependencies\DependenciesFolderTests.cs" />
<Compile Include="FileSystems\VirtualPath\DefaultVirtualPathProviderTests.cs" />
<Compile Include="Localization\CultureManagerTests.cs" />
<Compile Include="Localization\DateParseResultTests.cs" />
<Compile Include="Localization\DefaultDateFormatterTests.cs" />
<Compile Include="Logging\OrchardFileAppenderTests.cs" />
<Compile Include="Messaging\MessagingChannelStub.cs" />
<Compile Include="Mvc\Html\HtmlHelperExtensionsTests.cs" />

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Orchard.Framework.Localization.Models {
public struct DateParts {
public DateParts(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
private readonly int _day;
private readonly int _month;
private readonly int _year;
public int Year {
get {
return _year;
}
}
public int Month {
get {
return _month;
}
}
public int Day {
get {
return _day;
}
}
//public override bool Equals(object obj) {
// var other = (DateParts)obj;
// if (Year != other.Year ||
// Month != other.Month ||
// Day != other.Day)
// return false;
// return true;
//}
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Orchard.Framework.Localization.Models {
public struct DateTimeParts {
public DateTimeParts(int year, int month, int day, int hour, int minute, int second, int millisecond) {
_date = new DateParts(year, month, day);
_time = new TimeParts(hour, minute, second, millisecond);
}
public DateTimeParts(DateParts dateParts, TimeParts timeParts) {
_date = dateParts;
_time = timeParts;
}
private readonly DateParts _date;
private readonly TimeParts _time;
public DateParts Date {
get {
return _date;
}
}
public TimeParts Time {
get {
return _time;
}
}
//public override bool Equals(object obj) {
// var other = (DateTimeParts)obj;
// if (!(Date.Equals(other.Date)) ||
// !(Time.Equals(other.Time)))
// return false;
// return true;
//}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Orchard.Framework.Localization.Models {
public struct TimeParts {
public TimeParts(int hour, int minute, int second, int millisecond) {
_hour = hour;
_minute = minute;
_second = second;
_millisecond = millisecond;
}
private readonly int _hour;
private readonly int _minute;
private readonly int _second;
private readonly int _millisecond;
public int Hour {
get {
return _hour;
}
}
public int Minute {
get {
return _minute;
}
}
public int Second {
get {
return _second;
}
}
public int Millisecond {
get {
return _millisecond;
}
}
//public override bool Equals(object obj) {
// var other = (TimeParts)obj;
// if (Hour != other.Hour ||
// Minute != other.Minute ||
// Second != other.Second ||
// Millisecond != other.Millisecond)
// return false;
// return Hour == other.Hour;
//}
}
}

View File

@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Orchard.Framework.Localization.Models;
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);
}
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));
var dateFormat = ReplaceAll(dateFormatString, GetDateParseReplacements(culture));
if (!Regex.IsMatch(dateString, dateFormat, RegexOptions.IgnoreCase))
throw new FormatException("Invalid date format.");
Match dateMatch = Regex.Match(dateString, dateFormat, RegexOptions.IgnoreCase);
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);
}
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);
int hour = 0,
minute = 0,
second = 0,
millisecond = 0;
if (timeMatch.Groups["hour"].Success) {
int.TryParse(timeMatch.Groups["hour"].Value, out hour);
}
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);
}
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) {
// TODO: Mahsa should implement!
throw new NotImplementedException();
}
string IDateFormatter.FormatTime(TimeParts parts, CultureInfo culture) {
// TODO: Mahsa should implement!
throw new NotImplementedException();
}
private Dictionary<string, string> GetDateParseReplacements(CultureInfo culture) {
return new Dictionary<string, string>() {
{"dddd", "(?<day>[0-9]{4})"},
{"ddd", "(?<day>[0-9]{3})"},
{"dd", "(?<day>[0-9]{2})"},
{"d", "(?<day>[0-9]{1,2})"},
{"MMMM", String.Format("(?<month>{0})", String.Join("|", culture.DateTimeFormat.MonthNames))},
{"MMM", String.Format("(?<month>{0})", String.Join("|", culture.DateTimeFormat.AbbreviatedMonthNames))},
{"MM", "(?<month>[0-9]{2})"},
{"M", "(?<month>[0-9]{1,2})"},
{"yyyyy", "(?<year>[0-9]{5})"},
{"yyyy", "(?<year>[0-9]{4})"},
{"yyy", "(?<year>[0-9]{3})"},
{"yy", "(?<year>[0-9]{2})"},
{"y", "(?<year>[0-9]{1})"}
};
}
private Dictionary<string, string> GetTimeParseReplacements(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)}
};
}
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]);
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Orchard.Framework.Localization.Models;
namespace Orchard.Framework.Localization.Services {
public interface IDateFormatter {
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);
}
}

View File

@@ -261,8 +261,12 @@
<Compile Include="FileSystems\Media\FileSystemStorageProvider.cs" />
<Compile Include="FileSystems\Media\IMimeTypeProvider.cs" />
<Compile Include="Indexing\ISearchBits.cs" />
<Compile Include="Localization\Models\DateParts.cs" />
<Compile Include="Localization\Models\DateTimeParts.cs" />
<Compile Include="Localization\Models\TimeParts.cs" />
<Compile Include="Localization\Services\CurrentCalendarWorkContext.cs" />
<Compile Include="Localization\Services\CurrentCultureWorkContext.cs" />
<Compile Include="Localization\Services\DefaultDateFormatter.cs" />
<Compile Include="Localization\Services\DefaultCalendarManager.cs" />
<Compile Include="Localization\Services\CultureDateTimeFormatProvider.cs" />
<Compile Include="Localization\Services\DefaultDateLocalizationServices.cs" />
@@ -270,6 +274,7 @@
<Compile Include="Localization\Services\ICalendarSelector.cs" />
<Compile Include="Localization\Services\ICalendarManager.cs" />
<Compile Include="Localization\Services\IDateLocalizationServices.cs" />
<Compile Include="Localization\Services\IDateFormatter.cs" />
<Compile Include="Localization\Services\IDateTimeFormatProvider.cs" />
<Compile Include="Localization\Services\ILocalizedStringManager.cs" />
<Compile Include="Localization\Services\SiteCalendarSelector.cs" />