diff --git a/src/Orchard.Tests/Localization/DateParseResultTests.cs b/src/Orchard.Tests/Localization/DateParseResultTests.cs
new file mode 100644
index 000000000..32cd85003
--- /dev/null
+++ b/src/Orchard.Tests/Localization/DateParseResultTests.cs
@@ -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);
+ }
+ }
+}
diff --git a/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs b/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs
new file mode 100644
index 000000000..761a22813
--- /dev/null
+++ b/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs
@@ -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);
+ }
+ }
+ }
+}
diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj
index 90c04ced1..30c4e6718 100644
--- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj
+++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj
@@ -259,6 +259,8 @@
+
+
diff --git a/src/Orchard/Localization/Models/DateParts.cs b/src/Orchard/Localization/Models/DateParts.cs
new file mode 100644
index 000000000..d61930a86
--- /dev/null
+++ b/src/Orchard/Localization/Models/DateParts.cs
@@ -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;
+ //}
+ }
+}
diff --git a/src/Orchard/Localization/Models/DateTimeParts.cs b/src/Orchard/Localization/Models/DateTimeParts.cs
new file mode 100644
index 000000000..82f43bfa7
--- /dev/null
+++ b/src/Orchard/Localization/Models/DateTimeParts.cs
@@ -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;
+ //}
+ }
+}
diff --git a/src/Orchard/Localization/Models/TimeParts.cs b/src/Orchard/Localization/Models/TimeParts.cs
new file mode 100644
index 000000000..f71a50392
--- /dev/null
+++ b/src/Orchard/Localization/Models/TimeParts.cs
@@ -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;
+ //}
+ }
+}
diff --git a/src/Orchard/Localization/Services/DefaultDateFormatter.cs b/src/Orchard/Localization/Services/DefaultDateFormatter.cs
new file mode 100644
index 000000000..433264daf
--- /dev/null
+++ b/src/Orchard/Localization/Services/DefaultDateFormatter.cs
@@ -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, @"(? 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, @"(? 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 GetDateParseReplacements(CultureInfo culture) {
+ return new Dictionary() {
+ {"dddd", "(?[0-9]{4})"},
+ {"ddd", "(?[0-9]{3})"},
+ {"dd", "(?[0-9]{2})"},
+ {"d", "(?[0-9]{1,2})"},
+ {"MMMM", String.Format("(?{0})", String.Join("|", culture.DateTimeFormat.MonthNames))},
+ {"MMM", String.Format("(?{0})", String.Join("|", culture.DateTimeFormat.AbbreviatedMonthNames))},
+ {"MM", "(?[0-9]{2})"},
+ {"M", "(?[0-9]{1,2})"},
+ {"yyyyy", "(?[0-9]{5})"},
+ {"yyyy", "(?[0-9]{4})"},
+ {"yyy", "(?[0-9]{3})"},
+ {"yy", "(?[0-9]{2})"},
+ {"y", "(?[0-9]{1})"}
+ };
+ }
+
+ private Dictionary GetTimeParseReplacements(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)}
+ };
+ }
+
+ 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]);
+ }
+ }
+}
diff --git a/src/Orchard/Localization/Services/IDateFormatter.cs b/src/Orchard/Localization/Services/IDateFormatter.cs
new file mode 100644
index 000000000..555b3915d
--- /dev/null
+++ b/src/Orchard/Localization/Services/IDateFormatter.cs
@@ -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);
+ }
+}
diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj
index a1c48c56b..ac911703a 100644
--- a/src/Orchard/Orchard.Framework.csproj
+++ b/src/Orchard/Orchard.Framework.csproj
@@ -261,8 +261,12 @@
+
+
+
+
@@ -270,6 +274,7 @@
+