From ef5ccaadfef136da0538b7c5688fffabfc99deb4 Mon Sep 17 00:00:00 2001 From: Daniel Stolt Date: Mon, 4 Aug 2014 01:57:37 +0200 Subject: [PATCH] Added 15 unit tests to cover time zone conversion, and fixed a bug that the new tests revealed. --- .../Localization/DateTimePartsTests.cs | 2 +- .../Localization/DefaultDateFormatterTests.cs | 97 ++------ .../DefaultDateLocalizationServicesTests.cs | 219 ++++++++++++++++++ src/Orchard.Tests/Localization/TestHelpers.cs | 85 +++++++ .../Orchard.Framework.Tests.csproj | 2 + .../Orchard.Tokens/Providers/DateTokens.cs | 2 +- .../DefaultDateLocalizationServices.cs | 51 ++-- 7 files changed, 360 insertions(+), 98 deletions(-) create mode 100644 src/Orchard.Tests/Localization/DefaultDateLocalizationServicesTests.cs create mode 100644 src/Orchard.Tests/Localization/TestHelpers.cs diff --git a/src/Orchard.Tests/Localization/DateTimePartsTests.cs b/src/Orchard.Tests/Localization/DateTimePartsTests.cs index 7f79d0eae..4532b0448 100644 --- a/src/Orchard.Tests/Localization/DateTimePartsTests.cs +++ b/src/Orchard.Tests/Localization/DateTimePartsTests.cs @@ -2,7 +2,7 @@ using NUnit.Framework; using Orchard.Localization.Models; -namespace Orchard.Framework.Tests.Localization { +namespace Orchard.Tests.Localization { [TestFixture] public class DateTimePartsTests { diff --git a/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs b/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs index 5cea527af..5417cdcfd 100644 --- a/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs +++ b/src/Orchard.Tests/Localization/DefaultDateFormatterTests.cs @@ -7,12 +7,11 @@ using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; using Autofac; -using Moq; using NUnit.Framework; using Orchard.Localization.Models; using Orchard.Localization.Services; -namespace Orchard.Framework.Tests.Localization { +namespace Orchard.Tests.Localization { [TestFixture] public class DefaultDateFormatterTests { @@ -36,7 +35,7 @@ namespace Orchard.Framework.Tests.Localization { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine. - var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); var formats = container.Resolve(); var target = container.Resolve(); @@ -85,7 +84,7 @@ namespace Orchard.Framework.Tests.Localization { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine. - var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); var formats = container.Resolve(); var target = container.Resolve(); @@ -135,7 +134,7 @@ namespace Orchard.Framework.Tests.Localization { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine. foreach (var timeZone in new[] { TimeZoneInfo.Utc, TimeZoneInfo.Local, TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"), TimeZoneInfo.FindSystemTimeZoneById("Iran Standard Time") }) { // Enough time zones to get good coverage: UTC, local, one negative offset and one positive offset. - var container = InitializeContainer(culture.Name, "GregorianCalendar", timeZone); + var container = TestHelpers.InitializeContainer(culture.Name, "GregorianCalendar", timeZone); var formats = container.Resolve(); var target = container.Resolve(); @@ -192,7 +191,7 @@ namespace Orchard.Framework.Tests.Localization { [Description("Date/time parsing throws a FormatException for unparsable date/time strings.")] [ExpectedException(typeof(FormatException))] public void ParseDateTimeTest04() { - var container = InitializeContainer("en-US", null, TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer("en-US", null, TimeZoneInfo.Utc); var target = container.Resolve(); target.ParseDateTime("BlaBlaBla"); } @@ -211,7 +210,7 @@ namespace Orchard.Framework.Tests.Localization { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine. - var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); var formats = container.Resolve(); var target = container.Resolve(); @@ -250,7 +249,7 @@ namespace Orchard.Framework.Tests.Localization { [Description("Date parsing throws a FormatException for unparsable date strings.")] [ExpectedException(typeof(FormatException))] public void ParseDateTest02() { - var container = InitializeContainer("en-US", null, TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer("en-US", null, TimeZoneInfo.Utc); var target = container.Resolve(); target.ParseDate("BlaBlaBla"); } @@ -269,7 +268,7 @@ namespace Orchard.Framework.Tests.Localization { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine. - var container = InitializeContainer(culture.Name, null, TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer(culture.Name, null, TimeZoneInfo.Utc); var formats = container.Resolve(); var target = container.Resolve(); @@ -304,7 +303,7 @@ namespace Orchard.Framework.Tests.Localization { [Description("Time parsing throws a FormatException for unparsable time strings.")] [ExpectedException(typeof(FormatException))] public void ParseTimeTest02() { - var container = InitializeContainer("en-US", null, TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer("en-US", null, TimeZoneInfo.Utc); var target = container.Resolve(); target.ParseTime("BlaBlaBla"); } @@ -323,7 +322,7 @@ namespace Orchard.Framework.Tests.Localization { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine. - var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); var formats = container.Resolve(); var target = container.Resolve(); @@ -386,7 +385,7 @@ namespace Orchard.Framework.Tests.Localization { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine. - var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); var formats = container.Resolve(); var target = container.Resolve(); @@ -450,7 +449,7 @@ namespace Orchard.Framework.Tests.Localization { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine. foreach (var timeZone in new[] { TimeZoneInfo.Utc, TimeZoneInfo.Local, TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"), TimeZoneInfo.FindSystemTimeZoneById("Iran Standard Time") }) { // Enough time zones to get good coverage: UTC, local, one negative offset and one positive offset. - var container = InitializeContainer(culture.Name, "GregorianCalendar", timeZone); + var container = TestHelpers.InitializeContainer(culture.Name, "GregorianCalendar", timeZone); var formats = container.Resolve(); var target = container.Resolve(); @@ -540,7 +539,7 @@ namespace Orchard.Framework.Tests.Localization { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine. - var container = InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer(culture.Name, "GregorianCalendar", TimeZoneInfo.Utc); var formats = container.Resolve(); var target = container.Resolve(); @@ -603,7 +602,7 @@ namespace Orchard.Framework.Tests.Localization { var allCultures = CultureInfo.GetCultures(CultureTypes.AllCultures); Parallel.ForEach(allCultures, options, culture => { // All cultures on the machine. - var container = InitializeContainer(culture.Name, null, TimeZoneInfo.Utc); + var container = TestHelpers.InitializeContainer(culture.Name, null, TimeZoneInfo.Utc); var formats = container.Resolve(); var target = container.Resolve(); @@ -682,73 +681,5 @@ namespace Orchard.Framework.Tests.Localization { expectedOffset ); } - - private IContainer InitializeContainer(string cultureName, string calendarName, TimeZoneInfo timeZone) { - var builder = new ContainerBuilder(); - builder.RegisterInstance(new StubWorkContext(cultureName, calendarName, timeZone)); - builder.RegisterType().As(); - builder.RegisterType().As(); - builder.RegisterType().As(); - builder.RegisterInstance(new Mock().Object); - builder.RegisterType().As(); - return builder.Build(); - } - - private class StubWorkContext : WorkContext { - - private string _cultureName; - private string _calendarName; - private TimeZoneInfo _timeZone; - - public StubWorkContext(string cultureName, string calendarName, TimeZoneInfo timeZone) { - _cultureName = cultureName; - _calendarName = calendarName; - _timeZone = timeZone; - } - - 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)((object)_calendarName); - if (name == "CurrentTimeZone") return (T)((object)_timeZone); - 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.Tests/Localization/DefaultDateLocalizationServicesTests.cs b/src/Orchard.Tests/Localization/DefaultDateLocalizationServicesTests.cs new file mode 100644 index 000000000..3ec725b67 --- /dev/null +++ b/src/Orchard.Tests/Localization/DefaultDateLocalizationServicesTests.cs @@ -0,0 +1,219 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Autofac; +using NUnit.Framework; +using Orchard.Localization.Models; +using Orchard.Localization.Services; + +namespace Orchard.Tests.Localization { + + [TestFixture] + public class DefaultDateLocalizationServicesTests { + + [SetUp] + public void Init() { + //Regex.CacheSize = 1024; + } + + [Test] + [Description("Date component is decremented by one day when converting to time zone with negative offset greater than time component.")] + public void ConvertToSiteTimeZoneTest01() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); + Assert.That(timeZone.BaseUtcOffset, Is.LessThan(TimeSpan.FromHours(-3))); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeUtc = new DateTime(1998, 1, 15, 3, 0, 0, DateTimeKind.Utc); + var result = target.ConvertToSiteTimeZone(dateTimeUtc); + Assert.AreEqual(14, result.Day); + } + + [Test] + [Description("Date component is incremented by one day when converting to time zone with positive offset greater than 24 hours minus time component.")] + public void ConvertToSiteTimeZoneTest02() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("India Standard Time"); + Assert.That(timeZone.BaseUtcOffset, Is.GreaterThan(TimeSpan.FromHours(3))); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeUtc = new DateTime(1998, 1, 15, 21, 0, 0, DateTimeKind.Utc); + var result = target.ConvertToSiteTimeZone(dateTimeUtc); + Assert.AreEqual(16, result.Day); + } + + [Test] + [Description("DateTime which is DateTimeKind.Utc is converted to DateTimeKind.Local with offset when target time zone is not UTC.")] + public void ConvertToSiteTimeZoneTest03() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); + Assert.That(timeZone.BaseUtcOffset, Is.Not.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeUtc = new DateTime(1998, 1, 15, 3, 0, 0, DateTimeKind.Utc); + var result = target.ConvertToSiteTimeZone(dateTimeUtc); + Assert.AreEqual(DateTimeKind.Local, result.Kind); + Assert.AreEqual(dateTimeUtc.Hour + timeZone.BaseUtcOffset.Hours, result.Hour); + } + + [Test] + [Description("DateTime which is DateTimeKind.Utc is not converted when target time zone is UTC.")] + public void ConvertToSiteTimeZoneTest04() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("UTC"); + Assert.That(timeZone.BaseUtcOffset, Is.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeUtc = new DateTime(1998, 1, 15, 3, 0, 0, DateTimeKind.Utc); + var result = target.ConvertToSiteTimeZone(dateTimeUtc); + Assert.AreEqual(DateTimeKind.Utc, result.Kind); + Assert.AreEqual(dateTimeUtc, result); + } + + [Test] + [Description("DateTime which is DateTimeKind.Unspecified is converted to DateTimeKind.Local with offset when target time zone is not UTC.")] + public void ConvertToSiteTimeZoneTest05() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); + Assert.That(timeZone.BaseUtcOffset, Is.Not.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeUtc = new DateTime(1998, 1, 15, 3, 0, 0, DateTimeKind.Unspecified); + var result = target.ConvertToSiteTimeZone(dateTimeUtc); + Assert.AreEqual(DateTimeKind.Local, result.Kind); + Assert.AreEqual(dateTimeUtc.Hour + timeZone.BaseUtcOffset.Hours, result.Hour); + } + + [Test] + [Description("DateTime which is DateTimeKind.Unspecified is converted to DateTimeKind.Utc with no offset when target time zone is UTC.")] + public void ConvertToSiteTimeZoneTest06() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("UTC"); + Assert.That(timeZone.BaseUtcOffset, Is.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeUtc = new DateTime(1998, 1, 15, 3, 0, 0, DateTimeKind.Unspecified); + var result = target.ConvertToSiteTimeZone(dateTimeUtc); + Assert.AreEqual(DateTimeKind.Utc, result.Kind); + Assert.AreEqual(dateTimeUtc, result); + } + + [Test] + [Description("DateTime which is already DateTimeKind.Local is never converted.")] + public void ConvertToSiteTimeZoneTest07() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); + Assert.That(timeZone.BaseUtcOffset, Is.Not.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeUtc = new DateTime(1998, 1, 15, 3, 0, 0, DateTimeKind.Local); + var result = target.ConvertToSiteTimeZone(dateTimeUtc); + Assert.AreEqual(DateTimeKind.Local, result.Kind); + Assert.AreEqual(dateTimeUtc, result); + } + + [Test] + [Description("Resulting DateTime is DateTimeKind.Local even when target time zone is not configured time zone of local computer.")] + public void ConvertToSiteTimeZoneTest08() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); + if (timeZone == TimeZoneInfo.Local) { + timeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); + } + Assert.That(timeZone.BaseUtcOffset, Is.Not.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeUtc = new DateTime(1998, 1, 15, 3, 0, 0, DateTimeKind.Utc); + var result = target.ConvertToSiteTimeZone(dateTimeUtc); + Assert.AreEqual(DateTimeKind.Local, result.Kind); + } + + [Test] + [Description("Date component is incremented by one day when converting from time zone with negative offset greater than 24 hours minus time component.")] + public void ConvertFromSiteTimeZoneTest01() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); + Assert.That(timeZone.BaseUtcOffset, Is.LessThan(TimeSpan.FromHours(-3))); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeLocal = new DateTime(1998, 1, 15, 21, 0, 0, DateTimeKind.Local); + var result = target.ConvertFromSiteTimeZone(dateTimeLocal); + Assert.AreEqual(16, result.Day); + } + + [Test] + [Description("Date component is decremented by one day when converting from time zone with positive offset greater than time component.")] + public void ConvertFromSiteTimeZoneTest02() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("India Standard Time"); + Assert.That(timeZone.BaseUtcOffset, Is.GreaterThan(TimeSpan.FromHours(3))); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeLocal = new DateTime(1998, 1, 15, 3, 0, 0, DateTimeKind.Local); + var result = target.ConvertFromSiteTimeZone(dateTimeLocal); + Assert.AreEqual(14, result.Day); + } + + [Test] + [Description("DateTime which is DateTimeKind.Local is converted to DateTimeKind.Utc with offset when target time zone is not UTC.")] + public void ConvertFromSiteTimeZoneTest03() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); + Assert.That(timeZone.BaseUtcOffset, Is.Not.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeLocal = new DateTime(1998, 1, 15, 21, 0, 0, DateTimeKind.Local); + var result = target.ConvertFromSiteTimeZone(dateTimeLocal); + Assert.AreEqual(DateTimeKind.Utc, result.Kind); + Assert.AreEqual(dateTimeLocal.Hour - timeZone.BaseUtcOffset.Hours, result.Hour); + } + + [Test] + [Description("DateTime which is DateTimeKind.Local is converted to DateTimeKind.Utc with no offset when target time zone is UTC.")] + public void ConvertFromSiteTimeZoneTest04() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("UTC"); + Assert.That(timeZone.BaseUtcOffset, Is.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeLocal = new DateTime(1998, 1, 15, 21, 0, 0, DateTimeKind.Local); + var result = target.ConvertFromSiteTimeZone(dateTimeLocal); + Assert.AreEqual(DateTimeKind.Utc, result.Kind); + Assert.AreEqual(dateTimeLocal.Hour, result.Hour); + Assert.AreEqual(dateTimeLocal.Minute, result.Minute); + } + + [Test] + [Description("DateTime which is DateTimeKind.Unspecified is converted to DateTimeKind.Utc with offset when target time zone is not UTC.")] + public void ConvertFromSiteTimeZoneTest05() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); + Assert.That(timeZone.BaseUtcOffset, Is.Not.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeLocal = new DateTime(1998, 1, 15, 21, 0, 0, DateTimeKind.Unspecified); + var result = target.ConvertFromSiteTimeZone(dateTimeLocal); + Assert.AreEqual(DateTimeKind.Utc, result.Kind); + Assert.AreEqual(dateTimeLocal.Hour - timeZone.BaseUtcOffset.Hours, result.Hour); + } + + [Test] + [Description("DateTime which is DateTimeKind.Unspecified is converted to DateTimeKind.Utc with no offset when target time zone is UTC.")] + public void ConvertFromSiteTimeZoneTest06() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("UTC"); + Assert.That(timeZone.BaseUtcOffset, Is.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeLocal = new DateTime(1998, 1, 15, 21, 0, 0, DateTimeKind.Unspecified); + var result = target.ConvertFromSiteTimeZone(dateTimeLocal); + Assert.AreEqual(DateTimeKind.Utc, result.Kind); + Assert.AreEqual(dateTimeLocal.Hour, result.Hour); + Assert.AreEqual(dateTimeLocal.Minute, result.Minute); + } + + [Test] + [Description("DateTime which is already DateTimeKind.Utc is never converted.")] + public void ConvertFromSiteTimeZoneTest07() { + var timeZone = TimeZoneInfo.FindSystemTimeZoneById("Central European Standard Time"); + Assert.That(timeZone.BaseUtcOffset, Is.Not.EqualTo(TimeSpan.Zero)); + var container = TestHelpers.InitializeContainer(null, null, timeZone); + var target = container.Resolve(); + var dateTimeLocal = new DateTime(1998, 1, 15, 21, 0, 0, DateTimeKind.Utc); + var result = target.ConvertFromSiteTimeZone(dateTimeLocal); + Assert.AreEqual(DateTimeKind.Utc, result.Kind); + Assert.AreEqual(dateTimeLocal, result); + } + } +} diff --git a/src/Orchard.Tests/Localization/TestHelpers.cs b/src/Orchard.Tests/Localization/TestHelpers.cs new file mode 100644 index 000000000..3c3ed038d --- /dev/null +++ b/src/Orchard.Tests/Localization/TestHelpers.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Autofac; +using Moq; +using Orchard.Localization.Services; +using Orchard.Services; +using Orchard.Tests.Stubs; + +namespace Orchard.Tests.Localization { + + internal class TestHelpers { + public static IContainer InitializeContainer(string cultureName, string calendarName, TimeZoneInfo timeZone) { + var builder = new ContainerBuilder(); + builder.RegisterType().As(); + builder.RegisterInstance(new StubWorkContext(cultureName, calendarName, timeZone)); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterInstance(new Mock().Object); + builder.RegisterType().As(); + builder.RegisterType().As(); + return builder.Build(); + } + } + + internal class StubWorkContext : WorkContext { + + private string _cultureName; + private string _calendarName; + private TimeZoneInfo _timeZone; + + public StubWorkContext(string cultureName, string calendarName, TimeZoneInfo timeZone) { + _cultureName = cultureName; + _calendarName = calendarName; + _timeZone = timeZone; + } + + 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)((object)_calendarName); + if (name == "CurrentTimeZone") return (T)((object)_timeZone); + throw new NotImplementedException(String.Format("Property '{0}' is not implemented.", name)); + } + + public override void SetState(string name, T value) { + throw new NotImplementedException(); + } + } + + internal 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.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index dd85cb6c9..4391e0b10 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -260,7 +260,9 @@ + + diff --git a/src/Orchard.Web/Modules/Orchard.Tokens/Providers/DateTokens.cs b/src/Orchard.Web/Modules/Orchard.Tokens/Providers/DateTokens.cs index bff088347..e8b55ab14 100644 --- a/src/Orchard.Web/Modules/Orchard.Tokens/Providers/DateTokens.cs +++ b/src/Orchard.Web/Modules/Orchard.Tokens/Providers/DateTokens.cs @@ -81,7 +81,7 @@ namespace Orchard.Tokens.Providers { var time = _clock.UtcNow - dateTimeUtc.ToUniversalTime(); if (time.TotalDays > 7) - return _dateFormatter.FormatDateTime(DateTimeParts.FromDateTime(dateTimeUtc), T("'on' MMM d yyyy 'at' h:mm tt").ToString()); + return _dateLocalizationServices.ConvertToLocalizedString(dateTimeUtc, T("'on' MMM d yyyy 'at' h:mm tt").Text); if (time.TotalHours > 24) return T.Plural("1 day ago", "{0} days ago", time.Days).ToString(); if (time.TotalMinutes > 60) diff --git a/src/Orchard/Localization/Services/DefaultDateLocalizationServices.cs b/src/Orchard/Localization/Services/DefaultDateLocalizationServices.cs index 82ba8532e..b08eb4b1b 100644 --- a/src/Orchard/Localization/Services/DefaultDateLocalizationServices.cs +++ b/src/Orchard/Localization/Services/DefaultDateLocalizationServices.cs @@ -28,14 +28,26 @@ namespace Orchard.Localization.Services { _calendarManager = calendarManager; } - public virtual DateTime ConvertToSiteTimeZone(DateTime date) { - var workContext = _workContextAccessor.GetContext(); - return TimeZoneInfo.ConvertTimeFromUtc(date, workContext.CurrentTimeZone); + public virtual DateTime ConvertToSiteTimeZone(DateTime dateUtc) { + if (dateUtc.Kind == DateTimeKind.Local) { + return dateUtc; + } + if (CurrentTimeZone == TimeZoneInfo.Utc) { + if (dateUtc.Kind == DateTimeKind.Unspecified) { + return new DateTime(dateUtc.Ticks, DateTimeKind.Utc); + } + return dateUtc; + } + var dateLocal = dateUtc + CurrentTimeZone.BaseUtcOffset; + return new DateTime(dateLocal.Ticks, DateTimeKind.Local); } - public virtual DateTime ConvertFromSiteTimeZone(DateTime date) { - var workContext = _workContextAccessor.GetContext(); - return TimeZoneInfo.ConvertTimeToUtc(date, workContext.CurrentTimeZone); + public virtual DateTime ConvertFromSiteTimeZone(DateTime dateLocal) { + if (dateLocal.Kind == DateTimeKind.Utc) { + return dateLocal; + } + var dateUtc = dateLocal - CurrentTimeZone.BaseUtcOffset; + return new DateTime(dateUtc.Ticks, DateTimeKind.Utc); } public virtual DateTimeParts ConvertToSiteCalendar(DateTime date, TimeSpan offset) { @@ -48,12 +60,21 @@ namespace Orchard.Localization.Services { calendar.GetMinute(date), calendar.GetSecond(date), Convert.ToInt32(calendar.GetMilliseconds(date)), - DateTimeKind.Utc, + date.Kind, offset); } public virtual DateTime ConvertFromSiteCalendar(DateTimeParts parts) { - return CurrentCalendar.ToDateTime(parts.Date.Year, parts.Date.Month, parts.Date.Day, parts.Time.Hour, parts.Time.Minute, parts.Time.Second, parts.Time.Millisecond); + return new DateTime( + parts.Date.Year, + parts.Date.Month, + parts.Date.Day, + parts.Time.Hour, + parts.Time.Minute, + parts.Time.Second, + parts.Time.Millisecond, + CurrentCalendar, + parts.Time.Kind); } public string ConvertToLocalizedDateString(DateTime date, DateLocalizationOptions options = null) { @@ -85,13 +106,11 @@ namespace Orchard.Localization.Services { // * Time zone conversion cannot wrap DateTime.MinValue around to the previous day, resulting in undefined result. // Therefore we convert the date to today's date before the conversion, and back to DateTime.MinValue after. var now = _clock.UtcNow; - dateValue = new DateTime(now.Year, now.Month, now.Day, dateValue.Hour, dateValue.Minute, dateValue.Second, dateValue.Millisecond, dateValue.Kind); dateValue = ConvertToSiteTimeZone(dateValue); dateValue = new DateTime(DateTime.MinValue.Year, DateTime.MinValue.Month, DateTime.MinValue.Day, dateValue.Hour, dateValue.Minute, dateValue.Second, dateValue.Millisecond, dateValue.Kind); - var workContext = _workContextAccessor.GetContext(); - offset = workContext.CurrentTimeZone.BaseUtcOffset; + offset = CurrentTimeZone.BaseUtcOffset; } var parts = DateTimeParts.FromDateTime(dateValue, offset); @@ -125,8 +144,7 @@ namespace Orchard.Localization.Services { if (options.EnableTimeZoneConversion) { dateValue = ConvertToSiteTimeZone(dateValue); - var workContext = _workContextAccessor.GetContext(); - offset = workContext.CurrentTimeZone.BaseUtcOffset; + offset = CurrentTimeZone.BaseUtcOffset; } var parts = DateTimeParts.FromDateTime(dateValue, offset); @@ -226,6 +244,13 @@ namespace Orchard.Localization.Services { } } + protected virtual TimeZoneInfo CurrentTimeZone { + get { + var workContext = _workContextAccessor.GetContext(); + return workContext.CurrentTimeZone; + } + } + protected virtual DateTime? ToNullable(DateTime date) { return date == DateTime.MinValue ? new DateTime?() : new DateTime?(date); }